зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
e388e80d3e
Коммит
63bb86f569
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче