forked from mirrors/gecko-dev
For many browser-chrome (bc) tests is does not matter what the
document is, as long as it is does not cause state to bleed over
from the previous test.
For these cases this patch introduces a shorthand, setup(), which
calls setupForURL(url) with an empty document generated by toDataURL("").
Differential Revision: https://phabricator.services.mozilla.com/D40216
--HG--
extra : moz-landing-system : lando
249 lines
8.2 KiB
JavaScript
249 lines
8.2 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Test the Runtime.callFunctionOn
|
|
// See also browser_runtime_evaluate, which covers basic usages of this method.
|
|
|
|
add_task(async function() {
|
|
const { client } = await setup();
|
|
|
|
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);
|
|
|
|
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 testObjectReferences({ Runtime }, contextId) {
|
|
// First create a JS object remotely via Runtime.evaluate
|
|
const { result } = await Runtime.evaluate({
|
|
contextId,
|
|
expression: "({ foo: 1 })",
|
|
});
|
|
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({
|
|
executionContextId: contextId,
|
|
functionDeclaration: "arg => ++arg.foo",
|
|
arguments: [{ 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"
|
|
);
|
|
|
|
// 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 }],
|
|
});
|
|
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
|
|
// that reference the same remote object
|
|
ok(!!result3.objectId, "Got an object id");
|
|
isnot(result3.objectId, result.objectId, "The object id is stable");
|
|
|
|
// 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({
|
|
executionContextId,
|
|
functionDeclaration: "() => doesNotExists()",
|
|
}));
|
|
is(
|
|
exceptionDetails.text,
|
|
"doesNotExists is not defined",
|
|
"Exception message is passed to the client"
|
|
);
|
|
}
|
|
|
|
async function testReturnByValue({ Runtime }, executionContextId) {
|
|
const values = [
|
|
42,
|
|
"42",
|
|
42.0,
|
|
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({
|
|
executionContextId,
|
|
functionDeclaration: "() => (" + JSON.stringify(value) + ")",
|
|
returnByValue: true,
|
|
});
|
|
Assert.deepEqual(
|
|
result.value,
|
|
value,
|
|
"The returned value is the same than the input value"
|
|
);
|
|
}
|
|
|
|
// Test undefined individually as JSON.stringify doesn't return a string
|
|
const { result } = await Runtime.callFunctionOn({
|
|
executionContextId,
|
|
functionDeclaration: "() => {}",
|
|
returnByValue: true,
|
|
});
|
|
is(result.value, 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"
|
|
);
|
|
|
|
// 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");
|
|
|
|
// And delayed promise rejection
|
|
({ exceptionDetails } = await Runtime.callFunctionOn({
|
|
executionContextId,
|
|
functionDeclaration:
|
|
"() => new Promise((_,r) => setTimeout(() => r(42), 0))",
|
|
awaitPromise: true,
|
|
}));
|
|
is(
|
|
exceptionDetails.exception.value,
|
|
42,
|
|
"The result is the promise's rejection"
|
|
);
|
|
|
|
// Finally assert promise resolution without awaitPromise
|
|
({ result } = await Runtime.callFunctionOn({
|
|
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"
|
|
);
|
|
}
|