Bug 1785092 - [remote] Added Realm class for Windows and Sandboxes. r=webdriver-reviewers,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D154973
This commit is contained in:
Henrik Skupin 2022-08-19 18:15:30 +00:00
Родитель e388e80d3e
Коммит 63bb86f569
4 изменённых файлов: 237 добавлений и 132 удалений

Просмотреть файл

@ -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<Object>} 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,
}
);
}
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -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":

Просмотреть файл

@ -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;
}
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);
#getRealmFromSandboxName(sandboxName) {
if (sandboxName === null) {
return this.#defaultRealm;
}
const globalObject =
key == WINDOW_GLOBAL_KEY
? this.messageHandler.window
: this.#createSandbox();
if (this.#realms.has(sandboxName)) {
return this.#realms.get(sandboxName);
}
const globalObjectReference = lazy.dbg.makeGlobalObjectReference(
globalObject
);
lazy.dbg.enableAsyncStack(globalObject);
const realm = new lazy.WindowRealm(this.messageHandler.window, {
sandboxName,
});
this.#globals.set(key, globalObjectReference);
this.#realmIds.set(globalObjectReference, this.#buildRealmId(sandboxName));
this.#realms.set(sandboxName, realm);
return globalObjectReference;
}
#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;
thisParameter !== null ? lazy.deserialize(thisParameter) : null;
const expression = `(${functionDeclaration}).apply(__bidi_this, __bidi_args)`;
const realm = this.#getRealmFromSandboxName(sandboxName);
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,
}
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);
}
}