Bug 1952682 - Introduce support for SpecialPowers.spawn in a content process r=nika
This patch introduces SpecialPowersForProcess + spawn() to enable callers to spawn a task in the specified content process, and adds a helper to ContentPage that is used in tests in bug 1952681. Differential Revision: https://phabricator.services.mozilla.com/D240779
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<title>Page that embeds two cross-origin frames</title>
|
||||
<body>
|
||||
<script>
|
||||
async function addFrame(id, filename) {
|
||||
let url = new URL(filename, document.URL);
|
||||
url.hostname = url.hostname === "example.com" ? "example.org" : "example.com";
|
||||
|
||||
let f = document.createElement("iframe");
|
||||
f.id = id;
|
||||
f.src = url.href;
|
||||
await new Promise(resolve => {
|
||||
f.onload = resolve;
|
||||
document.body.append(f);
|
||||
});
|
||||
}
|
||||
|
||||
if (window === top) {
|
||||
window.loadedPromise = Promise.all([
|
||||
addFrame("frame1", "?this_is_child_frame_1"),
|
||||
addFrame("frame2", "?this_is_child_frame_2"),
|
||||
]).then(() => "frames_all_loaded");
|
||||
} else {
|
||||
document.body.append("This is child frame: " + location);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,153 @@
|
||||
"use strict";
|
||||
|
||||
// Tests SpecialPowersForProcess spawn() in xpcshell tests.
|
||||
// A browser chrome mochitest version of this test exists at
|
||||
// testing/mochitest/tests/browser/browser_SpecialPowersForProcessSpawn.js
|
||||
|
||||
const { XPCShellContentUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/XPCShellContentUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const { SpecialPowersForProcess } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/SpecialPowersProcessActor.sys.mjs"
|
||||
);
|
||||
|
||||
XPCShellContentUtils.init(this);
|
||||
|
||||
const server = XPCShellContentUtils.createHttpServer({
|
||||
hosts: ["example.com", "example.org"],
|
||||
});
|
||||
server.registerFile(
|
||||
"/file_xorigin_frames.html",
|
||||
do_get_file("file_xorigin_frames.html")
|
||||
);
|
||||
|
||||
const scope = this;
|
||||
const interceptedMessages = [];
|
||||
add_setup(() => {
|
||||
const orig_do_report_result = scope.do_report_result;
|
||||
scope.do_report_result = (passed, msg, stack) => {
|
||||
if (msg?.startsWith?.("CHECK_THIS:")) {
|
||||
interceptedMessages.push(msg);
|
||||
}
|
||||
return orig_do_report_result(passed, msg, stack);
|
||||
};
|
||||
const orig_info = scope.info;
|
||||
scope.info = msg => {
|
||||
if (msg?.startsWith?.("CHECK_THIS:")) {
|
||||
interceptedMessages.push(msg);
|
||||
}
|
||||
return orig_info(msg);
|
||||
};
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
scope.do_report_result = orig_do_report_result;
|
||||
scope.info = orig_info;
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that SpecialPowersForProcess can spawn() in processes that the test
|
||||
// grabbed off a contentPage, even after the original page navigated/closed.
|
||||
add_task(async function test_SpecialPowersForProcess_spawn() {
|
||||
interceptedMessages.length = 0;
|
||||
|
||||
const page = await XPCShellContentUtils.loadContentPage(
|
||||
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
|
||||
"http://example.com/file_xorigin_frames.html",
|
||||
{ remote: true, remoteSubframes: true }
|
||||
);
|
||||
await page.spawn([], async () => {
|
||||
Assert.equal(
|
||||
await this.content.wrappedJSObject.loadedPromise,
|
||||
"frames_all_loaded",
|
||||
"All (cross-origin) frames have finished loading"
|
||||
);
|
||||
});
|
||||
const proc1 = page.browsingContext.children[0].currentWindowGlobal.domProcess;
|
||||
const proc2 = page.browsingContext.children[1].currentWindowGlobal.domProcess;
|
||||
Assert.equal(proc1, proc2, "The two child frames share the same process");
|
||||
|
||||
const processBoundSpecialPowers = new SpecialPowersForProcess(scope, proc1);
|
||||
|
||||
await page.spawn([], async () => {
|
||||
info("ContentPage.spawn: Change frame1 process");
|
||||
const frame1 = this.content.document.getElementById("frame1");
|
||||
Assert.throws(
|
||||
() => frame1.contentDocument.location.search,
|
||||
/TypeError: can't access property "location", frame1.contentDocument is null/,
|
||||
"ContentPage.spawn: Assert, cannot read cross-origin content"
|
||||
);
|
||||
await new Promise(resolve => {
|
||||
frame1.onload = resolve;
|
||||
frame1.src = "/dummy?3";
|
||||
});
|
||||
// Verify that it is same-origin now.
|
||||
Assert.equal(
|
||||
frame1.contentDocument.location.search,
|
||||
"?3",
|
||||
"CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin"
|
||||
);
|
||||
info("CHECK_THIS: ContentPage.spawn: remove frame1");
|
||||
frame1.remove();
|
||||
|
||||
// spawn() implementation has special logic to route Assert messages;
|
||||
// Prepare to check that Assert can be called after spawn() returns.
|
||||
this.content.assertAfterSpawnReturns = () => {
|
||||
Assert.ok(true, "CHECK_THIS: ContentPage.spawn: asssert after return");
|
||||
};
|
||||
});
|
||||
|
||||
await page.spawn([], () => {
|
||||
this.content.assertAfterSpawnReturns();
|
||||
});
|
||||
|
||||
Assert.equal(page.browsingContext.children.length, 1, "frame1 was removed");
|
||||
|
||||
// Now frame1 has navigated (and switched processes) and removed, so if the
|
||||
// SpecialPowers implementation were to rely on JSWindowActor, then that
|
||||
// would break if we try to interact with it at this point. Check that we
|
||||
// can connect just fine (because JSProcessActor should be used instead).
|
||||
await processBoundSpecialPowers.spawn([], () => {
|
||||
info("CHECK_THIS: process-bound spawn: still works");
|
||||
Assert.equal(
|
||||
typeof content,
|
||||
"undefined",
|
||||
"CHECK_THIS: process-bound spawn: no content global"
|
||||
);
|
||||
// Need a shared object that outlives this SpecialPowersSandbox instance:
|
||||
const sharedGlobalObj = Cu.getGlobalForObject(Services);
|
||||
// spawn() implementation has special logic to route Assert messages;
|
||||
// Prepare to check that Assert can be called after spawn() returns.
|
||||
sharedGlobalObj.assertAfterProcessBoundSpawnReturns = () => {
|
||||
Assert.ok(true, "CHECK_THIS: process-bound spawn: asssert after return");
|
||||
};
|
||||
});
|
||||
await processBoundSpecialPowers.spawn([], () => {
|
||||
// Shared object that outlived the previous SpecialPowersSandbox instance:
|
||||
const sharedGlobalObj = Cu.getGlobalForObject(Services);
|
||||
sharedGlobalObj.assertAfterProcessBoundSpawnReturns();
|
||||
delete sharedGlobalObj.assertAfterProcessBoundSpawnReturns;
|
||||
});
|
||||
await page.close();
|
||||
await processBoundSpecialPowers.destroy();
|
||||
|
||||
Assert.throws(
|
||||
() => processBoundSpecialPowers.spawn([], () => {}),
|
||||
/this.actor is null/,
|
||||
"Cannot spawn after destroy()"
|
||||
);
|
||||
|
||||
const observedMessages = interceptedMessages.splice(0);
|
||||
Assert.deepEqual(
|
||||
observedMessages,
|
||||
[
|
||||
`CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin - "?3" == "?3"`,
|
||||
"CHECK_THIS: ContentPage.spawn: remove frame1",
|
||||
"CHECK_THIS: ContentPage.spawn: asssert after return - true == true",
|
||||
"CHECK_THIS: process-bound spawn: still works",
|
||||
`CHECK_THIS: process-bound spawn: no content global - "undefined" == "undefined"`,
|
||||
"CHECK_THIS: process-bound spawn: asssert after return - true == true",
|
||||
],
|
||||
"Observed calls through spawn"
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
[DEFAULT]
|
||||
|
||||
["test_SpecialPowersForProcessSpawn.js"]
|
||||
prefs = ["dom.security.https_first=false"] #Disable https-first because createHttpServer does not support https
|
||||
support-files = ["file_xorigin_frames.html"]
|
||||
|
||||
["test_SpecialPowersSandbox.js"]
|
||||
prefs = ["dom.security.https_first=false"] #Disable https-first because createHttpServer does not support https
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ skip-if = [
|
||||
"verify",
|
||||
]
|
||||
|
||||
["browser_SpecialPowersForProcessSpawn.js"]
|
||||
support-files = ["../Harness_sanity/file_xorigin_frames.html"]
|
||||
|
||||
["browser_add_task.js"]
|
||||
|
||||
["browser_async.js"]
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
|
||||
// Tests SpecialPowersForProcess spawn() in browser chrome mochitests.
|
||||
// An xpcshell version of this test exists at
|
||||
// testing/mochitest/tests/Harness_sanity/test_SpecialPowersForProcessSpawn.js
|
||||
|
||||
const { SpecialPowersForProcess } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/SpecialPowersProcessActor.sys.mjs"
|
||||
);
|
||||
|
||||
const scope = this;
|
||||
const interceptedMessages = [];
|
||||
add_setup(() => {
|
||||
const orig_record = SimpleTest.record;
|
||||
SimpleTest.record = (condition, name, diag, stack, expected) => {
|
||||
if (name?.startsWith?.("CHECK_THIS:")) {
|
||||
interceptedMessages.push(name);
|
||||
}
|
||||
return orig_record(condition, name, diag, stack, expected);
|
||||
};
|
||||
const orig_info = SimpleTest.info;
|
||||
SimpleTest.info = msg => {
|
||||
if (msg?.startsWith?.("CHECK_THIS:")) {
|
||||
interceptedMessages.push(msg);
|
||||
}
|
||||
return orig_info(msg);
|
||||
};
|
||||
registerCleanupFunction(() => {
|
||||
SimpleTest.record = orig_record;
|
||||
SimpleTest.info = orig_info;
|
||||
});
|
||||
});
|
||||
|
||||
// Tests that SpecialPowersForProcess can spawn() in processes that the test
|
||||
// grabbed off a contentPage, even after the original page navigated/closed.
|
||||
add_task(async function test_SpecialPowersForProcess_spawn() {
|
||||
interceptedMessages.length = 0;
|
||||
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"https://example.com/browser/testing/mochitest/tests/browser/file_xorigin_frames.html"
|
||||
);
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
|
||||
Assert.equal(
|
||||
await this.content.wrappedJSObject.loadedPromise,
|
||||
"frames_all_loaded",
|
||||
"All (cross-origin) frames have finished loading"
|
||||
);
|
||||
});
|
||||
const browsingContext = tab.linkedBrowser.browsingContext;
|
||||
const proc1 = browsingContext.children[0].currentWindowGlobal.domProcess;
|
||||
const proc2 = browsingContext.children[1].currentWindowGlobal.domProcess;
|
||||
Assert.equal(proc1, proc2, "The two child frames share the same process");
|
||||
|
||||
const processBoundSpecialPowers = new SpecialPowersForProcess(scope, proc1);
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
|
||||
info("ContentPage.spawn: Change frame1 process");
|
||||
const frame1 = this.content.document.getElementById("frame1");
|
||||
Assert.throws(
|
||||
() => frame1.contentDocument.location.search,
|
||||
/TypeError: can't access property "location", frame1.contentDocument is null/,
|
||||
"ContentPage.spawn: Assert, cannot read cross-origin content"
|
||||
);
|
||||
await new Promise(resolve => {
|
||||
frame1.onload = resolve;
|
||||
frame1.src = "/dummy?3";
|
||||
});
|
||||
// Verify that it is same-origin now.
|
||||
Assert.equal(
|
||||
frame1.contentDocument.location.search,
|
||||
"?3",
|
||||
"CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin"
|
||||
);
|
||||
info("CHECK_THIS: ContentPage.spawn: remove frame1");
|
||||
frame1.remove();
|
||||
|
||||
// spawn() implementation has special logic to route Assert messages;
|
||||
// Prepare to check that Assert can be called after spawn() returns.
|
||||
this.content.assertAfterSpawnReturns = () => {
|
||||
Assert.ok(true, "CHECK_THIS: ContentPage.spawn: asssert after return");
|
||||
};
|
||||
});
|
||||
|
||||
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
|
||||
this.content.assertAfterSpawnReturns();
|
||||
});
|
||||
|
||||
Assert.equal(browsingContext.children.length, 1, "frame1 was removed");
|
||||
|
||||
// Now frame1 has navigated (and switched processes) and removed, so if the
|
||||
// SpecialPowers implementation were to rely on JSWindowActor, then that
|
||||
// would break if we try to interact with it at this point. Check that we
|
||||
// can connect just fine (because JSProcessActor should be used instead).
|
||||
await processBoundSpecialPowers.spawn([], () => {
|
||||
info("CHECK_THIS: process-bound spawn: still works");
|
||||
Assert.equal(
|
||||
typeof content,
|
||||
"undefined",
|
||||
"CHECK_THIS: process-bound spawn: no content global"
|
||||
);
|
||||
// Need a shared object that outlives this SpecialPowersSandbox instance:
|
||||
const sharedGlobalObj = Cu.getGlobalForObject(Services);
|
||||
// spawn() implementation has special logic to route Assert messages;
|
||||
// Prepare to check that Assert can be called after spawn() returns.
|
||||
sharedGlobalObj.assertAfterProcessBoundSpawnReturns = () => {
|
||||
Assert.ok(true, "CHECK_THIS: process-bound spawn: asssert after return");
|
||||
};
|
||||
});
|
||||
await processBoundSpecialPowers.spawn([], () => {
|
||||
// Shared object that outlived the previous SpecialPowersSandbox instance:
|
||||
const sharedGlobalObj = Cu.getGlobalForObject(Services);
|
||||
sharedGlobalObj.assertAfterProcessBoundSpawnReturns();
|
||||
delete sharedGlobalObj.assertAfterProcessBoundSpawnReturns;
|
||||
});
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
await processBoundSpecialPowers.destroy();
|
||||
|
||||
Assert.throws(
|
||||
() => processBoundSpecialPowers.spawn([], () => {}),
|
||||
/this.actor is null/,
|
||||
"Cannot spawn after destroy()"
|
||||
);
|
||||
|
||||
const observedMessages = interceptedMessages.splice(0);
|
||||
Assert.deepEqual(
|
||||
observedMessages,
|
||||
[
|
||||
`CHECK_THIS: ContentPage.spawn: Assert, frame1 is now same-origin - "?3" == "?3"`,
|
||||
"CHECK_THIS: ContentPage.spawn: remove frame1",
|
||||
"CHECK_THIS: ContentPage.spawn: asssert after return - true == true",
|
||||
"CHECK_THIS: process-bound spawn: still works",
|
||||
`CHECK_THIS: process-bound spawn: no content global - "undefined" == "undefined"`,
|
||||
"CHECK_THIS: process-bound spawn: asssert after return - true == true",
|
||||
],
|
||||
"Observed calls through spawn"
|
||||
);
|
||||
});
|
||||
@@ -23,6 +23,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ContentTask: "resource://testing-common/ContentTask.sys.mjs",
|
||||
HttpServer: "resource://testing-common/httpd.sys.mjs",
|
||||
SpecialPowersParent: "resource://testing-common/SpecialPowersParent.sys.mjs",
|
||||
SpecialPowersForProcess:
|
||||
"resource://testing-common/SpecialPowersProcessActor.sys.mjs",
|
||||
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
||||
});
|
||||
|
||||
@@ -259,6 +261,15 @@ export class ContentPage {
|
||||
return this.SpecialPowers.spawn(this.browser, params, task);
|
||||
}
|
||||
|
||||
// Get a SpecialPowersForProcess instance associated with the content process
|
||||
// of the currently loaded page. This allows callers to spawn() tasks that
|
||||
// outlive the page (for as long as the page's process is around).
|
||||
getCurrentContentProcessSpecialPowers() {
|
||||
const testScope = XPCShellContentUtils.currentScope;
|
||||
const domProcess = this.browsingContext.currentWindowGlobal.domProcess;
|
||||
return new lazy.SpecialPowersForProcess(testScope, domProcess);
|
||||
}
|
||||
|
||||
// Like spawn(), but uses the legacy ContentTask infrastructure rather than
|
||||
// SpecialPowers. Exists only because the author of the SpecialPowers
|
||||
// migration did not have the time to fix all of the legacy users who relied
|
||||
|
||||
@@ -303,6 +303,7 @@ export class SpecialPowersChild extends JSWindowActorChild {
|
||||
|
||||
case "Assert":
|
||||
{
|
||||
// Handles info & Assert reports from SpecialPowersSandbox.sys.mjs.
|
||||
if ("info" in message.data) {
|
||||
(this.xpcshellScope || this.SimpleTest).info(message.data.info);
|
||||
break;
|
||||
@@ -311,12 +312,11 @@ export class SpecialPowersChild extends JSWindowActorChild {
|
||||
// An assertion has been done in a mochitest chrome script
|
||||
let { name, passed, stack, diag, expectFail } = message.data;
|
||||
|
||||
let { SimpleTest } = this;
|
||||
if (SimpleTest) {
|
||||
let expected = expectFail ? "fail" : "pass";
|
||||
SimpleTest.record(passed, name, diag, stack, expected);
|
||||
} else if (this.xpcshellScope) {
|
||||
if (this.xpcshellScope) {
|
||||
this.xpcshellScope.do_report_result(passed, name, stack);
|
||||
} else if (this.SimpleTest) {
|
||||
let expected = expectFail ? "fail" : "pass";
|
||||
this.SimpleTest.record(passed, name, diag, stack, expected);
|
||||
} else {
|
||||
// Well, this is unexpected.
|
||||
dump(name + "\n");
|
||||
@@ -1506,6 +1506,13 @@ export class SpecialPowersChild extends JSWindowActorChild {
|
||||
* The sandbox also has access to an Assert object, as provided by
|
||||
* Assert.sys.mjs. Any assertion methods called before the task resolves
|
||||
* will be relayed back to the test environment of the caller.
|
||||
* Assertions triggered after a task returns may be relayed back if
|
||||
* setAsDefaultAssertHandler() has been called, until this SpecialPowers
|
||||
* instance is destroyed.
|
||||
*
|
||||
* If your assertions need to outlive this SpecialPowers instance,
|
||||
* use SpecialPowersForProcess from SpecialPowersProcessActor.sys.mjs,
|
||||
* which lives until the specified child process terminates.
|
||||
*
|
||||
* @param {BrowsingContext or FrameLoaderOwner or WindowProxy} target
|
||||
* The target in which to run the task. This may be any element
|
||||
|
||||
@@ -194,10 +194,21 @@ export class SpecialPowersParent extends JSWindowActorParent {
|
||||
esModuleURI: "resource://testing-common/SpecialPowersParent.sys.mjs",
|
||||
},
|
||||
});
|
||||
ChromeUtils.registerProcessActor("SpecialPowersProcessActor", {
|
||||
child: {
|
||||
esModuleURI:
|
||||
"resource://testing-common/SpecialPowersProcessActor.sys.mjs",
|
||||
},
|
||||
parent: {
|
||||
esModuleURI:
|
||||
"resource://testing-common/SpecialPowersProcessActor.sys.mjs",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static unregisterActor() {
|
||||
ChromeUtils.unregisterWindowActor("SpecialPowers");
|
||||
ChromeUtils.unregisterProcessActor("SpecialPowersProcessActor");
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
170
testing/specialpowers/content/SpecialPowersProcessActor.sys.mjs
Normal file
170
testing/specialpowers/content/SpecialPowersProcessActor.sys.mjs
Normal file
@@ -0,0 +1,170 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SpecialPowersSandbox:
|
||||
"resource://testing-common/SpecialPowersSandbox.sys.mjs",
|
||||
});
|
||||
|
||||
let nextSpfpId = 1;
|
||||
|
||||
/**
|
||||
* SpecialPowersForProcess wraps a content process, and allows the caller to
|
||||
* spawn() tasks like SpecialPowers.spawn() and contentPage.spawn(), including
|
||||
* Assert functionality. Assertion messages are passed back to the test scope,
|
||||
* which must be passed along with the process to the constructor.
|
||||
*/
|
||||
export class SpecialPowersForProcess {
|
||||
static instances = new Map();
|
||||
|
||||
/**
|
||||
* Create a new SpecialPowersForProcess that enables callers to spawn tasks
|
||||
* in the given content process.
|
||||
*
|
||||
* @param {any} scope
|
||||
* The test scope to receive assertion messages.
|
||||
* In test files this is often equivalent to globalThis.
|
||||
* @param {nsIDOMProcessParent} domProcess
|
||||
* The content process where the spawned code should run.
|
||||
*/
|
||||
constructor(testScope, domProcess) {
|
||||
this.isXpcshellScope = !!testScope.do_get_profile;
|
||||
this.isSimpleTestScope = !this.isXpcshellScope && !!testScope.SimpleTest;
|
||||
if (!this.isXpcshellScope && !this.isSimpleTestScope) {
|
||||
// Must be global of xpcshell test, or global of browser chrome mochitest.
|
||||
throw new Error("testScope cannot receive assertion messages!");
|
||||
}
|
||||
|
||||
if (!(domProcess instanceof Ci.nsIDOMProcessParent)) {
|
||||
throw new Error("domProcess is not a nsIDOMProcessParent!");
|
||||
}
|
||||
|
||||
// This actor is registered via SpecialPowersParent.registerActor().
|
||||
// In mochitests that is part of the SpecialPowers add-on initialization.
|
||||
// Xpcshell tests can initialize it with XPCShellContentUtils.init(), via
|
||||
// XPCShellContentUtils.ensureInitialized().
|
||||
this.actor = domProcess.getActor("SpecialPowersProcessActor");
|
||||
|
||||
this.testScope = testScope;
|
||||
|
||||
this.spfpId = nextSpfpId++;
|
||||
SpecialPowersForProcess.instances.set(this.spfpId, this);
|
||||
|
||||
testScope.registerCleanupFunction(() => this.destroy());
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.testScope) {
|
||||
// Already destroyed.
|
||||
return;
|
||||
}
|
||||
SpecialPowersForProcess.instances.delete(this.spfpId);
|
||||
this.actor = null;
|
||||
this.testScope = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `SpecialPowers.spawn`, but spawns a task in a child process instead.
|
||||
* The task has access to Assert.
|
||||
*
|
||||
* @param {Array<any>} args
|
||||
* An array of arguments to pass to the task. All arguments
|
||||
* must be structured clone compatible, and will be cloned
|
||||
* before being passed to the task.
|
||||
* @param {function} task
|
||||
* The function to run in the context of the target process. The
|
||||
* function will be stringified and re-evaluated in the context
|
||||
* of the target's content process. It may return any structured
|
||||
* clone compatible value, or a Promise which resolves to the
|
||||
* same, which will be returned to the caller.
|
||||
* @returns {Promise<any>}
|
||||
* A promise which resolves to the return value of the task, or
|
||||
* which rejects if the task raises an exception. As this is
|
||||
* being written, the rejection value will always be undefined
|
||||
* in the cases where the task throws an error, though that may
|
||||
* change in the future.
|
||||
*/
|
||||
spawn(args, task) {
|
||||
return this.actor.sendQuery("Spawn", {
|
||||
args,
|
||||
task: String(task),
|
||||
caller: Cu.getFunctionSourceLocation(task),
|
||||
spfpId: this.spfpId,
|
||||
});
|
||||
}
|
||||
|
||||
// Called when ProxiedAssert is received; this data is sent from
|
||||
// SpecialPowersSandbox's reportCallback in response to assertion messages.
|
||||
reportCallback(data) {
|
||||
if ("info" in data) {
|
||||
if (this.isXpcshellScope) {
|
||||
this.testScope.info(data.info);
|
||||
} else if (this.isSimpleTestScope) {
|
||||
this.testScope.SimpleTest.info(data.info);
|
||||
} else {
|
||||
// We checked this in the constructor, so this is unexpected.
|
||||
throw new Error(`testScope cannot receive assertion messages!?!`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { name, diag, passed, stack, expectFail } = data;
|
||||
if (this.isXpcshellScope) {
|
||||
this.testScope.do_report_result(passed, name, stack);
|
||||
} else if (this.isSimpleTestScope) {
|
||||
// browser chrome mochitest
|
||||
let expected = expectFail ? "fail" : "pass";
|
||||
this.testScope.SimpleTest.record(passed, name, diag, stack, expected);
|
||||
} else {
|
||||
// We checked this in the constructor, so this is unexpected.
|
||||
throw new Error(`testScope cannot receive assertion messages!?!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A minimal process actor that allows spawn() to run in the given process.
|
||||
export class SpecialPowersProcessActorParent extends JSProcessActorParent {
|
||||
receiveMessage(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "ProxiedAssert": {
|
||||
const { spfpId, data } = aMessage.data;
|
||||
const spfp = SpecialPowersForProcess.instances.get(spfpId);
|
||||
if (!spfp) {
|
||||
dump(`Unexpected ProxiedAssert: ${uneval(data)}\n`);
|
||||
throw new Error(`Unexpected message for ${spfpId} `);
|
||||
}
|
||||
spfp.reportCallback(data);
|
||||
return undefined;
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown SpecialPowersProcessActorParent action: ${aMessage.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SpecialPowersProcessActorChild extends JSProcessActorChild {
|
||||
receiveMessage(aMessage) {
|
||||
switch (aMessage.name) {
|
||||
case "Spawn": {
|
||||
return this._spawnTaskInChild(aMessage.data);
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown SpecialPowersProcessActorChild action: ${aMessage.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_spawnTaskInChild({ task, args, caller, spfpId }) {
|
||||
let sb = new lazy.SpecialPowersSandbox(null, data => {
|
||||
this.sendAsyncMessage("ProxiedAssert", { spfpId, data });
|
||||
});
|
||||
|
||||
return sb.execute(task, args, caller);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ modules = [
|
||||
"content/SpecialPowersChild.sys.mjs",
|
||||
"content/SpecialPowersEventUtils.sys.mjs",
|
||||
"content/SpecialPowersParent.sys.mjs",
|
||||
"content/SpecialPowersProcessActor.sys.mjs",
|
||||
"content/SpecialPowersSandbox.sys.mjs",
|
||||
"content/WrapPrivileged.sys.mjs",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user