diff --git a/remote/webdriver-bidi/Realm.jsm b/remote/webdriver-bidi/Realm.jsm new file mode 100644 index 0000000000000..76da89ae8220d --- /dev/null +++ b/remote/webdriver-bidi/Realm.jsm @@ -0,0 +1,191 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +var EXPORTED_SYMBOLS = ["WindowRealm"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; +XPCOMUtils.defineLazyModuleGetters(lazy, { + addDebuggerToGlobal: "resource://gre/modules/jsdebugger.jsm", +}); + +XPCOMUtils.defineLazyGetter(lazy, "dbg", () => { + // eslint-disable-next-line mozilla/reject-globalThis-modification + lazy.addDebuggerToGlobal(globalThis); + return new Debugger(); +}); + +/** + * @typedef {string} RealmType + **/ + +/** + * Enum of realm types. + * + * @readonly + * @enum {RealmType} + **/ +const RealmType = { + AudioWorklet: "audio-worklet", + DedicatedWorker: "dedicated-worker", + PaintWorklet: "paint-worklet", + ServiceWorker: "service-worker", + SharedWorker: "shared-worker", + Window: "window", + Worker: "worker", + Worklet: "worklet", +}; + +/** + * Base class that wraps any kind of WebDriver BiDi realm. + */ +class Realm { + #id; + + constructor() { + this.#id = Services.uuid + .generateUUID() + .toString() + .slice(1, -1); + } + + /** + * Get the unique identifier of the realm instance. + * + * @return {string} The unique identifier. + */ + get id() { + return this.#id; + } +} + +/** + * Wrapper for Window realms including sandbox objects. + */ +class WindowRealm extends Realm { + #globalObject; + #globalObjectReference; + #window; + + static type = RealmType.Window; + + /** + * + * @param {Window} window + * The window global to wrap. + * @param {Object} options + * @param {string=} options.sandboxName + * Name of the sandbox to create if specified. Defaults to `null`. + */ + constructor(window, options = {}) { + const { sandboxName = null } = options; + + super(); + + this.#window = window; + this.#globalObject = + sandboxName === null ? this.#window : this.#createSandbox(); + this.#globalObjectReference = lazy.dbg.makeGlobalObjectReference( + this.#globalObject + ); + + lazy.dbg.enableAsyncStack(this.#globalObject); + } + + destroy() { + lazy.dbg.disableAsyncStack(this.#globalObject); + + this.#globalObjectReference = null; + this.#globalObject = null; + this.#window = null; + } + + get globalObjectReference() { + return this.#globalObjectReference; + } + + #cloneAsDebuggerObject(obj) { + // To use an object created in the priviledged Debugger compartment from + // the content compartment, we need to first clone it into the target + // compartment and then retrieve the corresponding Debugger.Object wrapper. + const proxyObject = Cu.cloneInto( + obj, + this.#globalObjectReference.unsafeDereference() + ); + + return this.#globalObjectReference.makeDebuggeeValue(proxyObject); + } + + #createSandbox() { + const win = this.#window; + const opts = { + sameZoneAs: win, + sandboxPrototype: win, + wantComponents: false, + wantXrays: true, + }; + + return new Cu.Sandbox(win, opts); + } + + /** + * Evaluates a provided expression in the context of the current realm. + * + * @param {string} expression + * The expression to evaluate. + * + * @return {Object} + * - evaluationStatus {EvaluationStatus} One of "normal", "throw". + * - exceptionDetails {ExceptionDetails=} the details of the exception if + * the evaluation status was "throw". + * - result {RemoteValue=} the result of the evaluation serialized as a + * RemoteValue if the evaluation status was "normal". + */ + executeInGlobal(expression) { + return this.#globalObjectReference.executeInGlobal(expression, { + url: this.#window.document.baseURI, + }); + } + + /** + * Call a function in the context of the current realm. + * + * @param {string} functionDeclaration + * The body of the function to call. + * @param {Array} functionArguments + * The arguments to pass to the function call. + * @param {Object} thisParameter + * The value of the `this` keyword for the function call. + * + * @return {Object} + * - evaluationStatus {EvaluationStatus} One of "normal", "throw". + * - exceptionDetails {ExceptionDetails=} the details of the exception if + * the evaluation status was "throw". + * - result {RemoteValue=} the result of the evaluation serialized as a + * RemoteValue if the evaluation status was "normal". + */ + executeInGlobalWithBindings( + functionDeclaration, + functionArguments, + thisParameter + ) { + const expression = `(${functionDeclaration}).apply(__bidi_this, __bidi_args)`; + + return this.#globalObjectReference.executeInGlobalWithBindings( + expression, + { + __bidi_args: this.#cloneAsDebuggerObject(functionArguments), + __bidi_this: this.#cloneAsDebuggerObject(thisParameter), + }, + { + url: this.#window.document.baseURI, + } + ); + } +} diff --git a/remote/webdriver-bidi/jar.mn b/remote/webdriver-bidi/jar.mn index c4bca881075a2..59b1ea7f14fd3 100644 --- a/remote/webdriver-bidi/jar.mn +++ b/remote/webdriver-bidi/jar.mn @@ -6,6 +6,7 @@ remote.jar: % content remote %content/ content/webdriver-bidi/NewSessionHandler.jsm (NewSessionHandler.jsm) + content/webdriver-bidi/Realm.jsm (Realm.jsm) content/webdriver-bidi/RemoteValue.jsm (RemoteValue.jsm) content/webdriver-bidi/WebDriverBiDi.jsm (WebDriverBiDi.jsm) content/webdriver-bidi/WebDriverBiDiConnection.jsm (WebDriverBiDiConnection.jsm) diff --git a/remote/webdriver-bidi/modules/root/script.jsm b/remote/webdriver-bidi/modules/root/script.jsm index 57a6237820b70..67681fcef1b09 100644 --- a/remote/webdriver-bidi/modules/root/script.jsm +++ b/remote/webdriver-bidi/modules/root/script.jsm @@ -297,7 +297,7 @@ class ScriptModule extends Module { } #buildReturnValue(evaluationResult) { - const rv = { realm: evaluationResult.realm.realm }; + const rv = { realm: evaluationResult.realmId }; switch (evaluationResult.evaluationStatus) { // TODO: Compare with EvaluationStatus.Normal after Bug 1774444 is fixed. case "normal": diff --git a/remote/webdriver-bidi/modules/windowglobal/script.jsm b/remote/webdriver-bidi/modules/windowglobal/script.jsm index 14ca923f11b2c..c5072ee2137ca 100644 --- a/remote/webdriver-bidi/modules/windowglobal/script.jsm +++ b/remote/webdriver-bidi/modules/windowglobal/script.jsm @@ -16,24 +16,17 @@ const { Module } = ChromeUtils.import( const lazy = {}; XPCOMUtils.defineLazyModuleGetters(lazy, { - addDebuggerToGlobal: "resource://gre/modules/jsdebugger.jsm", - deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.jsm", error: "chrome://remote/content/shared/webdriver/Errors.jsm", getFramesFromStack: "chrome://remote/content/shared/Stack.jsm", isChromeFrame: "chrome://remote/content/shared/Stack.jsm", serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.jsm", stringify: "chrome://remote/content/webdriver-bidi/RemoteValue.jsm", -}); - -XPCOMUtils.defineLazyGetter(lazy, "dbg", () => { - // eslint-disable-next-line mozilla/reject-globalThis-modification - lazy.addDebuggerToGlobal(globalThis); - return new Debugger(); + WindowRealm: "chrome://remote/content/webdriver-bidi/Realm.jsm", }); /** - * @typedef {Object} EvaluationStatus + * @typedef {string} EvaluationStatus **/ /** @@ -47,49 +40,26 @@ const EvaluationStatus = { Throw: "throw", }; -/** - * @typedef {Object} RealmType - **/ - -/** - * Enum of realm types. - * - * @readonly - * @enum {RealmType} - **/ -const RealmType = { - AudioWorklet: "audio-worklet", - DedicatedWorker: "dedicated-worker", - PaintWorklet: "paint-worklet", - ServiceWorker: "service-worker", - SharedWorker: "shared-worker", - Window: "window", - Worker: "worker", - Worklet: "worklet", -}; - -const WINDOW_GLOBAL_KEY = Symbol(); - class ScriptModule extends Module { - #globals; - #realmIds; + #defaultRealm; + #realms; constructor(messageHandler) { super(messageHandler); - // Maps sandbox names as keys to sandboxes as values and - // WINDOW_GLOBAL_KEY key to default window-global - this.#globals = new Map(); - // Maps default window-global and sandboxes as keys to realmIds as values - this.#realmIds = new WeakMap(); + this.#defaultRealm = new lazy.WindowRealm(this.messageHandler.window); + + // Maps sandbox names to instances of window realms. + this.#realms = new Map(); } destroy() { - for (const global of this.#globals.values()) { - lazy.dbg.disableAsyncStack(global.unsafeDereference()); + this.#defaultRealm.destroy(); + + for (const realm of this.#realms.values()) { + realm.destroy(); } - this.#globals = null; - this.#realmIds = null; + this.#realms = null; } #buildExceptionDetails(exception, stack) { @@ -119,9 +89,9 @@ class ScriptModule extends Module { }; } - async #buildReturnValue(rv, awaitPromise, globalObjectReference) { + async #buildReturnValue(rv, realm, awaitPromise) { let evaluationStatus, exception, result, stack; - const realm = this.#getRealm(globalObjectReference); + if ("return" in rv) { evaluationStatus = EvaluationStatus.Normal; if ( @@ -134,10 +104,12 @@ class ScriptModule extends Module { // Force wrapping the promise resolution result in a Debugger.Object // wrapper for consistency with the synchronous codepath. const asyncResult = await rv.return.unsafeDereference(); - result = globalObjectReference.makeDebuggeeValue(asyncResult); + result = realm.globalObjectReference.makeDebuggeeValue(asyncResult); } catch (asyncException) { evaluationStatus = EvaluationStatus.Throw; - exception = globalObjectReference.makeDebuggeeValue(asyncException); + exception = realm.globalObjectReference.makeDebuggeeValue( + asyncException + ); stack = rv.return.promiseResolutionSite; } } else { @@ -157,13 +129,13 @@ class ScriptModule extends Module { return { evaluationStatus, result: lazy.serialize(this.#toRawObject(result), 1), - realm, + realmId: realm.id, }; case EvaluationStatus.Throw: return { evaluationStatus, exceptionDetails: this.#buildExceptionDetails(exception, stack), - realm, + realmId: realm.id, }; default: throw new lazy.error.UnsupportedOperationError( @@ -172,67 +144,22 @@ class ScriptModule extends Module { } } - #buildRealmId(sandboxName) { - let realmId = String(this.messageHandler.innerWindowId); - if (sandboxName) { - realmId += "-sandbox-" + sandboxName; + #getRealmFromSandboxName(sandboxName) { + if (sandboxName === null) { + return this.#defaultRealm; } - return realmId; - } - - #cloneAsDebuggerObject(obj, globalObjectReference) { - // To use an object created in the priviledged Debugger compartment from - // the content compartment, we need to first clone it into the target - // compartment and then retrieve the corresponding Debugger.Object wrapper. - const proxyObject = Cu.cloneInto( - obj, - globalObjectReference.unsafeDereference() - ); - return globalObjectReference.makeDebuggeeValue(proxyObject); - } - #createSandbox() { - const win = this.messageHandler.window; - const opts = { - sameZoneAs: win, - sandboxPrototype: win, - wantComponents: false, - wantXrays: true, - }; - - return new Cu.Sandbox(win, opts); - } - - #getGlobalObjectReference(sandboxName) { - const key = sandboxName !== null ? sandboxName : WINDOW_GLOBAL_KEY; - if (this.#globals.has(key)) { - return this.#globals.get(key); + if (this.#realms.has(sandboxName)) { + return this.#realms.get(sandboxName); } - const globalObject = - key == WINDOW_GLOBAL_KEY - ? this.messageHandler.window - : this.#createSandbox(); - - const globalObjectReference = lazy.dbg.makeGlobalObjectReference( - globalObject - ); - lazy.dbg.enableAsyncStack(globalObject); - - this.#globals.set(key, globalObjectReference); - this.#realmIds.set(globalObjectReference, this.#buildRealmId(sandboxName)); + const realm = new lazy.WindowRealm(this.messageHandler.window, { + sandboxName, + }); - return globalObjectReference; - } + this.#realms.set(sandboxName, realm); - #getRealm(globalObjectReference) { - // TODO: Return an actual realm once we have proper realm support. - // See Bug 1766240. - return { - realm: this.#realmIds.get(globalObjectReference), - origin: null, - type: RealmType.Window, - }; + return realm; } #toRawObject(maybeDebuggerObject) { @@ -287,34 +214,22 @@ class ScriptModule extends Module { } = options; const deserializedArguments = - commandArguments != null - ? commandArguments.map(a => lazy.deserialize(a)) + commandArguments !== null + ? commandArguments.map(arg => lazy.deserialize(arg)) : []; const deserializedThis = - thisParameter != null ? lazy.deserialize(thisParameter) : null; - - const expression = `(${functionDeclaration}).apply(__bidi_this, __bidi_args)`; - - const globalObjectReference = this.#getGlobalObjectReference(sandboxName); - const rv = globalObjectReference.executeInGlobalWithBindings( - expression, - { - __bidi_args: this.#cloneAsDebuggerObject( - deserializedArguments, - globalObjectReference - ), - __bidi_this: this.#cloneAsDebuggerObject( - deserializedThis, - globalObjectReference - ), - }, - { - url: this.messageHandler.window.document.baseURI, - } + thisParameter !== null ? lazy.deserialize(thisParameter) : null; + + const realm = this.#getRealmFromSandboxName(sandboxName); + + const rv = realm.executeInGlobalWithBindings( + functionDeclaration, + deserializedArguments, + deserializedThis ); - return this.#buildReturnValue(rv, awaitPromise, globalObjectReference); + return this.#buildReturnValue(rv, realm, awaitPromise); } /** @@ -339,12 +254,10 @@ class ScriptModule extends Module { async evaluateExpression(options) { const { awaitPromise, expression, sandbox: sandboxName = null } = options; - const globalObjectReference = this.#getGlobalObjectReference(sandboxName); - const rv = globalObjectReference.executeInGlobal(expression, { - url: this.messageHandler.window.document.baseURI, - }); + const realm = this.#getRealmFromSandboxName(sandboxName); + const rv = realm.executeInGlobal(expression); - return this.#buildReturnValue(rv, awaitPromise, globalObjectReference); + return this.#buildReturnValue(rv, realm, awaitPromise); } }