forked from mirrors/gecko-dev
Depends on D158296 Differential Revision: https://phabricator.services.mozilla.com/D158297
344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
/* 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/. */
|
|
|
|
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
|
getFramesFromStack: "chrome://remote/content/shared/Stack.sys.mjs",
|
|
isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
|
|
serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
|
stringify: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
|
WindowRealm: "chrome://remote/content/webdriver-bidi/Realm.sys.mjs",
|
|
});
|
|
|
|
/**
|
|
* @typedef {string} EvaluationStatus
|
|
**/
|
|
|
|
/**
|
|
* Enum of possible evaluation states.
|
|
*
|
|
* @readonly
|
|
* @enum {EvaluationStatus}
|
|
**/
|
|
const EvaluationStatus = {
|
|
Normal: "normal",
|
|
Throw: "throw",
|
|
};
|
|
|
|
class ScriptModule extends Module {
|
|
#defaultRealm;
|
|
#realms;
|
|
|
|
constructor(messageHandler) {
|
|
super(messageHandler);
|
|
|
|
this.#defaultRealm = new lazy.WindowRealm(this.messageHandler.window);
|
|
|
|
// Maps sandbox names to instances of window realms.
|
|
this.#realms = new Map();
|
|
}
|
|
|
|
destroy() {
|
|
this.#defaultRealm.destroy();
|
|
|
|
for (const realm of this.#realms.values()) {
|
|
realm.destroy();
|
|
}
|
|
this.#realms = null;
|
|
}
|
|
|
|
#buildExceptionDetails(exception, stack, realm, resultOwnership) {
|
|
exception = this.#toRawObject(exception);
|
|
const frames = lazy.getFramesFromStack(stack) || [];
|
|
|
|
const callFrames = frames
|
|
// Remove chrome/internal frames
|
|
.filter(frame => !lazy.isChromeFrame(frame))
|
|
// Translate frames from getFramesFromStack to frames expected by
|
|
// WebDriver BiDi.
|
|
.map(frame => {
|
|
return {
|
|
columnNumber: frame.columnNumber,
|
|
functionName: frame.functionName,
|
|
lineNumber: frame.lineNumber - 1,
|
|
url: frame.filename,
|
|
};
|
|
});
|
|
|
|
return {
|
|
columnNumber: stack.column,
|
|
exception: lazy.serialize(
|
|
exception,
|
|
1,
|
|
resultOwnership,
|
|
new Map(),
|
|
realm
|
|
),
|
|
lineNumber: stack.line - 1,
|
|
stackTrace: { callFrames },
|
|
text: lazy.stringify(exception),
|
|
};
|
|
}
|
|
|
|
async #buildReturnValue(rv, realm, awaitPromise, resultOwnership) {
|
|
let evaluationStatus, exception, result, stack;
|
|
|
|
if ("return" in rv) {
|
|
evaluationStatus = EvaluationStatus.Normal;
|
|
if (
|
|
awaitPromise &&
|
|
// Only non-primitive return values are wrapped in Debugger.Object.
|
|
rv.return instanceof Debugger.Object &&
|
|
rv.return.isPromise
|
|
) {
|
|
try {
|
|
// Force wrapping the promise resolution result in a Debugger.Object
|
|
// wrapper for consistency with the synchronous codepath.
|
|
const asyncResult = await rv.return.unsafeDereference();
|
|
result = realm.globalObjectReference.makeDebuggeeValue(asyncResult);
|
|
} catch (asyncException) {
|
|
evaluationStatus = EvaluationStatus.Throw;
|
|
exception = realm.globalObjectReference.makeDebuggeeValue(
|
|
asyncException
|
|
);
|
|
stack = rv.return.promiseResolutionSite;
|
|
}
|
|
} else {
|
|
// rv.return is a Debugger.Object or a primitive.
|
|
result = rv.return;
|
|
}
|
|
} else if ("throw" in rv) {
|
|
// rv.throw will be set if the evaluation synchronously failed, either if
|
|
// the script contains a syntax error or throws an exception.
|
|
evaluationStatus = EvaluationStatus.Throw;
|
|
exception = rv.throw;
|
|
stack = rv.stack;
|
|
}
|
|
|
|
switch (evaluationStatus) {
|
|
case EvaluationStatus.Normal:
|
|
return {
|
|
evaluationStatus,
|
|
result: lazy.serialize(
|
|
this.#toRawObject(result),
|
|
1,
|
|
resultOwnership,
|
|
new Map(),
|
|
realm
|
|
),
|
|
realmId: realm.id,
|
|
};
|
|
case EvaluationStatus.Throw:
|
|
return {
|
|
evaluationStatus,
|
|
exceptionDetails: this.#buildExceptionDetails(
|
|
exception,
|
|
stack,
|
|
realm,
|
|
resultOwnership
|
|
),
|
|
realmId: realm.id,
|
|
};
|
|
default:
|
|
throw new lazy.error.UnsupportedOperationError(
|
|
`Unsupported completion value for expression evaluation`
|
|
);
|
|
}
|
|
}
|
|
|
|
#getRealm(realmId, sandboxName) {
|
|
if (realmId === null) {
|
|
return this.#getRealmFromSandboxName(sandboxName);
|
|
}
|
|
|
|
if (this.#defaultRealm.id == realmId) {
|
|
return this.#defaultRealm;
|
|
}
|
|
|
|
const sandboxRealm = Array.from(this.#realms.values()).find(
|
|
realm => realm.id === realmId
|
|
);
|
|
|
|
if (sandboxRealm) {
|
|
return sandboxRealm;
|
|
}
|
|
|
|
throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`);
|
|
}
|
|
|
|
#getRealmFromSandboxName(sandboxName) {
|
|
if (sandboxName === null) {
|
|
return this.#defaultRealm;
|
|
}
|
|
|
|
if (this.#realms.has(sandboxName)) {
|
|
return this.#realms.get(sandboxName);
|
|
}
|
|
|
|
const realm = new lazy.WindowRealm(this.messageHandler.window, {
|
|
sandboxName,
|
|
});
|
|
|
|
this.#realms.set(sandboxName, realm);
|
|
|
|
return realm;
|
|
}
|
|
|
|
#toRawObject(maybeDebuggerObject) {
|
|
if (maybeDebuggerObject instanceof Debugger.Object) {
|
|
// Retrieve the referent for the provided Debugger.object.
|
|
// See https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.object/index.html
|
|
const rawObject = maybeDebuggerObject.unsafeDereference();
|
|
|
|
// TODO: Getters for Maps and Sets iterators return "Opaque" objects and
|
|
// are not iterable. RemoteValue.jsm' serializer should handle calling
|
|
// waiveXrays on Maps/Sets/... and then unwaiveXrays on entries but since
|
|
// we serialize with maxDepth=1, calling waiveXrays once on the root
|
|
// object allows to return correctly serialized values.
|
|
return Cu.waiveXrays(rawObject);
|
|
}
|
|
|
|
// If maybeDebuggerObject was not a Debugger.Object, it is a primitive value
|
|
// which can be used as is.
|
|
return maybeDebuggerObject;
|
|
}
|
|
|
|
/**
|
|
* Call a function in the current window global.
|
|
*
|
|
* @param {Object} options
|
|
* @param {boolean} awaitPromise
|
|
* Determines if the command should wait for the return value of the
|
|
* expression to resolve, if this return value is a Promise.
|
|
* @param {Array<RemoteValue>=} commandArguments
|
|
* The arguments to pass to the function call.
|
|
* @param {string} functionDeclaration
|
|
* The body of the function to call.
|
|
* @param {string=} realmId
|
|
* The id of the realm.
|
|
* @param {OwnershipModel} resultOwnership
|
|
* The ownership model to use for the results of this evaluation.
|
|
* @param {string=} sandbox
|
|
* The name of the sandbox.
|
|
* @param {RemoteValue=} 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".
|
|
*/
|
|
async callFunctionDeclaration(options) {
|
|
const {
|
|
awaitPromise,
|
|
commandArguments = null,
|
|
functionDeclaration,
|
|
realmId = null,
|
|
resultOwnership,
|
|
sandbox: sandboxName = null,
|
|
thisParameter = null,
|
|
} = options;
|
|
|
|
const realm = this.#getRealm(realmId, sandboxName);
|
|
|
|
const deserializedArguments =
|
|
commandArguments !== null
|
|
? commandArguments.map(arg => lazy.deserialize(realm, arg))
|
|
: [];
|
|
|
|
const deserializedThis =
|
|
thisParameter !== null ? lazy.deserialize(realm, thisParameter) : null;
|
|
|
|
const rv = realm.executeInGlobalWithBindings(
|
|
functionDeclaration,
|
|
deserializedArguments,
|
|
deserializedThis
|
|
);
|
|
|
|
return this.#buildReturnValue(rv, realm, awaitPromise, resultOwnership);
|
|
}
|
|
|
|
/**
|
|
* Delete the provided handles from the realm corresponding to the provided
|
|
* sandbox name.
|
|
*
|
|
* @param {Object=} options
|
|
* @param {Array<string>} handles
|
|
* Array of handle ids to disown.
|
|
* @param {string=} realmId
|
|
* The id of the realm.
|
|
* @param {string=} sandbox
|
|
* The name of the sandbox.
|
|
*/
|
|
disownHandles(options) {
|
|
const { handles, realmId = null, sandbox: sandboxName = null } = options;
|
|
const realm = this.#getRealm(realmId, sandboxName);
|
|
for (const handle of handles) {
|
|
realm.removeObjectHandle(handle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evaluate a provided expression in the current window global.
|
|
*
|
|
* @param {Object} options
|
|
* @param {boolean} awaitPromise
|
|
* Determines if the command should wait for the return value of the
|
|
* expression to resolve, if this return value is a Promise.
|
|
* @param {string} expression
|
|
* The expression to evaluate.
|
|
* @param {string=} realmId
|
|
* The id of the realm.
|
|
* @param {OwnershipModel} resultOwnership
|
|
* The ownership model to use for the results of this evaluation.
|
|
* @param {string=} sandbox
|
|
* The name of the sandbox.
|
|
*
|
|
* @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".
|
|
*/
|
|
async evaluateExpression(options) {
|
|
const {
|
|
awaitPromise,
|
|
expression,
|
|
realmId = null,
|
|
resultOwnership,
|
|
sandbox: sandboxName = null,
|
|
} = options;
|
|
|
|
const realm = this.#getRealm(realmId, sandboxName);
|
|
const rv = realm.executeInGlobal(expression);
|
|
|
|
return this.#buildReturnValue(rv, realm, awaitPromise, resultOwnership);
|
|
}
|
|
|
|
/**
|
|
* Get realms for the current window global.
|
|
*
|
|
* @return {Array<Object>}
|
|
* - context {BrowsingContext} The browsing context, associated with the realm.
|
|
* - id {string} The realm unique identifier.
|
|
* - origin {string} The serialization of an origin.
|
|
* - sandbox {string=} The name of the sandbox.
|
|
* - type {RealmType.Window} The window realm type.
|
|
*/
|
|
getWindowRealms() {
|
|
return [this.#defaultRealm, ...this.#realms.values()].map(realm =>
|
|
realm.getInfo()
|
|
);
|
|
}
|
|
}
|
|
|
|
export const script = ScriptModule;
|