Bug 1750541 - [bidi] Implement basic support for "script.callFunction" command r=webdriver-reviewers,whimboo

Depends on D150245

Differential Revision: https://phabricator.services.mozilla.com/D150822
This commit is contained in:
Julian Descottes 2022-07-06 19:34:33 +00:00
parent 25cf447901
commit 87ebacdf14
4 changed files with 184 additions and 187 deletions

View File

@ -121,6 +121,90 @@ class ScriptModule extends Module {
* @property {RemoteValue} result * @property {RemoteValue} result
*/ */
/**
* Calls a provided function with given arguments and scope in the provided
* target, which is either a realm or a browsing context.
*
* @param {Object=} options
* @param {Array<RemoteValue>=} arguments
* The arguments to pass to the function call. [unsupported]
* @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} functionDeclaration
* The expression to evaluate.
* @param {OwnershipModel=} resultOwnership [unsupported]
* The ownership model to use for the results of this evaluation.
* @param {Object} target
* The target for the evaluation, which either matches the definition for
* a RealmTarget or for ContextTarget.
* @param {RemoteValue=} this
* The value of the this keyword for the function call. [unsupported]
*
* @returns {ScriptEvaluateResult}
*
* @throws {InvalidArgumentError}
* If any of the arguments has not the expected type.
* @throws {NoSuchFrameError}
* If the target cannot be found.
*/
async callFunction(options = {}) {
const {
arguments: commandArguments = null,
awaitPromise,
functionDeclaration,
resultOwnership = OwnershipModel.None,
target = {},
this: thisParameter = null,
} = options;
lazy.assert.string(
functionDeclaration,
`Expected "functionDeclaration" to be a string, got ${functionDeclaration}`
);
lazy.assert.boolean(
awaitPromise,
`Expected "awaitPromise" to be a boolean, got ${awaitPromise}`
);
this.#assertResultOwnership(resultOwnership);
if (commandArguments != null) {
lazy.assert.array(
commandArguments,
`Expected "arguments" to be an array, got ${commandArguments}`
);
throw new lazy.error.UnsupportedOperationError(
`"arguments" parameter is not supported yet`
);
}
if (thisParameter != null) {
throw new lazy.error.UnsupportedOperationError(
`"this" parameter is not supported yet`
);
}
const { contextId, realmId, sandbox } = this.#assertTarget(target);
const realm = this.#getRealmInfoFromTarget({ contextId, realmId, sandbox });
const evaluationResult = await this.messageHandler.forwardCommand({
moduleName: "script",
commandName: "callFunctionDeclaration",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
id: realm.context.id,
},
params: {
awaitPromise,
commandArguments,
functionDeclaration,
},
});
return this.#buildReturnValue(evaluationResult, realm);
}
/** /**
* Evaluate a provided expression in the provided target, which is either a * Evaluate a provided expression in the provided target, which is either a
* realm or a browsing context. * realm or a browsing context.
@ -162,6 +246,27 @@ class ScriptModule extends Module {
`Expected "awaitPromise" to be a boolean, got ${awaitPromise}` `Expected "awaitPromise" to be a boolean, got ${awaitPromise}`
); );
this.#assertResultOwnership(resultOwnership);
const { contextId, realmId, sandbox } = this.#assertTarget(target);
const realm = this.#getRealmInfoFromTarget({ contextId, realmId, sandbox });
const evaluationResult = await this.messageHandler.forwardCommand({
moduleName: "script",
commandName: "evaluateExpression",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
id: realm.context.id,
},
params: {
awaitPromise,
expression: source,
},
});
return this.#buildReturnValue(evaluationResult, realm);
}
#assertResultOwnership(resultOwnership) {
if (![OwnershipModel.None, OwnershipModel.Root].includes(resultOwnership)) { if (![OwnershipModel.None, OwnershipModel.Root].includes(resultOwnership)) {
throw new lazy.error.InvalidArgumentError( throw new lazy.error.InvalidArgumentError(
`Expected "resultOwnership" to be one of ${Object.values( `Expected "resultOwnership" to be one of ${Object.values(
@ -169,7 +274,9 @@ class ScriptModule extends Module {
)}, got ${resultOwnership}` )}, got ${resultOwnership}`
); );
} }
}
#assertTarget(target) {
lazy.assert.object( lazy.assert.object(
target, target,
`Expected "target" to be an object, got ${target}` `Expected "target" to be an object, got ${target}`
@ -180,6 +287,7 @@ class ScriptModule extends Module {
realm: realmId = null, realm: realmId = null,
sandbox = null, sandbox = null,
} = target; } = target;
if (contextId != null) { if (contextId != null) {
lazy.assert.string( lazy.assert.string(
contextId, contextId,
@ -207,20 +315,10 @@ class ScriptModule extends Module {
throw new lazy.error.InvalidArgumentError(`No context or realm provided`); throw new lazy.error.InvalidArgumentError(`No context or realm provided`);
} }
const realm = this.#getRealmInfoFromTarget({ contextId, realmId, sandbox }); return { contextId, realmId, sandbox };
const evaluationResult = await this.messageHandler.forwardCommand({ }
moduleName: "script",
commandName: "evaluateExpression",
destination: {
type: lazy.WindowGlobalMessageHandler.type,
id: realm.context.id,
},
params: {
awaitPromise,
expression: source,
},
});
#buildReturnValue(evaluationResult, realm) {
const rv = { realm: realm.realm }; const rv = { realm: realm.realm };
switch (evaluationResult.evaluationStatus) { switch (evaluationResult.evaluationStatus) {
// TODO: Compare with EvaluationStatus.Normal after Bug 1774444 is fixed. // TODO: Compare with EvaluationStatus.Normal after Bug 1774444 is fixed.

View File

@ -94,48 +94,7 @@ class ScriptModule extends Module {
}; };
} }
#toRawObject(maybeDebuggerObject) { async #buildReturnValue(rv, awaitPromise) {
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;
}
/**
* 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.
*
* @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 } = options;
const rv = this.#global.executeInGlobal(expression, {
url: this.messageHandler.window.document.baseURI,
});
let evaluationStatus, exception, result, stack; let evaluationStatus, exception, result, stack;
if ("return" in rv) { if ("return" in rv) {
evaluationStatus = EvaluationStatus.Normal; evaluationStatus = EvaluationStatus.Normal;
@ -184,6 +143,78 @@ class ScriptModule extends Module {
); );
} }
} }
#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.
*
* @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, functionDeclaration } = options;
const rv = this.#global.executeInGlobal(`(${functionDeclaration})()`, {
url: this.messageHandler.window.document.baseURI,
});
return this.#buildReturnValue(rv, awaitPromise);
}
/**
* 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.
*
* @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 } = options;
const rv = this.#global.executeInGlobal(expression, {
url: this.messageHandler.window.document.baseURI,
});
return this.#buildReturnValue(rv, awaitPromise);
}
} }
const script = ScriptModule; const script = ScriptModule;

View File

@ -1,13 +1,7 @@
[call_function.py] [call_function.py]
[test_exception]
expected: FAIL
[test_invalid_function] [test_invalid_function]
expected: FAIL expected: FAIL
[test_arrow_function]
expected: FAIL
[test_arguments] [test_arguments]
expected: FAIL expected: FAIL
@ -23,14 +17,8 @@
[test_remote_value_argument] [test_remote_value_argument]
expected: FAIL expected: FAIL
[test_async_arrow_await_promise[True\]]
expected: FAIL
[test_async_arrow_await_promise[False\]] [test_async_arrow_await_promise[False\]]
expected: FAIL expected: FAIL
[test_async_classic_await_promise[True\]]
expected: FAIL
[test_async_classic_await_promise[False\]] [test_async_classic_await_promise[False\]]
expected: FAIL expected: FAIL

View File

@ -1,82 +1,7 @@
[invalid.py] [invalid.py]
[test_params_target_invalid_type[None\]]
expected: FAIL
[test_params_target_invalid_type[False\]]
expected: FAIL
[test_params_target_invalid_type[42\]]
expected: FAIL
[test_params_target_invalid_type[target3\]]
expected: FAIL
[test_params_target_invalid_type[target4\]]
expected: FAIL
[test_params_context_invalid_type[None\]]
expected: FAIL
[test_params_context_invalid_type[False\]]
expected: FAIL
[test_params_context_invalid_type[42\]]
expected: FAIL
[test_params_context_invalid_type[context3\]]
expected: FAIL
[test_params_context_invalid_type[context4\]]
expected: FAIL
[test_params_sandbox_invalid_type[False\]]
expected: FAIL
[test_params_sandbox_invalid_type[42\]]
expected: FAIL
[test_params_sandbox_invalid_type[sandbox2\]]
expected: FAIL
[test_params_sandbox_invalid_type[sandbox3\]]
expected: FAIL
[test_params_context_unknown]
expected: FAIL
[test_params_realm_invalid_type[None\]]
expected: FAIL
[test_params_realm_invalid_type[False\]]
expected: FAIL
[test_params_realm_invalid_type[42\]]
expected: FAIL
[test_params_realm_invalid_type[realm3\]]
expected: FAIL
[test_params_realm_invalid_type[realm4\]]
expected: FAIL
[test_params_realm_unknown] [test_params_realm_unknown]
expected: FAIL expected: FAIL
[test_params_function_declaration_invalid_type[None\]]
expected: FAIL
[test_params_function_declaration_invalid_type[False\]]
expected: FAIL
[test_params_function_declaration_invalid_type[42\]]
expected: FAIL
[test_params_function_declaration_invalid_type[function_declaration3\]]
expected: FAIL
[test_params_function_declaration_invalid_type[function_declaration4\]]
expected: FAIL
[test_params_this_invalid_type[False\]] [test_params_this_invalid_type[False\]]
expected: FAIL expected: FAIL
@ -92,18 +17,6 @@
[test_params_this_invalid_type[this4\]] [test_params_this_invalid_type[this4\]]
expected: FAIL expected: FAIL
[test_params_arguments_invalid_type[False\]]
expected: FAIL
[test_params_arguments_invalid_type[SOME_STRING\]]
expected: FAIL
[test_params_arguments_invalid_type[42\]]
expected: FAIL
[test_params_arguments_invalid_type[arguments3\]]
expected: FAIL
[test_params_single_argument_invalid_type[False\]] [test_params_single_argument_invalid_type[False\]]
expected: FAIL expected: FAIL
@ -118,36 +31,3 @@
[test_params_single_argument_invalid_type[argument4\]] [test_params_single_argument_invalid_type[argument4\]]
expected: FAIL expected: FAIL
[test_params_await_promise_invalid_type[None\]]
expected: FAIL
[test_params_await_promise_invalid_type[False\]]
expected: FAIL
[test_params_await_promise_invalid_type[0\]]
expected: FAIL
[test_params_await_promise_invalid_type[42\]]
expected: FAIL
[test_params_await_promise_invalid_type[await_promise4\]]
expected: FAIL
[test_params_await_promise_invalid_type[await_promise5\]]
expected: FAIL
[test_params_result_ownership_invalid_value[False\]]
expected: FAIL
[test_params_result_ownership_invalid_value[_UNKNOWN_\]]
expected: FAIL
[test_params_result_ownership_invalid_value[42\]]
expected: FAIL
[test_params_result_ownership_invalid_value[result_ownership3\]]
expected: FAIL
[test_params_result_ownership_invalid_value[result_ownership4\]]
expected: FAIL