fune/remote/test/browser/browser_runtime_evaluate.js
Victor Porof 991b3c93c6 Bug 1561435 - Format remote/, a=automatic-formatting
# ignore-this-changeset

Differential Revision: https://phabricator.services.mozilla.com/D35925

--HG--
extra : source : b793788d0f38244b33eb59ea36e2c6624dbd12c5
2019-07-05 10:56:48 +02:00

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"
);
}