mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-03 23:30:46 +00:00
Bug 1623581 - [remote] Refactor Runtime.evaluate and Runtime.callFunctionOn browser chrome tests. r=remote-protocol-reviewers,maja_zf
Tests are mixing APIs between each other, which this patch removes. Also error messages have been adjusted for both methods to be on par with Chrome. Differential Revision: https://phabricator.services.mozilla.com/D67840 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
9f05df43d6
commit
bc1a51934e
@ -96,29 +96,6 @@ class Runtime extends ContentProcessDomain {
|
||||
}
|
||||
}
|
||||
|
||||
evaluate({ expression, contextId = null } = {}) {
|
||||
let context;
|
||||
if (contextId) {
|
||||
context = this.contexts.get(contextId);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`Unable to find execution context with id: ${contextId}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
context = this._getDefaultContextForWindow();
|
||||
}
|
||||
|
||||
if (typeof expression != "string") {
|
||||
throw new Error(
|
||||
`Expecting 'expression' attribute to be a string. ` +
|
||||
`But was: ${typeof expression}`
|
||||
);
|
||||
}
|
||||
|
||||
return context.evaluate(expression);
|
||||
}
|
||||
|
||||
releaseObject({ objectId }) {
|
||||
let context = null;
|
||||
for (const ctx of this.contexts.values()) {
|
||||
@ -133,53 +110,138 @@ class Runtime extends ContentProcessDomain {
|
||||
context.releaseObject(objectId);
|
||||
}
|
||||
|
||||
callFunctionOn(request) {
|
||||
/**
|
||||
* Calls function with given declaration on the given object.
|
||||
*
|
||||
* Object group of the result is inherited from the target object.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.functionDeclaration
|
||||
* Declaration of the function to call.
|
||||
* @param {Array.<Object>=} options.arguments
|
||||
* Call arguments. All call arguments must belong to the same
|
||||
* JavaScript world as the target object.
|
||||
* @param {boolean=} options.awaitPromise
|
||||
* Whether execution should `await` for resulting value
|
||||
* and return once awaited promise is resolved.
|
||||
* @param {number=} options.executionContextId
|
||||
* Specifies execution context which global object will be used
|
||||
* to call function on. Either executionContextId or objectId
|
||||
* should be specified.
|
||||
* @param {string=} options.objectId
|
||||
* Identifier of the object to call function on.
|
||||
* Either objectId or executionContextId should be specified.
|
||||
* @param {boolean=} options.returnByValue
|
||||
* Whether the result is expected to be a JSON object
|
||||
* which should be sent by value.
|
||||
*
|
||||
* @return {Object.<RemoteObject, ExceptionDetails>}
|
||||
*/
|
||||
callFunctionOn(options = {}) {
|
||||
if (typeof options.functionDeclaration != "string") {
|
||||
throw new TypeError("functionDeclaration: string value expected");
|
||||
}
|
||||
if (
|
||||
typeof options.arguments != "undefined" &&
|
||||
!Array.isArray(options.arguments)
|
||||
) {
|
||||
throw new TypeError("arguments: array value expected");
|
||||
}
|
||||
if (!["undefined", "boolean"].includes(typeof options.awaitPromise)) {
|
||||
throw new TypeError("awaitPromise: boolean value expected");
|
||||
}
|
||||
if (!["undefined", "number"].includes(typeof options.executionContextId)) {
|
||||
throw new TypeError("executionContextId: number value expected");
|
||||
}
|
||||
if (!["undefined", "string"].includes(typeof options.objectId)) {
|
||||
throw new TypeError("objectId: string value expected");
|
||||
}
|
||||
if (!["undefined", "boolean"].includes(typeof options.returnByValue)) {
|
||||
throw new TypeError("returnByValue: boolean value expected");
|
||||
}
|
||||
|
||||
if (
|
||||
typeof options.executionContextId == "undefined" &&
|
||||
typeof options.objectId == "undefined"
|
||||
) {
|
||||
throw new Error(
|
||||
"Either objectId or executionContextId must be specified"
|
||||
);
|
||||
}
|
||||
|
||||
let context = null;
|
||||
// When an `objectId` is passed, we want to execute the function of a given object
|
||||
// So we first have to find its ExecutionContext
|
||||
if (request.objectId) {
|
||||
if (options.objectId) {
|
||||
for (const ctx of this.contexts.values()) {
|
||||
if (ctx.hasRemoteObject(request.objectId)) {
|
||||
if (ctx.hasRemoteObject(options.objectId)) {
|
||||
context = ctx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`Unable to get the context for object with id: ${request.objectId}`
|
||||
`Unable to get the context for object with id: ${options.objectId}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
context = this.contexts.get(request.executionContextId);
|
||||
context = this.contexts.get(options.executionContextId);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`Unable to find execution context with id: ${request.executionContextId}`
|
||||
);
|
||||
throw new Error("Cannot find context with specified id");
|
||||
}
|
||||
}
|
||||
if (typeof request.functionDeclaration != "string") {
|
||||
throw new Error(
|
||||
"Expect 'functionDeclaration' attribute to be passed and be a string"
|
||||
);
|
||||
}
|
||||
if (request.arguments && !Array.isArray(request.arguments)) {
|
||||
throw new Error("Expect 'arguments' to be an array");
|
||||
}
|
||||
if (request.returnByValue && typeof request.returnByValue != "boolean") {
|
||||
throw new Error("Expect 'returnByValue' to be a boolean");
|
||||
}
|
||||
if (request.awaitPromise && typeof request.awaitPromise != "boolean") {
|
||||
throw new Error("Expect 'awaitPromise' to be a boolean");
|
||||
}
|
||||
|
||||
return context.callFunctionOn(
|
||||
request.functionDeclaration,
|
||||
request.arguments,
|
||||
request.returnByValue,
|
||||
request.awaitPromise,
|
||||
request.objectId
|
||||
options.functionDeclaration,
|
||||
options.arguments,
|
||||
options.returnByValue,
|
||||
options.awaitPromise,
|
||||
options.objectId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate expression on global object.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.expression
|
||||
* Expression to evaluate.
|
||||
* @param {boolean=} options.awaitPromise [unsupported]
|
||||
* Whether execution should `await` for resulting value
|
||||
* and return once awaited promise is resolved.
|
||||
* @param {number=} options.contextId
|
||||
* Specifies in which execution context to perform evaluation.
|
||||
* If the parameter is omitted the evaluation will be performed
|
||||
* in the context of the inspected page.
|
||||
* @param {boolean=} options.returnByValue
|
||||
* Whether the result is expected to be a JSON object
|
||||
* that should be sent by value. Defaults to false.
|
||||
* @param {boolean=} options.userGesture [unsupported]
|
||||
* Whether execution should be treated as initiated by user in the UI.
|
||||
*
|
||||
* @return {Object<RemoteObject, exceptionDetails>}
|
||||
* The evaluation result, and optionally exception details.
|
||||
*/
|
||||
evaluate(options = {}) {
|
||||
const { expression, contextId } = options;
|
||||
|
||||
if (typeof expression != "string") {
|
||||
throw new Error("expression: string value expected");
|
||||
}
|
||||
|
||||
let context;
|
||||
if (typeof contextId != "undefined") {
|
||||
context = this.contexts.get(contextId);
|
||||
if (!context) {
|
||||
throw new Error("Cannot find context with specified id");
|
||||
}
|
||||
} else {
|
||||
context = this._getDefaultContextForWindow();
|
||||
}
|
||||
|
||||
return context.evaluate(expression);
|
||||
}
|
||||
|
||||
getProperties({ objectId, ownProperties }) {
|
||||
for (const ctx of this.contexts.values()) {
|
||||
const obj = ctx.getRemoteObject(objectId);
|
||||
|
@ -3,132 +3,575 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the Runtime.callFunctionOn
|
||||
// See also browser_runtime_evaluate, which covers basic usages of this method.
|
||||
const TEST_DOC = toDataURL("default-test-page");
|
||||
|
||||
add_task(async function({ client }) {
|
||||
const firstContext = await testRuntimeEnable(client);
|
||||
const contextId = firstContext.id;
|
||||
await testObjectReferences(client, contextId);
|
||||
await testExceptions(client, contextId);
|
||||
await testReturnByValue(client, contextId);
|
||||
await testAwaitPromise(client, contextId);
|
||||
await testObjectId(client, contextId);
|
||||
add_task(async function FunctionDeclarationMissing({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn();
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("functionDeclaration: string value expected"));
|
||||
});
|
||||
|
||||
async function testRuntimeEnable({ Runtime }) {
|
||||
// Enable watching for new execution context
|
||||
await Runtime.enable();
|
||||
info("Runtime domain has been enabled");
|
||||
add_task(async function functionDeclarationInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// Calling Runtime.enable will emit executionContextCreated for the existing contexts
|
||||
const { context } = await Runtime.executionContextCreated();
|
||||
ok(!!context.id, "The execution context has an id");
|
||||
ok(context.auxData.isDefault, "The execution context is the default one");
|
||||
ok(!!context.auxData.frameId, "The execution context has a frame id set");
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
return context;
|
||||
}
|
||||
for (const functionDeclaration of [null, true, 1, [], {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({ functionDeclaration, executionContextId });
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("functionDeclaration: string value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
async function testObjectReferences({ Runtime }, contextId) {
|
||||
// First create a JS object remotely via Runtime.evaluate
|
||||
const { result } = await Runtime.evaluate({
|
||||
contextId,
|
||||
expression: "({ foo: 1 })",
|
||||
add_task(async function functionDeclarationGetCurrentLocation({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await loadURL(TEST_DOC);
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => location.href",
|
||||
executionContextId,
|
||||
});
|
||||
is(result.value, TEST_DOC, "Works against the test page");
|
||||
});
|
||||
|
||||
add_task(async function argumentsInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
for (const args of [null, true, 1, "foo", {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
arguments: args,
|
||||
executionContextId,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("arguments: array value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function argumentsPrimitiveTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
for (const args of [null, true, 1, "foo", {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
arguments: args,
|
||||
executionContextId,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("arguments: array value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
for (const awaitPromise of [null, 1, "foo", [], {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
awaitPromise,
|
||||
executionContextId,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("awaitPromise: boolean value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseResolve({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => Promise.resolve(42)",
|
||||
awaitPromise: true,
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "number", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for numbers");
|
||||
is(result.value, 42, "The result is the promise's resolution");
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseDelayedResolve({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: true,
|
||||
executionContextId,
|
||||
});
|
||||
is(result.type, "number", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for numbers");
|
||||
is(result.value, 42, "The result is the promise's resolution");
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseReject({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => Promise.reject(42)",
|
||||
awaitPromise: true,
|
||||
executionContextId,
|
||||
});
|
||||
// TODO: Implement all values for exceptionDetails (bug 1548480)
|
||||
is(
|
||||
exceptionDetails.exception.value,
|
||||
42,
|
||||
"The result is the promise's rejection"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseDelayedReject({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
functionDeclaration:
|
||||
"() => new Promise((_,r) => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: true,
|
||||
executionContextId,
|
||||
});
|
||||
is(
|
||||
exceptionDetails.exception.value,
|
||||
42,
|
||||
"The result is the promise's rejection"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseResolveWithoutWait({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => Promise.resolve(42)",
|
||||
awaitPromise: false,
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.value, "We do not receive any value");
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseDelayedResolveWithoutWait({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: false,
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.value, "We do not receive any value");
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseRejectWithoutWait({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => Promise.reject(42)",
|
||||
awaitPromise: false,
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.exceptionDetails, "We do not receive any exception");
|
||||
});
|
||||
|
||||
add_task(async function awaitPromiseDelayedRejectWithoutWait({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration:
|
||||
"() => new Promise((_,r) => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: false,
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.exceptionDetails, "We do not receive any exception");
|
||||
});
|
||||
|
||||
add_task(async function executionContextIdNorObjectIdSpecified({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(
|
||||
errorThrown.includes(
|
||||
"Either objectId or executionContextId must be specified"
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function executionContextIdInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
for (const executionContextId of [null, true, "foo", [], {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
executionContextId,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("executionContextId: number value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function executionContextIdInvalidValue({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
executionContextId: -1,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("Cannot find context with specified id"));
|
||||
});
|
||||
|
||||
add_task(async function objectIdInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
for (const objectId of [null, true, 1, [], {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({ functionDeclaration: "", objectId });
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("objectId: string value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function objectId({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
// First create an object
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => ({ foo: 42 })",
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for objects");
|
||||
ok(!!result.objectId, "Got an object id");
|
||||
|
||||
// Then increment the `foo` attribute of this JS object, while returning this
|
||||
// attribute value
|
||||
// Then apply a method on this object
|
||||
const { result: result2 } = await Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: "arg => ++arg.foo",
|
||||
arguments: [{ objectId: result.objectId }],
|
||||
functionDeclaration: "function () { return this.foo; }",
|
||||
executionContextId,
|
||||
objectId: result.objectId,
|
||||
});
|
||||
|
||||
is(result2.type, "number", "The type is correct");
|
||||
is(result2.subtype, null, "The subtype is null for numbers");
|
||||
is(
|
||||
result2.value,
|
||||
2,
|
||||
"Updated the existing object and returned the incremented value"
|
||||
is(result2.value, 42, "Expected value returned");
|
||||
});
|
||||
|
||||
add_task(async function objectIdArgumentReference({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
// First create a remote JS object
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => ({ foo: 1 })",
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for objects");
|
||||
ok(!!result.objectId, "Got an object id");
|
||||
|
||||
// Then increment the `foo` attribute of this JS object,
|
||||
// while returning this attribute value
|
||||
const { result: result2 } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "arg => ++arg.foo",
|
||||
arguments: [{ objectId: result.objectId }],
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
result2,
|
||||
{
|
||||
type: "number",
|
||||
value: 2,
|
||||
},
|
||||
"The result has the expected type and value"
|
||||
);
|
||||
|
||||
// Finally, try to pass this JS object and get it back. Ensure that it returns
|
||||
// the same object id. Also increment the attribute again.
|
||||
// Finally, try to pass this JS object and get it back. Ensure that it
|
||||
// returns the same object id. Also increment the attribute again.
|
||||
const { result: result3 } = await Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: "arg => { arg.foo++; return arg; }",
|
||||
arguments: [{ objectId: result.objectId }],
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
is(result3.type, "object", "The type is correct");
|
||||
is(result3.subtype, null, "The subtype is null for objects");
|
||||
// Remote object are not having unique id. So you may have multiple object ids
|
||||
// Remote objects don't have unique ids. So you may have multiple object ids
|
||||
// that reference the same remote object
|
||||
ok(!!result3.objectId, "Got an object id");
|
||||
isnot(result3.objectId, result.objectId, "The object id is stable");
|
||||
isnot(result3.objectId, result.objectId, "The object id is different");
|
||||
|
||||
// Assert that we can still access this object and that its foo attribute
|
||||
// has been incremented. Use the second object id we got from previous call
|
||||
// to callFunctionOn.
|
||||
const { result: result4 } = await Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: "arg => arg.foo",
|
||||
arguments: [{ objectId: result3.objectId }],
|
||||
});
|
||||
is(result4.type, "number", "The type is correct");
|
||||
is(result4.subtype, null, "The subtype is null for numbers");
|
||||
is(
|
||||
result4.value,
|
||||
3,
|
||||
"Updated the existing object and returned the incremented value"
|
||||
);
|
||||
}
|
||||
|
||||
async function testExceptions({ Runtime }, executionContextId) {
|
||||
// Test error when evaluating the function
|
||||
let { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "doesNotExists()",
|
||||
});
|
||||
is(
|
||||
exceptionDetails.text,
|
||||
"doesNotExists is not defined",
|
||||
"Exception message is passed to the client"
|
||||
);
|
||||
|
||||
// Test error when calling the function
|
||||
({ exceptionDetails } = await Runtime.callFunctionOn({
|
||||
Assert.deepEqual(
|
||||
result4,
|
||||
{
|
||||
type: "number",
|
||||
value: 3,
|
||||
},
|
||||
"The result has the expected type and value"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function returnAsObjectTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const expressions = [
|
||||
{ expression: "({foo:true})", type: "object", subtype: null },
|
||||
{ expression: "Symbol('foo')", type: "symbol", subtype: null },
|
||||
{ expression: "new Promise(()=>{})", type: "object", subtype: "promise" },
|
||||
{ expression: "new Int8Array(8)", type: "object", subtype: "typedarray" },
|
||||
{ expression: "new WeakMap()", type: "object", subtype: "weakmap" },
|
||||
{ expression: "new WeakSet()", type: "object", subtype: "weakset" },
|
||||
{ expression: "new Map()", type: "object", subtype: "map" },
|
||||
{ expression: "new Set()", type: "object", subtype: "set" },
|
||||
{ expression: "/foo/", type: "object", subtype: "regexp" },
|
||||
{ expression: "[1, 2]", type: "object", subtype: "array" },
|
||||
{ expression: "new Proxy({}, {})", type: "object", subtype: "proxy" },
|
||||
{ expression: "new Date()", type: "object", subtype: "date" },
|
||||
{ expression: "document", type: "object", subtype: "node" },
|
||||
{ expression: "document.documentElement", type: "object", subtype: "node" },
|
||||
{
|
||||
expression: "document.createElement('div')",
|
||||
type: "object",
|
||||
subtype: "node",
|
||||
},
|
||||
];
|
||||
|
||||
for (const { expression, type, subtype } of expressions) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: `() => ${expression}`,
|
||||
executionContextId,
|
||||
});
|
||||
is(
|
||||
result.subtype,
|
||||
subtype,
|
||||
`Evaluating '${expression}' has the expected subtype`
|
||||
);
|
||||
is(result.type, type, "The type is correct");
|
||||
ok(!!result.objectId, "Got an object id");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnAsObjectPrimitiveTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const expressions = [42, "42", true, 4.2];
|
||||
for (const expression of expressions) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: `() => ${JSON.stringify(expression)}`,
|
||||
executionContextId,
|
||||
});
|
||||
is(result.value, expression, `Evaluating primitive '${expression}' works`);
|
||||
is(result.type, typeof expression, `${expression} type is correct`);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnAsObjectNotSerializable({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const notSerializableNumbers = {
|
||||
number: ["-0", "NaN", "Infinity", "-Infinity"],
|
||||
bigint: ["42n"],
|
||||
};
|
||||
|
||||
for (const type in notSerializableNumbers) {
|
||||
for (const expression of notSerializableNumbers[type]) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: `() => ${expression}`,
|
||||
executionContextId,
|
||||
});
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type,
|
||||
unserializableValue: expression,
|
||||
description: expression,
|
||||
},
|
||||
`Evaluating unserializable '${expression}' works`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// `null` is special as it has its own subtype, is of type 'object'
|
||||
// but is returned as a value, without an `objectId` attribute
|
||||
add_task(async function returnAsObjectNull({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => null",
|
||||
executionContextId,
|
||||
functionDeclaration: "() => doesNotExists()",
|
||||
}));
|
||||
is(
|
||||
exceptionDetails.text,
|
||||
"doesNotExists is not defined",
|
||||
"Exception message is passed to the client"
|
||||
});
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: "object",
|
||||
subtype: "null",
|
||||
value: null,
|
||||
},
|
||||
"Null type is correct"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// undefined doesn't work with JSON.stringify, so test it independently
|
||||
add_task(async function returnAsObjectUndefined({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => undefined",
|
||||
executionContextId,
|
||||
});
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: "undefined",
|
||||
},
|
||||
"Undefined type is correct"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function returnByValueInvalidTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
for (const returnByValue of [null, 1, "foo", [], {}]) {
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "",
|
||||
executionContextId,
|
||||
returnByValue,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("returnByValue: boolean value expected"));
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnByValue({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
async function testReturnByValue({ Runtime }, executionContextId) {
|
||||
const values = [
|
||||
null,
|
||||
42,
|
||||
"42",
|
||||
42.0,
|
||||
"42",
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
{ foo: true },
|
||||
{ foo: { bar: 42, str: "str", array: [1, 2, 3] } },
|
||||
[42, "42", true],
|
||||
[{ foo: true }],
|
||||
];
|
||||
|
||||
for (const value of values) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: `() => (${JSON.stringify(value)})`,
|
||||
executionContextId,
|
||||
functionDeclaration: "() => (" + JSON.stringify(value) + ")",
|
||||
returnByValue: true,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
@ -139,21 +582,26 @@ async function testReturnByValue({ Runtime }, executionContextId) {
|
||||
"The returned value is the same than the input value"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Test non-serializable values
|
||||
const nonSerializableNumbers = {
|
||||
add_task(async function returnByValueNotSerializable({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const notSerializableNumbers = {
|
||||
number: ["-0", "NaN", "Infinity", "-Infinity"],
|
||||
bigint: ["42n"],
|
||||
};
|
||||
|
||||
for (const type in nonSerializableNumbers) {
|
||||
for (const unserializableValue of nonSerializableNumbers[type]) {
|
||||
for (const type in notSerializableNumbers) {
|
||||
for (const unserializableValue of notSerializableNumbers[type]) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: `() => (${unserializableValue})`,
|
||||
executionContextId,
|
||||
functionDeclaration: "a => a",
|
||||
arguments: [{ unserializableValue }],
|
||||
returnByValue: true,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
@ -165,106 +613,190 @@ async function testReturnByValue({ Runtime }, executionContextId) {
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Test undefined individually as JSON.stringify doesn't return a string
|
||||
add_task(async function returnByValueUndefined({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
// Test undefined individually as JSON.stringify doesn't return a string
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => {}",
|
||||
executionContextId,
|
||||
returnByValue: true,
|
||||
});
|
||||
is(result.type, "undefined", "The returned value is undefined");
|
||||
}
|
||||
|
||||
async function testAwaitPromise({ Runtime }, executionContextId) {
|
||||
// First assert promise resolution with awaitPromise
|
||||
let { result } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => Promise.resolve(42)",
|
||||
awaitPromise: true,
|
||||
});
|
||||
is(result.type, "number", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for numbers");
|
||||
is(result.value, 42, "The result is the promise's resolution");
|
||||
|
||||
// Also test promise rejection with awaitPromise
|
||||
let { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => Promise.reject(42)",
|
||||
awaitPromise: true,
|
||||
});
|
||||
is(
|
||||
exceptionDetails.exception.value,
|
||||
42,
|
||||
"The result is the promise's rejection"
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: "undefined",
|
||||
},
|
||||
"Undefined type is correct"
|
||||
);
|
||||
});
|
||||
|
||||
// Then check delayed promise resolution
|
||||
({ result } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: true,
|
||||
}));
|
||||
is(result.type, "number", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for numbers");
|
||||
is(result.value, 42, "The result is the promise's resolution");
|
||||
add_task(async function returnByValueArguments({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// And delayed promise rejection
|
||||
({ exceptionDetails } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration:
|
||||
"() => new Promise((_,r) => setTimeout(() => r(42), 0))",
|
||||
awaitPromise: true,
|
||||
}));
|
||||
is(
|
||||
exceptionDetails.exception.value,
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const values = [
|
||||
42,
|
||||
"The result is the promise's rejection"
|
||||
);
|
||||
42.0,
|
||||
"42",
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
{ foo: true },
|
||||
{ foo: { bar: 42, str: "str", array: [1, 2, 3] } },
|
||||
[42, "42", true],
|
||||
[{ foo: true }],
|
||||
];
|
||||
|
||||
// Finally assert promise resolution without awaitPromise
|
||||
({ result } = await Runtime.callFunctionOn({
|
||||
for (const value of values) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "a => a",
|
||||
arguments: [{ value }],
|
||||
executionContextId,
|
||||
returnByValue: true,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: typeof value,
|
||||
value,
|
||||
description: value != null ? value.toString() : value,
|
||||
},
|
||||
"The returned value is the same than the input value"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnByValueArgumentsNotSerializable({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const notSerializableNumbers = {
|
||||
number: ["-0", "NaN", "Infinity", "-Infinity"],
|
||||
bigint: ["42n"],
|
||||
};
|
||||
|
||||
for (const type in notSerializableNumbers) {
|
||||
for (const unserializableValue of notSerializableNumbers[type]) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "a => a",
|
||||
arguments: [{ unserializableValue }],
|
||||
executionContextId,
|
||||
returnByValue: true,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type,
|
||||
unserializableValue,
|
||||
description: unserializableValue,
|
||||
},
|
||||
"The returned value is the same than the input value"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnByValueArgumentsSymbol({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
functionDeclaration: "a => a",
|
||||
arguments: [{ unserializableValue: "Symbol('42')" }],
|
||||
executionContextId,
|
||||
returnByValue: true,
|
||||
});
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown, "Symbol cannot be returned as value");
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsJavascriptError({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "doesNotExists()",
|
||||
executionContextId,
|
||||
functionDeclaration: "() => Promise.resolve(42)",
|
||||
awaitPromise: false,
|
||||
}));
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.value, "We do not receive any value");
|
||||
|
||||
// As well as promise rejection without awaitPromise
|
||||
({ result } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => Promise.reject(42)",
|
||||
awaitPromise: false,
|
||||
}));
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, "promise", "The subtype is promise");
|
||||
ok(!!result.objectId, "We got the object id for the promise");
|
||||
ok(!result.exceptionDetails, "We do not receive any exception");
|
||||
}
|
||||
|
||||
async function testObjectId({ Runtime }, contextId) {
|
||||
// First create an object via Runtime.evaluate
|
||||
const { result } = await Runtime.evaluate({
|
||||
contextId,
|
||||
expression: "({ foo: 42 })",
|
||||
});
|
||||
is(result.type, "object", "The type is correct");
|
||||
is(result.subtype, null, "The subtype is null for objects");
|
||||
ok(!!result.objectId, "Got an object id");
|
||||
|
||||
// Then apply a method on this object
|
||||
const { result: result2 } = await Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: "function () { return this.foo; }",
|
||||
objectId: result.objectId,
|
||||
});
|
||||
is(result2.type, "number", "The type is correct");
|
||||
is(result2.subtype, null, "The subtype is null for numbers");
|
||||
is(
|
||||
result2.value,
|
||||
42,
|
||||
"We have a good proof that the function was ran against the target object"
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
text: "doesNotExists is not defined",
|
||||
},
|
||||
"Javascript error is passed to the client"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsThrowError({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => { throw new Error('foo') }",
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
text: "foo",
|
||||
},
|
||||
"Exception details are passed to the client"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsThrowValue({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
const executionContextId = await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.callFunctionOn({
|
||||
functionDeclaration: "() => { throw 'foo' }",
|
||||
executionContextId,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
exception: {
|
||||
type: "string",
|
||||
value: "foo",
|
||||
},
|
||||
},
|
||||
"Exception details are passed as a RemoteObject"
|
||||
);
|
||||
});
|
||||
|
||||
async function enableRuntime(client) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// Enable watching for new execution context
|
||||
await Runtime.enable();
|
||||
info("Runtime domain has been enabled");
|
||||
|
||||
// Calling Runtime.enable will emit executionContextCreated for the existing contexts
|
||||
const { context } = await Runtime.executionContextCreated();
|
||||
ok(!!context.id, "The execution context has an id");
|
||||
ok(context.auxData.isDefault, "The execution context is the default one");
|
||||
ok(!!context.auxData.frameId, "The execution context has a frame id set");
|
||||
|
||||
return context.id;
|
||||
}
|
||||
|
@ -3,196 +3,48 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the Runtime execution context events
|
||||
|
||||
const TEST_DOC = toDataURL("default-test-page");
|
||||
|
||||
add_task(async function({ client }) {
|
||||
await loadURL(TEST_DOC);
|
||||
|
||||
const firstContext = await testRuntimeEnable(client);
|
||||
const contextId = firstContext.id;
|
||||
|
||||
await testEvaluate(client);
|
||||
await testEvaluateWithContextId(client, contextId);
|
||||
await testEvaluateInvalidContextId(client, contextId);
|
||||
|
||||
await testCallFunctionOn(client, contextId);
|
||||
await testCallFunctionOnInvalidContextId(client, contextId);
|
||||
|
||||
add_task(async function contextIdInvalidValue({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// First test Runtime.evaluate, which accepts an JS expression string.
|
||||
// This string may have instructions separated with `;` before ending
|
||||
// with a JS value that is returned as a CDP `RemoteObject`.
|
||||
function runtimeEvaluate(expression) {
|
||||
return Runtime.evaluate({ contextId, expression });
|
||||
}
|
||||
|
||||
// Then test Runtime.callFunctionOn, which accepts a JS string, but this
|
||||
// time, it has to be a function. In this first test against callFunctionOn,
|
||||
// we only assert the returned type and ignore the arguments.
|
||||
function callFunctionOn(expression, instruction = false) {
|
||||
if (instruction) {
|
||||
return Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: `() => { ${expression} }`,
|
||||
});
|
||||
}
|
||||
return Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: `() => ${expression}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Finally, run another test against Runtime.callFunctionOn in order to assert
|
||||
// the arguments being passed to the executed function.
|
||||
async function callFunctionOnArguments(expression, instruction = false) {
|
||||
// First evaluate the expression via Runtime.evaluate in order to generate the
|
||||
// CDP's `RemoteObject` for the given expression. A previous test already
|
||||
// asserted the returned value of Runtime.evaluate, so we can trust this.
|
||||
const { result } = await Runtime.evaluate({ contextId, expression });
|
||||
|
||||
// We then pass this RemoteObject as an argument to Runtime.callFunctionOn.
|
||||
return Runtime.callFunctionOn({
|
||||
executionContextId: contextId,
|
||||
functionDeclaration: `arg => arg`,
|
||||
arguments: [result],
|
||||
});
|
||||
}
|
||||
|
||||
for (const fun of [
|
||||
runtimeEvaluate,
|
||||
callFunctionOn,
|
||||
callFunctionOnArguments,
|
||||
]) {
|
||||
info("Test " + fun.name);
|
||||
await testPrimitiveTypes(fun);
|
||||
await testUnserializable(fun);
|
||||
await testObjectTypes(fun);
|
||||
|
||||
// Tests involving an instruction (exception throwing, or errors) are not
|
||||
// using any argument. So ignore these particular tests.
|
||||
if (fun != callFunctionOnArguments) {
|
||||
await testThrowError(fun);
|
||||
await testThrowValue(fun);
|
||||
await testJSError(fun);
|
||||
}
|
||||
let errorThrown = "";
|
||||
try {
|
||||
await Runtime.evaluate({ expression: "", contextId: -1 });
|
||||
} catch (e) {
|
||||
errorThrown = e.message;
|
||||
}
|
||||
ok(errorThrown.includes("Cannot find context with specified id"));
|
||||
});
|
||||
|
||||
async function testRuntimeEnable({ Runtime }) {
|
||||
// Enable watching for new execution context
|
||||
await Runtime.enable();
|
||||
info("Runtime domain has been enabled");
|
||||
add_task(async function contextIdNotSpecified({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// Calling Runtime.enable will emit executionContextCreated for the existing contexts
|
||||
const { context } = await Runtime.executionContextCreated();
|
||||
ok(!!context.id, "The execution context has an id");
|
||||
ok(context.auxData.isDefault, "The execution context is the default one");
|
||||
ok(!!context.auxData.frameId, "The execution context has a frame id set");
|
||||
await loadURL(TEST_DOC);
|
||||
await enableRuntime(client);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async function testEvaluate({ Runtime }) {
|
||||
const { result } = await Runtime.evaluate({ expression: "location.href" });
|
||||
is(
|
||||
result.value,
|
||||
TEST_DOC,
|
||||
"Runtime.evaluate works against the current document"
|
||||
);
|
||||
}
|
||||
is(result.value, TEST_DOC, "Works against the current document");
|
||||
});
|
||||
|
||||
add_task(async function contextIdSpecified({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await loadURL(TEST_DOC);
|
||||
const contextId = await enableRuntime(client);
|
||||
|
||||
async function testEvaluateWithContextId({ Runtime }, contextId) {
|
||||
const { result } = await Runtime.evaluate({
|
||||
contextId,
|
||||
expression: "location.href",
|
||||
contextId,
|
||||
});
|
||||
is(
|
||||
result.value,
|
||||
TEST_DOC,
|
||||
"Runtime.evaluate works against the targetted document"
|
||||
);
|
||||
}
|
||||
is(result.value, TEST_DOC, "Works against the targetted document");
|
||||
});
|
||||
|
||||
async function testEvaluateInvalidContextId({ Runtime }, contextId) {
|
||||
try {
|
||||
await Runtime.evaluate({ contextId: -1, expression: "" });
|
||||
ok(false, "Evaluate shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.includes("Unable to find execution context with id: -1"),
|
||||
"Throws with the expected error message"
|
||||
);
|
||||
}
|
||||
}
|
||||
add_task(async function returnAsObjectTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
async function testCallFunctionOn({ Runtime }, executionContextId) {
|
||||
const { result } = await Runtime.callFunctionOn({
|
||||
executionContextId,
|
||||
functionDeclaration: "() => location.href",
|
||||
});
|
||||
is(
|
||||
result.value,
|
||||
TEST_DOC,
|
||||
"Runtime.callFunctionOn works and is against the test page"
|
||||
);
|
||||
}
|
||||
await enableRuntime(client);
|
||||
|
||||
async function testCallFunctionOnInvalidContextId(
|
||||
{ Runtime },
|
||||
executionContextId
|
||||
) {
|
||||
try {
|
||||
await Runtime.callFunctionOn({
|
||||
executionContextId: -1,
|
||||
functionDeclaration: "",
|
||||
});
|
||||
ok(false, "callFunctionOn shouldn't pass");
|
||||
} catch (e) {
|
||||
ok(
|
||||
e.message.includes("Unable to find execution context with id: -1"),
|
||||
"Throws with the expected error message"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function testPrimitiveTypes(testFunction) {
|
||||
const expressions = [42, "42", true, 4.2];
|
||||
for (const expression of expressions) {
|
||||
const { result } = await testFunction(JSON.stringify(expression));
|
||||
is(result.value, expression, `Evaluating primitive '${expression}' works`);
|
||||
is(result.type, typeof expression, `${expression} type is correct`);
|
||||
}
|
||||
|
||||
// undefined doesn't work with JSON.stringify, so test it independently
|
||||
let { result } = await testFunction("undefined");
|
||||
is(result.value, undefined, "undefined works");
|
||||
is(result.type, "undefined", "undefined type is correct");
|
||||
|
||||
// `null` is special as it has its own subtype, is of type 'object' but is returned as
|
||||
// a value, without an `objectId` attribute
|
||||
({ result } = await testFunction("null"));
|
||||
is(result.value, null, "Evaluating 'null' works");
|
||||
is(result.type, "object", "'null' type is correct");
|
||||
is(result.subtype, "null", "'null' subtype is correct");
|
||||
ok(!result.objectId, "'null' has no objectId");
|
||||
}
|
||||
|
||||
async function testUnserializable(testFunction) {
|
||||
const expressions = ["-0", "NaN", "Infinity", "-Infinity"];
|
||||
for (const expression of expressions) {
|
||||
const { result } = await testFunction(expression);
|
||||
is(
|
||||
result.unserializableValue,
|
||||
expression,
|
||||
`Evaluating unserializable '${expression}' works`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function testObjectTypes(testFunction) {
|
||||
const expressions = [
|
||||
{ expression: "({foo:true})", type: "object", subtype: null },
|
||||
{ expression: "Symbol('foo')", type: "symbol", subtype: null },
|
||||
@ -217,7 +69,7 @@ async function testObjectTypes(testFunction) {
|
||||
];
|
||||
|
||||
for (const { expression, type, subtype } of expressions) {
|
||||
const { result } = await testFunction(expression);
|
||||
const { result } = await Runtime.evaluate({ expression });
|
||||
is(
|
||||
result.subtype,
|
||||
subtype,
|
||||
@ -226,31 +78,149 @@ async function testObjectTypes(testFunction) {
|
||||
is(result.type, type, "The type is correct");
|
||||
ok(!!result.objectId, "Got an object id");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function testThrowError(testFunction) {
|
||||
const { exceptionDetails } = await testFunction(
|
||||
"throw new Error('foo')",
|
||||
true
|
||||
);
|
||||
is(exceptionDetails.text, "foo", "Exception message is passed to the client");
|
||||
}
|
||||
add_task(async function returnAsObjectPrimitiveTypes({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
async function testThrowValue(testFunction) {
|
||||
const { exceptionDetails } = await testFunction("throw 'foo'", true);
|
||||
is(exceptionDetails.exception.type, "string", "Exception type is correct");
|
||||
is(
|
||||
exceptionDetails.exception.value,
|
||||
"foo",
|
||||
"Exception value is passed as a RemoteObject"
|
||||
);
|
||||
}
|
||||
await enableRuntime(client);
|
||||
|
||||
async function testJSError(testFunction) {
|
||||
const { exceptionDetails } = await testFunction("doesNotExists()", true);
|
||||
is(
|
||||
exceptionDetails.text,
|
||||
"doesNotExists is not defined",
|
||||
"Exception message is passed to the client"
|
||||
const expressions = [42, "42", true, 4.2];
|
||||
for (const expression of expressions) {
|
||||
const { result } = await Runtime.evaluate({
|
||||
expression: JSON.stringify(expression),
|
||||
});
|
||||
is(result.value, expression, `Evaluating primitive '${expression}' works`);
|
||||
is(result.type, typeof expression, `${expression} type is correct`);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function returnAsObjectNotSerializable({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const expressions = ["-0", "NaN", "Infinity", "-Infinity"];
|
||||
for (const expression of expressions) {
|
||||
const { result } = await Runtime.evaluate({ expression });
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
unserializableValue: expression,
|
||||
},
|
||||
`Evaluating unserializable '${expression}' works`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// `null` is special as it has its own subtype, is of type 'object'
|
||||
// but is returned as a value, without an `objectId` attribute
|
||||
add_task(async function returnAsObjectNull({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.evaluate({
|
||||
expression: "null",
|
||||
});
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: "object",
|
||||
subtype: "null",
|
||||
value: null,
|
||||
},
|
||||
"Null type is correct"
|
||||
);
|
||||
});
|
||||
|
||||
// undefined doesn't work with JSON.stringify, so test it independently
|
||||
add_task(async function returnAsObjectUndefined({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const { result } = await Runtime.evaluate({
|
||||
expression: "undefined",
|
||||
});
|
||||
Assert.deepEqual(
|
||||
result,
|
||||
{
|
||||
type: "undefined",
|
||||
},
|
||||
"Undefined type is correct"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsJavascriptError({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.evaluate({
|
||||
expression: "doesNotExists()",
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
text: "doesNotExists is not defined",
|
||||
},
|
||||
"Javascript error is passed to the client"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsThrowError({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.evaluate({
|
||||
expression: "throw new Error('foo')",
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
text: "foo",
|
||||
},
|
||||
"Exception details are passed to the client"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function exceptionDetailsThrowValue({ client }) {
|
||||
const { Runtime } = client;
|
||||
|
||||
await enableRuntime(client);
|
||||
|
||||
const { exceptionDetails } = await Runtime.evaluate({
|
||||
expression: "throw 'foo'",
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
exceptionDetails,
|
||||
{
|
||||
exception: {
|
||||
type: "string",
|
||||
value: "foo",
|
||||
},
|
||||
},
|
||||
"Exception details are passed as a RemoteObject"
|
||||
);
|
||||
});
|
||||
|
||||
async function enableRuntime(client) {
|
||||
const { Runtime } = client;
|
||||
|
||||
// Enable watching for new execution context
|
||||
await Runtime.enable();
|
||||
info("Runtime domain has been enabled");
|
||||
|
||||
// Calling Runtime.enable will emit executionContextCreated for the existing contexts
|
||||
const { context } = await Runtime.executionContextCreated();
|
||||
ok(!!context.id, "The execution context has an id");
|
||||
ok(context.auxData.isDefault, "The execution context is the default one");
|
||||
ok(!!context.auxData.frameId, "The execution context has a frame id set");
|
||||
|
||||
return context.id;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user