summaryrefslogtreecommitdiffstats
path: root/dom/ipc/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/ipc/tests')
-rw-r--r--dom/ipc/tests/.eslintrc.js9
-rw-r--r--dom/ipc/tests/JSProcessActor/browser.ini11
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_getActor.js47
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_getActor_filter.js80
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_observer_notification.js41
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js16
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js51
-rw-r--r--dom/ipc/tests/JSProcessActor/browser_sendQuery.js82
-rw-r--r--dom/ipc/tests/JSProcessActor/head.js66
-rw-r--r--dom/ipc/tests/JSWindowActor/audio.oggbin0 -> 14293 bytes
-rw-r--r--dom/ipc/tests/JSWindowActor/browser.ini19
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_contentWindow.js47
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_crash_report.js112
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js194
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_event_listener.js43
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_getActor.js36
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_getActor_filter.js259
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_observer_notification.js111
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_process_childid.js27
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js12
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js51
-rw-r--r--dom/ipc/tests/JSWindowActor/browser_sendQuery.js117
-rw-r--r--dom/ipc/tests/JSWindowActor/file_mediaPlayback.html2
-rw-r--r--dom/ipc/tests/JSWindowActor/head.js71
-rw-r--r--dom/ipc/tests/blob_verify.sjs20
-rw-r--r--dom/ipc/tests/browser.ini22
-rw-r--r--dom/ipc/tests/browser_CrashService_crash.js72
-rw-r--r--dom/ipc/tests/browser_ProcessPriorityManager.js520
-rw-r--r--dom/ipc/tests/browser_bug1646088.js70
-rw-r--r--dom/ipc/tests/browser_cancel_content_js.js68
-rw-r--r--dom/ipc/tests/browser_crash_oopiframe.js167
-rw-r--r--dom/ipc/tests/browser_domainPolicy.js187
-rw-r--r--dom/ipc/tests/browser_memory_distribution_telemetry.js92
-rw-r--r--dom/ipc/tests/chrome.ini11
-rw-r--r--dom/ipc/tests/file_cancel_content_js.html18
-rw-r--r--dom/ipc/tests/file_disableScript.html11
-rw-r--r--dom/ipc/tests/file_domainPolicy_base.html8
-rw-r--r--dom/ipc/tests/file_dummy.html4
-rw-r--r--dom/ipc/tests/mochitest.ini12
-rw-r--r--dom/ipc/tests/process_error.xhtml63
-rw-r--r--dom/ipc/tests/test_Preallocated.html52
-rw-r--r--dom/ipc/tests/test_bcg_processes.html46
-rw-r--r--dom/ipc/tests/test_blob_sliced_from_child_process.js140
-rw-r--r--dom/ipc/tests/test_blob_sliced_from_parent_process.js167
-rw-r--r--dom/ipc/tests/test_bug1086684.js99
-rw-r--r--dom/ipc/tests/test_child_docshell.js93
-rw-r--r--dom/ipc/tests/test_process_error.xhtml22
-rw-r--r--dom/ipc/tests/test_process_error_oom.xhtml22
-rw-r--r--dom/ipc/tests/test_sharedMap.js382
-rw-r--r--dom/ipc/tests/test_temporaryfile_stream.html84
-rw-r--r--dom/ipc/tests/test_window_open_discarded_bc.html37
-rw-r--r--dom/ipc/tests/xpcshell.ini10
52 files changed, 4003 insertions, 0 deletions
diff --git a/dom/ipc/tests/.eslintrc.js b/dom/ipc/tests/.eslintrc.js
new file mode 100644
index 0000000000..4e9d6a74a8
--- /dev/null
+++ b/dom/ipc/tests/.eslintrc.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = {
+ extends: [
+ "plugin:mozilla/browser-test",
+ "plugin:mozilla/mochitest-test",
+ "plugin:mozilla/xpcshell-test",
+ ],
+};
diff --git a/dom/ipc/tests/JSProcessActor/browser.ini b/dom/ipc/tests/JSProcessActor/browser.ini
new file mode 100644
index 0000000000..c3d8645a33
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_getActor.js]
+[browser_getActor_filter.js]
+[browser_observer_notification.js]
+[browser_registerProcessActor.js]
+[browser_sendAsyncMessage.js]
+[browser_sendQuery.js]
+
diff --git a/dom/ipc/tests/JSProcessActor/browser_getActor.js b/dom/ipc/tests/JSProcessActor/browser_getActor.js
new file mode 100644
index 0000000000..e972eaaac9
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_getActor.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor on both sides", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ ok(parent, "WindowGlobalParent should have value.");
+ let actorParent = parent.getActor("TestProcessActor");
+ is(
+ actorParent.show(),
+ "TestProcessActorParent",
+ "actor `show` should have value."
+ );
+ is(
+ actorParent.manager,
+ parent,
+ "manager should match WindowGlobalParent.domProcess"
+ );
+
+ ok(
+ actorParent.sawActorCreated,
+ "Checking that we can observe parent creation"
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = ChromeUtils.domProcessChild;
+ ok(child, "WindowGlobalChild should have value.");
+ let actorChild = child.getActor("TestProcessActor");
+ is(
+ actorChild.show(),
+ "TestProcessActorChild",
+ "actor show should have vaule."
+ );
+ is(
+ actorChild.manager,
+ child,
+ "manager should match ChromeUtils.domProcessChild."
+ );
+
+ ok(
+ actorChild.sawActorCreated,
+ "Checking that we can observe child creation"
+ );
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js b/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js
new file mode 100644
index 0000000000..3d5c620d16
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor with remoteType match", {
+ remoteTypes: ["web"],
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ ok(
+ parent.getActor("TestProcessActor"),
+ "JSProcessActorParent should have value."
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = ChromeUtils.domProcessChild;
+ ok(child, "DOMProcessChild should have value.");
+ ok(
+ child.getActor("TestProcessActor"),
+ "JSProcessActorChild should have value."
+ );
+ });
+ },
+});
+
+declTest("getActor with remoteType mismatch", {
+ remoteTypes: ["privilegedabout"],
+ url: TEST_URL,
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ Assert.throws(
+ () => parent.getActor("TestProcessActor"),
+ /NotSupportedError/,
+ "Should throw if its remoteTypes don't match."
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = ChromeUtils.domProcessChild;
+ ok(child, "DOMProcessChild should have value.");
+ Assert.throws(
+ () => child.getActor("TestProcessActor"),
+ /NotSupportedError/,
+ "Should throw if its remoteTypes don't match."
+ );
+ });
+ },
+});
+
+declTest("getActor without includeParent", {
+ includeParent: false,
+
+ async test(_browser, win) {
+ let parent = win.docShell.browsingContext.currentWindowGlobal.domProcess;
+ SimpleTest.doesThrow(
+ () => parent.getActor("TestProcessActor"),
+ "Should throw if includeParent is false."
+ );
+
+ let child = ChromeUtils.domProcessChild;
+ SimpleTest.doesThrow(
+ () => child.getActor("TestProcessActor"),
+ "Should throw if includeParent is false."
+ );
+ },
+});
+
+declTest("getActor with includeParent", {
+ includeParent: true,
+
+ async test(_browser, win) {
+ let parent = win.docShell.browsingContext.currentWindowGlobal.domProcess;
+ let actorParent = parent.getActor("TestProcessActor");
+ ok(actorParent, "JSProcessActorParent should have value.");
+
+ let child = ChromeUtils.domProcessChild;
+ let actorChild = child.getActor("TestProcessActor");
+ ok(actorChild, "JSProcessActorChild should have value.");
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/browser_observer_notification.js b/dom/ipc/tests/JSProcessActor/browser_observer_notification.js
new file mode 100644
index 0000000000..dfe92ad240
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_observer_notification.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* eslint-disable no-unused-vars */
+declTest("test observer triggering actor creation", {
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ const TOPIC = "test-js-content-actor-child-observer";
+ Services.obs.notifyObservers(content.window, TOPIC, "dataString");
+
+ let child = ChromeUtils.domProcessChild;
+ let actorChild = child.getActor("TestProcessActor");
+ ok(actorChild, "JSProcessActorChild should have value.");
+ ok(
+ actorChild.lastObserved,
+ "JSProcessActorChild lastObserved should have value."
+ );
+ let { subject, topic, data } = actorChild.lastObserved;
+ is(topic, TOPIC, "Topic matches");
+ is(data, "dataString", "Data matches");
+ });
+ },
+});
+
+declTest("test observers with null data", {
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ const TOPIC = "test-js-content-actor-child-observer";
+ Services.obs.notifyObservers(content.window, TOPIC);
+
+ let child = ChromeUtils.domProcessChild;
+ let actorChild = child.getActor("TestProcessActor");
+ ok(actorChild, "JSProcessActorChild should have value.");
+ let { subject, topic, data } = actorChild.lastObserved;
+
+ is(topic, TOPIC, "Topic matches");
+ is(data, null, "Data matches");
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js b/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js
new file mode 100644
index 0000000000..e57f762744
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("double register", {
+ async test() {
+ SimpleTest.doesThrow(
+ () =>
+ ChromeUtils.registerContentActor(
+ "TestProcessActor",
+ processActorOptions
+ ),
+ "Should throw if register has duplicate name."
+ );
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js b/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js
new file mode 100644
index 0000000000..f81d4ddbee
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("asyncMessage testing", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ let actorParent = parent.getActor("TestProcessActor");
+ ok(actorParent, "JSProcessActorParent should have value.");
+
+ await ContentTask.spawn(browser, {}, async function() {
+ let child = ChromeUtils.domProcessChild;
+ let actorChild = child.getActor("TestProcessActor");
+ ok(actorChild, "JSProcessActorChild should have value.");
+
+ let promise = new Promise(resolve => {
+ actorChild.sendAsyncMessage("init", {});
+ actorChild.done = data => resolve(data);
+ }).then(data => {
+ ok(data.initial, "Initial should be true.");
+ ok(data.toParent, "ToParent should be true.");
+ ok(data.toChild, "ToChild should be true.");
+ });
+
+ await promise;
+ });
+ },
+});
+
+declTest("asyncMessage without both sides", {
+ async test(browser) {
+ // If we don't create a parent actor, make sure the parent actor
+ // gets created by having sent the message.
+ await ContentTask.spawn(browser, {}, async function() {
+ let child = ChromeUtils.domProcessChild;
+ let actorChild = child.getActor("TestProcessActor");
+ ok(actorChild, "JSProcessActorChild should have value.");
+
+ let promise = new Promise(resolve => {
+ actorChild.sendAsyncMessage("init", {});
+ actorChild.done = data => resolve(data);
+ }).then(data => {
+ ok(data.initial, "Initial should be true.");
+ ok(data.toParent, "ToParent should be true.");
+ ok(data.toChild, "ToChild should be true.");
+ });
+
+ await promise;
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/browser_sendQuery.js b/dom/ipc/tests/JSProcessActor/browser_sendQuery.js
new file mode 100644
index 0000000000..69fe881367
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/browser_sendQuery.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function maybeAsyncStack(offset, column) {
+ if (
+ Services.prefs.getBoolPref(
+ "javascript.options.asyncstack_capture_debuggee_only"
+ )
+ ) {
+ return "";
+ }
+
+ let stack = Error().stack.replace(/^.*?\n/, "");
+ return (
+ "JSActor query*" +
+ stack.replace(
+ /^([^\n]+?):(\d+):\d+/,
+ (m0, m1, m2) => `${m1}:${+m2 + offset}:${column}`
+ )
+ );
+}
+
+declTest("sendQuery Error", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ let actorParent = parent.getActor("TestProcessActor");
+
+ let asyncStack = maybeAsyncStack(2, 8);
+ let error = await actorParent
+ .sendQuery("error", { message: "foo" })
+ .catch(e => e);
+
+ is(error.message, "foo", "Error should have the correct message");
+ is(error.name, "SyntaxError", "Error should have the correct name");
+ is(
+ error.stack,
+ "receiveMessage@resource://testing-common/TestProcessActorChild.jsm:33:31\n" +
+ asyncStack,
+ "Error should have the correct stack"
+ );
+ },
+});
+
+declTest("sendQuery Exception", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ let actorParent = parent.getActor("TestProcessActor");
+
+ let asyncStack = maybeAsyncStack(2, 8);
+ let error = await actorParent
+ .sendQuery("exception", {
+ message: "foo",
+ result: Cr.NS_ERROR_INVALID_ARG,
+ })
+ .catch(e => e);
+
+ is(error.message, "foo", "Error should have the correct message");
+ is(
+ error.result,
+ Cr.NS_ERROR_INVALID_ARG,
+ "Error should have the correct result code"
+ );
+ is(
+ error.stack,
+ "receiveMessage@resource://testing-common/TestProcessActorChild.jsm:36:22\n" +
+ asyncStack,
+ "Error should have the correct stack"
+ );
+ },
+});
+
+declTest("sendQuery testing", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal.domProcess;
+ let actorParent = parent.getActor("TestProcessActor");
+ ok(actorParent, "JSWindowActorParent should have value.");
+
+ let { result } = await actorParent.sendQuery("asyncAdd", { a: 10, b: 20 });
+ is(result, 30);
+ },
+});
diff --git a/dom/ipc/tests/JSProcessActor/head.js b/dom/ipc/tests/JSProcessActor/head.js
new file mode 100644
index 0000000000..86f35f4c57
--- /dev/null
+++ b/dom/ipc/tests/JSProcessActor/head.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provide infrastructure for JSProcessActor tests.
+ */
+
+const URL = "about:blank";
+const TEST_URL = "http://test2.example.org/";
+let processActorOptions = {
+ parent: {
+ moduleURI: "resource://testing-common/TestProcessActorParent.jsm",
+ },
+ child: {
+ moduleURI: "resource://testing-common/TestProcessActorChild.jsm",
+ observers: ["test-js-content-actor-child-observer"],
+ },
+};
+
+function promiseNotification(aNotification) {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+ let notificationResolve;
+ let notificationObserver = function observer() {
+ notificationResolve();
+ Services.obs.removeObserver(notificationObserver, aNotification);
+ };
+ return new Promise(resolve => {
+ notificationResolve = resolve;
+ Services.obs.addObserver(notificationObserver, aNotification);
+ });
+}
+
+function declTest(name, cfg) {
+ let { url = "about:blank", includeParent = false, remoteTypes, test } = cfg;
+
+ // Build the actor options object which will be used to register & unregister
+ // our process actor.
+ let actorOptions = {
+ parent: Object.assign({}, processActorOptions.parent),
+ child: Object.assign({}, processActorOptions.child),
+ };
+ actorOptions.includeParent = includeParent;
+ if (remoteTypes !== undefined) {
+ actorOptions.remoteTypes = remoteTypes;
+ }
+
+ // Add a new task for the actor test declared here.
+ add_task(async function() {
+ info("Entering test: " + name);
+
+ // Register our actor, and load a new tab with the provided URL
+ ChromeUtils.registerProcessActor("TestProcessActor", actorOptions);
+ try {
+ await BrowserTestUtils.withNewTab(url, async browser => {
+ info("browser ready");
+ await Promise.resolve(test(browser, window));
+ });
+ } finally {
+ // Unregister the actor after the test is complete.
+ ChromeUtils.unregisterProcessActor("TestProcessActor");
+ info("Exiting test: " + name);
+ }
+ });
+}
diff --git a/dom/ipc/tests/JSWindowActor/audio.ogg b/dom/ipc/tests/JSWindowActor/audio.ogg
new file mode 100644
index 0000000000..bed764fbf1
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/audio.ogg
Binary files differ
diff --git a/dom/ipc/tests/JSWindowActor/browser.ini b/dom/ipc/tests/JSWindowActor/browser.ini
new file mode 100644
index 0000000000..fe07d9a97d
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_contentWindow.js]
+[browser_crash_report.js]
+[browser_destroy_callbacks.js]
+skip-if = !debug && (os == 'mac') #Bug 1604538
+[browser_event_listener.js]
+[browser_getActor.js]
+[browser_getActor_filter.js]
+[browser_observer_notification.js]
+support-files=
+ file_mediaPlayback.html
+ audio.ogg
+[browser_process_childid.js]
+[browser_registerWindowActor.js]
+[browser_sendAsyncMessage.js]
+[browser_sendQuery.js]
diff --git a/dom/ipc/tests/JSWindowActor/browser_contentWindow.js b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js
new file mode 100644
index 0000000000..cfdc5b114f
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("contentWindow null when inner window inactive", {
+ matches: [TEST_URL + "*"],
+ url: TEST_URL + "?1",
+
+ async test(browser) {
+ {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+
+ await actorParent.sendQuery("storeActor");
+ }
+
+ {
+ let url = TEST_URL + "?2";
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ await BrowserTestUtils.loadURI(browser, url);
+ await loaded;
+ }
+
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+
+ let result = await actorParent.sendQuery("checkActor");
+ if (SpecialPowers.useRemoteSubframes) {
+ is(
+ result.status,
+ "error",
+ "Should get an error when bfcache is disabled for Fission"
+ );
+ is(
+ result.errorType,
+ "InvalidStateError",
+ "Should get an InvalidStateError without bfcache"
+ );
+ } else {
+ is(result.status, "success", "Should succeed when bfcache is enabled");
+ ok(
+ result.valueIsNull,
+ "Should get a null contentWindow when inner window is inactive"
+ );
+ }
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_crash_report.js b/dom/ipc/tests/JSWindowActor/browser_crash_report.js
new file mode 100644
index 0000000000..a1ef293cc0
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_crash_report.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("crash actor", {
+ allFrames: true,
+
+ async test(browser) {
+ if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
+ ok(true, "Cannot test crash annotations without a crash reporter");
+ return;
+ }
+
+ {
+ info("Creating a new tab.");
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ let newTabBrowser = newTab.linkedBrowser;
+
+ let parent = newTabBrowser.browsingContext.currentWindowGlobal.getActor(
+ "TestWindow"
+ );
+ ok(parent, "JSWindowActorParent should have value.");
+
+ await SpecialPowers.spawn(newTabBrowser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ is(
+ child.isInProcess,
+ false,
+ "Actor should be loaded in the content process."
+ );
+ // Make sure that the actor is loaded.
+ let actorChild = child.getActor("TestWindow");
+ is(
+ actorChild.show(),
+ "TestWindowChild",
+ "actor show should have value."
+ );
+ is(
+ actorChild.manager,
+ child,
+ "manager should match WindowGlobalChild."
+ );
+ });
+
+ info(
+ "Crashing from withing an actor. We should have an actor name and a message name."
+ );
+ let report = await BrowserTestUtils.crashFrame(
+ newTabBrowser,
+ /* shouldShowTabCrashPage = */ false,
+ /* shouldClearMinidumps = */ true,
+ /* browsingContext = */ null,
+ { asyncCrash: false }
+ );
+
+ is(report.JSActorName, "BrowserTestUtils");
+ is(report.JSActorMessage, "BrowserTestUtils:CrashFrame");
+
+ BrowserTestUtils.removeTab(newTab);
+ }
+
+ {
+ info("Creating a new tab for async crash");
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ let newTabBrowser = newTab.linkedBrowser;
+
+ let parent = newTabBrowser.browsingContext.currentWindowGlobal.getActor(
+ "TestWindow"
+ );
+ ok(parent, "JSWindowActorParent should have value.");
+
+ await SpecialPowers.spawn(newTabBrowser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ is(
+ child.isInProcess,
+ false,
+ "Actor should be loaded in the content process."
+ );
+ // Make sure that the actor is loaded.
+ let actorChild = child.getActor("TestWindow");
+ is(
+ actorChild.show(),
+ "TestWindowChild",
+ "actor show should have value."
+ );
+ is(
+ actorChild.manager,
+ child,
+ "manager should match WindowGlobalChild."
+ );
+ });
+
+ info(
+ "Crashing from without an actor. We should have neither an actor name nor a message name."
+ );
+ let report = await BrowserTestUtils.crashFrame(
+ newTabBrowser,
+ /* shouldShowTabCrashPage = */ false,
+ /* shouldClearMinidumps = */ true,
+ /* browsingContext = */ null,
+ { asyncCrash: true }
+ );
+
+ ok(!report.JSActorName);
+ ok(!report.JSActorMessage);
+
+ BrowserTestUtils.removeTab(newTab);
+ }
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js
new file mode 100644
index 0000000000..25feb47179
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js
@@ -0,0 +1,194 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("destroy actor by iframe remove", {
+ allFrames: true,
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ frame.id = "frame";
+ content.document.body.appendChild(frame);
+ await ContentTaskUtils.waitForEvent(frame, "load");
+ is(content.window.frames.length, 1, "There should be an iframe.");
+ let child = frame.contentWindow.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ {
+ let error = actorChild.uninitializedGetterError;
+ const prop = "contentWindow";
+ Assert.ok(
+ error,
+ `Should get error accessing '${prop}' before actor initialization`
+ );
+ if (error) {
+ Assert.equal(
+ error.name,
+ "InvalidStateError",
+ "Error should be an InvalidStateError"
+ );
+ Assert.equal(
+ error.message,
+ `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' before actor is initialized`,
+ "Error should have informative message"
+ );
+ }
+ }
+
+ let didDestroyPromise = new Promise(resolve => {
+ const TOPIC = "test-js-window-actor-diddestroy";
+ Services.obs.addObserver(function obs(subject, topic, data) {
+ ok(data, "didDestroyCallback data should be true.");
+ is(subject, actorChild, "Should have this value");
+
+ Services.obs.removeObserver(obs, TOPIC);
+ // Make a trip through the event loop to ensure that the
+ // actor's manager has been cleared before running remaining
+ // checks.
+ Services.tm.dispatchToMainThread(resolve);
+ }, TOPIC);
+ });
+
+ info("Remove frame");
+ content.document.getElementById("frame").remove();
+ await didDestroyPromise;
+
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /InvalidStateError/,
+ "Should throw if frame destroy."
+ );
+
+ for (let prop of [
+ "document",
+ "browsingContext",
+ "docShell",
+ "contentWindow",
+ ]) {
+ let error;
+ try {
+ void actorChild[prop];
+ } catch (e) {
+ error = e;
+ }
+ Assert.ok(
+ error,
+ `Should get error accessing '${prop}' after actor destruction`
+ );
+ if (error) {
+ Assert.equal(
+ error.name,
+ "InvalidStateError",
+ "Error should be an InvalidStateError"
+ );
+ Assert.equal(
+ error.message,
+ `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' after actor 'TestWindow' has been destroyed`,
+ "Error should have informative message"
+ );
+ }
+ }
+ });
+ },
+});
+
+declTest("destroy actor by page navigates", {
+ allFrames: true,
+
+ async test(browser) {
+ info("creating an in-process frame");
+ await SpecialPowers.spawn(browser, [URL], async function(url) {
+ let frame = content.document.createElement("iframe");
+ frame.src = url;
+ content.document.body.appendChild(frame);
+ });
+
+ info("navigating page");
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ let frame = content.document.querySelector("iframe");
+ frame.contentWindow.location = url;
+ let child = frame.contentWindow.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ let didDestroyPromise = new Promise(resolve => {
+ const TOPIC = "test-js-window-actor-diddestroy";
+ Services.obs.addObserver(function obs(subject, topic, data) {
+ ok(data, "didDestroyCallback data should be true.");
+ is(subject, actorChild, "Should have this value");
+
+ Services.obs.removeObserver(obs, TOPIC);
+ resolve();
+ }, TOPIC);
+ });
+
+ await Promise.all([
+ didDestroyPromise,
+ ContentTaskUtils.waitForEvent(frame, "load"),
+ ]);
+
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /InvalidStateError/,
+ "Should throw if frame destroy."
+ );
+ });
+ },
+});
+
+declTest("destroy actor by tab being closed", {
+ allFrames: true,
+
+ async test(browser) {
+ info("creating a new tab");
+ let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ let newTabBrowser = newTab.linkedBrowser;
+
+ let parent = newTabBrowser.browsingContext.currentWindowGlobal.getActor(
+ "TestWindow"
+ );
+ ok(parent, "JSWindowActorParent should have value.");
+
+ // We can't depend on `SpecialPowers.spawn` to resolve our promise, as the
+ // frame message manager will be being shut down at the same time. Instead
+ // send messages over the per-process message manager which should still be
+ // active.
+ let didDestroyPromise = new Promise(resolve => {
+ Services.ppmm.addMessageListener(
+ "test-jswindowactor-diddestroy",
+ function onmessage(msg) {
+ Services.ppmm.removeMessageListener(
+ "test-jswindowactor-diddestroy",
+ onmessage
+ );
+ resolve();
+ }
+ );
+ });
+
+ info("setting up destroy listeners");
+ await SpecialPowers.spawn(newTabBrowser, [], () => {
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ Services.obs.addObserver(function obs(subject, topic, data) {
+ if (subject != actorChild) {
+ return;
+ }
+ dump("DidDestroy called\n");
+ Services.obs.removeObserver(obs, "test-js-window-actor-diddestroy");
+ Services.cpmm.sendAsyncMessage("test-jswindowactor-diddestroy", data);
+ }, "test-js-window-actor-diddestroy");
+ });
+
+ info("removing new tab");
+ await BrowserTestUtils.removeTab(newTab);
+ info("waiting for destroy callbacks to fire");
+ await didDestroyPromise;
+ info("got didDestroy callback");
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_event_listener.js b/dom/ipc/tests/JSWindowActor/browser_event_listener.js
new file mode 100644
index 0000000000..1874a0a174
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_event_listener.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("test event triggering actor creation", {
+ async test(browser) {
+ // Add a select element to the DOM of the loaded document.
+ await SpecialPowers.spawn(browser, [], async function() {
+ content.document.body.innerHTML += `
+ <select id="testSelect">
+ <option>A</option>
+ <option>B</option>
+ </select>`;
+ });
+
+ // Wait for the observer notification.
+ let observePromise = new Promise(resolve => {
+ const TOPIC = "test-js-window-actor-parent-event";
+ Services.obs.addObserver(function obs(subject, topic, data) {
+ is(topic, TOPIC, "topic matches");
+
+ Services.obs.removeObserver(obs, TOPIC);
+ resolve({ subject, data });
+ }, TOPIC);
+ });
+
+ // Click on the select to show the dropdown.
+ await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser);
+
+ // Wait for the observer notification to fire, and inspect the results.
+ let { subject, data } = await observePromise;
+ is(data, "mozshowdropdown");
+
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+ ok(actorParent, "JSWindowActorParent should have value.");
+ is(
+ subject.wrappedJSObject,
+ actorParent,
+ "Should have been recieved on the right actor"
+ );
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor.js b/dom/ipc/tests/JSWindowActor/browser_getActor.js
new file mode 100644
index 0000000000..53205fc7d9
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("getActor on both sides", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(parent, "WindowGlobalParent should have value.");
+ let actorParent = parent.getActor("TestWindow");
+ is(actorParent.show(), "TestWindowParent", "actor show should have vaule.");
+ is(actorParent.manager, parent, "manager should match WindowGlobalParent.");
+
+ ok(
+ actorParent.sawActorCreated,
+ "Checking that we can observe parent creation"
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ is(
+ child.isInProcess,
+ false,
+ "Actor should be loaded in the content process."
+ );
+ let actorChild = child.getActor("TestWindow");
+ is(actorChild.show(), "TestWindowChild", "actor show should have vaule.");
+ is(actorChild.manager, child, "manager should match WindowGlobalChild.");
+
+ ok(
+ actorChild.sawActorCreated,
+ "Checking that we can observe child creation"
+ );
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js
new file mode 100644
index 0000000000..7ee938dddb
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js
@@ -0,0 +1,259 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+requestLongerTimeout(2);
+
+declTest("getActor with mismatch", {
+ matches: ["*://*/*"],
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(parent, "WindowGlobalParent should have value.");
+ Assert.throws(
+ () => parent.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if it doesn't match."
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if it doesn't match."
+ );
+ });
+ },
+});
+
+declTest("getActor with matches", {
+ matches: ["*://*/*"],
+ url: TEST_URL,
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value.");
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ ok(child.getActor("TestWindow"), "JSWindowActorChild should have value.");
+ });
+ },
+});
+
+declTest("getActor with iframe matches", {
+ allFrames: true,
+ matches: ["*://*/*"],
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ frame.src = url;
+ content.document.body.appendChild(frame);
+ await ContentTaskUtils.waitForEvent(frame, "load");
+
+ is(content.frames.length, 1, "There should be an iframe.");
+ await content.SpecialPowers.spawn(frame, [], () => {
+ let child = content.windowGlobalChild;
+ Assert.ok(
+ child.getActor("TestWindow"),
+ "JSWindowActorChild should have value."
+ );
+ });
+ });
+ },
+});
+
+declTest("getActor with iframe mismatch", {
+ allFrames: true,
+ matches: ["about:home"],
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ frame.src = url;
+ content.document.body.appendChild(frame);
+ await ContentTaskUtils.waitForEvent(frame, "load");
+
+ is(content.frames.length, 1, "There should be an iframe.");
+ await content.SpecialPowers.spawn(frame, [], () => {
+ let child = content.windowGlobalChild;
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if it doesn't match."
+ );
+ });
+ });
+ },
+});
+
+declTest("getActor with remoteType match", {
+ remoteTypes: ["web"],
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value.");
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ ok(child.getActor("TestWindow"), "JSWindowActorChild should have value.");
+ });
+ },
+});
+
+declTest("getActor with iframe remoteType match", {
+ allFrames: true,
+ remoteTypes: ["web"],
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ ok(child.getActor("TestWindow"), "JSWindowActorChild should have value.");
+
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ frame.src = url;
+ content.document.body.appendChild(frame);
+ await ContentTaskUtils.waitForEvent(frame, "load");
+
+ is(content.frames.length, 1, "There should be an iframe.");
+ await content.SpecialPowers.spawn(frame, [], () => {
+ child = content.windowGlobalChild;
+ Assert.ok(
+ child.getActor("TestWindow"),
+ "JSWindowActorChild should have value."
+ );
+ });
+ });
+ },
+});
+
+declTest("getActor with remoteType mismatch", {
+ remoteTypes: ["privilegedabout"],
+ url: TEST_URL,
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ Assert.throws(
+ () => parent.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if its remoteTypes don't match."
+ );
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if its remoteTypes don't match."
+ );
+ });
+ },
+});
+
+declTest("getActor with iframe messageManagerGroups match", {
+ allFrames: true,
+ messageManagerGroups: ["browsers"],
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value.");
+
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ ok(child.getActor("TestWindow"), "JSWindowActorChild should have value.");
+ });
+ },
+});
+
+declTest("getActor with iframe messageManagerGroups mismatch", {
+ allFrames: true,
+ messageManagerGroups: ["sidebars"],
+
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ Assert.throws(
+ () => parent.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if its messageManagerGroups doesn't match."
+ );
+
+ await SpecialPowers.spawn(browser, [TEST_URL], async function(url) {
+ let child = content.windowGlobalChild;
+ ok(child, "WindowGlobalChild should have value.");
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if its messageManagerGroups doesn't match."
+ );
+ });
+ },
+});
+
+declTest("getActor without allFrames", {
+ allFrames: false,
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+ is(content.frames.length, 1, "There should be an iframe.");
+ let child = frame.contentWindow.windowGlobalChild;
+ Assert.throws(
+ () => child.getActor("TestWindow"),
+ /NotSupportedError/,
+ "Should throw if allFrames is false."
+ );
+ });
+ },
+});
+
+declTest("getActor with allFrames", {
+ allFrames: true,
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ // Create and append an iframe into the window's document.
+ let frame = content.document.createElement("iframe");
+ content.document.body.appendChild(frame);
+ is(content.frames.length, 1, "There should be an iframe.");
+ let child = frame.contentWindow.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+ });
+ },
+});
+
+declTest("getActor without includeChrome", {
+ includeChrome: false,
+
+ async test(_browser, win) {
+ let parent = win.docShell.browsingContext.currentWindowGlobal;
+ SimpleTest.doesThrow(
+ () => parent.getActor("TestWindow"),
+ "Should throw if includeChrome is false."
+ );
+ },
+});
+
+declTest("getActor with includeChrome", {
+ includeChrome: true,
+
+ async test(_browser, win) {
+ let parent = win.docShell.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+ ok(actorParent, "JSWindowActorParent should have value.");
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_observer_notification.js b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js
new file mode 100644
index 0000000000..ae8c40f781
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* eslint-disable no-unused-vars */
+declTest("test observer triggering actor creation", {
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ const TOPIC = "test-js-window-actor-child-observer";
+ Services.obs.notifyObservers(content.window, TOPIC, "dataString");
+
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+ let { subject, topic, data } = actorChild.lastObserved;
+
+ is(
+ subject.windowGlobalChild.getActor("TestWindow"),
+ actorChild,
+ "Should have been recieved on the right actor"
+ );
+ is(topic, TOPIC, "Topic matches");
+ is(data, "dataString", "Data matches");
+ });
+ },
+});
+
+declTest("test observers with null data", {
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ const TOPIC = "test-js-window-actor-child-observer";
+ Services.obs.notifyObservers(content.window, TOPIC);
+
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+ let { subject, topic, data } = actorChild.lastObserved;
+
+ is(
+ subject.windowGlobalChild.getActor("TestWindow"),
+ actorChild,
+ "Should have been recieved on the right actor"
+ );
+ is(topic, TOPIC, "Topic matches");
+ is(data, null, "Data matches");
+ });
+ },
+});
+
+declTest("observers don't notify with wrong window", {
+ async test(browser) {
+ const MSG_RE = /JSWindowActor TestWindow: expected window subject for topic 'test-js-window-actor-child-observer'/;
+ let expectMessage = new Promise(resolve => {
+ Services.console.registerListener(function consoleListener(msg) {
+ // Run everything async in order to avoid logging messages from the
+ // console listener.
+ Cu.dispatch(() => {
+ if (!MSG_RE.test(msg.message)) {
+ info("ignoring non-matching console message: " + msg.message);
+ return;
+ }
+ info("received console message: " + msg.message);
+ is(msg.logLevel, Ci.nsIConsoleMessage.error, "should be an error");
+
+ Services.console.unregisterListener(consoleListener);
+ resolve();
+ });
+ });
+ });
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ const TOPIC = "test-js-window-actor-child-observer";
+ Services.obs.notifyObservers(null, TOPIC);
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+ is(
+ actorChild.lastObserved,
+ undefined,
+ "Should not receive wrong window's observer notification!"
+ );
+ });
+
+ await expectMessage;
+ },
+});
+
+declTest("observers notify with audio-playback", {
+ url:
+ "http://example.com/browser/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html",
+
+ async test(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ let audio = content.document.querySelector("audio");
+ audio.play();
+
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ let observePromise = new Promise(resolve => {
+ actorChild.done = ({ subject, topic, data }) =>
+ resolve({ subject, topic, data });
+ });
+
+ let { subject, topic, data } = await observePromise;
+ is(topic, "audio-playback", "Topic matches");
+ is(data, "active", "Data matches");
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_process_childid.js b/dom/ipc/tests/JSWindowActor/browser_process_childid.js
new file mode 100644
index 0000000000..95e1a0c422
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_process_childid.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that `process.childID` is defined.
+
+declTest("test childid", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ ok(
+ parent.domProcess.childID,
+ "parent domProcess.childID should have a value."
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [parent.domProcess.childID],
+ async function(parentChildID) {
+ ok(
+ ChromeUtils.domProcessChild.childID,
+ "child process.childID should have a value."
+ );
+ let childID = ChromeUtils.domProcessChild.childID;
+ is(parentChildID, childID);
+ }
+ );
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js
new file mode 100644
index 0000000000..838fa653b4
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js
@@ -0,0 +1,12 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("double register", {
+ async test() {
+ SimpleTest.doesThrow(
+ () => ChromeUtils.registerWindowActor("TestWindow", windowActorOptions),
+ "Should throw if register has duplicate name."
+ );
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js
new file mode 100644
index 0000000000..077732c45e
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+declTest("asyncMessage testing", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+ ok(actorParent, "JSWindowActorParent should have value.");
+
+ await ContentTask.spawn(browser, {}, async function() {
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ let promise = new Promise(resolve => {
+ actorChild.sendAsyncMessage("init", {});
+ actorChild.done = data => resolve(data);
+ }).then(data => {
+ ok(data.initial, "Initial should be true.");
+ ok(data.toParent, "ToParent should be true.");
+ ok(data.toChild, "ToChild should be true.");
+ });
+
+ await promise;
+ });
+ },
+});
+
+declTest("asyncMessage without both sides", {
+ async test(browser) {
+ // If we don't create a parent actor, make sure the parent actor
+ // gets created by having sent the message.
+ await ContentTask.spawn(browser, {}, async function() {
+ let child = content.windowGlobalChild;
+ let actorChild = child.getActor("TestWindow");
+ ok(actorChild, "JSWindowActorChild should have value.");
+
+ let promise = new Promise(resolve => {
+ actorChild.sendAsyncMessage("init", {});
+ actorChild.done = data => resolve(data);
+ }).then(data => {
+ ok(data.initial, "Initial should be true.");
+ ok(data.toParent, "ToParent should be true.");
+ ok(data.toChild, "ToChild should be true.");
+ });
+
+ await promise;
+ });
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/browser_sendQuery.js b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
new file mode 100644
index 0000000000..149744a2fc
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const ERROR_LINE_NUMBER = 41;
+const EXCEPTION_LINE_NUMBER = ERROR_LINE_NUMBER + 3;
+
+function maybeAsyncStack(offset, column) {
+ if (
+ Services.prefs.getBoolPref(
+ "javascript.options.asyncstack_capture_debuggee_only"
+ )
+ ) {
+ return "";
+ }
+
+ let stack = Error().stack.replace(/^.*?\n/, "");
+ return (
+ "JSActor query*" +
+ stack.replace(
+ /^([^\n]+?):(\d+):\d+/,
+ (m0, m1, m2) => `${m1}:${+m2 + offset}:${column}`
+ )
+ );
+}
+
+declTest("sendQuery Error", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+
+ let asyncStack = maybeAsyncStack(2, 8);
+ let error = await actorParent
+ .sendQuery("error", { message: "foo" })
+ .catch(e => e);
+
+ is(error.message, "foo", "Error should have the correct message");
+ is(error.name, "SyntaxError", "Error should have the correct name");
+ is(
+ error.stack,
+ `receiveMessage@resource://testing-common/TestWindowChild.jsm:${ERROR_LINE_NUMBER}:31\n` +
+ asyncStack,
+ "Error should have the correct stack"
+ );
+ },
+});
+
+declTest("sendQuery Exception", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+
+ let asyncStack = maybeAsyncStack(2, 8);
+ let error = await actorParent
+ .sendQuery("exception", {
+ message: "foo",
+ result: Cr.NS_ERROR_INVALID_ARG,
+ })
+ .catch(e => e);
+
+ is(error.message, "foo", "Error should have the correct message");
+ is(
+ error.result,
+ Cr.NS_ERROR_INVALID_ARG,
+ "Error should have the correct result code"
+ );
+ is(
+ error.stack,
+ `receiveMessage@resource://testing-common/TestWindowChild.jsm:${EXCEPTION_LINE_NUMBER}:22\n` +
+ asyncStack,
+ "Error should have the correct stack"
+ );
+ },
+});
+
+declTest("sendQuery testing", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+ ok(actorParent, "JSWindowActorParent should have value.");
+
+ let { result } = await actorParent.sendQuery("asyncAdd", { a: 10, b: 20 });
+ is(result, 30);
+ },
+});
+
+declTest("sendQuery in-process early lifetime", {
+ url: "about:mozilla",
+ allFrames: true,
+
+ async test(browser) {
+ let iframe = browser.contentDocument.createElement("iframe");
+ browser.contentDocument.body.appendChild(iframe);
+ let wgc = iframe.contentWindow.windowGlobalChild;
+ let actorChild = wgc.getActor("TestWindow");
+ let { result } = await actorChild.sendQuery("asyncMul", { a: 10, b: 20 });
+ is(result, 200);
+ },
+});
+
+declTest("sendQuery unserializable reply", {
+ async test(browser) {
+ let parent = browser.browsingContext.currentWindowGlobal;
+ let actorParent = parent.getActor("TestWindow");
+ ok(actorParent, "JSWindowActorParent should have value");
+
+ try {
+ await actorParent.sendQuery("noncloneReply", {});
+ ok(false, "expected noncloneReply to be rejected");
+ } catch (error) {
+ ok(
+ error.message.includes("message reply cannot be cloned"),
+ "Error should have the correct message"
+ );
+ }
+ },
+});
diff --git a/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html b/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html
new file mode 100644
index 0000000000..a6979287e2
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<audio src="audio.ogg" controls loop>
diff --git a/dom/ipc/tests/JSWindowActor/head.js b/dom/ipc/tests/JSWindowActor/head.js
new file mode 100644
index 0000000000..a1a98f66e0
--- /dev/null
+++ b/dom/ipc/tests/JSWindowActor/head.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provide infrastructure for JSWindowActor tests.
+ */
+
+const URL = "about:blank";
+const TEST_URL = "http://test2.example.org/";
+let windowActorOptions = {
+ parent: {
+ moduleURI: "resource://testing-common/TestWindowParent.jsm",
+ },
+ child: {
+ moduleURI: "resource://testing-common/TestWindowChild.jsm",
+
+ events: {
+ mozshowdropdown: {},
+ },
+
+ observers: ["test-js-window-actor-child-observer", "audio-playback"],
+ },
+};
+
+function declTest(name, cfg) {
+ let {
+ url = "about:blank",
+ allFrames = false,
+ includeChrome = false,
+ matches,
+ remoteTypes,
+ messageManagerGroups,
+ test,
+ } = cfg;
+
+ // Build the actor options object which will be used to register & unregister
+ // our window actor.
+ let actorOptions = {
+ parent: Object.assign({}, windowActorOptions.parent),
+ child: Object.assign({}, windowActorOptions.child),
+ };
+ actorOptions.allFrames = allFrames;
+ actorOptions.includeChrome = includeChrome;
+ if (matches !== undefined) {
+ actorOptions.matches = matches;
+ }
+ if (remoteTypes !== undefined) {
+ actorOptions.remoteTypes = remoteTypes;
+ }
+ if (messageManagerGroups !== undefined) {
+ actorOptions.messageManagerGroups = messageManagerGroups;
+ }
+
+ // Add a new task for the actor test declared here.
+ add_task(async function() {
+ info("Entering test: " + name);
+
+ // Register our actor, and load a new tab with the relevant URL
+ ChromeUtils.registerWindowActor("TestWindow", actorOptions);
+ try {
+ await BrowserTestUtils.withNewTab(url, async browser => {
+ info("browser ready");
+ await Promise.resolve(test(browser, window));
+ });
+ } finally {
+ // Unregister the actor after the test is complete.
+ ChromeUtils.unregisterWindowActor("TestWindow");
+ info("Exiting test: " + name);
+ }
+ });
+}
diff --git a/dom/ipc/tests/blob_verify.sjs b/dom/ipc/tests/blob_verify.sjs
new file mode 100644
index 0000000000..cf50371c8a
--- /dev/null
+++ b/dom/ipc/tests/blob_verify.sjs
@@ -0,0 +1,20 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream");
+
+function handleRequest(request, response) {
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bodyBytes = [];
+ while ((bodyAvail = bodyStream.available()) > 0)
+ Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail));
+
+ var bos = new BinaryOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(bodyBytes);
+ response.finish();
+}
diff --git a/dom/ipc/tests/browser.ini b/dom/ipc/tests/browser.ini
new file mode 100644
index 0000000000..aa60226764
--- /dev/null
+++ b/dom/ipc/tests/browser.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+support-files =
+ file_disableScript.html
+ file_domainPolicy_base.html
+ file_cancel_content_js.html
+ ../../media/test/short.mp4
+ ../../media/test/owl.mp3
+
+[browser_CrashService_crash.js]
+skip-if = !crashreporter
+[browser_ProcessPriorityManager.js]
+skip-if = os != "win" # The Process Priority Manager is only enabled for Windows so far. Bug 1522879.
+[browser_crash_oopiframe.js]
+skip-if = !fission || !crashreporter || verify
+[browser_domainPolicy.js]
+[browser_memory_distribution_telemetry.js]
+skip-if = true || !e10s # This is an e10s only probe, but the test is currently broken. See Bug 1449991
+[browser_cancel_content_js.js]
+skip-if = !e10s
+[browser_bug1646088.js]
+support-files = file_dummy.html
+skip-if = !e10s
diff --git a/dom/ipc/tests/browser_CrashService_crash.js b/dom/ipc/tests/browser_CrashService_crash.js
new file mode 100644
index 0000000000..9504d68ae7
--- /dev/null
+++ b/dom/ipc/tests/browser_CrashService_crash.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Ensures that content crashes are reported to the crash service
+// (nsICrashService and CrashManager.jsm).
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.requestCompleteLog();
+
+add_task(async function() {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ forceNewProcess: true,
+ });
+
+ SimpleTest.expectChildProcessCrash();
+
+ let crashMan = Services.crashmanager;
+
+ // First, clear the crash record store.
+ info("Waiting for pruneOldCrashes");
+ var future = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ await crashMan.pruneOldCrashes(future);
+
+ var crashDateMS = Date.now();
+
+ let crashPromise = BrowserTestUtils.crashFrame(tab.linkedBrowser);
+
+ // Finally, poll for the new crash record.
+ await new Promise((resolve, reject) => {
+ function tryGetCrash() {
+ info("Waiting for getCrashes");
+ crashMan.getCrashes().then(
+ function(crashes) {
+ if (crashes.length) {
+ is(crashes.length, 1, "There should be only one record");
+ var crash = crashes[0];
+ ok(
+ crash.isOfType(
+ crashMan.PROCESS_TYPE_CONTENT,
+ crashMan.CRASH_TYPE_CRASH
+ ),
+ "Record should be a content crash"
+ );
+ ok(!!crash.id, "Record should have an ID");
+ ok(!!crash.crashDate, "Record should have a crash date");
+ var dateMS = crash.crashDate.valueOf();
+ var twoMin = 1000 * 60 * 2;
+ ok(
+ crashDateMS - twoMin <= dateMS && dateMS <= crashDateMS + twoMin,
+ `Record's crash date should be nowish: ` +
+ `now=${crashDateMS} recordDate=${dateMS}`
+ );
+ resolve();
+ } else {
+ setTimeout(tryGetCrash, 1000);
+ }
+ },
+ function(err) {
+ reject(err);
+ }
+ );
+ }
+ setTimeout(tryGetCrash, 1000);
+ });
+
+ await crashPromise;
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/dom/ipc/tests/browser_ProcessPriorityManager.js b/dom/ipc/tests/browser_ProcessPriorityManager.js
new file mode 100644
index 0000000000..b74b2b84cd
--- /dev/null
+++ b/dom/ipc/tests/browser_ProcessPriorityManager.js
@@ -0,0 +1,520 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PRIORITY_SET_TOPIC =
+ "process-priority-manager:TEST-ONLY:process-priority-set";
+
+// Copied from Hal.cpp
+const PROCESS_PRIORITY_FOREGROUND = "FOREGROUND";
+const PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE = "BACKGROUND_PERCEIVABLE";
+const PROCESS_PRIORITY_BACKGROUND = "BACKGROUND";
+
+// This is how many milliseconds we'll wait for a process priority
+// change before we assume that it's just not happening.
+const WAIT_FOR_CHANGE_TIME_MS = 2000;
+
+/**
+ * This class is responsible for watching process priority changes, and
+ * mapping them to tabs in a single window.
+ */
+class TabPriorityWatcher {
+ /**
+ * Constructing a TabPriorityWatcher should happen before any tests
+ * start when there's only a single tab in the window.
+ *
+ * Callers must call `destroy()` on any instance that is constructed
+ * when the test is completed.
+ *
+ * @param tabbrowser (<tabbrowser>)
+ * The tabbrowser (gBrowser) for the window to be tested.
+ */
+ constructor(tabbrowser) {
+ this.tabbrowser = tabbrowser;
+ Assert.equal(
+ tabbrowser.tabs.length,
+ 1,
+ "TabPriorityWatcher must be constructed in a window " +
+ "with a single tab to start."
+ );
+
+ this.priorityMap = new WeakMap();
+ this.priorityMap.set(
+ this.tabbrowser.selectedBrowser,
+ PROCESS_PRIORITY_FOREGROUND
+ );
+ this.noChangeBrowsers = new WeakMap();
+ Services.obs.addObserver(this, PRIORITY_SET_TOPIC);
+ }
+
+ /**
+ * Cleans up lingering references for an instance of
+ * TabPriorityWatcher to avoid leaks. This should be called when
+ * finishing the test.
+ */
+ destroy() {
+ Services.obs.removeObserver(this, PRIORITY_SET_TOPIC);
+ this.window = null;
+ }
+
+ /**
+ * Returns a Promise that resolves when a particular <browser>
+ * has its content process reach a particular priority. Will
+ * eventually time out if that priority is never reached.
+ *
+ * @param browser (<browser>)
+ * The <browser> that we expect to change priority.
+ * @param expectedPriority (String)
+ * One of the PROCESS_PRIORITY_ constants defined at the
+ * top of this file.
+ * @return Promise
+ * @resolves undefined
+ * Once the browser reaches the expected priority.
+ */
+ async waitForPriorityChange(browser, expectedPriority) {
+ return TestUtils.waitForCondition(() => {
+ let currentPriority = this.priorityMap.get(browser);
+ if (currentPriority == expectedPriority) {
+ Assert.ok(
+ true,
+ `Browser at ${browser.currentURI.spec} reached expected ` +
+ `priority: ${currentPriority}`
+ );
+ return true;
+ }
+ return false;
+ }, `Waiting for browser at ${browser.currentURI.spec} to reach priority ` + expectedPriority);
+ }
+
+ /**
+ * Returns a Promise that resolves after a duration of
+ * WAIT_FOR_CHANGE_TIME_MS. During that time, if the passed browser
+ * changes priority, a test failure will be registered.
+ *
+ * @param browser (<browser>)
+ * The <browser> that we expect to change priority.
+ * @return Promise
+ * @resolves undefined
+ * Once the WAIT_FOR_CHANGE_TIME_MS duration has passed.
+ */
+ async ensureNoPriorityChange(browser) {
+ this.noChangeBrowsers.set(browser, null);
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS));
+ let priority = this.noChangeBrowsers.get(browser);
+ Assert.equal(
+ priority,
+ null,
+ `Should have seen no process priority change for a browser at ${browser.currentURI.spec}`
+ );
+ this.noChangeBrowsers.delete(browser);
+ }
+
+ /**
+ * Makes sure that a particular foreground browser has been
+ * registered in the priority map. This is needed because browsers are
+ * only registered when their priorities change - and if a browser's
+ * priority never changes during a test, then they wouldn't be registered.
+ *
+ * The passed browser must be a foreground browser, since it's assumed that
+ * the associated content process is running with foreground priority.
+ *
+ * @param browser (browser)
+ * A _foreground_ browser.
+ */
+ ensureForegroundRegistered(browser) {
+ if (!this.priorityMap.has(browser)) {
+ this.priorityMap.set(browser, PROCESS_PRIORITY_FOREGROUND);
+ }
+ }
+
+ /**
+ * Synchronously returns the priority of a particular browser's
+ * content process.
+ *
+ * @param browser (browser)
+ * The browser to get the content process priority for.
+ * @return String
+ * The priority that the browser's content process is at.
+ */
+ currentPriority(browser) {
+ return this.priorityMap.get(browser);
+ }
+
+ /**
+ * A utility function that takes a string passed via the
+ * PRIORITY_SET_TOPIC observer notification and extracts the
+ * childID and priority string.
+ *
+ * @param ppmDataString (String)
+ * The string data passed through the PRIORITY_SET_TOPIC observer
+ * notification.
+ * @return Object
+ * An object with the following properties:
+ *
+ * childID (Number)
+ * The ID of the content process that changed priority.
+ *
+ * priority (String)
+ * The priority that the content process was set to.
+ */
+ parsePPMData(ppmDataString) {
+ let [childIDStr, priority] = ppmDataString.split(":");
+ return {
+ childID: parseInt(childIDStr, 10),
+ priority,
+ };
+ }
+
+ /** nsIObserver **/
+ observe(subject, topic, data) {
+ if (topic != PRIORITY_SET_TOPIC) {
+ Assert.ok(false, "TabPriorityWatcher is observing the wrong topic");
+ return;
+ }
+
+ let { childID, priority } = this.parsePPMData(data);
+ for (let browser of this.tabbrowser.browsers) {
+ if (browser.frameLoader.childID == childID) {
+ info(
+ `Browser at: ${browser.currentURI.spec} transitioning to ${priority}`
+ );
+ if (this.noChangeBrowsers.has(browser)) {
+ this.noChangeBrowsers.set(browser, priority);
+ }
+ this.priorityMap.set(browser, priority);
+ }
+ }
+ }
+}
+
+let gTabPriorityWatcher;
+
+add_task(async function setup() {
+ // We need to turn on testMode for the process priority manager in
+ // order to receive the observer notifications that this test relies on.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processPriorityManager.testMode", true],
+ ["dom.ipc.processPriorityManager.enabled", true],
+ ],
+ });
+ gTabPriorityWatcher = new TabPriorityWatcher(gBrowser);
+});
+
+registerCleanupFunction(() => {
+ gTabPriorityWatcher.destroy();
+ gTabPriorityWatcher = null;
+});
+
+/**
+ * Utility function that switches the current tabbrowser from one
+ * tab to another, and ensures that the tab that goes into the background
+ * has (or reaches) a particular content process priority.
+ *
+ * It is expected that the fromTab and toTab belong to two separate content
+ * processes.
+ *
+ * @param Object
+ * An object with the following properties:
+ *
+ * fromTab (<tab>)
+ * The tab that will be switched from to the toTab. The fromTab
+ * is the one that will be going into the background.
+ *
+ * toTab (<tab>)
+ * The tab that will be switched to from the fromTab. The toTab
+ * is presumed to start in the background, and will enter the
+ * foreground.
+ *
+ * fromTabExpectedPriority (String)
+ * The priority that the content process for the fromTab is
+ * expected to be (or reach) after the tab goes into the background.
+ * This should be one of the PROCESS_PRIORITY_ strings defined at the
+ * top of the file.
+ *
+ * @return Promise
+ * @resolves undefined
+ * Once the tab switch is complete, and the two content processes for the
+ * tabs have reached the expected priority levels.
+ */
+async function assertPriorityChangeOnBackground({
+ fromTab,
+ toTab,
+ fromTabExpectedPriority,
+}) {
+ let fromBrowser = fromTab.linkedBrowser;
+ let toBrowser = toTab.linkedBrowser;
+
+ // If the tabs aren't running in separate processes, none of the
+ // rest of this is going to work.
+ Assert.notEqual(
+ toBrowser.frameLoader.remoteTab.osPid,
+ fromBrowser.frameLoader.remoteTab.osPid,
+ "Tabs should be running in separate processes."
+ );
+
+ gTabPriorityWatcher.ensureForegroundRegistered(fromBrowser);
+
+ let fromPromise;
+ if (
+ gTabPriorityWatcher.currentPriority(fromBrowser) == fromTabExpectedPriority
+ ) {
+ fromPromise = gTabPriorityWatcher.ensureNoPriorityChange(fromBrowser);
+ } else {
+ fromPromise = gTabPriorityWatcher.waitForPriorityChange(
+ fromBrowser,
+ fromTabExpectedPriority
+ );
+ }
+
+ let toPromise;
+ if (
+ gTabPriorityWatcher.currentPriority(toBrowser) ==
+ PROCESS_PRIORITY_FOREGROUND
+ ) {
+ toPromise = gTabPriorityWatcher.ensureNoPriorityChange(toBrowser);
+ } else {
+ toPromise = gTabPriorityWatcher.waitForPriorityChange(
+ toBrowser,
+ PROCESS_PRIORITY_FOREGROUND
+ );
+ }
+
+ await BrowserTestUtils.switchTab(gBrowser, toTab);
+ await Promise.all([fromPromise, toPromise]);
+}
+
+/**
+ * Test that if a normal tab goes into the background,
+ * it has its process priority lowered to
+ * PROCESS_PRIORITY_BACKGROUND.
+ */
+add_task(async function test_normal_background_tab() {
+ let originalTab = gBrowser.selectedTab;
+
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ let tab = gBrowser.getTabForBrowser(browser);
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+ });
+});
+
+/**
+ * Test that if a tab with video goes into the background,
+ * it has its process priority lowered to
+ * PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE if it has no audio,
+ * and that it has its priority remain at
+ * PROCESS_PRIORITY_FOREGROUND if it does have audio.
+ */
+add_task(async function test_video_background_tab() {
+ let originalTab = gBrowser.selectedTab;
+
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ // Let's load up a video in the tab, but mute it, so that this tab should
+ // reach PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let video = content.document.createElement("video");
+ video.src = "http://mochi.test:8888/browser/dom/ipc/tests/short.mp4";
+ video.muted = true;
+ content.document.body.appendChild(video);
+ // We'll loop the video to avoid it ending before the test is done.
+ video.loop = true;
+ await video.play();
+ });
+
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // The tab with the muted video should reach
+ // PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ // Let's unmute the video now.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let video = content.document.querySelector("video");
+ video.muted = false;
+ });
+
+ // The tab with the unmuted video should stay at
+ // PROCESS_PRIORITY_FOREGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+ });
+});
+
+/**
+ * Test that if a tab with a playing <audio> element goes into
+ * the background, the process priority does not change, unless
+ * that audio is muted (in which case, it reaches
+ * PROCESS_PRIORITY_BACKGROUND).
+ */
+add_task(async function test_audio_background_tab() {
+ let originalTab = gBrowser.selectedTab;
+
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ // Let's load up some audio in the tab, but mute it, so that this tab should
+ // reach PROCESS_PRIORITY_BACKGROUND.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let audio = content.document.createElement("audio");
+ audio.src = "http://mochi.test:8888/browser/dom/ipc/tests/owl.mp3";
+ audio.muted = true;
+ content.document.body.appendChild(audio);
+ // We'll loop the audio to avoid it ending before the test is done.
+ audio.loop = true;
+ await audio.play();
+ });
+
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // The tab with the muted audio should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ // Now unmute the audio. Unfortuntely, there's a bit of a race here,
+ // since the wakelock on the audio element is released and then
+ // re-acquired if the audio reaches its end and loops around. This
+ // will cause an unexpected priority change on the content process.
+ //
+ // To avoid this race, we'll seek the audio back to the beginning,
+ // and lower its playback rate to the minimum to increase the
+ // likelihood that the check completes before the audio loops around.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let audio = content.document.querySelector("audio");
+ let seeked = ContentTaskUtils.waitForEvent(audio, "seeked");
+ audio.muted = false;
+ // 0.25 is the minimum playback rate that still keeps the audio audible.
+ audio.playbackRate = 0.25;
+ audio.currentTime = 0;
+ await seeked;
+ });
+
+ // The tab with the unmuted audio should stay at
+ // PROCESS_PRIORITY_FOREGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+ });
+});
+
+/**
+ * Test that if a tab with a WebAudio playing goes into the background,
+ * the process priority does not change, unless that WebAudio context is
+ * suspended.
+ */
+add_task(async function test_web_audio_background_tab() {
+ let originalTab = gBrowser.selectedTab;
+
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ // Let's synthesize a basic square wave as WebAudio.
+ await SpecialPowers.spawn(browser, [], async () => {
+ let audioCtx = new content.AudioContext();
+ let oscillator = audioCtx.createOscillator();
+ oscillator.type = "square";
+ oscillator.frequency.setValueAtTime(440, audioCtx.currentTime);
+ oscillator.connect(audioCtx.destination);
+ oscillator.start();
+ while (audioCtx.state != "running") {
+ info(`wait until AudioContext starts running`);
+ await new Promise(r => (audioCtx.onstatechange = r));
+ }
+ // we'll stash the AudioContext away so that it's easier to access
+ // in the next SpecialPowers.spawn.
+ content.audioCtx = audioCtx;
+ });
+
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // The tab with the WebAudio should stay at
+ // PROCESS_PRIORITY_FOREGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ // Now suspend the WebAudio. This will cause it to stop
+ // playing.
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.audioCtx.suspend();
+ });
+
+ // The tab with the suspended WebAudio should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: tab,
+ toTab: originalTab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+
+ // Now switch back. The initial blank tab should reach
+ // PROCESS_PRIORITY_BACKGROUND when backgrounded.
+ await assertPriorityChangeOnBackground({
+ fromTab: originalTab,
+ toTab: tab,
+ fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
+ });
+ });
+});
diff --git a/dom/ipc/tests/browser_bug1646088.js b/dom/ipc/tests/browser_bug1646088.js
new file mode 100644
index 0000000000..4466c67129
--- /dev/null
+++ b/dom/ipc/tests/browser_bug1646088.js
@@ -0,0 +1,70 @@
+const { PromiseUtils } = ChromeUtils.import(
+ "resource://gre/modules/PromiseUtils.jsm"
+);
+
+let dir = getChromeDir(getResolvedURI(gTestPath));
+dir.append("file_dummy.html");
+const uriString = Services.io.newFileURI(dir).spec;
+
+add_task(async function() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function(
+ browser
+ ) {
+ // Override the browser's `prepareToChangeRemoteness` so that we can delay
+ // the process switch for an indefinite amount of time. This will allow us
+ // to control the timing of the resolve call to trigger the bug.
+ let prepareToChangeCalled = PromiseUtils.defer();
+ let finishSwitch = PromiseUtils.defer();
+ let oldPrepare = browser.prepareToChangeRemoteness;
+ browser.prepareToChangeRemoteness = async () => {
+ prepareToChangeCalled.resolve();
+ await oldPrepare.call(browser);
+ await finishSwitch.promise;
+ };
+
+ // Begin a process switch, which should cause `prepareToChangeRemoteness` to
+ // be called. We do this from the content process to make sure the frontend
+ // has no chance to trigger an eager process switch.
+ info("Beginning process switch into file URI process");
+ let browserLoaded = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [uriString], uri => {
+ content.location = uri;
+ });
+ await prepareToChangeCalled.promise;
+
+ // The tab we opened is now midway through process switching. Open another
+ // browser within the same tab, and immediately close it after the load
+ // finishes.
+ info("Creating new tab loaded in file URI process");
+ let fileProcess;
+ let browserParentDestroyed = PromiseUtils.defer();
+ await BrowserTestUtils.withNewTab(uriString, async function(otherBrowser) {
+ let remoteTab = otherBrowser.frameLoader.remoteTab;
+ fileProcess = remoteTab.contentProcessId;
+ info("Loaded test URI in pid: " + fileProcess);
+
+ browserParentDestroyed.resolve(
+ TestUtils.topicObserved(
+ "ipc:browser-destroyed",
+ subject => subject === remoteTab
+ )
+ );
+ });
+ await browserParentDestroyed.promise;
+
+ // This browser has now been closed, which could cause the file content
+ // process to begin shutting down, despite us process switching into it.
+ // We can now allow the process switch to finish, and wait for the load to
+ // finish as well.
+ info("BrowserParent has been destroyed, finishing process switch");
+ finishSwitch.resolve();
+ await browserLoaded;
+
+ info("Load complete");
+ is(
+ browser.frameLoader.remoteTab.contentProcessId,
+ fileProcess,
+ "Should have loaded in the same file URI process"
+ );
+ });
+});
diff --git a/dom/ipc/tests/browser_cancel_content_js.js b/dom/ipc/tests/browser_cancel_content_js.js
new file mode 100644
index 0000000000..d319de87e4
--- /dev/null
+++ b/dom/ipc/tests/browser_cancel_content_js.js
@@ -0,0 +1,68 @@
+/* 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/. */
+
+"use strict";
+
+requestLongerTimeout(10);
+
+const TEST_PAGE =
+ "http://mochi.test:8888/browser/dom/ipc/tests/file_cancel_content_js.html";
+const NEXT_PAGE = "http://mochi.test:8888/browser/dom/ipc/tests/";
+const JS_URI = "javascript:void(document.title = 'foo')";
+
+async function test_navigation(nextPage, cancelContentJSPref, shouldCancel) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.cancel_content_js_when_navigating", cancelContentJSPref],
+ ["dom.max_script_run_time", 20],
+ ],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PAGE,
+ });
+
+ const loopEnded = ContentTask.spawn(tab.linkedBrowser, [], async function() {
+ await new Promise(resolve => {
+ content.addEventListener("LongLoopEnded", resolve, {
+ once: true,
+ });
+ });
+ });
+
+ // Wait for the test page's long-running JS loop to start.
+ await ContentTask.spawn(tab.linkedBrowser, [], function() {
+ content.dispatchEvent(new content.Event("StartLongLoop"));
+ });
+
+ info(
+ `navigating to ${nextPage} with cancel content JS ${
+ cancelContentJSPref ? "enabled" : "disabled"
+ }`
+ );
+ const nextPageLoaded = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "DOMTitleChanged"
+ );
+ BrowserTestUtils.loadURI(gBrowser, nextPage);
+
+ const result = await Promise.race([
+ nextPageLoaded,
+ loopEnded.then(() => "timeout"),
+ ]);
+
+ const timedOut = result === "timeout";
+ if (shouldCancel) {
+ ok(timedOut === false, "expected next page to be loaded");
+ } else {
+ ok(timedOut === true, "expected timeout");
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async () => test_navigation(NEXT_PAGE, true, true));
+add_task(async () => test_navigation(NEXT_PAGE, false, false));
+add_task(async () => test_navigation(JS_URI, true, false));
+add_task(async () => test_navigation(JS_URI, false, false));
diff --git a/dom/ipc/tests/browser_crash_oopiframe.js b/dom/ipc/tests/browser_crash_oopiframe.js
new file mode 100644
index 0000000000..2cf133e5e3
--- /dev/null
+++ b/dom/ipc/tests/browser_crash_oopiframe.js
@@ -0,0 +1,167 @@
+"use strict";
+
+/**
+ * Helper function for testing frame crashing. Some tabs are opened
+ * containing frames from example.com and then the process for
+ * example.com is crashed. Notifications should apply to each tab
+ * and all should close when one of the notifications is closed.
+ *
+ * @param numTabs the number of tabs to open.
+ */
+async function testFrameCrash(numTabs) {
+ let browser, rootBC, iframeBC;
+
+ for (let count = 0; count < numTabs; count++) {
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: "about:blank",
+ });
+
+ browser = tab.linkedBrowser;
+ rootBC = browser.browsingContext;
+
+ // If we load example.com in an injected subframe, we assume that this
+ // will load in its own subprocess, which we can then crash.
+ iframeBC = await SpecialPowers.spawn(browser, [], async () => {
+ let iframe = content.document.createElement("iframe");
+ iframe.setAttribute("src", "http://example.com");
+
+ content.document.body.appendChild(iframe);
+ await ContentTaskUtils.waitForEvent(iframe, "load");
+ return iframe.frameLoader.browsingContext;
+ });
+ }
+
+ is(iframeBC.parent, rootBC, "oop frame has root as parent");
+
+ let eventFiredPromise = BrowserTestUtils.waitForEvent(
+ browser,
+ "oop-browser-crashed"
+ );
+
+ BrowserTestUtils.crashFrame(
+ browser,
+ true /* shouldShowTabCrashPage */,
+ true /* shouldClearMinidumps */,
+ iframeBC
+ );
+
+ let notificationPromise = BrowserTestUtils.waitForNotificationBar(
+ gBrowser,
+ browser,
+ "subframe-crashed"
+ );
+
+ info("Waiting for oop-browser-crashed event.");
+ await eventFiredPromise.then(event => {
+ ok(!event.isTopFrame, "should not be reporting top-level frame crash");
+ ok(event.childID != 0, "childID is non-zero");
+
+ isnot(
+ event.browsingContextId,
+ rootBC,
+ "top frame browsing context id not expected."
+ );
+
+ is(
+ event.browsingContextId,
+ iframeBC.id,
+ "oop frame browsing context id expected."
+ );
+ });
+
+ if (numTabs == 1) {
+ // The BrowsingContext is re-used, but the window global might still be
+ // getting set up at this point, so wait until it's been initialized.
+ let {
+ subject: windowGlobal,
+ } = await BrowserUtils.promiseObserved("window-global-created", wgp =>
+ wgp.documentURI.spec.startsWith("about:framecrashed")
+ );
+
+ is(
+ windowGlobal,
+ iframeBC.currentWindowGlobal,
+ "Resolved on expected window global"
+ );
+
+ let newIframeURI = await SpecialPowers.spawn(iframeBC, [], async () => {
+ return content.document.documentURI;
+ });
+
+ ok(
+ newIframeURI.startsWith("about:framecrashed"),
+ "The iframe is now pointing at about:framecrashed"
+ );
+ }
+
+ // Next, check that the crash notification bar has appeared.
+ await notificationPromise;
+
+ for (let count = 1; count <= numTabs; count++) {
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.browsers[count]);
+ let notification = notificationBox.currentNotification;
+ ok(notification, "Notification " + count + " should be visible");
+ is(
+ notification.getAttribute("value"),
+ "subframe-crashed",
+ "Should be showing the right notification" + count
+ );
+
+ let buttons = notification.querySelectorAll(".notification-button");
+ is(
+ buttons.length,
+ 2,
+ "Notification " + count + " should have only two buttons."
+ );
+ }
+
+ // Press the ignore button on the visible notification.
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+ let notification = notificationBox.currentNotification;
+ notification.dismiss();
+
+ for (let count = 1; count <= numTabs; count++) {
+ let nb = gBrowser.getNotificationBox(gBrowser.browsers[count]);
+
+ await TestUtils.waitForCondition(
+ () => !nb.currentNotification,
+ "notification closed"
+ );
+
+ ok(
+ !nb.currentNotification,
+ "notification " + count + " closed when dismiss button is pressed"
+ );
+ }
+
+ for (let count = 1; count <= numTabs; count++) {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+}
+
+/**
+ * In this test, we crash an out-of-process iframe and
+ * verify that :
+ * 1. the "oop-browser-crashed" event is dispatched with
+ * the browsing context of the crashed oop subframe.
+ * 2. the crashed subframe is now pointing at "about:framecrashed"
+ * page.
+ */
+add_task(async function() {
+ // Open a new window with fission enabled.
+ ok(
+ SpecialPowers.useRemoteSubframes,
+ "This test only makes sense of we can use OOP iframes."
+ );
+
+ // Create the crash reporting directory if it doesn't yet exist, otherwise, a failure
+ // sometimes occurs. See bug 1687855 for fixing this.
+ const uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path;
+ let path = PathUtils.join(uAppDataPath, "Crash Reports", "pending");
+ await IOUtils.makeDirectory(path, { ignoreExisting: true });
+
+ // Test both one tab and when four tabs are opened.
+ await testFrameCrash(1);
+ await testFrameCrash(4);
+});
diff --git a/dom/ipc/tests/browser_domainPolicy.js b/dom/ipc/tests/browser_domainPolicy.js
new file mode 100644
index 0000000000..cec360f2e1
--- /dev/null
+++ b/dom/ipc/tests/browser_domainPolicy.js
@@ -0,0 +1,187 @@
+// This test waits for a lot of subframe loads, causing it to take a long time,
+// especially with Fission enabled.
+requestLongerTimeout(2);
+
+const BASE_FILE =
+ "http://mochi.test:8888/browser/dom/ipc/tests/file_domainPolicy_base.html";
+const SCRIPT_PATH = "/browser/dom/ipc/tests/file_disableScript.html";
+
+const TEST_POLICY = {
+ exceptions: ["http://test1.example.com", "http://example.com"],
+ superExceptions: ["http://test2.example.org", "https://test1.example.com"],
+ exempt: [
+ "http://test1.example.com",
+ "http://example.com",
+ "http://test2.example.org",
+ "http://sub1.test2.example.org",
+ "https://sub1.test1.example.com",
+ ],
+ notExempt: [
+ "http://test2.example.com",
+ "http://sub1.test1.example.com",
+ "http://www.example.com",
+ "https://test2.example.com",
+ "https://example.com",
+ "http://test1.example.org",
+ ],
+};
+
+// To make sure we never leave up an activated domain policy after a failed
+// test, let's make this global.
+var policy;
+
+function activateDomainPolicy(isBlock) {
+ policy = Services.scriptSecurityManager.activateDomainPolicy();
+
+ if (isBlock === undefined) {
+ return;
+ }
+
+ let set = isBlock ? policy.blocklist : policy.allowlist;
+ for (let e of TEST_POLICY.exceptions) {
+ set.add(makeURI(e));
+ }
+
+ let superSet = isBlock ? policy.superBlocklist : policy.superAllowlist;
+ for (let e of TEST_POLICY.superExceptions) {
+ superSet.add(makeURI(e));
+ }
+}
+
+function deactivateDomainPolicy() {
+ if (policy) {
+ policy.deactivate();
+ policy = null;
+ }
+}
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.pagethumbnails.capturing_disabled", false]],
+ });
+
+ registerCleanupFunction(() => {
+ deactivateDomainPolicy();
+ });
+});
+
+add_task(async function test_domainPolicy() {
+ function test(testFunc, { activateFirst, isBlock }) {
+ if (activateFirst) {
+ activateDomainPolicy(isBlock);
+ }
+ return BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ opening: BASE_FILE,
+ forceNewProcess: true,
+ },
+ async browser => {
+ if (!activateFirst) {
+ activateDomainPolicy(isBlock);
+ }
+ await testFunc(browser);
+ deactivateDomainPolicy();
+ }
+ );
+ }
+
+ async function testDomain(browser, domain, expectEnabled = false) {
+ function navigateFrame() {
+ let url = domain + SCRIPT_PATH;
+ return SpecialPowers.spawn(browser, [url], async src => {
+ let iframe = content.document.getElementById("root");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.src = src;
+ });
+ return iframe.browsingContext;
+ });
+ }
+
+ function checkScriptEnabled(bc) {
+ return SpecialPowers.spawn(bc, [expectEnabled], enabled => {
+ content.wrappedJSObject.gFiredOnclick = false;
+ content.document.body.dispatchEvent(new content.Event("click"));
+ Assert.equal(
+ content.wrappedJSObject.gFiredOnclick,
+ enabled,
+ `Checking script-enabled for ${content.name} (${content.location})`
+ );
+ });
+ }
+
+ let browsingContext = await navigateFrame();
+ return checkScriptEnabled(browsingContext);
+ }
+
+ async function testList(browser, list, expectEnabled) {
+ // Run these sequentially to avoid navigating multiple domains at once.
+ for (let domain of list) {
+ await testDomain(browser, domain, expectEnabled);
+ }
+ }
+
+ info("1. Testing simple blocklist policy");
+
+ info("1A. Creating child process first, activating domainPolicy after");
+ await test(
+ async browser => {
+ policy.blocklist.add(Services.io.newURI("http://example.com"));
+ await testDomain(browser, "http://example.com");
+ },
+ { activateFirst: false }
+ );
+
+ info("1B. Activating domainPolicy first, creating child process after");
+ await test(
+ async browser => {
+ policy.blocklist.add(Services.io.newURI("http://example.com"));
+ await testDomain(browser, "http://example.com");
+ },
+ { activateFirst: true }
+ );
+
+ info("2. Testing Blocklist-style Domain Policy");
+
+ info("2A. Activating domainPolicy first, creating child process after");
+ await test(
+ async browser => {
+ await testList(browser, TEST_POLICY.notExempt, true);
+ await testList(browser, TEST_POLICY.exempt, false);
+ },
+ { activateFirst: true, isBlock: true }
+ );
+
+ info("2B. Creating child process first, activating domainPolicy after");
+ await test(
+ async browser => {
+ await testList(browser, TEST_POLICY.notExempt, true);
+ await testList(browser, TEST_POLICY.exempt, false);
+ },
+ { activateFirst: false, isBlock: true }
+ );
+
+ info("3. Testing Allowlist-style Domain Policy");
+ await SpecialPowers.pushPrefEnv({ set: [["javascript.enabled", false]] });
+
+ info("3A. Activating domainPolicy first, creating child process after");
+ await test(
+ async browser => {
+ await testList(browser, TEST_POLICY.notExempt, false);
+ await testList(browser, TEST_POLICY.exempt, true);
+ },
+ { activateFirst: true, isBlock: false }
+ );
+
+ info("3B. Creating child process first, activating domainPolicy after");
+ await test(
+ async browser => {
+ await testList(browser, TEST_POLICY.notExempt, false);
+ await testList(browser, TEST_POLICY.exempt, true);
+ },
+ { activateFirst: false, isBlock: false }
+ );
+
+ finish();
+});
diff --git a/dom/ipc/tests/browser_memory_distribution_telemetry.js b/dom/ipc/tests/browser_memory_distribution_telemetry.js
new file mode 100644
index 0000000000..bec51fa1cd
--- /dev/null
+++ b/dom/ipc/tests/browser_memory_distribution_telemetry.js
@@ -0,0 +1,92 @@
+"use strict";
+
+var session = ChromeUtils.import(
+ "resource://gre/modules/TelemetrySession.jsm",
+ null
+);
+
+const DUMMY_PAGE_DATA_URI = `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Dummy</title>
+ </head>
+ <body>
+ <h1 id='header'>Just a regular everyday normal page.</h1>
+ </body>
+ </html>`;
+
+/**
+ * Tests the MEMORY_DISTRIBUTION_AMONG_CONTENT probe by opening a few tabs, then triggering
+ * the memory probes and waiting for the "gather-memory-telemetry-finished" notification.
+ */
+add_task(async function test_memory_distribution() {
+ waitForExplicitFinish();
+
+ if (SpecialPowers.getIntPref("dom.ipc.processCount", 1) < 2) {
+ ok(true, "Skip this test if e10s-multi is disabled.");
+ finish();
+ return;
+ }
+
+ Services.telemetry.canRecordExtended = true;
+
+ let histogram = Services.telemetry.getKeyedHistogramById(
+ "MEMORY_DISTRIBUTION_AMONG_CONTENT"
+ );
+ histogram.clear();
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ DUMMY_PAGE_DATA_URI
+ );
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ DUMMY_PAGE_DATA_URI
+ );
+ let tab3 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ DUMMY_PAGE_DATA_URI
+ );
+
+ let finishedGathering = new Promise(resolve => {
+ let obs = function() {
+ Services.obs.removeObserver(obs, "gather-memory-telemetry-finished");
+ resolve();
+ };
+ Services.obs.addObserver(obs, "gather-memory-telemetry-finished");
+ });
+
+ session.TelemetrySession.getPayload();
+
+ await finishedGathering;
+
+ let s = histogram.snapshot();
+ ok("0 - 10 tabs" in s, "We should have some samples by now in this bucket.");
+ for (var key in s) {
+ is(key, "0 - 10 tabs");
+ let fewTabsSnapshot = s[key];
+ ok(
+ fewTabsSnapshot.sum > 0,
+ "Zero difference between all the content processes is unlikely, what happened?"
+ );
+ ok(
+ fewTabsSnapshot.sum < 80,
+ "20 percentage difference on average is unlikely, what happened?"
+ );
+ let values = fewTabsSnapshot.values;
+ for (let [bucket, value] of Object.entries(values)) {
+ if (bucket >= 10) {
+ // If this check fails it means that one of the content processes uses at least 20% more or 20% less than the mean.
+ is(value, 0, "All the buckets above 10 should be empty");
+ }
+ }
+ }
+
+ histogram.clear();
+
+ BrowserTestUtils.removeTab(tab3);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab1);
+ finish();
+});
diff --git a/dom/ipc/tests/chrome.ini b/dom/ipc/tests/chrome.ini
new file mode 100644
index 0000000000..206f0a1501
--- /dev/null
+++ b/dom/ipc/tests/chrome.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ process_error.xhtml
+
+[test_process_error.xhtml]
+skip-if = !crashreporter
+
+
+[test_process_error_oom.xhtml]
+skip-if = !crashreporter
diff --git a/dom/ipc/tests/file_cancel_content_js.html b/dom/ipc/tests/file_cancel_content_js.html
new file mode 100644
index 0000000000..d2caf03c6a
--- /dev/null
+++ b/dom/ipc/tests/file_cancel_content_js.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Wait for it...</title>
+ </head>
+ <body>
+ Try to go to another page.
+ <script>
+ addEventListener("StartLongLoop", function() {
+ setTimeout(() => {
+ const start = Date.now();
+ while (Date.now() - start < 7500);
+ window.dispatchEvent(new CustomEvent("LongLoopEnded"));
+ });
+ });
+ </script>
+ </body>
+</html>
diff --git a/dom/ipc/tests/file_disableScript.html b/dom/ipc/tests/file_disableScript.html
new file mode 100644
index 0000000000..f4888cd586
--- /dev/null
+++ b/dom/ipc/tests/file_disableScript.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+var gFiredOnload = false;
+var gFiredOnclick = false;
+</script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>
diff --git a/dom/ipc/tests/file_domainPolicy_base.html b/dom/ipc/tests/file_domainPolicy_base.html
new file mode 100644
index 0000000000..6e3ec7aec4
--- /dev/null
+++ b/dom/ipc/tests/file_domainPolicy_base.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<iframe id="root" name="root"/>
+</body>
+</html>
diff --git a/dom/ipc/tests/file_dummy.html b/dom/ipc/tests/file_dummy.html
new file mode 100644
index 0000000000..1a481c4163
--- /dev/null
+++ b/dom/ipc/tests/file_dummy.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<body>
+ <h1>This is a dummy file</h1>
+</body>
diff --git a/dom/ipc/tests/mochitest.ini b/dom/ipc/tests/mochitest.ini
new file mode 100644
index 0000000000..263a1cfb1f
--- /dev/null
+++ b/dom/ipc/tests/mochitest.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+
+[test_temporaryfile_stream.html]
+skip-if = !e10s || toolkit == 'android' || (os == "win" && processor == "aarch64") # Bug 1525959, aarch64 due to 1531150
+support-files =
+ blob_verify.sjs
+ !/dom/canvas/test/captureStream_common.js
+[test_Preallocated.html]
+skip-if = !e10s || toolkit == 'android' || tsan # Bug 1525959. tsan: Bug 1683730
+[test_window_open_discarded_bc.html]
+skip-if = toolkit == 'android'
+[test_bcg_processes.html]
diff --git a/dom/ipc/tests/process_error.xhtml b/dom/ipc/tests/process_error.xhtml
new file mode 100644
index 0000000000..7f0a5e20dd
--- /dev/null
+++ b/dom/ipc/tests/process_error.xhtml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ orient="vertical">
+
+ <browser id="thebrowser" type="content" remote="true" />
+ <script type="application/javascript"><![CDATA[
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+ const ok = window.arguments[0].ok;
+ const is = window.arguments[0].is;
+ const done = window.arguments[0].done;
+ const SimpleTest = window.arguments[0].SimpleTest;
+
+ // Parse test options.
+ const url = new URL(document.location);
+ const crashType = url.searchParams.get("crashType");
+
+ // Allow the browser to get connected before using the messageManager to cause
+ // a crash:
+ addEventListener("DOMContentLoaded", () => {
+ let browser = document.getElementById('thebrowser');
+
+ let observerPromise = new Promise(resolve => {
+ let crashObserver = (subject, topic, data) => {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+
+ var dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+
+ // Let's check whether we have correctly reported OOM.
+ var isLikelyOOM = subject.getPropertyAsBool('isLikelyOOM');
+ is(isLikelyOOM, crashType == 'CRASH_OOM', 'isLikelyOOM is correct');
+ }
+
+ Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown');
+ resolve();
+ }
+
+ Services.obs.addObserver(crashObserver, 'ipc:content-shutdown');
+ });
+
+ let browsingContextId = browser.frameLoader.browsingContext.id;
+
+ let eventFiredPromise = BrowserTestUtils.waitForEvent(browser, "oop-browser-crashed");
+ let eventPromise = eventFiredPromise.then(event => {
+ is(event.browsingContextId, browsingContextId,
+ "Expected the right browsing context id on the oop-browser-crashed event.");
+ })
+
+ BrowserTestUtils.crashFrame(browser, true, false, /* Default browsing context */ null, { crashType });
+
+ Promise.all([observerPromise, eventPromise]).then(done);
+ });
+ ]]></script>
+
+</window>
diff --git a/dom/ipc/tests/test_Preallocated.html b/dom/ipc/tests/test_Preallocated.html
new file mode 100644
index 0000000000..5d2b2bd3a6
--- /dev/null
+++ b/dom/ipc/tests/test_Preallocated.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the preallocated process starts up.
+-->
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../browserElementTestHelpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript">
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+SimpleTest.waitForExplicitFinish();
+
+function expectProcessCreated() {
+ return new Promise(resolve => {
+ function parentExpectProcessCreated() {
+ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ let topic = "ipc:content-initializing";
+ let obs = { observe() {
+ Services.obs.removeObserver(obs, topic);
+ sendAsyncMessage("process-created");
+ }};
+ Services.obs.addObserver(obs, topic);
+ }
+
+ let helper = SpecialPowers.loadChromeScript(parentExpectProcessCreated);
+ SimpleTest.registerCleanupFunction(function() { helper.destroy(); });
+ helper.addMessageListener("process-created", resolve);
+ });
+}
+
+expectProcessCreated().then(() => {
+ ok(true, "Process creation detected.");
+ SimpleTest.finish();
+});
+
+// Kill existing preallocated process.
+SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processPrelaunch.enabled", false]]}).then(() => {
+ // Make sure we have the capacity to launch preallocated process.
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 100]]}).then(() => {
+ // Enable preallocated process and run the test.
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processPrelaunch.enabled", true]]});
+ });
+});
+</script>
+</body>
+</html>
diff --git a/dom/ipc/tests/test_bcg_processes.html b/dom/ipc/tests/test_bcg_processes.html
new file mode 100644
index 0000000000..8109b59a42
--- /dev/null
+++ b/dom/ipc/tests/test_bcg_processes.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript">
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+add_task(async function main_test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount.webIsolated", 10]],
+ });
+
+ let frame1 = document.createElement("iframe");
+ frame1.src = "http://example.com";
+ document.body.appendChild(frame1);
+ await new Promise(resolve => {
+ frame1.addEventListener("load", resolve, { once: true })
+ });
+ info("frame 1 loaded");
+
+ let frame2 = document.createElement("iframe");
+ frame2.src = "http://example.com";
+ document.body.appendChild(frame2);
+ await new Promise(resolve => {
+ frame2.addEventListener("load", resolve, { once: true })
+ });
+ info("frame 2 loaded");
+
+ let id1 = await SpecialPowers.spawn(frame1, [], () => {
+ return ChromeUtils.domProcessChild.childId;
+ });
+ let id2 = await SpecialPowers.spawn(frame2, [], () => {
+ return ChromeUtils.domProcessChild.childId;
+ });
+
+ is(id1, id2, "childID for example.com subframes should match");
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/ipc/tests/test_blob_sliced_from_child_process.js b/dom/ipc/tests/test_blob_sliced_from_child_process.js
new file mode 100644
index 0000000000..e3498320ee
--- /dev/null
+++ b/dom/ipc/tests/test_blob_sliced_from_child_process.js
@@ -0,0 +1,140 @@
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+ExtensionTestUtils.init(this);
+
+function childFrameScript() {
+ "use strict";
+
+ const messageName = "test:blob-slice-test";
+ const blobData = ["So", " ", "many", " ", "blobs!"];
+ const blobType = "text/plain";
+
+ let blob = new Blob(blobData, { type: blobType });
+
+ let firstSliceStart = blobData[0].length + blobData[1].length;
+ let firstSliceEnd = firstSliceStart + blobData[2].length;
+
+ let slice = blob.slice(firstSliceStart, firstSliceEnd, blobType);
+
+ let secondSliceStart = blobData[2].indexOf("a");
+ let secondSliceEnd = secondSliceStart + 2;
+
+ slice = slice.slice(secondSliceStart, secondSliceEnd, blobType);
+
+ sendAsyncMessage(messageName, { blob });
+ sendAsyncMessage(messageName, { slice });
+}
+
+add_task(async function test() {
+ let page = await ExtensionTestUtils.loadContentPage(
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>",
+ {
+ remote: true,
+ }
+ );
+
+ page.loadFrameScript(childFrameScript);
+
+ const messageName = "test:blob-slice-test";
+ const blobData = ["So", " ", "many", " ", "blobs!"];
+ const blobText = blobData.join("");
+ const blobType = "text/plain";
+
+ const sliceText = "an";
+
+ let receivedBlob = false;
+ let receivedSlice = false;
+
+ let resolveBlob, resolveSlice;
+ let blobPromise = new Promise(resolve => {
+ resolveBlob = resolve;
+ });
+ let slicePromise = new Promise(resolve => {
+ resolveSlice = resolve;
+ });
+
+ let mm = page.browser.messageManager;
+ mm.addMessageListener(messageName, function(message) {
+ if ("blob" in message.data) {
+ equal(receivedBlob, false, "Have not yet received Blob");
+ equal(receivedSlice, false, "Have not yet received Slice");
+
+ receivedBlob = true;
+
+ let blob = message.data.blob;
+
+ ok(Blob.isInstance(blob), "Received a Blob");
+ equal(blob.size, blobText.length, "Blob has correct size");
+ equal(blob.type, blobType, "Blob has correct type");
+
+ let slice = blob.slice(
+ blobText.length - blobData[blobData.length - 1].length,
+ blob.size,
+ blobType
+ );
+
+ ok(Blob.isInstance(slice), "Slice returned a Blob");
+ equal(
+ slice.size,
+ blobData[blobData.length - 1].length,
+ "Slice has correct size"
+ );
+ equal(slice.type, blobType, "Slice has correct type");
+
+ let reader = new FileReader();
+ reader.onload = function() {
+ equal(
+ reader.result,
+ blobData[blobData.length - 1],
+ "Slice has correct data"
+ );
+
+ resolveBlob();
+ };
+ reader.readAsText(slice);
+ } else if ("slice" in message.data) {
+ equal(receivedBlob, true, "Already received Blob");
+ equal(receivedSlice, false, "Have not yet received Slice");
+
+ receivedSlice = true;
+
+ let slice = message.data.slice;
+
+ ok(Blob.isInstance(slice), "Received a Blob for slice");
+ equal(slice.size, sliceText.length, "Slice has correct size");
+ equal(slice.type, blobType, "Slice has correct type");
+
+ let reader = new FileReader();
+ reader.onload = function() {
+ equal(reader.result, sliceText, "Slice has correct data");
+
+ let slice2 = slice.slice(1, 2, blobType);
+
+ ok(Blob.isInstance(slice2), "Slice returned a Blob");
+ equal(slice2.size, 1, "Slice has correct size");
+ equal(slice2.type, blobType, "Slice has correct type");
+
+ let reader2 = new FileReader();
+ reader2.onload = function() {
+ equal(reader2.result, sliceText[1], "Slice has correct data");
+
+ resolveSlice();
+ };
+ reader2.readAsText(slice2);
+ };
+ reader.readAsText(slice);
+ } else {
+ ok(false, "Received a bad message: " + JSON.stringify(message.data));
+ }
+ });
+
+ await blobPromise;
+ await slicePromise;
+
+ await page.close();
+});
diff --git a/dom/ipc/tests/test_blob_sliced_from_parent_process.js b/dom/ipc/tests/test_blob_sliced_from_parent_process.js
new file mode 100644
index 0000000000..4f3b10d7a2
--- /dev/null
+++ b/dom/ipc/tests/test_blob_sliced_from_parent_process.js
@@ -0,0 +1,167 @@
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+ExtensionTestUtils.init(this);
+
+function childFrameScript() {
+ const messageName = "test:blob-slice-test";
+ const blobData = ["So", " ", "many", " ", "blobs!"];
+ const blobText = blobData.join("");
+ const blobType = "text/plain";
+
+ const sliceText = "an";
+
+ function info(msg) {
+ sendAsyncMessage(messageName, { op: "info", msg });
+ }
+
+ function ok(condition, name, diag) {
+ sendAsyncMessage(messageName, { op: "ok", condition, name, diag });
+ }
+
+ function is(a, b, name) {
+ let pass = a == b;
+ let diag = pass ? "" : "got " + a + ", expected " + b;
+ ok(pass, name, diag);
+ }
+
+ function finish(result) {
+ sendAsyncMessage(messageName, { op: "done", result });
+ }
+
+ function grabAndContinue(arg) {
+ testGenerator.next(arg);
+ }
+
+ function* testSteps() {
+ addMessageListener(messageName, grabAndContinue);
+ let message = yield undefined;
+
+ let blob = message.data;
+
+ ok(Blob.isInstance(blob), "Received a Blob");
+ is(blob.size, blobText.length, "Blob has correct length");
+ is(blob.type, blobType, "Blob has correct type");
+
+ info("Reading blob");
+
+ let reader = new FileReader();
+ reader.addEventListener("load", grabAndContinue);
+ reader.readAsText(blob);
+
+ yield undefined;
+
+ is(reader.result, blobText, "Blob has correct data");
+
+ let firstSliceStart = blobData[0].length + blobData[1].length;
+ let firstSliceEnd = firstSliceStart + blobData[2].length;
+
+ let slice = blob.slice(firstSliceStart, firstSliceEnd, blobType);
+
+ ok(Blob.isInstance(slice), "Slice returned a Blob");
+ is(slice.size, blobData[2].length, "Slice has correct length");
+ is(slice.type, blobType, "Slice has correct type");
+
+ info("Reading slice");
+
+ reader = new FileReader();
+ reader.addEventListener("load", grabAndContinue);
+ reader.readAsText(slice);
+
+ yield undefined;
+
+ is(reader.result, blobData[2], "Slice has correct data");
+
+ let secondSliceStart = blobData[2].indexOf("a");
+ let secondSliceEnd = secondSliceStart + sliceText.length;
+
+ slice = slice.slice(secondSliceStart, secondSliceEnd, blobType);
+
+ ok(Blob.isInstance(slice), "Second slice returned a Blob");
+ is(slice.size, sliceText.length, "Second slice has correct length");
+ is(slice.type, blobType, "Second slice has correct type");
+
+ info("Sending second slice");
+ finish(slice);
+ }
+
+ let testGenerator = testSteps();
+ testGenerator.next();
+}
+
+add_task(async function test() {
+ let page = await ExtensionTestUtils.loadContentPage(
+ "data:text/html,<!DOCTYPE HTML><html><body></body></html>",
+ {
+ remote: true,
+ }
+ );
+
+ page.loadFrameScript(childFrameScript);
+
+ const messageName = "test:blob-slice-test";
+ const blobData = ["So", " ", "many", " ", "blobs!"];
+ const blobType = "text/plain";
+
+ const sliceText = "an";
+
+ await new Promise(resolve => {
+ function grabAndContinue(arg) {
+ testGenerator.next(arg);
+ }
+
+ function* testSteps() {
+ let slice = yield undefined;
+
+ ok(Blob.isInstance(slice), "Received a Blob");
+ equal(slice.size, sliceText.length, "Slice has correct size");
+ equal(slice.type, blobType, "Slice has correct type");
+
+ let reader = new FileReader();
+ reader.onload = grabAndContinue;
+ reader.readAsText(slice);
+ yield undefined;
+
+ equal(reader.result, sliceText, "Slice has correct data");
+ resolve();
+ }
+
+ let testGenerator = testSteps();
+ testGenerator.next();
+
+ let mm = page.browser.messageManager;
+ mm.addMessageListener(messageName, function(message) {
+ let data = message.data;
+ switch (data.op) {
+ case "info": {
+ info(data.msg);
+ break;
+ }
+
+ case "ok": {
+ ok(data.condition, data.name + " - " + data.diag);
+ break;
+ }
+
+ case "done": {
+ testGenerator.next(data.result);
+ break;
+ }
+
+ default: {
+ ok(false, "Unknown op: " + data.op);
+ resolve();
+ }
+ }
+ });
+
+ let blob = new Blob(blobData, { type: blobType });
+ mm.sendAsyncMessage(messageName, blob);
+ });
+
+ await page.close();
+});
diff --git a/dom/ipc/tests/test_bug1086684.js b/dom/ipc/tests/test_bug1086684.js
new file mode 100644
index 0000000000..c5d4836d98
--- /dev/null
+++ b/dom/ipc/tests/test_bug1086684.js
@@ -0,0 +1,99 @@
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+AddonTestUtils.init(this);
+ExtensionTestUtils.init(this);
+
+const childFramePath = "/file_bug1086684.html";
+const childFrameURL = "http://example.com" + childFramePath;
+
+const childFrameContents = `<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+<div id="content">
+ <input type="file" id="f">
+</div>
+</body>
+</html>`;
+
+const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });
+server.registerPathHandler(childFramePath, (request, response) => {
+ response.write(childFrameContents);
+});
+
+function childFrameScript() {
+ "use strict";
+
+ let { MockFilePicker } = ChromeUtils.import(
+ "resource://testing-common/MockFilePicker.jsm"
+ );
+
+ function parentReady(message) {
+ MockFilePicker.init(content);
+ MockFilePicker.setFiles([message.data.file]);
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+
+ let input = content.document.getElementById("f");
+ input.addEventListener("change", () => {
+ MockFilePicker.cleanup();
+ let value = input.value;
+ message.target.sendAsyncMessage("testBug1086684:childDone", { value });
+ });
+
+ input.focus();
+ input.click();
+ }
+
+ addMessageListener("testBug1086684:parentReady", function(message) {
+ parentReady(message);
+ });
+}
+
+add_task(async function() {
+ let page = await ExtensionTestUtils.loadContentPage(childFrameURL, {
+ remote: true,
+ });
+
+ page.loadFrameScript(childFrameScript);
+
+ await new Promise(resolve => {
+ let test;
+ function* testStructure(mm) {
+ let value;
+
+ function testDone(msg) {
+ test.next(msg.data.value);
+ }
+
+ mm.addMessageListener("testBug1086684:childDone", testDone);
+
+ let blob = new Blob([]);
+ let file = new File([blob], "helloworld.txt", { type: "text/plain" });
+
+ mm.sendAsyncMessage("testBug1086684:parentReady", { file });
+ value = yield;
+
+ // Note that the "helloworld.txt" passed in above doesn't affect the
+ // 'value' getter. Because we're mocking a file using a blob, we ask the
+ // blob for its path, which is the empty string.
+ equal(value, "", "got the right answer and didn't crash");
+
+ resolve();
+ }
+
+ test = testStructure(page.browser.messageManager);
+ test.next();
+ });
+
+ await page.close();
+});
diff --git a/dom/ipc/tests/test_child_docshell.js b/dom/ipc/tests/test_child_docshell.js
new file mode 100644
index 0000000000..3871c35496
--- /dev/null
+++ b/dom/ipc/tests/test_child_docshell.js
@@ -0,0 +1,93 @@
+"use strict";
+/* eslint-env mozilla/frame-script */
+
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+ExtensionTestUtils.init(this);
+
+add_task(async function test() {
+ let page = await ExtensionTestUtils.loadContentPage("about:blank", {
+ remote: true,
+ });
+
+ await new Promise(resolve => {
+ let mm = page.browser.messageManager;
+ mm.addMessageListener("chromeEventHandler", function(msg) {
+ var result = msg.json;
+ equal(
+ result.processType,
+ Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT,
+ "The frame script is running in a real distinct child process"
+ );
+ ok(
+ result.hasCorrectInterface,
+ "docshell.chromeEventHandler has EventTarget interface"
+ );
+ });
+
+ mm.addMessageListener("DOMWindowCreatedReceived", function(msg) {
+ ok(true, "the chrome event handler looks functional");
+ var result = msg.json;
+ ok(
+ result.stableChromeEventHandler,
+ "docShell.chromeEventHandler is stable"
+ );
+ ok(result.iframeHasNewDocShell, "iframe spawns a new docShell");
+ ok(
+ result.iframeHasSameChromeEventHandler,
+ "but iframe has the same chrome event handler"
+ );
+ resolve();
+ });
+
+ // Inject a frame script in the child process:
+ page.loadFrameScript(async function() {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+
+ var chromeEventHandler = docShell.chromeEventHandler;
+ sendAsyncMessage("chromeEventHandler", {
+ processType: Services.appinfo.processType,
+ hasCorrectInterface:
+ chromeEventHandler && EventTarget.isInstance(chromeEventHandler),
+ });
+
+ /*
+ Ensure that this chromeEventHandler actually works,
+ by creating a new window and listening for its DOMWindowCreated event
+ */
+ chromeEventHandler.addEventListener("DOMWindowCreated", function listener(
+ evt
+ ) {
+ if (evt.target == content.document) {
+ return;
+ }
+ chromeEventHandler.removeEventListener("DOMWindowCreated", listener);
+ let new_win = evt.target.defaultView;
+ let new_docShell = new_win.docShell;
+ sendAsyncMessage("DOMWindowCreatedReceived", {
+ stableChromeEventHandler:
+ chromeEventHandler === docShell.chromeEventHandler,
+ iframeHasNewDocShell: new_docShell !== docShell,
+ iframeHasSameChromeEventHandler:
+ new_docShell.chromeEventHandler === chromeEventHandler,
+ });
+ });
+
+ if (content.document.readyState != "complete") {
+ await new Promise(res =>
+ addEventListener("load", res, { once: true, capture: true })
+ );
+ }
+
+ let iframe = content.document.createElement("iframe");
+ iframe.setAttribute("src", "data:text/html,foo");
+ content.document.documentElement.appendChild(iframe);
+ });
+ });
+
+ await page.close();
+});
diff --git a/dom/ipc/tests/test_process_error.xhtml b/dom/ipc/tests/test_process_error.xhtml
new file mode 100644
index 0000000000..d122e7fedd
--- /dev/null
+++ b/dom/ipc/tests/test_process_error.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.expectChildProcessCrash();
+
+ var w = window.browsingContext.topChromeWindow.openDialog('process_error.xhtml', '_blank', 'chrome,resizable=yes,width=400,height=600', window);
+
+ function done()
+ {
+ w.close();
+ SimpleTest.finish();
+ }
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>
diff --git a/dom/ipc/tests/test_process_error_oom.xhtml b/dom/ipc/tests/test_process_error_oom.xhtml
new file mode 100644
index 0000000000..03de3b07eb
--- /dev/null
+++ b/dom/ipc/tests/test_process_error_oom.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.expectChildProcessCrash();
+
+ var w = window.browsingContext.topChromeWindow.openDialog('process_error.xhtml?crashType=CRASH_OOM', '_blank', 'chrome,resizable=yes,width=400,height=600', window);
+
+ function done()
+ {
+ w.close();
+ SimpleTest.finish();
+ }
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>
diff --git a/dom/ipc/tests/test_sharedMap.js b/dom/ipc/tests/test_sharedMap.js
new file mode 100644
index 0000000000..112093e44c
--- /dev/null
+++ b/dom/ipc/tests/test_sharedMap.js
@@ -0,0 +1,382 @@
+"use strict";
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ExtensionUtils } = ChromeUtils.import(
+ "resource://gre/modules/ExtensionUtils.jsm"
+);
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+const PROCESS_COUNT_PREF = "dom.ipc.processCount";
+
+const remote = AppConstants.platform !== "android";
+
+ExtensionTestUtils.init(this);
+
+let contentPage;
+
+async function readBlob(key, sharedData = Services.cpmm.sharedData) {
+ let reader = new FileReader();
+ reader.readAsText(sharedData.get(key));
+ await ExtensionUtils.promiseEvent(reader, "loadend");
+ return reader.result;
+}
+
+function getKey(key, sharedData = Services.cpmm.sharedData) {
+ return sharedData.get(key);
+}
+
+function hasKey(key, sharedData = Services.cpmm.sharedData) {
+ return sharedData.has(key);
+}
+
+function getContents(sharedMap = Services.cpmm.sharedData) {
+ return {
+ keys: Array.from(sharedMap.keys()),
+ values: Array.from(sharedMap.values()),
+ entries: Array.from(sharedMap.entries()),
+ getValues: Array.from(sharedMap.keys(), key => sharedMap.get(key)),
+ };
+}
+
+function checkMap(contents, expected) {
+ expected = Array.from(expected);
+
+ equal(contents.keys.length, expected.length, "Got correct number of keys");
+ equal(
+ contents.values.length,
+ expected.length,
+ "Got correct number of values"
+ );
+ equal(
+ contents.entries.length,
+ expected.length,
+ "Got correct number of entries"
+ );
+
+ for (let [i, [key, val]] of contents.entries.entries()) {
+ equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`);
+ deepEqual(
+ val,
+ contents.values[i],
+ `values()[${i}] matches entries()[${i}]`
+ );
+ }
+
+ expected.sort(([a], [b]) => a.localeCompare(b));
+ contents.entries.sort(([a], [b]) => a.localeCompare(b));
+
+ for (let [i, [key, val]] of contents.entries.entries()) {
+ equal(
+ key,
+ expected[i][0],
+ `expected[${i}].key matches entries()[${i}].key`
+ );
+ deepEqual(
+ val,
+ expected[i][1],
+ `expected[${i}].value matches entries()[${i}].value`
+ );
+ }
+}
+
+function checkParentMap(expected) {
+ info("Checking parent map");
+ checkMap(getContents(Services.ppmm.sharedData), expected);
+}
+
+async function checkContentMaps(expected, parentOnly = false) {
+ info("Checking in-process content map");
+ checkMap(getContents(Services.cpmm.sharedData), expected);
+
+ if (!parentOnly) {
+ info("Checking out-of-process content map");
+ let contents = await contentPage.spawn(undefined, getContents);
+ checkMap(contents, expected);
+ }
+}
+
+async function loadContentPage() {
+ let page = await ExtensionTestUtils.loadContentPage("about:blank", {
+ remote,
+ });
+ registerCleanupFunction(() => page.close());
+
+ page.addFrameScriptHelper(`
+ var {ExtensionUtils} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
+ Cu.importGlobalProperties(["FileReader"]);
+ `);
+ return page;
+}
+
+add_task(async function setup() {
+ // Start with one content process so that we can increase the number
+ // later and test the behavior of a fresh content process.
+ Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1);
+
+ contentPage = await loadContentPage();
+});
+
+add_task(async function test_sharedMap() {
+ let { sharedData } = Services.ppmm;
+
+ info("Check that parent and child maps are both initially empty");
+
+ checkParentMap([]);
+ await checkContentMaps([]);
+
+ let expected = [
+ ["foo-a", { foo: "a" }],
+ ["foo-b", { foo: "b" }],
+ ["bar-c", null],
+ ["bar-d", 42],
+ ];
+
+ function setKey(key, val) {
+ sharedData.set(key, val);
+ expected = expected.filter(([k]) => k != key);
+ expected.push([key, val]);
+ }
+ function deleteKey(key) {
+ sharedData.delete(key);
+ expected = expected.filter(([k]) => k != key);
+ }
+
+ for (let [key, val] of expected) {
+ sharedData.set(key, val);
+ }
+
+ info(
+ "Add some entries, test that they are initially only available in the parent"
+ );
+
+ checkParentMap(expected);
+ await checkContentMaps([]);
+
+ info("Flush. Check that changes are visible in both parent and children");
+
+ sharedData.flush();
+
+ checkParentMap(expected);
+ await checkContentMaps(expected);
+
+ info(
+ "Add another entry. Check that it is initially only available in the parent"
+ );
+
+ let oldExpected = Array.from(expected);
+
+ setKey("baz-a", { meh: "meh" });
+
+ // When we do several checks in a row, we can't check the values in
+ // the content process, since the async checks may allow the idle
+ // flush task to run, and update it before we're ready.
+
+ checkParentMap(expected);
+ checkContentMaps(oldExpected, true);
+
+ info(
+ "Add another entry. Check that both new entries are only available in the parent"
+ );
+
+ setKey("baz-a", { meh: 12 });
+
+ checkParentMap(expected);
+ checkContentMaps(oldExpected, true);
+
+ info(
+ "Delete an entry. Check that all changes are only visible in the parent"
+ );
+
+ deleteKey("foo-b");
+
+ checkParentMap(expected);
+ checkContentMaps(oldExpected, true);
+
+ info(
+ "Flush. Check that all entries are available in both parent and children"
+ );
+
+ sharedData.flush();
+
+ checkParentMap(expected);
+ await checkContentMaps(expected);
+
+ info("Test that entries are automatically flushed on idle:");
+
+ info(
+ "Add a new entry. Check that it is initially only available in the parent"
+ );
+
+ // Test the idle flush task.
+ oldExpected = Array.from(expected);
+
+ setKey("thing", "stuff");
+
+ checkParentMap(expected);
+ checkContentMaps(oldExpected, true);
+
+ info(
+ "Wait for an idle timeout. Check that changes are now visible in all children"
+ );
+
+ await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+
+ checkParentMap(expected);
+ await checkContentMaps(expected);
+
+ // Test that has() rebuilds map after a flush.
+ sharedData.set("grick", true);
+ sharedData.flush();
+ equal(
+ await contentPage.spawn("grick", hasKey),
+ true,
+ "has() should see key after flush"
+ );
+
+ sharedData.set("grack", true);
+ sharedData.flush();
+ equal(
+ await contentPage.spawn("gruck", hasKey),
+ false,
+ "has() should return false for nonexistent key"
+ );
+});
+
+add_task(async function test_blobs() {
+ let { sharedData } = Services.ppmm;
+
+ let text = [
+ "The quick brown fox jumps over the lazy dog",
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ ];
+ let blobs = text.map(str => new Blob([str]));
+
+ let data = { foo: { bar: "baz" } };
+
+ sharedData.set("blob0", blobs[0]);
+ sharedData.set("blob1", blobs[1]);
+ sharedData.set("data", data);
+
+ equal(
+ await readBlob("blob0", sharedData),
+ text[0],
+ "Expected text for blob0 in parent ppmm"
+ );
+
+ sharedData.flush();
+
+ equal(
+ await readBlob("blob0", sharedData),
+ text[0],
+ "Expected text for blob0 in parent ppmm"
+ );
+ equal(
+ await readBlob("blob1", sharedData),
+ text[1],
+ "Expected text for blob1 in parent ppmm"
+ );
+
+ equal(
+ await readBlob("blob0"),
+ text[0],
+ "Expected text for blob0 in parent cpmm"
+ );
+ equal(
+ await readBlob("blob1"),
+ text[1],
+ "Expected text for blob1 in parent cpmm"
+ );
+
+ equal(
+ await contentPage.spawn("blob0", readBlob),
+ text[0],
+ "Expected text for blob0 in child 1 cpmm"
+ );
+ equal(
+ await contentPage.spawn("blob1", readBlob),
+ text[1],
+ "Expected text for blob1 in child 1 cpmm"
+ );
+
+ // Start a second child process
+ Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2);
+
+ let page2 = await loadContentPage();
+
+ equal(
+ await page2.spawn("blob0", readBlob),
+ text[0],
+ "Expected text for blob0 in child 2 cpmm"
+ );
+ equal(
+ await page2.spawn("blob1", readBlob),
+ text[1],
+ "Expected text for blob1 in child 2 cpmm"
+ );
+
+ sharedData.set("blob0", blobs[2]);
+
+ equal(
+ await readBlob("blob0", sharedData),
+ text[2],
+ "Expected text for blob0 in parent ppmm"
+ );
+
+ sharedData.flush();
+
+ equal(
+ await readBlob("blob0", sharedData),
+ text[2],
+ "Expected text for blob0 in parent ppmm"
+ );
+ equal(
+ await readBlob("blob1", sharedData),
+ text[1],
+ "Expected text for blob1 in parent ppmm"
+ );
+
+ equal(
+ await readBlob("blob0"),
+ text[2],
+ "Expected text for blob0 in parent cpmm"
+ );
+ equal(
+ await readBlob("blob1"),
+ text[1],
+ "Expected text for blob1 in parent cpmm"
+ );
+
+ equal(
+ await contentPage.spawn("blob0", readBlob),
+ text[2],
+ "Expected text for blob0 in child 1 cpmm"
+ );
+ equal(
+ await contentPage.spawn("blob1", readBlob),
+ text[1],
+ "Expected text for blob1 in child 1 cpmm"
+ );
+
+ equal(
+ await page2.spawn("blob0", readBlob),
+ text[2],
+ "Expected text for blob0 in child 2 cpmm"
+ );
+ equal(
+ await page2.spawn("blob1", readBlob),
+ text[1],
+ "Expected text for blob1 in child 2 cpmm"
+ );
+
+ deepEqual(
+ await page2.spawn("data", getKey),
+ data,
+ "Expected data for data key in child 2 cpmm"
+ );
+});
diff --git a/dom/ipc/tests/test_temporaryfile_stream.html b/dom/ipc/tests/test_temporaryfile_stream.html
new file mode 100644
index 0000000000..9fa76a2155
--- /dev/null
+++ b/dom/ipc/tests/test_temporaryfile_stream.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Send an nsTemporaryFileInputStream cross-process</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<div id="content">
+</div>
+<script class="testbody" type="text/javascript">
+function startTest() {
+ var canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 100;
+ document.getElementById("content").appendChild(canvas);
+
+
+ // eslint-disable-next-line no-undef
+ var helper = new CaptureStreamTestHelper2D(100, 100);
+ helper.drawColor(canvas, helper.red);
+
+ var stream = canvas.captureStream(0);
+
+ var blob;
+
+ let mediaRecorder = new MediaRecorder(stream);
+ is(mediaRecorder.stream, stream,
+ "Media recorder stream = canvas stream at the start of recording");
+
+ mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired");
+
+ mediaRecorder.onerror = () => ok(false, "Recording failed");
+
+ mediaRecorder.ondataavailable = ev => {
+ is(blob, undefined, "Should only get one dataavailable event");
+ blob = ev.data;
+ };
+
+ mediaRecorder.onstart = () => {
+ info("Got 'start' event");
+ // We just want one frame encoded, to see that the recorder produces something readable.
+ mediaRecorder.stop();
+ };
+
+ mediaRecorder.onstop = () => {
+ info("Got 'stop' event");
+ ok(blob, "Should have gotten a data blob");
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", "blob_verify.sjs", true);
+ xhr.onload = () => {
+ var video = document.createElement("video");
+ video.id = "recorded-video";
+ video.src = URL.createObjectURL(xhr.response);
+ video.play();
+ video.onerror = err => {
+ ok(false, "Should be able to play the recording. Got error. code=" + video.error.code);
+ SimpleTest.finish();
+ };
+ document.getElementById("content").appendChild(video);
+ helper.pixelMustBecome(video, helper.red, {
+ threshold: 128,
+ infoString: "Should become red",
+ }).then(SimpleTest.finish);
+ };
+ xhr.onerror = () => {
+ ok(false, "XHR error");
+ SimpleTest.finish();
+ };
+ xhr.responseType = "blob";
+ xhr.send(blob);
+ };
+
+ mediaRecorder.start();
+ is(mediaRecorder.state, "recording", "Media recorder should be recording");
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["media.recorder.max_memory", 1]]}, startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/ipc/tests/test_window_open_discarded_bc.html b/dom/ipc/tests/test_window_open_discarded_bc.html
new file mode 100644
index 0000000000..f101264eeb
--- /dev/null
+++ b/dom/ipc/tests/test_window_open_discarded_bc.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Discard a new BrowsingContext during window.open nested event loop</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+add_task(async function() {
+ const TOPIC = "dangerous:test-only:new-browser-child-ready";
+
+ let found = false;
+ function observer(subject, topic, data) {
+ let win = SpecialPowers.wrap(subject);
+ if (SpecialPowers.compare(win.opener, window)) {
+ found = true;
+ SpecialPowers.removeObserver(observer, TOPIC);
+
+ win.close();
+ // window.close() is not synchronous, so we need to wait for the
+ // BrowsingContext to actually become discarded after we call it, to
+ // make sure that the window provider actually has a discarded BC at the
+ // end of its own nested event loop.
+ SpecialPowers.Services.tm.spinEventLoopUntil(() => !win.opener);
+ }
+ }
+ SpecialPowers.addObserver(observer, TOPIC);
+
+ let win = window.open();
+
+ is(found, true, "Our observer should have fired for the new window");
+ is(win, null, "window.open() should return null when new window is already closed");
+});
+</script>
+</body>
+</html>
diff --git a/dom/ipc/tests/xpcshell.ini b/dom/ipc/tests/xpcshell.ini
new file mode 100644
index 0000000000..a93537c9b6
--- /dev/null
+++ b/dom/ipc/tests/xpcshell.ini
@@ -0,0 +1,10 @@
+[test_sharedMap.js]
+skip-if = os == "android" && processor == "x86_64"
+[test_blob_sliced_from_child_process.js]
+skip-if = os == "android" && processor == "x86_64"
+[test_blob_sliced_from_parent_process.js]
+skip-if = os == "android" && processor == "x86_64"
+[test_bug1086684.js]
+skip-if = os == "android" && processor == "x86_64"
+[test_child_docshell.js]
+skip-if = os == "android" && processor == "x86_64"