forked from mirrors/gecko-dev
# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D35925 --HG-- extra : source : b793788d0f38244b33eb59ea36e2c6624dbd12c5
269 lines
8.9 KiB
JavaScript
269 lines
8.9 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Test the Runtime execution context events
|
|
|
|
const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
|
|
|
|
add_task(async function() {
|
|
// Open a test page, to prevent debugging the random default page
|
|
await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
|
|
|
|
// Start the CDP server
|
|
await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
|
|
|
|
// Retrieve the chrome-remote-interface library object
|
|
const CDP = await getCDP();
|
|
|
|
// Connect to the server
|
|
const client = await CDP({
|
|
target(list) {
|
|
// Ensure debugging the right target, i.e. the one for our test tab.
|
|
return list.find(target => target.url == TEST_URI);
|
|
},
|
|
});
|
|
ok(true, "CDP client has been instantiated");
|
|
|
|
const firstContext = await testRuntimeEnable(client);
|
|
const contextId = firstContext.id;
|
|
|
|
await testEvaluate(client, contextId);
|
|
await testEvaluateInvalidContextId(client, contextId);
|
|
|
|
await testCallFunctionOn(client, contextId);
|
|
await testCallFunctionOnInvalidContextId(client, contextId);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
await client.close();
|
|
ok(true, "The client is closed");
|
|
|
|
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
|
|
|
await RemoteAgent.close();
|
|
});
|
|
|
|
async function testRuntimeEnable({ Runtime }) {
|
|
// Enable watching for new execution context
|
|
await Runtime.enable();
|
|
ok(true, "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;
|
|
}
|
|
|
|
async function testEvaluate({ Runtime }, contextId) {
|
|
const { result } = await Runtime.evaluate({
|
|
contextId,
|
|
expression: "location.href",
|
|
});
|
|
is(
|
|
result.value,
|
|
TEST_URI,
|
|
"Runtime.evaluate works and is against the test page"
|
|
);
|
|
}
|
|
|
|
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"
|
|
);
|
|
}
|
|
}
|
|
|
|
async function testCallFunctionOn({ Runtime }, executionContextId) {
|
|
const { result } = await Runtime.callFunctionOn({
|
|
executionContextId,
|
|
functionDeclaration: "() => location.href",
|
|
});
|
|
is(
|
|
result.value,
|
|
TEST_URI,
|
|
"Runtime.callFunctionOn works and is against the test page"
|
|
);
|
|
}
|
|
|
|
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 = ["NaN", "-0", "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 },
|
|
{ expression: "BigInt(42)", type: "bigint", 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 testFunction(expression);
|
|
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");
|
|
}
|
|
}
|
|
|
|
async function testThrowError(testFunction) {
|
|
const { exceptionDetails } = await testFunction(
|
|
"throw new Error('foo')",
|
|
true
|
|
);
|
|
is(exceptionDetails.text, "foo", "Exception message is passed to the 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"
|
|
);
|
|
}
|
|
|
|
async function testJSError(testFunction) {
|
|
const { exceptionDetails } = await testFunction("doesNotExists()", true);
|
|
is(
|
|
exceptionDetails.text,
|
|
"doesNotExists is not defined",
|
|
"Exception message is passed to the client"
|
|
);
|
|
}
|