summaryrefslogtreecommitdiffstats
path: root/dom/workers/test/xpcshell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/workers/test/xpcshell
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/workers/test/xpcshell/data/chrome.manifest1
-rw-r--r--dom/workers/test/xpcshell/data/worker.js6
-rw-r--r--dom/workers/test/xpcshell/data/worker_fileReader.js7
-rw-r--r--dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js415
-rw-r--r--dom/workers/test/xpcshell/test_fileReader.js33
-rw-r--r--dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js94
-rw-r--r--dom/workers/test/xpcshell/test_workers.js37
-rw-r--r--dom/workers/test/xpcshell/test_workers_clone_error.js42
-rw-r--r--dom/workers/test/xpcshell/xpcshell.ini28
9 files changed, 663 insertions, 0 deletions
diff --git a/dom/workers/test/xpcshell/data/chrome.manifest b/dom/workers/test/xpcshell/data/chrome.manifest
new file mode 100644
index 0000000000..611e81fd4e
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/chrome.manifest
@@ -0,0 +1 @@
+content workers ./
diff --git a/dom/workers/test/xpcshell/data/worker.js b/dom/workers/test/xpcshell/data/worker.js
new file mode 100644
index 0000000000..6fb17798c6
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+self.onmessage = function(msg) {
+ self.postMessage("OK");
+};
diff --git a/dom/workers/test/xpcshell/data/worker_fileReader.js b/dom/workers/test/xpcshell/data/worker_fileReader.js
new file mode 100644
index 0000000000..fce6666316
--- /dev/null
+++ b/dom/workers/test/xpcshell/data/worker_fileReader.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+self.onmessage = function(msg) {
+ var fr = new FileReader();
+ self.postMessage("OK");
+};
diff --git a/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js
new file mode 100644
index 0000000000..50525d5e8c
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js
@@ -0,0 +1,415 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { TestUtils } = ChromeUtils.import(
+ "resource://testing-common/TestUtils.jsm"
+);
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+const { createHttpServer } = AddonTestUtils;
+
+// Force ServiceWorkerRegistrar to init by calling do_get_profile.
+// (This has to be called before AddonTestUtils.init, because it does
+// also call do_get_profile internally but it doesn't notify
+// profile-after-change).
+do_get_profile(true);
+
+AddonTestUtils.init(this);
+ExtensionTestUtils.init(this);
+
+const server = createHttpServer({ hosts: ["localhost"] });
+
+server.registerPathHandler("/page.html", (request, response) => {
+ info(`/page.html is being requested: ${JSON.stringify(request)}`);
+ response.write(`<!DOCTYPE html>`);
+});
+
+server.registerPathHandler("/sw.js", (request, response) => {
+ info(`/sw.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write(`
+ dump('Executing http://localhost/sw.js\\n');
+ importScripts('sw-imported.js');
+ dump('Executed importScripts from http://localhost/sw.js\\n');
+ `);
+});
+
+server.registerPathHandler("/sw-imported.js", (request, response) => {
+ info(`/sw-imported.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write(`
+ dump('importScript loaded from http://localhost/sw-imported.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('original-imported-script');
+ `);
+});
+
+Services.prefs.setBoolPref("dom.serviceWorkers.testing.enabled", true);
+// Make sure this test file doesn't run with the legacy behavior by
+// setting explicitly the expected default value.
+Services.prefs.setBoolPref(
+ "extensions.filterResponseServiceWorkerScript.disabled",
+ false
+);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled");
+ Services.prefs.clearUserPref(
+ "extensions.filterResponseServiceWorkerScript.disabled"
+ );
+});
+
+// Helper function used to be sure to clear any data that a previous test case
+// may have left (e.g. service worker registration, cached service worker
+// scripts).
+//
+// NOTE: Given that xpcshell test are running isolated from each other (unlike
+// mochitests), we can just clear every storage type supported by clear data
+// (instead of cherry picking what we want to clear based on the test cases
+// part of this test file).
+async function ensureDataCleanup() {
+ info("Clear any service worker or data previous test cases may have left");
+ await new Promise(resolve =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve)
+ );
+}
+
+// Note that the update algorithm (https://w3c.github.io/ServiceWorker/#update-algorithm)
+// builds an "updatedResourceMap" as part of its check process. This means that only a
+// single fetch will be performed for "sw-imported.js" as part of the update check and its
+// resulting install invocation. The installation's call to importScripts when evaluated
+// will load the script directly out of the Cache API.
+function testSWUpdate(contentPage) {
+ return contentPage.spawn([], async () => {
+ const oldReg = await this.content.navigator.serviceWorker.ready;
+ const reg = await oldReg.update();
+ const sw = reg.installing || reg.waiting || reg.active;
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+}
+
+add_task(async function test_extension_invalid_sw_scripts_redirect_ignored() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["<all_urls>", "webRequest", "webRequestBlocking"],
+ },
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ if (req.url == "http://localhost/sw.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ filter.ondata = event => filter.write(event.data);
+ filter.onstop = event => filter.disconnect();
+ filter.onerror = () => {
+ browser.test.sendMessage(
+ "filter-response-error:mainscript",
+ filter.error
+ );
+ };
+ return {
+ redirectUrl: browser.runtime.getURL("sw-unexpected-redirect.js"),
+ };
+ }
+
+ if (req.url == "http://localhost/sw-imported.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ filter.ondata = event => filter.write(event.data);
+ filter.onstop = event => filter.disconnect();
+ filter.onerror = () => {
+ browser.test.sendMessage(
+ "filter-response-error:importscript",
+ filter.error
+ );
+ };
+ return { redirectUrl: "about:blank" };
+ }
+
+ return {};
+ },
+ { urls: ["http://localhost/sw.js", "http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ files: {
+ "sw-unexpected-redirect.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-unexpected-redirect.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('sw-unexpected-redirect');
+ `,
+ },
+ });
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ function awaitConsoleMessage(regexp) {
+ return new Promise(resolve => {
+ Services.console.registerListener(function listener(message) {
+ if (regexp.test(message.message)) {
+ Services.console.unregisterListener(listener);
+ resolve(message);
+ }
+ });
+ });
+ }
+
+ const awaitIgnoredMainScriptRedirect = awaitConsoleMessage(
+ /Invalid redirectUrl .* on service worker main script/
+ );
+ const awaitIgnoredImportScriptRedirect = awaitConsoleMessage(
+ /Invalid redirectUrl .* on service worker imported script/
+ );
+
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ // Register the worker while the test extension isn't loaded and cannot
+ // intercept and redirect the importedScripts requests.
+ info("Register service worker from a content webpage");
+ let workerMessage = await contentPage.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ info("Wait for the expected error message on main script redirect");
+ const errorMsg = await awaitIgnoredMainScriptRedirect;
+ ok(errorMsg?.message, `Got error message: ${errorMsg?.message}`);
+ ok(
+ errorMsg?.message?.includes(extension.id),
+ "error message should include the addon id"
+ );
+ ok(
+ errorMsg?.message?.includes("http://localhost/sw.js"),
+ "error message should include the sw main script url"
+ );
+
+ info("Wait for the expected error message on import script redirect");
+ const errorMsg2 = await awaitIgnoredImportScriptRedirect;
+ ok(errorMsg2?.message, `Got error message: ${errorMsg2?.message}`);
+ ok(
+ errorMsg2?.message?.includes(extension.id),
+ "error message should include the addon id"
+ );
+ ok(
+ errorMsg2?.message?.includes("http://localhost/sw-imported.js"),
+ "error message should include the sw main script url"
+ );
+
+ info("Wait filterResponse error on main script");
+ equal(
+ await extension.awaitMessage("filter-response-error:mainscript"),
+ "Invalid request ID",
+ "Got expected error on main script"
+ );
+ info("Wait filterResponse error on import script");
+ equal(
+ await extension.awaitMessage("filter-response-error:importscript"),
+ "Invalid request ID",
+ "Got expected error on import script"
+ );
+
+ await extension.unload();
+ await contentPage.close();
+});
+
+add_task(async function test_filter_sw_script() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "<all_urls>",
+ "webRequest",
+ "webRequestBlocking",
+ "webRequestFilterResponse.serviceWorkerScript",
+ ],
+ },
+ background() {
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ if (req.url == "http://localhost/sw.js") {
+ const filter = browser.webRequest.filterResponseData(req.requestId);
+ let decoder = new TextDecoder("utf-8");
+ let encoder = new TextEncoder();
+ filter.ondata = event => {
+ let str = decoder.decode(event.data, { stream: true });
+ browser.test.log(`Got filter ondata event: ${str}\n`);
+ str = `
+ dump('Executing filterResponse script for http://localhost/sw.js\\n');
+ self.onmessage = evt => evt.ports[0].postMessage('filter-response-script');
+ dump('Executed firlterResponse script for http://localhost/sw.js\\n');
+ `;
+ filter.write(encoder.encode(str));
+ filter.disconnect();
+ };
+ }
+
+ return {};
+ },
+ { urls: ["http://localhost/sw.js", "http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ await ensureDataCleanup();
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ let workerMessage = await contentPage.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "filter-response-script",
+ "Got expected worker reply (filterResponse script)"
+ );
+
+ await extension.unload();
+ workerMessage = await testSWUpdate(contentPage);
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (original script)"
+ );
+
+ await contentPage.close();
+});
+
+add_task(async function test_extension_redirect_sw_imported_script() {
+ const extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["<all_urls>", "webRequest", "webRequestBlocking"],
+ web_accessible_resources: ["sw-imported-1.js", "sw-imported-2.js"],
+ },
+ background() {
+ let i = 1;
+ browser.webRequest.onBeforeRequest.addListener(
+ req => {
+ browser.test.log(
+ "Extension is redirecting http://localhost/sw-imported.js"
+ );
+ browser.test.sendMessage("request-redirected");
+ return {
+ redirectUrl: browser.runtime.getURL(`sw-imported-${i++}.js`),
+ };
+ },
+ { urls: ["http://localhost/sw-imported.js"] },
+ ["blocking"]
+ );
+ },
+ files: {
+ "sw-imported-1.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-imported1.js \\n');
+ self.onmessage = evt => evt.ports[0].postMessage('redirected-imported-script-1');
+ `,
+ "sw-imported-2.js": `
+ dump('importScript redirected to moz-extension://UUID/sw-imported2.js \\n');
+ self.onmessage = evt => evt.ports[0].postMessage('redirected-imported-script-2');
+ `,
+ },
+ });
+
+ await ensureDataCleanup();
+ const contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://localhost/page.html"
+ );
+
+ // Register the worker while the test extension isn't loaded and cannot
+ // intercept and redirect the importedScripts requests.
+ let workerMessage = await contentPage.spawn([], async () => {
+ const reg = await this.content.navigator.serviceWorker.register("/sw.js");
+ return new Promise(resolve => {
+ const { port1, port2 } = new MessageChannel();
+ port1.onmessage = evt => resolve(evt.data);
+ const sw = reg.active || reg.waiting || reg.installing;
+ sw.postMessage("worker-message", [port2]);
+ });
+ });
+
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ // Start the test extension to redirect importScripts requests.
+ await extension.startup();
+
+ // Trigger an update on the registered service worker, then assert that the
+ // reply got is coming from the script where the extension is redirecting the
+ // request.
+ info("Update service worker and expect extension script to reply");
+ workerMessage = await testSWUpdate(contentPage);
+ await extension.awaitMessage("request-redirected");
+ equal(
+ workerMessage,
+ "redirected-imported-script-1",
+ "Got expected worker reply (importScripts redirected to moz-extension url)"
+ );
+
+ // Trigger a new update of the registered service worker, then assert that the
+ // reply got is coming from a different script where the extension is
+ // redirecting the second request (this confirms that the extension can
+ // intercept and can change the redirected imported scripts on new service
+ // worker updates).
+ info("Update service worker and expect new extension script to reply");
+ workerMessage = await testSWUpdate(contentPage);
+ await extension.awaitMessage("request-redirected");
+ equal(
+ workerMessage,
+ "redirected-imported-script-2",
+ "Got expected worker reply (importScripts redirected to moz-extension url again)"
+ );
+
+ // Uninstall the extension and trigger one more update of the registered
+ // service worker, then assert that the reply got is the one coming from the
+ // server (because difference from the one got from the cache).
+ // This verify that the service worker are updated as expected after the
+ // extension is uninstalled and the worker is not stuck on the script where
+ // the extension did redirect the request the last time.
+ info(
+ "Unload extension, update service worker and expect original script to reply"
+ );
+ await extension.unload();
+ workerMessage = await testSWUpdate(contentPage);
+ equal(
+ workerMessage,
+ "original-imported-script",
+ "Got expected worker reply (importScripts not intercepted)"
+ );
+
+ await contentPage.close();
+});
diff --git a/dom/workers/test/xpcshell/test_fileReader.js b/dom/workers/test/xpcshell/test_fileReader.js
new file mode 100644
index 0000000000..84e91c6c02
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_fileReader.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker_fileReader.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function(event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function(event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+ worker.postMessage("START");
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js
new file mode 100644
index 0000000000..953940e39f
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js
@@ -0,0 +1,94 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+const { createHttpServer } = AddonTestUtils;
+
+// Force ServiceWorkerRegistrar to init by calling do_get_profile.
+// (This has to be called before AddonTestUtils.init, because it does
+// also call do_get_profile internally but it doesn't notify
+// profile-after-change).
+do_get_profile(true);
+
+AddonTestUtils.init(this);
+
+const server = createHttpServer({ hosts: ["localhost"] });
+
+server.registerPathHandler("/sw.js", (request, response) => {
+ info(`/sw.js is being requested: ${JSON.stringify(request)}`);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write("");
+});
+
+add_task(async function setup_prefs() {
+ equal(
+ Services.prefs.getBoolPref("browser.tabs.remote.autostart"),
+ true,
+ "e10s is expected to be enabled"
+ );
+
+ // Enable nsIServiceWorkerManager.registerForTest.
+ Services.prefs.setBoolPref("dom.serviceWorkers.testing.enabled", true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled");
+ });
+});
+
+/**
+ * This test installs a ServiceWorker via test API and verify that the install
+ * process spawns a new process. (Normally ServiceWorker installation won't
+ * cause a new content process to be spawned because the call to register must
+ * be coming from within an existing content process, but the registerForTest
+ * API allows us to bypass this restriction.)
+ *
+ * This models the real-world situation of a push notification being received
+ * from the network which results in a ServiceWorker being spawned without their
+ * necessarily being an existing content process to host it (especially under Fission).
+ */
+add_task(async function launch_remoteworkers_in_new_processes() {
+ const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+ );
+
+ const ssm = Services.scriptSecurityManager;
+
+ const initialChildCount = Services.ppmm.childCount;
+
+ // A test service worker that should spawn a regular web content child process.
+ const swRegInfoWeb = await swm.registerForTest(
+ ssm.createContentPrincipal(Services.io.newURI("http://localhost"), {}),
+ "http://localhost/scope",
+ "http://localhost/sw.js"
+ );
+ swRegInfoWeb.QueryInterface(Ci.nsIServiceWorkerRegistrationInfo);
+
+ info(
+ `web content service worker registered: ${JSON.stringify({
+ principal: swRegInfoWeb.principal.spec,
+ scope: swRegInfoWeb.scope,
+ })}`
+ );
+
+ info("Wait new process to be launched");
+ await TestUtils.waitForCondition(() => {
+ return Services.ppmm.childCount - initialChildCount >= 1;
+ }, "wait for a new child processes to be started");
+
+ // Wait both workers to become active to be sure that. besides spawning
+ // the new child processes as expected, the two remote worker have been
+ // able to run successfully (in other word their remote worker data did
+ // pass successfull the IsRemoteTypeAllowed check in RemoteworkerChild).
+ info("Wait for webcontent worker to become active");
+ await TestUtils.waitForCondition(
+ () => swRegInfoWeb.activeWorker,
+ `wait workers for scope ${swRegInfoWeb.scope} to be active`
+ );
+});
diff --git a/dom/workers/test/xpcshell/test_workers.js b/dom/workers/test/xpcshell/test_workers.js
new file mode 100644
index 0000000000..d070022664
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function(event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function(event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+ worker.postMessage("START");
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
+
+add_task(function test_worker() {
+ return talk_with_worker(new Worker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/test_workers_clone_error.js b/dom/workers/test/xpcshell/test_workers_clone_error.js
new file mode 100644
index 0000000000..32733bad01
--- /dev/null
+++ b/dom/workers/test/xpcshell/test_workers_clone_error.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Worker must be loaded from a chrome:// uri, not a file://
+// uri, so we first need to load it.
+var WORKER_SOURCE_URI = "chrome://workers/content/worker.js";
+do_load_manifest("data/chrome.manifest");
+
+function talk_with_worker(worker) {
+ return new Promise((resolve, reject) => {
+ worker.onmessage = function(event) {
+ let success = true;
+ if (event.data == "OK") {
+ resolve();
+ } else {
+ success = false;
+ reject(event);
+ }
+ Assert.ok(success);
+ worker.terminate();
+ };
+ worker.onerror = function(event) {
+ let error = new Error(event.message, event.filename, event.lineno);
+ worker.terminate();
+ reject(error);
+ };
+
+ try {
+ eval("/");
+ } catch (e) {
+ worker.postMessage(new ClonedErrorHolder(e));
+ }
+ });
+}
+
+add_task(function test_chrome_worker() {
+ return talk_with_worker(new ChromeWorker(WORKER_SOURCE_URI));
+});
+
+add_task(function test_worker() {
+ return talk_with_worker(new Worker(WORKER_SOURCE_URI));
+});
diff --git a/dom/workers/test/xpcshell/xpcshell.ini b/dom/workers/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..7b24396197
--- /dev/null
+++ b/dom/workers/test/xpcshell/xpcshell.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+head =
+skip-if = toolkit == 'android'
+support-files =
+ data/worker.js
+ data/worker_fileReader.js
+ data/chrome.manifest
+
+[test_ext_redirects_sw_scripts.js]
+# The following firefox-appdir make sure that ExtensionTestUtils.loadExtension
+# will be able to successfully start the background page (it does fail without
+# it because there wouldn't be a global.tabTracker implementation as we would
+# expect in a real Firefox, Fenix or Thunderbird instance).
+firefox-appdir = browser
+[test_workers.js]
+[test_workers_clone_error.js]
+[test_fileReader.js]
+[test_remoteworker_launch_new_process.js]
+# The following firefox-appdir make sure that this xpcshell test will run
+# with e10s enabled (which is needed to make sure that the test case is
+# going to launch the expected new processes)
+firefox-appdir = browser
+# Disable plugin loading to make it rr able to record and replay this test.
+prefs =
+ plugin.disable=true
+skip-if =
+ socketprocess_networking # Bug 1759035
+