diff options
Diffstat (limited to 'dom/workers/test')
339 files changed, 16609 insertions, 0 deletions
diff --git a/dom/workers/test/404_server.sjs b/dom/workers/test/404_server.sjs new file mode 100644 index 0000000000..f83281efa8 --- /dev/null +++ b/dom/workers/test/404_server.sjs @@ -0,0 +1,9 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 404, "Not found"); + + // Any valid JS. + if (request.queryString == "js") { + response.setHeader("Content-Type", "text/javascript", false); + response.write("4 + 4"); + } +} diff --git a/dom/workers/test/WorkerDebugger.console_childWorker.js b/dom/workers/test/WorkerDebugger.console_childWorker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.console_childWorker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebugger.console_debugger.js b/dom/workers/test/WorkerDebugger.console_debugger.js new file mode 100644 index 0000000000..a8b2493200 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.console_debugger.js @@ -0,0 +1,49 @@ +"use strict"; + +function ok(a, msg) { + postMessage(JSON.stringify({ type: "status", what: !!a, msg })); +} + +function is(a, b, msg) { + ok(a === b, msg); +} + +function finish() { + postMessage(JSON.stringify({ type: "finish" })); +} + +function magic() { + console.log("Hello from the debugger script!"); + + var foo = retrieveConsoleEvents(); + ok(Array.isArray(foo), "We received an array."); + ok(foo.length >= 2, "At least 2 messages."); + + is( + foo[0].arguments[0], + "Can you see this console message?", + "First message ok." + ); + is( + foo[1].arguments[0], + "Can you see this second console message?", + "Second message ok." + ); + + setConsoleEventHandler(function (consoleData) { + is(consoleData.arguments[0], "Random message.", "Random message ok!"); + + // The consoleEventHandler can be null. + setConsoleEventHandler(null); + + finish(); + }); +} + +this.onmessage = function (event) { + switch (event.data) { + case "do magic": + magic(); + break; + } +}; diff --git a/dom/workers/test/WorkerDebugger.console_worker.js b/dom/workers/test/WorkerDebugger.console_worker.js new file mode 100644 index 0000000000..a4d6af2e0f --- /dev/null +++ b/dom/workers/test/WorkerDebugger.console_worker.js @@ -0,0 +1,10 @@ +"use strict"; + +console.log("Can you see this console message?"); +console.warn("Can you see this second console message?"); + +var worker = new Worker("WorkerDebugger.console_childWorker.js"); + +setInterval(function () { + console.log("Random message."); +}, 200); diff --git a/dom/workers/test/WorkerDebugger.initialize_childWorker.js b/dom/workers/test/WorkerDebugger.initialize_childWorker.js new file mode 100644 index 0000000000..a3a6d2bf80 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_childWorker.js @@ -0,0 +1,7 @@ +"use strict"; + +self.onmessage = function () {}; + +// eslint-disable-next-line no-debugger +debugger; +postMessage("worker"); diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger.js b/dom/workers/test/WorkerDebugger.initialize_debugger.js new file mode 100644 index 0000000000..f52e95b159 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_debugger.js @@ -0,0 +1,6 @@ +"use strict"; + +var dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + frame.eval("postMessage('debugger');"); +}; diff --git a/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js b/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js new file mode 100644 index 0000000000..d357fca2d3 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_debugger_es_worker.js @@ -0,0 +1,10 @@ +"use strict"; + +// The following check is one possible way to identify +// if this script is loaded as a ES Module or the classic way. +const isLoadedAsEsModule = this != globalThis; + +// We expect the debugger script to always be loaded as classic +if (!isLoadedAsEsModule) { + postMessage("debugger script ran"); +} diff --git a/dom/workers/test/WorkerDebugger.initialize_es_worker.js b/dom/workers/test/WorkerDebugger.initialize_es_worker.js new file mode 100644 index 0000000000..1b9ddda769 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_es_worker.js @@ -0,0 +1,10 @@ +"use strict"; + +// The following check is one possible way to identify +// if this script is loaded as a ES Module or the classic way. +const isLoadedAsEsModule = this != globalThis; + +// Here we expect the worker to be loaded a ES Module +if (isLoadedAsEsModule) { + postMessage("worker"); +} diff --git a/dom/workers/test/WorkerDebugger.initialize_worker.js b/dom/workers/test/WorkerDebugger.initialize_worker.js new file mode 100644 index 0000000000..2317335edc --- /dev/null +++ b/dom/workers/test/WorkerDebugger.initialize_worker.js @@ -0,0 +1,10 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger.initialize_childWorker.js"); +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +// eslint-disable-next-line no-debugger +debugger; +postMessage("worker"); diff --git a/dom/workers/test/WorkerDebugger.postMessage_childWorker.js b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_childWorker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebugger.postMessage_debugger.js b/dom/workers/test/WorkerDebugger.postMessage_debugger.js new file mode 100644 index 0000000000..14236a4430 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_debugger.js @@ -0,0 +1,9 @@ +"use strict"; + +this.onmessage = function (event) { + switch (event.data) { + case "ping": + postMessage("pong"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebugger.postMessage_worker.js b/dom/workers/test/WorkerDebugger.postMessage_worker.js new file mode 100644 index 0000000000..8ddf6cf865 --- /dev/null +++ b/dom/workers/test/WorkerDebugger.postMessage_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger.postMessage_childWorker.js"); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js new file mode 100644 index 0000000000..908c9f3161 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_debugger.js @@ -0,0 +1,9 @@ +"use strict"; + +const SANDBOX_URL = "WorkerDebuggerGlobalScope.createSandbox_sandbox.js"; + +var prototype = { + self: this, +}; +var sandbox = createSandbox(SANDBOX_URL, prototype); +loadSubScript(SANDBOX_URL, sandbox); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js new file mode 100644 index 0000000000..d2de6de924 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_sandbox.js @@ -0,0 +1,9 @@ +"use strict"; + +self.addEventListener("message", function (event) { + switch (event.data) { + case "ping": + self.postMessage("pong"); + break; + } +}); diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.createSandbox_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js new file mode 100644 index 0000000000..495212066c --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js @@ -0,0 +1,16 @@ +"use strict"; + +function f() { + // eslint-disable-next-line no-debugger + debugger; +} + +self.onmessage = function (event) { + switch (event.data) { + case "ping": + // eslint-disable-next-line no-debugger + debugger; + postMessage("pong"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js new file mode 100644 index 0000000000..d2a1a21e85 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_debugger.js @@ -0,0 +1,29 @@ +"use strict"; + +var frames = []; + +var dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + frames.push(frame); + postMessage("paused"); + enterEventLoop(); + frames.pop(); + postMessage("resumed"); +}; + +this.onmessage = function (event) { + switch (event.data) { + case "eval": + frames[frames.length - 1].eval("f()"); + postMessage("evalled"); + break; + + case "ping": + postMessage("pong"); + break; + + case "resume": + leaveEventLoop(); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js new file mode 100644 index 0000000000..7b30109615 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.enterEventLoop_worker.js @@ -0,0 +1,29 @@ +"use strict"; + +function f() { + // eslint-disable-next-line no-debugger + debugger; +} + +var worker = new Worker( + "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js" +); + +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +self.onmessage = function (event) { + var message = event.data; + if (message.includes(":")) { + worker.postMessage(message.split(":")[1]); + return; + } + switch (message) { + case "ping": + // eslint-disable-next-line no-debugger + debugger; + postMessage("pong"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js new file mode 100644 index 0000000000..0fb4113a24 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_childWorker.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onerror = function () { + postMessage("error"); +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js new file mode 100644 index 0000000000..b3d8e5d440 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_debugger.js @@ -0,0 +1,11 @@ +"use strict"; + +this.onmessage = function (event) { + switch (event.data) { + case "report": + reportError("reported"); + break; + case "throw": + throw new Error("thrown"); + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js new file mode 100644 index 0000000000..67ccfc2ca0 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.reportError_worker.js @@ -0,0 +1,11 @@ +"use strict"; + +var worker = new Worker("WorkerDebuggerGlobalScope.reportError_childWorker.js"); + +worker.onmessage = function (event) { + postMessage("child:" + event.data); +}; + +self.onerror = function () { + postMessage("error"); +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js new file mode 100644 index 0000000000..b2a01d380c --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_debugger.js @@ -0,0 +1,12 @@ +"use strict"; + +this.onmessage = function (event) { + switch (event.data) { + case "ping": + setImmediate(function () { + postMessage("pong1"); + }); + postMessage("pong2"); + break; + } +}; diff --git a/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerGlobalScope.setImmediate_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerManager_childWorker.js b/dom/workers/test/WorkerDebuggerManager_childWorker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebuggerManager_childWorker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebuggerManager_worker.js b/dom/workers/test/WorkerDebuggerManager_worker.js new file mode 100644 index 0000000000..0737d17ebc --- /dev/null +++ b/dom/workers/test/WorkerDebuggerManager_worker.js @@ -0,0 +1,3 @@ +"use strict"; + +var worker = new Worker("WorkerDebuggerManager_childWorker.js"); diff --git a/dom/workers/test/WorkerDebugger_childWorker.js b/dom/workers/test/WorkerDebugger_childWorker.js new file mode 100644 index 0000000000..8cee6809e5 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_childWorker.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/dom/workers/test/WorkerDebugger_frozen_window1.html b/dom/workers/test/WorkerDebugger_frozen_window1.html new file mode 100644 index 0000000000..05d65bbb54 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_frozen_window1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script> + var worker = new Worker("WorkerDebugger_frozen_worker1.js"); + worker.onmessage = function () { + opener.postMessage("ready", "*"); + }; + </script> + </head> + <body> + This is page 1. + </body> +<html> diff --git a/dom/workers/test/WorkerDebugger_frozen_window2.html b/dom/workers/test/WorkerDebugger_frozen_window2.html new file mode 100644 index 0000000000..3a2445ba54 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_frozen_window2.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <script> + var worker = new Worker("WorkerDebugger_frozen_worker2.js"); + worker.onmessage = function () { + opener.postMessage("ready", "*"); + }; + </script> + </head> + <body> + This is page 2. + </body> +<html> diff --git a/dom/workers/test/WorkerDebugger_frozen_worker1.js b/dom/workers/test/WorkerDebugger_frozen_worker1.js new file mode 100644 index 0000000000..371d2c064b --- /dev/null +++ b/dom/workers/test/WorkerDebugger_frozen_worker1.js @@ -0,0 +1,5 @@ +"use strict"; + +onmessage = function () {}; + +postMessage("ready"); diff --git a/dom/workers/test/WorkerDebugger_frozen_worker2.js b/dom/workers/test/WorkerDebugger_frozen_worker2.js new file mode 100644 index 0000000000..371d2c064b --- /dev/null +++ b/dom/workers/test/WorkerDebugger_frozen_worker2.js @@ -0,0 +1,5 @@ +"use strict"; + +onmessage = function () {}; + +postMessage("ready"); diff --git a/dom/workers/test/WorkerDebugger_promise_debugger.js b/dom/workers/test/WorkerDebugger_promise_debugger.js new file mode 100644 index 0000000000..dd4d1bf2c9 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_promise_debugger.js @@ -0,0 +1,34 @@ +"use strict"; + +var self = this; + +self.onmessage = function (event) { + if (event.data !== "resolve") { + return; + } + // This then-handler should be executed inside the top-level event loop, + // within the context of the debugger's global. + Promise.resolve().then(function () { + var dbg = new Debugger(global); + dbg.onDebuggerStatement = function () { + self.onmessage = function (e) { + if (e.data !== "resume") { + return; + } + // This then-handler should be executed inside the nested event loop, + // within the context of the debugger's global. + Promise.resolve().then(function () { + postMessage("resumed"); + leaveEventLoop(); + }); + }; + // Test bug 1392540 where DOM Promises from debugger principal + // where frozen while hitting a worker breakpoint. + Promise.resolve().then(() => { + postMessage("paused"); + }); + enterEventLoop(); + }; + postMessage("resolved"); + }); +}; diff --git a/dom/workers/test/WorkerDebugger_promise_worker.js b/dom/workers/test/WorkerDebugger_promise_worker.js new file mode 100644 index 0000000000..04db24b512 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_promise_worker.js @@ -0,0 +1,26 @@ +"use strict"; + +self.onmessage = function (event) { + if (event.data !== "resolve") { + return; + } + // This then-handler should be executed inside the top-level event loop, + // within the context of the worker's global. + Promise.resolve().then(function () { + self.onmessage = function (e) { + if (e.data !== "pause") { + return; + } + // This then-handler should be executed inside the top-level event loop, + // within the context of the worker's global. Because the debugger + // statement here below enters a nested event loop, the then-handler + // should not be executed until the debugger statement returns. + Promise.resolve().then(function () { + postMessage("resumed"); + }); + // eslint-disable-next-line no-debugger + debugger; + }; + postMessage("resolved"); + }); +}; diff --git a/dom/workers/test/WorkerDebugger_sharedWorker.js b/dom/workers/test/WorkerDebugger_sharedWorker.js new file mode 100644 index 0000000000..037abe6d6d --- /dev/null +++ b/dom/workers/test/WorkerDebugger_sharedWorker.js @@ -0,0 +1,16 @@ +"use strict"; + +self.onconnect = function (event) { + event.ports[0].onmessage = function (e) { + switch (e.data) { + case "close": + close(); + break; + + case "close_loop": + close(); + // Let's loop forever. + while (1) {} + } + }; +}; diff --git a/dom/workers/test/WorkerDebugger_suspended_debugger.js b/dom/workers/test/WorkerDebugger_suspended_debugger.js new file mode 100644 index 0000000000..2ed4e16c44 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_suspended_debugger.js @@ -0,0 +1,6 @@ +"use strict"; + +var dbg = new Debugger(global); +dbg.onDebuggerStatement = function (frame) { + postMessage("debugger"); +}; diff --git a/dom/workers/test/WorkerDebugger_suspended_worker.js b/dom/workers/test/WorkerDebugger_suspended_worker.js new file mode 100644 index 0000000000..0ea27d29e8 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_suspended_worker.js @@ -0,0 +1,7 @@ +"use strict"; + +self.onmessage = function () { + postMessage("worker"); + // eslint-disable-next-line no-debugger + debugger; +}; diff --git a/dom/workers/test/WorkerDebugger_worker.js b/dom/workers/test/WorkerDebugger_worker.js new file mode 100644 index 0000000000..e33eeaa4d3 --- /dev/null +++ b/dom/workers/test/WorkerDebugger_worker.js @@ -0,0 +1,9 @@ +"use strict"; + +var worker = new Worker("WorkerDebugger_childWorker.js"); +self.onmessage = function (event) { + postMessage("child:" + event.data); +}; +// eslint-disable-next-line no-debugger +debugger; +postMessage("worker"); diff --git a/dom/workers/test/WorkerTest.jsm b/dom/workers/test/WorkerTest.jsm new file mode 100644 index 0000000000..702dc9e46a --- /dev/null +++ b/dom/workers/test/WorkerTest.jsm @@ -0,0 +1,15 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var EXPORTED_SYMBOLS = ["WorkerTest"]; + +var WorkerTest = { + go(message, messageCallback, errorCallback) { + let worker = new ChromeWorker("WorkerTest_worker.js"); + worker.onmessage = messageCallback; + worker.onerror = errorCallback; + worker.postMessage(message); + return worker; + }, +}; diff --git a/dom/workers/test/WorkerTest_badworker.js b/dom/workers/test/WorkerTest_badworker.js new file mode 100644 index 0000000000..5fbd8a5e63 --- /dev/null +++ b/dom/workers/test/WorkerTest_badworker.js @@ -0,0 +1 @@ +// doesn't matter what is in here if the URL is bad diff --git a/dom/workers/test/WorkerTest_subworker.js b/dom/workers/test/WorkerTest_subworker.js new file mode 100644 index 0000000000..0022fc5046 --- /dev/null +++ b/dom/workers/test/WorkerTest_subworker.js @@ -0,0 +1,37 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + let chromeURL = event.data.replace( + "test_chromeWorkerJSM.xhtml", + "WorkerTest_badworker.js" + ); + + let mochitestURL = event.data + .replace("test_chromeWorkerJSM.xhtml", "WorkerTest_badworker.js") + .replace( + "chrome://mochitests/content/chrome", + "http://mochi.test:8888/tests" + ); + + // We should be able to XHR to anything we want, including a chrome URL. + let xhr = new XMLHttpRequest(); + xhr.open("GET", mochitestURL, false); + xhr.send(); + + if (!xhr.responseText) { + throw "Can't load script file via XHR!"; + } + + // We shouldn't be able to make a ChromeWorker to a non-chrome URL. + try { + new ChromeWorker(mochitestURL); + } catch (e) { + if (e.name === "SecurityError") { + postMessage("Done"); + return; + } + } + throw "creating a chrome worker with a bad URL should throw a SecurityError"; +}; diff --git a/dom/workers/test/WorkerTest_worker.js b/dom/workers/test/WorkerTest_worker.js new file mode 100644 index 0000000000..53a380be50 --- /dev/null +++ b/dom/workers/test/WorkerTest_worker.js @@ -0,0 +1,11 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + let worker = new ChromeWorker("WorkerTest_subworker.js"); + worker.onmessage = function (e) { + postMessage(e.data); + }; + worker.postMessage(event.data); +}; diff --git a/dom/workers/test/atob_worker.js b/dom/workers/test/atob_worker.js new file mode 100644 index 0000000000..f55ec77e81 --- /dev/null +++ b/dom/workers/test/atob_worker.js @@ -0,0 +1,55 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var data = [ + -1, + 0, + 1, + 1.5, + /* null ,*/ undefined, + true, + false, + "foo", + "123456789012345", + "1234567890123456", + "12345678901234567", +]; + +var str = ""; +for (var i = 0; i < 30; i++) { + data.push(str); + str += i % 2 ? "b" : "a"; +} + +onmessage = function (event) { + data.forEach(function (string) { + var encoded = btoa(string); + postMessage({ type: "btoa", value: encoded }); + postMessage({ type: "atob", value: atob(encoded) }); + }); + + var threw; + try { + atob(); + } catch (e) { + threw = true; + } + + if (!threw) { + throw "atob didn't throw when called without an argument!"; + } + threw = false; + + try { + btoa(); + } catch (e) { + threw = true; + } + + if (!threw) { + throw "btoa didn't throw when called without an argument!"; + } + + postMessage({ type: "done" }); +}; diff --git a/dom/workers/test/blank.html b/dom/workers/test/blank.html new file mode 100644 index 0000000000..fcbbdb17e9 --- /dev/null +++ b/dom/workers/test/blank.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Blank</title> + </head> + <body onload="notifyOnload();"> + <script type="application/javascript"> + function notifyOnload() { + opener.postMessage({event: 'load'}, '*'); + } + </script> + </body> +</html> diff --git a/dom/workers/test/browser.toml b/dom/workers/test/browser.toml new file mode 100644 index 0000000000..fb8148a159 --- /dev/null +++ b/dom/workers/test/browser.toml @@ -0,0 +1,53 @@ +[DEFAULT] +support-files = [ + "bug1047663_tab.html", + "bug1047663_worker.sjs", + "head.js", + "!/dom/base/test/file_empty.html", +] + +["browser_WorkerDebugger.initialize.js"] +support-files = [ + "WorkerDebugger.initialize_debugger_es_worker.js", + "WorkerDebugger.initialize_es_worker.js", +] +skip-if = ["!nightly_build"] # to be enabled once ES module in workers is enabled (bug 1812591) + +["browser_bug1047663.js"] + +["browser_bug1104623.js"] +run-if = ["buildapp == 'browser'"] + +["browser_consoleSharedWorkers.js"] +skip-if = ["release_or_beta"] # requires dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled +support-files = [ + "sharedWorker_console.js", + "empty.html", +] + +["browser_fileURL.js"] +support-files = [ + "empty.html", + "empty_worker.js", +] + +["browser_privilegedmozilla_remoteworker.js"] +support-files = [ + "file_service_worker.js", + "file_service_worker_container.html", +] + +["browser_serviceworker_fetch_new_process.js"] +support-files = [ + "file_service_worker_fetch_synthetic.js", + "server_fetch_synthetic.sjs", +] + +["browser_worker_use_counters.js"] +support-files = [ + "file_use_counter_worker.html", + "file_use_counter_worker.js", + "file_use_counter_shared_worker.js", + "file_use_counter_shared_worker_microtask.js", + "file_use_counter_service_worker.js", +] diff --git a/dom/workers/test/browser_WorkerDebugger.initialize.js b/dom/workers/test/browser_WorkerDebugger.initialize.js new file mode 100644 index 0000000000..edc20af4e0 --- /dev/null +++ b/dom/workers/test/browser_WorkerDebugger.initialize.js @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].getService( + Ci.nsIWorkerDebuggerManager +); + +const BASE_URL = "chrome://mochitests/content/browser/dom/workers/test/"; +const WORKER_URL = BASE_URL + "WorkerDebugger.initialize_es_worker.js"; +const DEBUGGER_URL = + BASE_URL + "WorkerDebugger.initialize_debugger_es_worker.js"; + +add_task(async function test() { + const onDbg = waitForRegister(WORKER_URL); + const worker = new Worker(WORKER_URL, { type: "module" }); + + info("Wait for worker message"); + await new Promise(resolve => (worker.onmessage = resolve)); + + const dbg = await onDbg; + + info("Calling WorkerDebugger::Initialize"); + const onDebuggerScriptEvaluated = new Promise(resolve => { + const listener = { + onMessage(msg) { + is(msg, "debugger script ran"); + dbg.removeListener(listener); + resolve(); + }, + }; + dbg.addListener(listener); + }); + dbg.initialize(DEBUGGER_URL); + ok(true, "dbg.initialize didn't throw"); + + info("Waiting for debugger script to be evaluated and dispatching a message"); + await onDebuggerScriptEvaluated; +}); + +function waitForRegister(url, dbgUrl) { + return new Promise(function (resolve) { + wdm.addListener({ + onRegister(dbg) { + if (dbg.url !== url) { + return; + } + ok(true, "Debugger with url " + url + " should be registered."); + wdm.removeListener(this); + resolve(dbg); + }, + }); + }); +} diff --git a/dom/workers/test/browser_bug1047663.js b/dom/workers/test/browser_bug1047663.js new file mode 100644 index 0000000000..f55f1152f6 --- /dev/null +++ b/dom/workers/test/browser_bug1047663.js @@ -0,0 +1,56 @@ +/* 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"; + +const EXAMPLE_URL = "https://example.com/browser/dom/workers/test/"; +const TAB_URL = EXAMPLE_URL + "bug1047663_tab.html"; +const WORKER_URL = EXAMPLE_URL + "bug1047663_worker.sjs"; + +function test() { + waitForExplicitFinish(); + + (async function () { + // Disable rcwn to make cache behavior deterministic. + await SpecialPowers.pushPrefEnv({ + set: [["network.http.rcwn.enabled", false]], + }); + + let tab = await addTab(TAB_URL); + + // Create a worker. Post a message to it, and check the reply. Since the + // server side JavaScript file returns the first source for the first + // request, the reply should be "one". If the reply is correct, terminate + // the worker. + await createWorkerInTab(tab, WORKER_URL); + let message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping"); + is(message, "one"); + await terminateWorkerInTab(tab, WORKER_URL); + + // Create a second worker with the same URL. Post a message to it, and check + // the reply. The server side JavaScript file returns the second source for + // all subsequent requests, but since the cache is still enabled, the reply + // should still be "one". If the reply is correct, terminate the worker. + await createWorkerInTab(tab, WORKER_URL); + message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping"); + is(message, "one"); + await terminateWorkerInTab(tab, WORKER_URL); + + // Disable the cache in this tab. This should also disable the cache for all + // workers in this tab. + await disableCacheInTab(tab); + + // Create a third worker with the same URL. Post a message to it, and check + // the reply. Since the server side JavaScript file returns the second + // source for all subsequent requests, and the cache is now disabled, the + // reply should now be "two". If the reply is correct, terminate the worker. + await createWorkerInTab(tab, WORKER_URL); + message = await postMessageToWorkerInTab(tab, WORKER_URL, "ping"); + is(message, "two"); + await terminateWorkerInTab(tab, WORKER_URL); + + removeTab(tab); + + finish(); + })(); +} diff --git a/dom/workers/test/browser_bug1104623.js b/dom/workers/test/browser_bug1104623.js new file mode 100644 index 0000000000..7dc421b873 --- /dev/null +++ b/dom/workers/test/browser_bug1104623.js @@ -0,0 +1,60 @@ +/* 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/. */ + +function whenBrowserLoaded(aBrowser, aCallback) { + aBrowser.addEventListener( + "load", + function onLoad(event) { + if (event.target == aBrowser.contentDocument) { + aBrowser.removeEventListener("load", onLoad, true); + executeSoon(aCallback); + } + }, + true + ); +} + +function test() { + waitForExplicitFinish(); + + let testURL = + "chrome://mochitests/content/chrome/dom/base/test/file_empty.html"; + + let tab = BrowserTestUtils.addTab(gBrowser, testURL); + gBrowser.selectedTab = tab; + + whenBrowserLoaded(tab.linkedBrowser, function () { + let doc = tab.linkedBrowser.contentDocument; + let contentWin = tab.linkedBrowser.contentWindow; + + let blob = new contentWin.Blob([ + "onmessage = function() { postMessage(true); }", + ]); + ok(blob, "Blob has been created"); + + let blobURL = contentWin.URL.createObjectURL(blob); + ok(blobURL, "Blob URL has been created"); + + let worker = new contentWin.Worker(blobURL); + ok(worker, "Worker has been created"); + + worker.onerror = function (error) { + ok(false, "Worker.onerror:" + error.message); + worker.terminate(); + contentWin.URL.revokeObjectURL(blob); + gBrowser.removeTab(tab); + executeSoon(finish); + }; + + worker.onmessage = function () { + ok(true, "Worker.onmessage"); + worker.terminate(); + contentWin.URL.revokeObjectURL(blob); + gBrowser.removeTab(tab); + executeSoon(finish); + }; + + worker.postMessage(true); + }); +} diff --git a/dom/workers/test/browser_consoleSharedWorkers.js b/dom/workers/test/browser_consoleSharedWorkers.js new file mode 100644 index 0000000000..54c1d8a73f --- /dev/null +++ b/dom/workers/test/browser_consoleSharedWorkers.js @@ -0,0 +1,95 @@ +/* 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/. */ + +add_task(async function test() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled", + true, + ], + ], + }); + + const testURL = getRootDirectory(gTestPath) + "empty.html"; + let tab = BrowserTestUtils.addTab(gBrowser, testURL); + gBrowser.selectedTab = tab; + + await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); + + let promise = new Promise(resolve => { + const ConsoleAPIStorage = SpecialPowers.Cc[ + "@mozilla.org/consoleAPI-storage;1" + ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage); + + function consoleListener() { + this.onConsoleLogEvent = this.onConsoleLogEvent.bind(this); + ConsoleAPIStorage.addLogEventListener( + this.onConsoleLogEvent, + SpecialPowers.wrap(document).nodePrincipal + ); + Services.obs.addObserver(this, "console-api-profiler"); + } + + var order = 0; + consoleListener.prototype = { + onConsoleLogEvent(aSubject) { + var obj = aSubject.wrappedJSObject; + if (order == 1) { + is( + obj.arguments[0], + "Hello world from a SharedWorker!", + "A message from a SharedWorker \\o/" + ); + is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker"); + is(obj.innerID, "SharedWorker", "The ID is SharedWorker"); + is(order++, 1, "Then a first log message."); + } else { + is( + obj.arguments[0], + "Here is a SAB", + "A message from a SharedWorker \\o/" + ); + is( + obj.arguments[1].constructor.name, + "SharedArrayBuffer", + "We got a direct reference to the SharedArrayBuffer coming from the worker thread" + ); + is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker"); + is(obj.innerID, "SharedWorker", "The ID is SharedWorker"); + is(order++, 2, "Then a second log message."); + + ConsoleAPIStorage.removeLogEventListener(this.onConsoleLogEvent); + resolve(); + } + }, + + observe: (aSubject, aTopic) => { + ok(true, "Something has been received"); + + if (aTopic == "console-api-profiler") { + var obj = aSubject.wrappedJSObject; + is( + obj.arguments[0], + "Hello profiling from a SharedWorker!", + "A message from a SharedWorker \\o/" + ); + is(order++, 0, "First a profiler message."); + + Services.obs.removeObserver(cl, "console-api-profiler"); + } + }, + }; + + var cl = new consoleListener(); + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { + new content.SharedWorker("sharedWorker_console.js"); + }); + + await promise; + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/workers/test/browser_fileURL.js b/dom/workers/test/browser_fileURL.js new file mode 100644 index 0000000000..9891bdcba9 --- /dev/null +++ b/dom/workers/test/browser_fileURL.js @@ -0,0 +1,73 @@ +/* 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"; + +const EMPTY_URL = "/browser/dom/workers/test/empty.html"; +const WORKER_URL = "/browser/dom/workers/test/empty_worker.js"; + +add_task(async function () { + let tab = BrowserTestUtils.addTab( + gBrowser, + "http://mochi.test:8888" + EMPTY_URL + ); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + ["http://example.org" + WORKER_URL], + function (spec) { + return new content.Promise((resolve, reject) => { + let w = new content.window.Worker(spec); + w.onerror = _ => { + resolve(); + }; + w.onmessage = _ => { + reject(); + }; + }); + } + ); + ok( + true, + "The worker is not loaded when the script is from different origin." + ); + + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function () { + let tab = BrowserTestUtils.addTab( + gBrowser, + "https://example.org" + EMPTY_URL + ); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + await SpecialPowers.spawn( + browser, + ["http://example.org" + WORKER_URL], + function (spec) { + return new content.Promise((resolve, reject) => { + let w = new content.window.Worker(spec); + w.onerror = _ => { + resolve(); + }; + w.onmessage = _ => { + reject(); + }; + }); + } + ); + ok( + true, + "The worker is not loaded when the script is from different origin." + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/workers/test/browser_privilegedmozilla_remoteworker.js b/dom/workers/test/browser_privilegedmozilla_remoteworker.js new file mode 100644 index 0000000000..573cae5e61 --- /dev/null +++ b/dom/workers/test/browser_privilegedmozilla_remoteworker.js @@ -0,0 +1,116 @@ +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true], + ["browser.tabs.remote.separatedMozillaDomains", "example.org"], + ["dom.ipc.processCount.web", 1], + ["dom.ipc.processCount.privilegedmozilla", 1], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); +}); + +// This test attempts to verify proper placement of spawned remoteworkers +// by spawning them and then verifying that they were spawned in the expected +// process by way of nsIWorkerDebuggerManager enumeration. +// +// Unfortunately, there's no other way to introspect where a worker global was +// spawned at this time. (devtools just ends up enumerating all workers in all +// processes and we don't want to depend on devtools in this test). +// +// As a result, this test currently only tests situations where it's known that +// a remote worker will be spawned in the same process that is initiating its +// spawning. +// +// This should be enhanced in the future. +add_task(async function test_serviceworker() { + const basePath = "browser/dom/workers/test"; + const pagePath = `${basePath}/file_service_worker_container.html`; + const scriptPath = `${basePath}/file_service_worker.js`; + + Services.ppmm.releaseCachedProcesses(); + + async function runWorkerInProcess() { + function getActiveWorkerURLs() { + const wdm = Cc[ + "@mozilla.org/dom/workers/workerdebuggermanager;1" + ].getService(Ci.nsIWorkerDebuggerManager); + + const workerDebuggerUrls = Array.from( + wdm.getWorkerDebuggerEnumerator() + ).map(wd => { + return wd.url; + }); + + return workerDebuggerUrls; + } + + return new Promise(resolve => { + content.navigator.serviceWorker.ready.then(({ active }) => { + const { port1, port2 } = new content.MessageChannel(); + active.postMessage("webpage->serviceworker", [port2]); + port1.onmessage = evt => { + resolve({ + msg: evt.data, + workerUrls: getActiveWorkerURLs(), + }); + }; + }); + }).then(async res => { + // Unregister the service worker used in this test. + const registration = await content.navigator.serviceWorker.ready; + await registration.unregister(); + return res; + }); + } + + const testCaseList = [ + // TODO: find a reasonable way to test the non-privileged scenario + // (because more than 1 process is usually available and the worker + // can be launched in a different one from the one where the tab + // is running). + /*{ + remoteType: "web", + hostname: "example.com", + },*/ + { + remoteType: "privilegedmozilla", + hostname: `example.org`, + }, + ]; + + for (const testCase of testCaseList) { + const { remoteType, hostname } = testCase; + + info(`Test remote serviceworkers launch selects a ${remoteType} process`); + + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: `https://${hostname}/${pagePath}`, + }); + + is( + tab.linkedBrowser.remoteType, + remoteType, + `Got the expected remoteType for ${hostname} tab` + ); + + const results = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + runWorkerInProcess + ); + + Assert.deepEqual( + results, + { + msg: "serviceworker-reply", + workerUrls: [`https://${hostname}/${scriptPath}`], + }, + `Got the expected results for ${hostname} tab` + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/dom/workers/test/browser_serviceworker_fetch_new_process.js b/dom/workers/test/browser_serviceworker_fetch_new_process.js new file mode 100644 index 0000000000..ae7d71c222 --- /dev/null +++ b/dom/workers/test/browser_serviceworker_fetch_new_process.js @@ -0,0 +1,405 @@ +const DIRPATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "" +); + +/** + * We choose blob contents that will roundtrip cleanly through the `textContent` + * of our returned HTML page. + */ +const TEST_BLOB_CONTENTS = `I'm a disk-backed test blob! Hooray!`; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + // Set preferences so that opening a page with the origin "example.org" + // will result in a remoteType of "privilegedmozilla" for both the + // page and the ServiceWorker. + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true], + ["browser.tabs.remote.separatedMozillaDomains", "example.org"], + ["dom.ipc.processCount.privilegedmozilla", 1], + ["dom.ipc.processPrelaunch.enabled", false], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + // ServiceWorker worker instances should stay alive until explicitly + // caused to terminate by dropping these timeouts to 0 in + // `waitForWorkerAndProcessShutdown`. + ["dom.serviceWorkers.idle_timeout", 299999], + ["dom.serviceWorkers.idle_extended_timeout", 299999], + ], + }); +}); + +function countRemoteType(remoteType) { + return ChromeUtils.getAllDOMProcesses().filter( + p => p.remoteType == remoteType + ).length; +} + +/** + * Helper function to get a list of all current processes and their remote + * types. Note that when in used in a templated literal that it is + * synchronously invoked when the string is evaluated and captures system state + * at that instant. + */ +function debugRemotes() { + return ChromeUtils.getAllDOMProcesses() + .map(p => p.remoteType || "parent") + .join(","); +} + +/** + * Wait for there to be zero processes of the given remoteType. This check is + * considered successful if there are already no processes of the given type + * at this very moment. + */ +async function waitForNoProcessesOfType(remoteType) { + info(`waiting for there to be no ${remoteType} procs`); + await TestUtils.waitForCondition( + () => countRemoteType(remoteType) == 0, + "wait for the worker's process to shutdown" + ); +} + +/** + * Given a ServiceWorkerRegistrationInfo with an active ServiceWorker that + * has no active ExtendableEvents but would otherwise continue running thanks + * to the idle keepalive: + * - Assert that there is a ServiceWorker instance in the given registration's + * active slot. (General invariant check.) + * - Assert that a single process with the given remoteType currently exists. + * (This doesn't mean the SW is alive in that process, though this test + * verifies that via other checks when appropriate.) + * - Induce the worker to shutdown by temporarily dropping the idle timeout to 0 + * and causing the idle timer to be reset due to rapid debugger attach/detach. + * - Wait for the the single process with the given remoteType to go away. + * - Reset the idle timeouts back to their previous high values. + */ +async function waitForWorkerAndProcessShutdown(swRegInfo, remoteType) { + info(`terminating worker and waiting for ${remoteType} procs to shut down`); + ok(swRegInfo.activeWorker, "worker should be in the active slot"); + is( + countRemoteType(remoteType), + 1, + `should have a single ${remoteType} process but have: ${debugRemotes()}` + ); + + // Let's not wait too long for the process to shutdown. + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.serviceWorkers.idle_timeout", 0], + ["dom.serviceWorkers.idle_extended_timeout", 0], + ], + }); + + // We need to cause the worker to re-evaluate its idle timeout. The easiest + // way to do this I could think of is to attach and then detach the debugger + // from the active worker. + swRegInfo.activeWorker.attachDebugger(); + await new Promise(resolve => Cu.dispatch(resolve)); + swRegInfo.activeWorker.detachDebugger(); + + // Eventually the length will reach 0, meaning we're done! + await waitForNoProcessesOfType(remoteType); + + is( + countRemoteType(remoteType), + 0, + `processes with remoteType=${remoteType} type should have shut down` + ); + + // Make sure we never kill workers on idle except when this is called. + await SpecialPowers.popPrefEnv(); +} + +async function do_test_sw(host, remoteType, swMode, fileBlob) { + info( + `### entering test: host=${host}, remoteType=${remoteType}, mode=${swMode}` + ); + + const prin = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(`https://${host}`), + {} + ); + const sw = `https://${host}/${DIRPATH}file_service_worker_fetch_synthetic.js`; + const scope = `https://${host}/${DIRPATH}server_fetch_synthetic.sjs`; + + const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + const swRegInfo = await swm.registerForTest(prin, scope, sw); + swRegInfo.QueryInterface(Ci.nsIServiceWorkerRegistrationInfo); + + info( + `service worker registered: ${JSON.stringify({ + principal: swRegInfo.principal.spec, + scope: swRegInfo.scope, + })}` + ); + + // Wait for the worker to install & shut down. + await TestUtils.waitForCondition( + () => swRegInfo.activeWorker, + "wait for the worker to become active" + ); + await waitForWorkerAndProcessShutdown(swRegInfo, remoteType); + + info( + `test navigation interception with mode=${swMode} starting from about:blank` + ); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async browser => { + // NOTE: We intentionally trigger the navigation from content in order to + // make sure frontend doesn't eagerly process-switch for us. + SpecialPowers.spawn( + browser, + [scope, swMode, fileBlob], + // eslint-disable-next-line no-shadow + async (scope, swMode, fileBlob) => { + const pageUrl = `${scope}?mode=${swMode}`; + if (!fileBlob) { + content.location.href = pageUrl; + } else { + const doc = content.document; + const formElem = doc.createElement("form"); + doc.body.appendChild(formElem); + + formElem.action = pageUrl; + formElem.method = "POST"; + formElem.enctype = "multipart/form-data"; + + const fileElem = doc.createElement("input"); + formElem.appendChild(fileElem); + + fileElem.type = "file"; + fileElem.name = "foo"; + + fileElem.mozSetFileArray([fileBlob]); + + formElem.submit(); + } + } + ); + + await BrowserTestUtils.browserLoaded(browser); + + is( + countRemoteType(remoteType), + 1, + `should have spawned a content process with remoteType=${remoteType}` + ); + + const { source, blobContents } = await SpecialPowers.spawn( + browser, + [], + () => { + return { + source: content.document.getElementById("source").textContent, + blobContents: content.document.getElementById("blob").textContent, + }; + } + ); + + is( + source, + swMode === "synthetic" ? "ServiceWorker" : "ServerJS", + "The page contents should come from the right place." + ); + + is( + blobContents, + fileBlob ? TEST_BLOB_CONTENTS : "", + "The request blob contents should be the blob/empty as appropriate." + ); + + // Ensure the worker was loaded in this process. + const workerDebuggerURLs = await SpecialPowers.spawn( + browser, + [sw], + async url => { + if (!content.navigator.serviceWorker.controller) { + throw new Error("document not controlled!"); + } + const wdm = Cc[ + "@mozilla.org/dom/workers/workerdebuggermanager;1" + ].getService(Ci.nsIWorkerDebuggerManager); + + return Array.from(wdm.getWorkerDebuggerEnumerator()) + .map(wd => { + return wd.url; + }) + .filter(swURL => swURL == url); + } + ); + if (remoteType.startsWith("webServiceWorker=")) { + Assert.notDeepEqual( + workerDebuggerURLs, + [sw], + "Isolated workers should not be running in the content child process" + ); + } else { + Assert.deepEqual( + workerDebuggerURLs, + [sw], + "The worker should be running in the correct child process" + ); + } + + // Unregister the ServiceWorker. The registration will continue to control + // `browser` and therefore continue to exist and its worker to continue + // running until the tab is closed. + await SpecialPowers.spawn(browser, [], async () => { + let registration = await content.navigator.serviceWorker.ready; + await registration.unregister(); + }); + } + ); + + // Now that the controlled tab is closed and the registration has been + // removed, the ServiceWorker will be made redundant which will forcibly + // terminate it, which will result in the shutdown of the given content + // process. Wait for that to happen both as a verification and so the next + // test has a sufficiently clean slate. + await waitForNoProcessesOfType(remoteType); +} + +/** + * Create a File-backed blob. This will happen synchronously from the main + * thread, which isn't optimal, but the test blocks on this progress anyways. + * Bug 1669578 has been filed on improving this idiom and avoiding the sync + * writes. + */ +async function makeFileBlob(blobContents) { + const tmpFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + tmpFile.append("test-file-backed-blob.txt"); + tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + var outStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + outStream.init( + tmpFile, + 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, + 0 + ); + outStream.write(blobContents, blobContents.length); + outStream.close(); + + const fileBlob = await File.createFromNsIFile(tmpFile); + return fileBlob; +} + +function getSWTelemetrySums() { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService( + Ci.nsITelemetry + ); + let keyedhistograms = telemetry.getSnapshotForKeyedHistograms( + "main", + false + ).parent; + let keyedscalars = telemetry.getSnapshotForKeyedScalars("main", false).parent; + // We're not looking at the distribution of the histograms, just that they changed + return { + SERVICE_WORKER_RUNNING_All: keyedhistograms.SERVICE_WORKER_RUNNING + ? keyedhistograms.SERVICE_WORKER_RUNNING.All.sum + : 0, + SERVICE_WORKER_RUNNING_Fetch: keyedhistograms.SERVICE_WORKER_RUNNING + ? keyedhistograms.SERVICE_WORKER_RUNNING.Fetch.sum + : 0, + SERVICEWORKER_RUNNING_MAX_All: keyedscalars["serviceworker.running_max"] + ? keyedscalars["serviceworker.running_max"].All + : 0, + SERVICEWORKER_RUNNING_MAX_Fetch: keyedscalars["serviceworker.running_max"] + ? keyedscalars["serviceworker.running_max"].Fetch + : 0, + }; +} + +add_task(async function test() { + // Can't test telemetry without this since we may not be on the nightly channel + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + registerCleanupFunction(() => { + Services.telemetry.canRecordExtended = oldCanRecord; + }); + + let initialSums = getSWTelemetrySums(); + + // ## Isolated Privileged Process + // Trigger a straightforward intercepted navigation with no request body that + // returns a synthetic response. + await do_test_sw("example.org", "privilegedmozilla", "synthetic", null); + + // Trigger an intercepted navigation with FormData containing an + // <input type="file"> which will result in the request body containing a + // RemoteLazyInputStream which will be consumed in the content process by the + // ServiceWorker while generating the synthetic response. + const fileBlob = await makeFileBlob(TEST_BLOB_CONTENTS); + await do_test_sw("example.org", "privilegedmozilla", "synthetic", fileBlob); + + // Trigger an intercepted navigation with FormData containing an + // <input type="file"> which will result in the request body containing a + // RemoteLazyInputStream which will be relayed back to the parent process + // via direct invocation of fetch() on the event.request but without any + // cloning. + await do_test_sw("example.org", "privilegedmozilla", "fetch", fileBlob); + + // Same as the above but cloning the request before fetching it. + await do_test_sw("example.org", "privilegedmozilla", "clone", fileBlob); + + // ## Fission Isolation + if (Services.appinfo.fissionAutostart) { + // ## ServiceWorker isolation + const isolateUrl = "example.com"; + const isolateRemoteType = `webServiceWorker=https://` + isolateUrl; + await do_test_sw(isolateUrl, isolateRemoteType, "synthetic", null); + await do_test_sw(isolateUrl, isolateRemoteType, "synthetic", fileBlob); + } + let telemetrySums = getSWTelemetrySums(); + info(JSON.stringify(telemetrySums)); + info( + "Initial Running All: " + + initialSums.SERVICE_WORKER_RUNNING_All + + ", Fetch: " + + initialSums.SERVICE_WORKER_RUNNING_Fetch + ); + info( + "Initial Max Running All: " + + initialSums.SERVICEWORKER_RUNNING_MAX_All + + ", Fetch: " + + initialSums.SERVICEWORKER_RUNNING_MAX_Fetch + ); + info( + "Running All: " + + telemetrySums.SERVICE_WORKER_RUNNING_All + + ", Fetch: " + + telemetrySums.SERVICE_WORKER_RUNNING_Fetch + ); + info( + "Max Running All: " + + telemetrySums.SERVICEWORKER_RUNNING_MAX_All + + ", Fetch: " + + telemetrySums.SERVICEWORKER_RUNNING_MAX_Fetch + ); + Assert.greater( + telemetrySums.SERVICE_WORKER_RUNNING_All, + initialSums.SERVICE_WORKER_RUNNING_All, + "ServiceWorker running count changed" + ); + Assert.greater( + telemetrySums.SERVICE_WORKER_RUNNING_Fetch, + initialSums.SERVICE_WORKER_RUNNING_Fetch, + "ServiceWorker running count changed" + ); + // We don't use ok()'s for MAX because MAX may have been set before we + // set canRecordExtended, and if so we won't record a new value unless + // the max increases again. +}); diff --git a/dom/workers/test/browser_worker_use_counters.js b/dom/workers/test/browser_worker_use_counters.js new file mode 100644 index 0000000000..d651da31d8 --- /dev/null +++ b/dom/workers/test/browser_worker_use_counters.js @@ -0,0 +1,180 @@ +/* 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"; + +const gHttpTestRoot = "https://example.com/browser/dom/workers/test/"; + +function unscream(s) { + // Takes SCREAMINGCASE `s` and returns "Screamingcase". + return s.charAt(0) + s.slice(1).toLowerCase(); +} + +function screamToCamel(s) { + // Takes SCREAMING_CASE `s` and returns "screamingCase". + const pascal = s.split("_").map(unscream).join(""); + return pascal.charAt(0).toLowerCase() + pascal.slice(1); +} + +var check_use_counter_worker = async function ( + use_counter_name, + worker_type, + content_task +) { + info(`checking ${use_counter_name} use counters for ${worker_type} worker`); + + let newTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + gBrowser.selectedTab = newTab; + newTab.linkedBrowser.stop(); + + // Hold on to the current values of the instrumentation we're + // interested in. + await Services.fog.testFlushAllChildren(); + let glean_before = + Glean[`useCounterWorker${unscream(worker_type)}`][ + screamToCamel(use_counter_name) + ].testGetValue(); + let glean_destructions_before = + Glean.useCounter[ + `${worker_type.toLowerCase()}WorkersDestroyed` + ].testGetValue(); + + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + gHttpTestRoot + "file_use_counter_worker.html" + ); + await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await content_task(gBrowser.selectedBrowser); + + // Tear down the page. + let tabClosed = BrowserTestUtils.waitForTabClosing(newTab); + gBrowser.removeTab(newTab); + await tabClosed; + + // Grab data again and compare. + // We'd like for this to be synchronous, but use counters are reported on + // worker destruction which we don't directly observe. + // So we check in a quick loop. + await BrowserTestUtils.waitForCondition(async () => { + await Services.fog.testFlushAllChildren(); + return ( + glean_before != + Glean[`useCounterWorker${unscream(worker_type)}`][ + screamToCamel(use_counter_name) + ].testGetValue() + ); + }); + let glean_after = + Glean[`useCounterWorker${unscream(worker_type)}`][ + screamToCamel(use_counter_name) + ].testGetValue(); + let glean_destructions_after = + Glean.useCounter[ + `${worker_type.toLowerCase()}WorkersDestroyed` + ].testGetValue(); + + is( + glean_after, + glean_before + 1, + `Glean counter ${use_counter_name} for ${worker_type} worker is correct.` + ); + // There might be other workers created by prior tests get destroyed during + // this tests. + Assert.greater( + glean_destructions_after, + glean_destructions_before ?? 0, + `Glean ${worker_type} worker counts are correct` + ); +}; + +add_task(async function test_dedicated_worker() { + await check_use_counter_worker("CONSOLE_LOG", "DEDICATED", async browser => { + await ContentTask.spawn(browser, {}, function () { + return new Promise(resolve => { + let worker = new content.Worker("file_use_counter_worker.js"); + worker.onmessage = function (e) { + if (e.data === "DONE") { + worker.terminate(); + resolve(); + } + }; + }); + }); + }); +}); + +add_task(async function test_shared_worker() { + await check_use_counter_worker("CONSOLE_LOG", "SHARED", async browser => { + await ContentTask.spawn(browser, {}, function () { + return new Promise(resolve => { + let worker = new content.SharedWorker( + "file_use_counter_shared_worker.js" + ); + worker.port.onmessage = function (e) { + if (e.data === "DONE") { + resolve(); + } + }; + worker.port.postMessage("RUN"); + }); + }); + }); +}); + +add_task(async function test_shared_worker_microtask() { + await check_use_counter_worker("CONSOLE_LOG", "SHARED", async browser => { + await ContentTask.spawn(browser, {}, function () { + return new Promise(resolve => { + let worker = new content.SharedWorker( + "file_use_counter_shared_worker_microtask.js" + ); + worker.port.onmessage = function (e) { + if (e.data === "DONE") { + resolve(); + } + }; + worker.port.postMessage("RUN"); + }); + }); + }); +}); + +add_task(async function test_service_worker() { + await check_use_counter_worker("CONSOLE_LOG", "SERVICE", async browser => { + await ContentTask.spawn(browser, {}, function () { + let waitForActivated = async function (registration) { + return new Promise(resolve => { + let worker = + registration.installing || + registration.waiting || + registration.active; + if (worker.state === "activated") { + resolve(worker); + return; + } + + worker.addEventListener("statechange", function onStateChange() { + if (worker.state === "activated") { + worker.removeEventListener("statechange", onStateChange); + resolve(worker); + } + }); + }); + }; + + return new Promise(resolve => { + content.navigator.serviceWorker + .register("file_use_counter_service_worker.js") + .then(async registration => { + content.navigator.serviceWorker.onmessage = function (e) { + if (e.data === "DONE") { + registration.unregister().then(resolve); + } + }; + let worker = await waitForActivated(registration); + worker.postMessage("RUN"); + }); + }); + }); + }); +}); diff --git a/dom/workers/test/bug1014466_data1.txt b/dom/workers/test/bug1014466_data1.txt new file mode 100644 index 0000000000..a32a4347a4 --- /dev/null +++ b/dom/workers/test/bug1014466_data1.txt @@ -0,0 +1 @@ +1234567890 diff --git a/dom/workers/test/bug1014466_data2.txt b/dom/workers/test/bug1014466_data2.txt new file mode 100644 index 0000000000..4d40154cef --- /dev/null +++ b/dom/workers/test/bug1014466_data2.txt @@ -0,0 +1 @@ +ABCDEFGH diff --git a/dom/workers/test/bug1014466_worker.js b/dom/workers/test/bug1014466_worker.js new file mode 100644 index 0000000000..2161954d2b --- /dev/null +++ b/dom/workers/test/bug1014466_worker.js @@ -0,0 +1,63 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function ok(a, msg) { + postMessage({ type: "status", status: !!a, msg }); +} + +onmessage = function (event) { + function getResponse(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(); + return xhr.responseText; + } + + const testFile1 = "bug1014466_data1.txt"; + const testFile2 = "bug1014466_data2.txt"; + const testData1 = getResponse(testFile1); + const testData2 = getResponse(testFile2); + + var response_count = 0; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState == xhr.DONE && xhr.status == 200) { + response_count++; + switch (response_count) { + case 1: + ok(xhr.responseText == testData1, "Check data 1"); + test_data2(); + break; + case 2: + ok(xhr.responseText == testData2, "Check data 2"); + postMessage({ type: "finish" }); + break; + default: + ok(false, "Unexpected response received"); + postMessage({ type: "finish" }); + break; + } + } + }; + xhr.onerror = function (e) { + ok(false, "Got an error event: " + e); + postMessage({ type: "finish" }); + }; + + function test_data1() { + xhr.open("GET", testFile1, true); + xhr.responseType = "text"; + xhr.send(); + } + + function test_data2() { + xhr.abort(); + xhr.open("GET", testFile2, true); + xhr.responseType = "text"; + xhr.send(); + } + + test_data1(); +}; diff --git a/dom/workers/test/bug1020226_frame.html b/dom/workers/test/bug1020226_frame.html new file mode 100644 index 0000000000..039dc9480a --- /dev/null +++ b/dom/workers/test/bug1020226_frame.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1020226 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1020226</title> +</head> +<body> + +<script type="application/javascript"> + var worker = new Worker("bug1020226_worker.js"); + worker.onmessage = function(e) { + window.parent.postMessage("loaded", "*"); + } +</script> +</body> +</html> diff --git a/dom/workers/test/bug1020226_worker.js b/dom/workers/test/bug1020226_worker.js new file mode 100644 index 0000000000..c09abdd7de --- /dev/null +++ b/dom/workers/test/bug1020226_worker.js @@ -0,0 +1,12 @@ +var p = new Promise(function (resolve, reject) { + // This causes a runnable to be queued. + reject(new Error()); + postMessage("loaded"); + + // This prevents that runnable from running until the window calls terminate(), + // at which point the worker goes into the Canceling state and then an + // HoldWorker() is attempted, which fails, which used to result in + // multiple calls to the error reporter, one after the worker's context had + // been GCed. + while (true) {} +}); diff --git a/dom/workers/test/bug1047663_tab.html b/dom/workers/test/bug1047663_tab.html new file mode 100644 index 0000000000..62ab9be7d2 --- /dev/null +++ b/dom/workers/test/bug1047663_tab.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + </head> + <body> + </body> +</html> diff --git a/dom/workers/test/bug1047663_worker.sjs b/dom/workers/test/bug1047663_worker.sjs new file mode 100644 index 0000000000..169ca05f89 --- /dev/null +++ b/dom/workers/test/bug1047663_worker.sjs @@ -0,0 +1,41 @@ +/* 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"; + +const WORKER_1 = ` + "use strict"; + + self.onmessage = function () { + postMessage("one"); + }; +`; + +const WORKER_2 = ` + "use strict"; + + self.onmessage = function () { + postMessage("two"); + }; +`; + +function handleRequest(request, response) { + let count = getState("count"); + if (count === "") { + count = "1"; + } + + // This header is necessary for the cache to trigger. + response.setHeader("Cache-control", "max-age=3600"); + response.setHeader("Content-Type", "text/javascript", false); + + // If this is the first request, return the first source. + if (count === "1") { + response.write(WORKER_1); + setState("count", "2"); + } + // For all subsequent requests, return the second source. + else { + response.write(WORKER_2); + } +} diff --git a/dom/workers/test/bug1060621_worker.js b/dom/workers/test/bug1060621_worker.js new file mode 100644 index 0000000000..4137e3b06f --- /dev/null +++ b/dom/workers/test/bug1060621_worker.js @@ -0,0 +1,2 @@ +navigator.foobar = 42; +postMessage("done"); diff --git a/dom/workers/test/bug1062920_worker.js b/dom/workers/test/bug1062920_worker.js new file mode 100644 index 0000000000..238bf949ec --- /dev/null +++ b/dom/workers/test/bug1062920_worker.js @@ -0,0 +1,8 @@ +postMessage({ + appCodeName: navigator.appCodeName, + appName: navigator.appName, + appVersion: navigator.appVersion, + platform: navigator.platform, + userAgent: navigator.userAgent, + product: navigator.product, +}); diff --git a/dom/workers/test/bug1063538.sjs b/dom/workers/test/bug1063538.sjs new file mode 100644 index 0000000000..7e4ba33998 --- /dev/null +++ b/dom/workers/test/bug1063538.sjs @@ -0,0 +1,11 @@ +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +function handleRequest(request, response) { + response.processAsync(); + response.write("Hello"); + setTimeout(function () { + response.finish(); + }, 100000); // wait 100 seconds. +} diff --git a/dom/workers/test/bug1063538_worker.js b/dom/workers/test/bug1063538_worker.js new file mode 100644 index 0000000000..33e60f0a16 --- /dev/null +++ b/dom/workers/test/bug1063538_worker.js @@ -0,0 +1,25 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var gURL = "http://example.org/tests/dom/workers/test/bug1063538.sjs"; +var xhr = new XMLHttpRequest({ mozAnon: true, mozSystem: true }); +var progressFired = false; + +xhr.onloadend = function (e) { + postMessage({ type: "finish", progressFired }); + self.close(); +}; + +xhr.onprogress = function (e) { + if (e.loaded > 0) { + progressFired = true; + xhr.abort(); + } +}; + +onmessage = function (e) { + xhr.open("GET", gURL, true); + xhr.send(); +}; diff --git a/dom/workers/test/bug1104064_worker.js b/dom/workers/test/bug1104064_worker.js new file mode 100644 index 0000000000..02b802a826 --- /dev/null +++ b/dom/workers/test/bug1104064_worker.js @@ -0,0 +1,10 @@ +onmessage = function () { + var counter = 0; + var id = setInterval(function () { + ++counter; + if (counter == 2) { + clearInterval(id); + postMessage("done"); + } + }, 0); +}; diff --git a/dom/workers/test/bug1132395_sharedWorker.js b/dom/workers/test/bug1132395_sharedWorker.js new file mode 100644 index 0000000000..851c7f7f6c --- /dev/null +++ b/dom/workers/test/bug1132395_sharedWorker.js @@ -0,0 +1,12 @@ +dump("SW created\n"); +onconnect = function (evt) { + dump("SW onconnect\n"); + evt.ports[0].onmessage = function (e) { + dump("SW onmessage\n"); + var blob = new Blob(["123"], { type: "text/plain" }); + dump("SW blob created\n"); + var url = URL.createObjectURL(blob); + dump("SW url created: " + url + "\n"); + evt.ports[0].postMessage("alive \\o/"); + }; +}; diff --git a/dom/workers/test/bug1132924_worker.js b/dom/workers/test/bug1132924_worker.js new file mode 100644 index 0000000000..db67ba323c --- /dev/null +++ b/dom/workers/test/bug1132924_worker.js @@ -0,0 +1,10 @@ +onmessage = function () { + var a = new XMLHttpRequest(); + a.open("GET", "empty.html", false); + a.onreadystatechange = function () { + if (a.readyState == 4) { + postMessage(a.response); + } + }; + a.send(null); +}; diff --git a/dom/workers/test/bug978260_worker.js b/dom/workers/test/bug978260_worker.js new file mode 100644 index 0000000000..126b9c901a --- /dev/null +++ b/dom/workers/test/bug978260_worker.js @@ -0,0 +1,7 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Tell the main thread we're here. +postMessage("loaded"); diff --git a/dom/workers/test/bug998474_worker.js b/dom/workers/test/bug998474_worker.js new file mode 100644 index 0000000000..6f6b6a9212 --- /dev/null +++ b/dom/workers/test/bug998474_worker.js @@ -0,0 +1,7 @@ +self.addEventListener("connect", function (e) { + var port = e.ports[0]; + port.onmessage = function (msg) { + // eslint-disable-next-line no-eval + port.postMessage(eval(msg.data)); + }; +}); diff --git a/dom/workers/test/chrome.toml b/dom/workers/test/chrome.toml new file mode 100644 index 0000000000..0b2d68da39 --- /dev/null +++ b/dom/workers/test/chrome.toml @@ -0,0 +1,120 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "WorkerDebugger.console_childWorker.js", + "WorkerDebugger.console_debugger.js", + "WorkerDebugger.console_worker.js", + "WorkerDebugger.initialize_childWorker.js", + "WorkerDebugger.initialize_debugger.js", + "WorkerDebugger.initialize_worker.js", + "WorkerDebugger.postMessage_childWorker.js", + "WorkerDebugger.postMessage_debugger.js", + "WorkerDebugger.postMessage_worker.js", + "WorkerDebuggerGlobalScope.createSandbox_debugger.js", + "WorkerDebuggerGlobalScope.createSandbox_sandbox.js", + "WorkerDebuggerGlobalScope.createSandbox_worker.js", + "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js", + "WorkerDebuggerGlobalScope.enterEventLoop_debugger.js", + "WorkerDebuggerGlobalScope.enterEventLoop_worker.js", + "WorkerDebuggerGlobalScope.reportError_childWorker.js", + "WorkerDebuggerGlobalScope.reportError_debugger.js", + "WorkerDebuggerGlobalScope.reportError_worker.js", + "WorkerDebuggerGlobalScope.setImmediate_debugger.js", + "WorkerDebuggerGlobalScope.setImmediate_worker.js", + "WorkerDebuggerManager_childWorker.js", + "WorkerDebuggerManager_worker.js", + "WorkerDebugger_childWorker.js", + "WorkerDebugger_frozen_window1.html", + "WorkerDebugger_frozen_window2.html", + "WorkerDebugger_frozen_worker1.js", + "WorkerDebugger_frozen_worker2.js", + "WorkerDebugger_promise_debugger.js", + "WorkerDebugger_promise_worker.js", + "WorkerDebugger_sharedWorker.js", + "WorkerDebugger_suspended_debugger.js", + "WorkerDebugger_suspended_worker.js", + "WorkerDebugger_worker.js", + "WorkerTest.jsm", + "WorkerTest_subworker.js", + "WorkerTest_worker.js", + "bug1062920_worker.js", + "chromeWorker_subworker.js", + "chromeWorker_worker_submod.sys.mjs", + "chromeWorker_worker.js", + "chromeWorker_worker.sys.mjs", + "dom_worker_helper.js", + "empty.html", + "fileBlobSubWorker_worker.js", + "fileBlob_worker.js", + "filePosting_worker.js", + "fileReadSlice_worker.js", + "fileReaderSyncErrors_worker.js", + "fileReaderSync_worker.js", + "fileSlice_worker.js", + "fileSubWorker_worker.js", + "file_worker.js", + "sharedWorker_privateBrowsing.js", + "sourcemap_header.js", + "sourcemap_header_debugger.js", +] + +["test_WorkerDebugger.initialize.xhtml"] + +["test_WorkerDebugger.postMessage.xhtml"] + +["test_WorkerDebugger.xhtml"] + +["test_WorkerDebuggerGlobalScope.createSandbox.xhtml"] + +["test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml"] + +["test_WorkerDebuggerGlobalScope.reportError.xhtml"] + +["test_WorkerDebuggerGlobalScope.setImmediate.xhtml"] + +["test_WorkerDebuggerManager.xhtml"] + +["test_WorkerDebugger_console.xhtml"] + +["test_WorkerDebugger_frozen.xhtml"] + +["test_WorkerDebugger_promise.xhtml"] + +["test_WorkerDebugger_suspended.xhtml"] + +["test_bug1062920.xhtml"] + +["test_chromeWorker.xhtml"] + +["test_chromeWorkerJSM.xhtml"] + +["test_file.xhtml"] +skip-if = [ + "os == 'linux' && bits == 64 && debug", # Bug 1765445 + "apple_catalina && !debug", # Bug 1765445 +] + +["test_fileBlobPosting.xhtml"] + +["test_fileBlobSubWorker.xhtml"] + +["test_filePosting.xhtml"] + +["test_fileReadSlice.xhtml"] + +["test_fileReaderSync.xhtml"] + +["test_fileReaderSyncErrors.xhtml"] + +["test_fileSlice.xhtml"] + +["test_fileSubWorker.xhtml"] + +["test_readableStream_when_closing.html"] + +["test_sharedWorker_privateBrowsing.html"] + +["test_shutdownCheck.xhtml"] +support-files = ["worker_shutdownCheck.js"] + +["test_sourcemap_header.html"] diff --git a/dom/workers/test/chromeWorker_subworker.js b/dom/workers/test/chromeWorker_subworker.js new file mode 100644 index 0000000000..6e89453150 --- /dev/null +++ b/dom/workers/test/chromeWorker_subworker.js @@ -0,0 +1,7 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + postMessage("Done!"); +}; diff --git a/dom/workers/test/chromeWorker_worker.js b/dom/workers/test/chromeWorker_worker.js new file mode 100644 index 0000000000..2d354b087b --- /dev/null +++ b/dom/workers/test/chromeWorker_worker.js @@ -0,0 +1,20 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +if (!("ctypes" in self)) { + throw "No ctypes!"; +} + +// Go ahead and verify that the ctypes lazy getter actually works. +if (ctypes.toString() != "[object ctypes]") { + throw "Bad ctypes object: " + ctypes.toString(); +} + +onmessage = function (event) { + let worker = new ChromeWorker("chromeWorker_subworker.js"); + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + worker.postMessage(event.data); +}; diff --git a/dom/workers/test/chromeWorker_worker.sys.mjs b/dom/workers/test/chromeWorker_worker.sys.mjs new file mode 100644 index 0000000000..ee96d7829b --- /dev/null +++ b/dom/workers/test/chromeWorker_worker.sys.mjs @@ -0,0 +1,16 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// our onmessage handler lives in the module. +import _ from "./chromeWorker_worker_submod.sys.mjs"; + +if (!("ctypes" in self)) { + throw "No ctypes!"; +} + +// Go ahead and verify that the ctypes lazy getter actually works. +if (ctypes.toString() != "[object ctypes]") { + throw "Bad ctypes object: " + ctypes.toString(); +} diff --git a/dom/workers/test/chromeWorker_worker_submod.sys.mjs b/dom/workers/test/chromeWorker_worker_submod.sys.mjs new file mode 100644 index 0000000000..c2fc29175c --- /dev/null +++ b/dom/workers/test/chromeWorker_worker_submod.sys.mjs @@ -0,0 +1,9 @@ +onmessage = function (event) { + let worker = new ChromeWorker("chromeWorker_subworker.js"); + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + worker.postMessage(event.data); +}; + +export default "go away linter"; diff --git a/dom/workers/test/clearTimeoutsImplicit_worker.js b/dom/workers/test/clearTimeoutsImplicit_worker.js new file mode 100644 index 0000000000..dfb8d0f6ce --- /dev/null +++ b/dom/workers/test/clearTimeoutsImplicit_worker.js @@ -0,0 +1,11 @@ +var count = 0; +function timerFunction() { + if (++count == 30) { + postMessage("ready"); + while (true) {} + } +} + +for (var i = 0; i < 10; i++) { + setInterval(timerFunction, 500); +} diff --git a/dom/workers/test/clearTimeouts_worker.js b/dom/workers/test/clearTimeouts_worker.js new file mode 100644 index 0000000000..6e6198c6b5 --- /dev/null +++ b/dom/workers/test/clearTimeouts_worker.js @@ -0,0 +1,12 @@ +var count = 0; +function timerFunction() { + if (++count == 30) { + close(); + postMessage("ready"); + while (true) {} + } +} + +for (var i = 0; i < 10; i++) { + setInterval(timerFunction, 500); +} diff --git a/dom/workers/test/consoleReplaceable_worker.js b/dom/workers/test/consoleReplaceable_worker.js new file mode 100644 index 0000000000..7e6be8cdd6 --- /dev/null +++ b/dom/workers/test/consoleReplaceable_worker.js @@ -0,0 +1,24 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function (event) { + postMessage({ event: "console exists", status: !!console, last: false }); + var logCalled = false; + console.log = function () { + logCalled = true; + }; + console.log("foo"); + postMessage({ + event: "console.log is replaceable", + status: logCalled, + last: false, + }); + console = 42; + postMessage({ + event: "console is replaceable", + status: console === 42, + last: true, + }); +}; diff --git a/dom/workers/test/console_worker.js b/dom/workers/test/console_worker.js new file mode 100644 index 0000000000..811dc12bae --- /dev/null +++ b/dom/workers/test/console_worker.js @@ -0,0 +1,113 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function (event) { + // TEST: does console exist? + postMessage({ event: "console exists", status: !!console, last: false }); + + postMessage({ + event: "console is the same object", + // eslint-disable-next-line no-self-compare + status: console === console, + last: false, + }); + + postMessage({ event: "trace without function", status: true, last: false }); + + for (var i = 0; i < 10; ++i) { + console.log(i, i, i); + } + + function trace1() { + function trace2() { + function trace3() { + console.trace("trace " + i); + } + trace3(); + } + trace2(); + } + trace1(); + + foobar585956c = function (a) { + console.trace(); + return a + "c"; + }; + + function foobar585956b(a) { + return foobar585956c(a + "b"); + } + + function foobar585956a(omg) { + return foobar585956b(omg + "a"); + } + + function foobar646025(omg) { + console.log(omg, "o", "d"); + } + + function startTimer(timer) { + console.time(timer); + } + + function stopTimer(timer) { + console.timeEnd(timer); + } + + function timeStamp(label) { + console.timeStamp(label); + } + + function testGroups() { + console.groupCollapsed("a", "group"); + console.group("b", "group"); + console.groupEnd(); + } + + foobar585956a("omg"); + foobar646025("omg"); + timeStamp(); + timeStamp("foo"); + testGroups(); + startTimer("foo"); + setTimeout(function () { + stopTimer("foo"); + nextSteps(event); + }, 10); +}; + +function nextSteps(event) { + function namelessTimer() { + console.time(); + console.timeEnd(); + } + + namelessTimer(); + + var str = "Test Message."; + console.log(str); + console.info(str); + console.warn(str); + console.error(str); + console.exception(str); + console.assert(true, str); + console.assert(false, str); + console.profile(str); + console.profileEnd(str); + console.timeStamp(); + console.clear(); + postMessage({ event: "4 messages", status: true, last: false }); + + // Recursive: + if (event.data == true) { + var worker = new Worker("console_worker.js"); + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + worker.postMessage(false); + } else { + postMessage({ event: "bye bye", status: true, last: true }); + } +} diff --git a/dom/workers/test/content_worker.js b/dom/workers/test/content_worker.js new file mode 100644 index 0000000000..d79732b281 --- /dev/null +++ b/dom/workers/test/content_worker.js @@ -0,0 +1,12 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var props = { + ctypes: 1, + OS: 1, +}; +for (var prop in props) { + postMessage({ prop, value: self[prop] }); +} +postMessage({ testfinished: 1 }); diff --git a/dom/workers/test/crashtests/1153636.html b/dom/workers/test/crashtests/1153636.html new file mode 100644 index 0000000000..6ad0d550fd --- /dev/null +++ b/dom/workers/test/crashtests/1153636.html @@ -0,0 +1,5 @@ +<script> + +new Worker("data:text/javascript;charset=UTF-8,self.addEventListener('',function(){},false);"); + +</script> diff --git a/dom/workers/test/crashtests/1158031.html b/dom/workers/test/crashtests/1158031.html new file mode 100644 index 0000000000..6d896bc466 --- /dev/null +++ b/dom/workers/test/crashtests/1158031.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script> + +function boom() +{ + var w = new Worker("data:text/javascript;charset=UTF-8,"); + w.postMessage(new Blob([], {})); +} + +</script> +<body onload="boom();"></body> diff --git a/dom/workers/test/crashtests/1228456.html b/dom/workers/test/crashtests/1228456.html new file mode 100644 index 0000000000..6d1f0f0a72 --- /dev/null +++ b/dom/workers/test/crashtests/1228456.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script> + +function boom() +{ + var w; + for (var i = 0; i < 99; ++i) { + w = new SharedWorker("data:text/javascript;charset=UTF-8," + encodeURIComponent(i + ";")); + } + w.port.postMessage(""); +} + +</script> +<body onload="boom();"></body> diff --git a/dom/workers/test/crashtests/1348882.html b/dom/workers/test/crashtests/1348882.html new file mode 100644 index 0000000000..e0288c4ccb --- /dev/null +++ b/dom/workers/test/crashtests/1348882.html @@ -0,0 +1,18 @@ +<!DOCTYPE> +<html> +<head> +<meta charset="UTF-8"> +<script> +function boom() { + let r = new Request("#a#a"); + setTimeout(function(){ + r.formData(); + setTimeout(function(){ + r.blob(); + }, 0); + }, 0); +} +addEventListener("DOMContentLoaded", boom); +</script> +</head> +</html> diff --git a/dom/workers/test/crashtests/1819146.html b/dom/workers/test/crashtests/1819146.html new file mode 100644 index 0000000000..611acb3ed2 --- /dev/null +++ b/dom/workers/test/crashtests/1819146.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <script id="worker1" type="javascript/worker"> + self.onmessage = async function (e) { + const abort = new AbortController() + const signal = abort.signal + abort.abort() + close() + try { await fetch(undefined, { signal: signal }) } catch (e) {} + await navigator.locks.request("weblock_0", { signal: signal }, () => {}) + await fetch(undefined, { headers: [] }) + } + </script> + <script> + document.addEventListener('DOMContentLoaded', () => { + const blob = new Blob([document.querySelector('#worker1').textContent], { type: 'text/javascript' }) + const worker = new Worker(window.URL.createObjectURL(blob)) + worker.postMessage([], []) + }) + </script> +</head> +</html> diff --git a/dom/workers/test/crashtests/1821066.html b/dom/workers/test/crashtests/1821066.html new file mode 100644 index 0000000000..d654b37768 --- /dev/null +++ b/dom/workers/test/crashtests/1821066.html @@ -0,0 +1,9 @@ +<!DOCTYPE> +<html> +<head> +<meta charset="UTF-8"> +<script> +new Worker("data:text/javascript;charset=UTF-8,import { o } from '404.js'", {type: 'module'}); +</script> +</head> +</html> diff --git a/dom/workers/test/crashtests/779707.html b/dom/workers/test/crashtests/779707.html new file mode 100644 index 0000000000..97a8113dab --- /dev/null +++ b/dom/workers/test/crashtests/779707.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +function boom() +{ + var x = new XMLHttpRequest(); + x.open('GET', "data:text/plain,2", false); + x.send(); + + new Worker("data:text/javascript,3"); +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/dom/workers/test/crashtests/943516.html b/dom/workers/test/crashtests/943516.html new file mode 100644 index 0000000000..5f4667850f --- /dev/null +++ b/dom/workers/test/crashtests/943516.html @@ -0,0 +1,10 @@ +<!-- +Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<script> +// Using a DOM bindings object as a weak map key should not crash when attempting to +// call the preserve wrapper callback. +new Worker("data:text/javascript;charset=UTF-8,(new WeakMap()).set(self, 0);") +</script> diff --git a/dom/workers/test/crashtests/crashtests.list b/dom/workers/test/crashtests/crashtests.list new file mode 100644 index 0000000000..528f4c8a10 --- /dev/null +++ b/dom/workers/test/crashtests/crashtests.list @@ -0,0 +1,8 @@ +load 779707.html +load 943516.html +load 1153636.html +load 1158031.html +load 1228456.html +load 1348882.html +load 1821066.html +load 1819146.html diff --git a/dom/workers/test/csp_worker.js b/dom/workers/test/csp_worker.js new file mode 100644 index 0000000000..04afa4bda1 --- /dev/null +++ b/dom/workers/test/csp_worker.js @@ -0,0 +1,26 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + if (event.data.do == "eval") { + var res; + try { + // eslint-disable-next-line no-eval + res = eval("40+2"); + } catch (ex) { + res = ex + ""; + } + postMessage(res); + } else if (event.data.do == "nest") { + var worker = new Worker(event.data.uri); + if (--event.data.level) { + worker.postMessage(event.data); + } else { + worker.postMessage({ do: "eval" }); + } + worker.onmessage = e => { + postMessage(e.data); + }; + } +}; diff --git a/dom/workers/test/csp_worker.js^headers^ b/dom/workers/test/csp_worker.js^headers^ new file mode 100644 index 0000000000..7b835bf2a8 --- /dev/null +++ b/dom/workers/test/csp_worker.js^headers^ @@ -0,0 +1 @@ +Content-Security-Policy: default-src 'self' blob: ; script-src 'unsafe-eval' diff --git a/dom/workers/test/dom_worker_helper.js b/dom/workers/test/dom_worker_helper.js new file mode 100644 index 0000000000..e61b4793f2 --- /dev/null +++ b/dom/workers/test/dom_worker_helper.js @@ -0,0 +1,157 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"].getService( + Ci.nsIWorkerDebuggerManager +); + +const BASE_URL = "chrome://mochitests/content/chrome/dom/workers/test/"; + +var gRemainingTests = 0; + +function waitForWorkerFinish() { + if (gRemainingTests == 0) { + SimpleTest.waitForExplicitFinish(); + } + ++gRemainingTests; +} + +function finish() { + --gRemainingTests; + if (gRemainingTests == 0) { + SimpleTest.finish(); + } +} + +function assertThrows(fun, message) { + let throws = false; + try { + fun(); + } catch (e) { + throws = true; + } + ok(throws, message); +} + +function generateDebuggers() { + return wdm.getWorkerDebuggerEnumerator(); +} + +function findDebugger(url) { + for (let dbg of generateDebuggers()) { + if (dbg.url === url) { + return dbg; + } + } + return null; +} + +function waitForRegister(url, dbgUrl) { + return new Promise(function (resolve) { + wdm.addListener({ + onRegister(dbg) { + if (dbg.url !== url) { + return; + } + ok(true, "Debugger with url " + url + " should be registered."); + wdm.removeListener(this); + if (dbgUrl) { + info("Initializing worker debugger with url " + url + "."); + dbg.initialize(dbgUrl); + } + resolve(dbg); + }, + }); + }); +} + +function waitForUnregister(url) { + return new Promise(function (resolve) { + wdm.addListener({ + onUnregister(dbg) { + if (dbg.url !== url) { + return; + } + ok(true, "Debugger with url " + url + " should be unregistered."); + wdm.removeListener(this); + resolve(); + }, + }); + }); +} + +function waitForDebuggerClose(dbg) { + return new Promise(function (resolve) { + dbg.addListener({ + onClose() { + ok(true, "Debugger should be closed."); + dbg.removeListener(this); + resolve(); + }, + }); + }); +} + +function waitForDebuggerError(dbg) { + return new Promise(function (resolve) { + dbg.addListener({ + onError(filename, lineno, message) { + dbg.removeListener(this); + resolve(new Error(message, filename, lineno)); + }, + }); + }); +} + +function waitForDebuggerMessage(dbg, message) { + return new Promise(function (resolve) { + dbg.addListener({ + onMessage(message1) { + if (message !== message1) { + return; + } + ok(true, "Should receive " + message + " message from debugger."); + dbg.removeListener(this); + resolve(); + }, + }); + }); +} + +function waitForWindowMessage(window, message) { + return new Promise(function (resolve) { + let onmessage = function (event) { + // eslint-disable-next-line no-self-compare + if (event.data !== event.data) { + return; + } + window.removeEventListener("message", onmessage); + resolve(); + }; + window.addEventListener("message", onmessage); + }); +} + +function waitForWorkerMessage(worker, message) { + return new Promise(function (resolve) { + worker.addEventListener("message", function onmessage(event) { + if (event.data !== message) { + return; + } + ok(true, "Should receive " + message + " message from worker."); + worker.removeEventListener("message", onmessage); + resolve(); + }); + }); +} + +function waitForMultiple(promises) { + // There used to be old logic which expects promises to be resolved in + // succession, but where it seems like this was an incorrect assumption. + // Assuming this change sticks, bug 1861778 tracks removing this method + // entirely in favor of Promise.all at the call-sites or transform the callers + // into explicitly documented awaited sequences. + return Promise.all(promises); +} diff --git a/dom/workers/test/dynamicImport_nested.mjs b/dom/workers/test/dynamicImport_nested.mjs new file mode 100644 index 0000000000..688078d803 --- /dev/null +++ b/dom/workers/test/dynamicImport_nested.mjs @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +await import("./dynamicImport_postMessage.mjs"); + +export const message = "first"; diff --git a/dom/workers/test/dynamicImport_postMessage.mjs b/dom/workers/test/dynamicImport_postMessage.mjs new file mode 100644 index 0000000000..ddb9ee0644 --- /dev/null +++ b/dom/workers/test/dynamicImport_postMessage.mjs @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +postMessage("second"); + +export const message = "second"; diff --git a/dom/workers/test/dynamicImport_worker.js b/dom/workers/test/dynamicImport_worker.js new file mode 100644 index 0000000000..90bd422efe --- /dev/null +++ b/dom/workers/test/dynamicImport_worker.js @@ -0,0 +1,14 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function (event) { + switch (event.data) { + case "start": + import("./dynamicImport_nested.mjs").then(m => postMessage(m.message)); + break; + default: + throw new Error("Bad message: " + event.data); + } +}; diff --git a/dom/workers/test/empty.html b/dom/workers/test/empty.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/workers/test/empty.html diff --git a/dom/workers/test/empty_worker.js b/dom/workers/test/empty_worker.js new file mode 100644 index 0000000000..9dda1c9fd1 --- /dev/null +++ b/dom/workers/test/empty_worker.js @@ -0,0 +1 @@ +postMessage(42); diff --git a/dom/workers/test/errorPropagation_iframe.html b/dom/workers/test/errorPropagation_iframe.html new file mode 100644 index 0000000000..cbaed1778f --- /dev/null +++ b/dom/workers/test/errorPropagation_iframe.html @@ -0,0 +1,56 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <meta charset="utf-8"> + <body> + <script type="text/javascript"> + var worker; + + function start(workerCount, messageCallback) { + var seenWindowError; + window.onerror = function(message, filename, lineno) { + if (!seenWindowError) { + seenWindowError = true; + messageCallback({ + type: "window", + data: { message, filename, lineno } + }); + return true; + } + return undefined; + }; + + worker = new Worker("errorPropagation_worker.js"); + + worker.onmessage = function(event) { + messageCallback(event.data); + }; + + var seenWorkerError; + worker.onerror = function(event) { + if (!seenWorkerError) { + seenWorkerError = true; + messageCallback({ + type: "worker", + data: { + message: event.message, + filename: event.filename, + lineno: event.lineno + } + }); + event.preventDefault(); + } + }; + + worker.postMessage(workerCount); + } + + function stop() { + worker.terminate(); + } + </script> + </body> +</html> diff --git a/dom/workers/test/errorPropagation_worker.js b/dom/workers/test/errorPropagation_worker.js new file mode 100644 index 0000000000..679181e83a --- /dev/null +++ b/dom/workers/test/errorPropagation_worker.js @@ -0,0 +1,51 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var seenScopeError; +onerror = function (message, filename, lineno) { + if (!seenScopeError) { + seenScopeError = true; + postMessage({ + type: "scope", + data: { message, filename, lineno }, + }); + return true; + } + return undefined; +}; + +onmessage = function (event) { + var workerId = parseInt(event.data); + + if (workerId > 1) { + var worker = new Worker("errorPropagation_worker.js"); + + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + + var seenWorkerError; + worker.onerror = function (error) { + if (!seenWorkerError) { + seenWorkerError = true; + postMessage({ + type: "worker", + data: { + message: error.message, + filename: error.filename, + lineno: error.lineno, + }, + }); + error.preventDefault(); + } + }; + + worker.postMessage(workerId - 1); + return; + } + + var interval = setInterval(function () { + throw new Error("expectedError"); + }, 100); +}; diff --git a/dom/workers/test/errorwarning_worker.js b/dom/workers/test/errorwarning_worker.js new file mode 100644 index 0000000000..39e71f8414 --- /dev/null +++ b/dom/workers/test/errorwarning_worker.js @@ -0,0 +1,46 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +function errorHandler() { + postMessage({ type: "error" }); +} + +onmessage = function (event) { + if (event.data.errors) { + try { + // This is an error: + postMessage({ type: "ignore", value: b.aaa }); + } catch (e) { + errorHandler(); + } + } else { + var a = {}; + // This is a warning: + postMessage({ type: "ignore", value: a.foo }); + } + + if (event.data.loop != 0) { + var worker = new Worker("errorwarning_worker.js"); + worker.onerror = errorHandler; + worker.postMessage({ + loop: event.data.loop - 1, + errors: event.data.errors, + }); + + worker.onmessage = function (e) { + postMessage(e.data); + }; + } else { + postMessage({ type: "finish" }); + } +}; + +onerror = errorHandler; +// eslint-disable-next-line no-self-assign +onerror = onerror; +// eslint-disable-next-line no-self-compare +if (!onerror || onerror != onerror) { + throw "onerror wasn't set properly"; +} diff --git a/dom/workers/test/eventDispatch_worker.js b/dom/workers/test/eventDispatch_worker.js new file mode 100644 index 0000000000..57bbc99aa3 --- /dev/null +++ b/dom/workers/test/eventDispatch_worker.js @@ -0,0 +1,84 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +const fakeEventType = "foo"; + +function testEventTarget(event) { + if (event.target !== self) { + throw new Error("Event has a bad target!"); + } + if (event.currentTarget) { + throw new Error("Event has a bad currentTarget!"); + } + postMessage(event.data); +} + +addEventListener( + fakeEventType, + function (event) { + throw new Error("Trusted event listener received untrusted event!"); + }, + false, + false +); + +addEventListener( + fakeEventType, + function (event) { + if (event.target !== self || event.currentTarget !== self) { + throw new Error("Fake event has bad target!"); + } + if (event.isTrusted) { + throw new Error("Event should be untrusted!"); + } + event.stopImmediatePropagation(); + postMessage(event.data); + }, + false, + true +); + +addEventListener( + fakeEventType, + function (event) { + throw new Error( + "This shouldn't get called because of stopImmediatePropagation." + ); + }, + false, + true +); + +var count = 0; +onmessage = function (event) { + if (event.target !== self || event.currentTarget !== self) { + throw new Error("Event has bad target!"); + } + + if (!count++) { + var exception; + try { + self.dispatchEvent(event); + } catch (e) { + exception = e; + } + + if (!exception) { + throw new Error("Recursive dispatch didn't fail!"); + } + + event = new MessageEvent(fakeEventType, { + bubbles: event.bubbles, + cancelable: event.cancelable, + data: event.data, + origin: "*", + source: null, + }); + self.dispatchEvent(event); + + return; + } + + setTimeout(testEventTarget, 0, event); +}; diff --git a/dom/workers/test/fibonacci_worker.js b/dom/workers/test/fibonacci_worker.js new file mode 100644 index 0000000000..0efe5a18d9 --- /dev/null +++ b/dom/workers/test/fibonacci_worker.js @@ -0,0 +1,24 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + var n = parseInt(event.data); + + if (n < 2) { + postMessage(n); + return; + } + + var results = []; + for (var i = 1; i <= 2; i++) { + var worker = new Worker("fibonacci_worker.js"); + worker.onmessage = function (msg) { + results.push(parseInt(msg.data)); + if (results.length == 2) { + postMessage(results[0] + results[1]); + } + }; + worker.postMessage(n - i); + } +}; diff --git a/dom/workers/test/fileBlobSubWorker_worker.js b/dom/workers/test/fileBlobSubWorker_worker.js new file mode 100644 index 0000000000..170362b1a4 --- /dev/null +++ b/dom/workers/test/fileBlobSubWorker_worker.js @@ -0,0 +1,17 @@ +/** + * Expects a blob. Returns an object containing the size, type. + * Used to test posting of blob from worker to worker. + */ +onmessage = function (event) { + var worker = new Worker("fileBlob_worker.js"); + + worker.postMessage(event.data); + + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + + worker.onerror = function (error) { + postMessage(undefined); + }; +}; diff --git a/dom/workers/test/fileBlob_worker.js b/dom/workers/test/fileBlob_worker.js new file mode 100644 index 0000000000..fbb50c7c10 --- /dev/null +++ b/dom/workers/test/fileBlob_worker.js @@ -0,0 +1,13 @@ +/** + * Expects a blob. Returns an object containing the size, type. + */ +onmessage = function (event) { + var file = event.data; + + var rtnObj = new Object(); + + rtnObj.size = file.size; + rtnObj.type = file.type; + + postMessage(rtnObj); +}; diff --git a/dom/workers/test/filePosting_worker.js b/dom/workers/test/filePosting_worker.js new file mode 100644 index 0000000000..052d400374 --- /dev/null +++ b/dom/workers/test/filePosting_worker.js @@ -0,0 +1,3 @@ +onmessage = function (event) { + postMessage(event.data); +}; diff --git a/dom/workers/test/fileReadSlice_worker.js b/dom/workers/test/fileReadSlice_worker.js new file mode 100644 index 0000000000..edbad88802 --- /dev/null +++ b/dom/workers/test/fileReadSlice_worker.js @@ -0,0 +1,16 @@ +/** + * Expects an object containing a blob, a start index and an end index + * for slicing. Returns the contents of the blob read as text. + */ +onmessage = function (event) { + var blob = event.data.blob; + var start = event.data.start; + var end = event.data.end; + + var slicedBlob = blob.slice(start, end); + + var fileReader = new FileReaderSync(); + var text = fileReader.readAsText(slicedBlob); + + postMessage(text); +}; diff --git a/dom/workers/test/fileReaderSyncErrors_worker.js b/dom/workers/test/fileReaderSyncErrors_worker.js new file mode 100644 index 0000000000..0ba91469c8 --- /dev/null +++ b/dom/workers/test/fileReaderSyncErrors_worker.js @@ -0,0 +1,82 @@ +/** + * Delegates "is" evaluation back to main thread. + */ +function is(actual, expected, message) { + var rtnObj = new Object(); + rtnObj.actual = actual; + rtnObj.expected = expected; + rtnObj.message = message; + postMessage(rtnObj); +} + +/** + * Tries to write to property. + */ +function writeProperty(file, property) { + var oldValue = file[property]; + file[property] = -1; + is(file[property], oldValue, "Property " + property + " should be readonly."); +} + +/** + * Passes junk arguments to FileReaderSync methods and expects an exception to + * be thrown. + */ +function fileReaderJunkArgument(blob) { + var fileReader = new FileReaderSync(); + + try { + fileReader.readAsBinaryString(blob); + is( + false, + true, + "Should have thrown an exception calling readAsBinaryString." + ); + } catch (ex) { + is(true, true, "Should have thrown an exception."); + } + + try { + fileReader.readAsDataURL(blob); + is(false, true, "Should have thrown an exception calling readAsDataURL."); + } catch (ex) { + is(true, true, "Should have thrown an exception."); + } + + try { + fileReader.readAsArrayBuffer(blob); + is( + false, + true, + "Should have thrown an exception calling readAsArrayBuffer." + ); + } catch (ex) { + is(true, true, "Should have thrown an exception."); + } + + try { + fileReader.readAsText(blob); + is(false, true, "Should have thrown an exception calling readAsText."); + } catch (ex) { + is(true, true, "Should have thrown an exception."); + } +} + +onmessage = function (event) { + var file = event.data; + + // Test read only properties. + writeProperty(file, "size"); + writeProperty(file, "type"); + writeProperty(file, "name"); + + // Bad types. + fileReaderJunkArgument(undefined); + fileReaderJunkArgument(-1); + fileReaderJunkArgument(1); + fileReaderJunkArgument(new Object()); + fileReaderJunkArgument("hello"); + + // Post undefined to indicate that testing has finished. + postMessage(undefined); +}; diff --git a/dom/workers/test/fileReaderSync_worker.js b/dom/workers/test/fileReaderSync_worker.js new file mode 100644 index 0000000000..d5512ef024 --- /dev/null +++ b/dom/workers/test/fileReaderSync_worker.js @@ -0,0 +1,25 @@ +var reader = new FileReaderSync(); + +/** + * Expects an object containing a file and an encoding then uses a + * FileReaderSync to read the file. Returns an object containing the + * file read a binary string, text, url and ArrayBuffer. + */ +onmessage = function (event) { + var file = event.data.file; + var encoding = event.data.encoding; + + var rtnObj = new Object(); + + if (encoding != undefined) { + rtnObj.text = reader.readAsText(file, encoding); + } else { + rtnObj.text = reader.readAsText(file); + } + + rtnObj.bin = reader.readAsBinaryString(file); + rtnObj.url = reader.readAsDataURL(file); + rtnObj.arrayBuffer = reader.readAsArrayBuffer(file); + + postMessage(rtnObj); +}; diff --git a/dom/workers/test/fileSlice_worker.js b/dom/workers/test/fileSlice_worker.js new file mode 100644 index 0000000000..94a283033a --- /dev/null +++ b/dom/workers/test/fileSlice_worker.js @@ -0,0 +1,27 @@ +/** + * Expects an object containing a blob, a start offset, an end offset + * and an optional content type to slice the blob. Returns an object + * containing the size and type of the sliced blob. + */ +onmessage = function (event) { + var blob = event.data.blob; + var start = event.data.start; + var end = event.data.end; + var contentType = event.data.contentType; + + var slicedBlob; + if (contentType == undefined && end == undefined) { + slicedBlob = blob.slice(start); + } else if (contentType == undefined) { + slicedBlob = blob.slice(start, end); + } else { + slicedBlob = blob.slice(start, end, contentType); + } + + var rtnObj = new Object(); + + rtnObj.size = slicedBlob.size; + rtnObj.type = slicedBlob.type; + + postMessage(rtnObj); +}; diff --git a/dom/workers/test/fileSubWorker_worker.js b/dom/workers/test/fileSubWorker_worker.js new file mode 100644 index 0000000000..8a5c002865 --- /dev/null +++ b/dom/workers/test/fileSubWorker_worker.js @@ -0,0 +1,17 @@ +/** + * Expects a file. Returns an object containing the size, type, name and path + * using another worker. Used to test posting of file from worker to worker. + */ +onmessage = function (event) { + var worker = new Worker("file_worker.js"); + + worker.postMessage(event.data); + + worker.onmessage = function (msg) { + postMessage(msg.data); + }; + + worker.onerror = function (error) { + postMessage(undefined); + }; +}; diff --git a/dom/workers/test/file_bug1010784_worker.js b/dom/workers/test/file_bug1010784_worker.js new file mode 100644 index 0000000000..3e88f5cc97 --- /dev/null +++ b/dom/workers/test/file_bug1010784_worker.js @@ -0,0 +1,9 @@ +onmessage = function (event) { + var xhr = new XMLHttpRequest(); + + xhr.open("GET", event.data, false); + xhr.send(); + xhr.open("GET", event.data, false); + xhr.send(); + postMessage("done"); +}; diff --git a/dom/workers/test/file_service_worker.js b/dom/workers/test/file_service_worker.js new file mode 100644 index 0000000000..dd264e340e --- /dev/null +++ b/dom/workers/test/file_service_worker.js @@ -0,0 +1,3 @@ +self.onmessage = evt => { + evt.ports[0].postMessage("serviceworker-reply"); +}; diff --git a/dom/workers/test/file_service_worker_container.html b/dom/workers/test/file_service_worker_container.html new file mode 100644 index 0000000000..625e911adc --- /dev/null +++ b/dom/workers/test/file_service_worker_container.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <script> + (async function () { + await navigator.serviceWorker.register("file_service_worker.js"); + await navigator.serviceWorker.ready; + })(); + </script> + </head> + <body> + Service Worker Container + </body> +</html> diff --git a/dom/workers/test/file_service_worker_fetch_synthetic.js b/dom/workers/test/file_service_worker_fetch_synthetic.js new file mode 100644 index 0000000000..c58aeb294a --- /dev/null +++ b/dom/workers/test/file_service_worker_fetch_synthetic.js @@ -0,0 +1,59 @@ +addEventListener("install", function (evt) { + evt.waitUntil(self.skipWaiting()); +}); + +/** + * Given a multipart/form-data encoded string that we know to have only a single + * part, return the contents of the part. (MIME multipart encoding is too + * exciting to delve into.) + */ +function extractBlobFromMultipartFormData(text) { + const lines = text.split(/\r\n/g); + const firstBlank = lines.indexOf(""); + const foo = lines.slice(firstBlank + 1, -2).join("\n"); + return foo; +} + +self.addEventListener("fetch", event => { + const url = new URL(event.request.url); + const mode = url.searchParams.get("mode"); + + if (mode === "synthetic") { + event.respondWith( + (async () => { + // This works even if there wasn't a body explicitly associated with the + // request. We just get a zero-length string in that case. + const requestBodyContents = await event.request.text(); + const blobContents = + extractBlobFromMultipartFormData(requestBodyContents); + + return new Response( + `<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body> + <h1 id="url">${event.request.url}</h1> + <div id="source">ServiceWorker</div> + <div id="blob">${blobContents}</div> + </body>`, + { headers: { "Content-Type": "text/html" } } + ); + })() + ); + } else if (mode === "fetch") { + event.respondWith(fetch(event.request)); + } else if (mode === "clone") { + // In order for the act of cloning to be interesting, we want the original + // request to remain alive so that any pipes end up having to buffer. + self.originalRequest = event.request; + event.respondWith(fetch(event.request.clone())); + } else { + event.respondWith( + new Response( + `<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body> + <h1 id="error">Bad mode: ${mode}</h1> + <div id="source">ServiceWorker::Error</div> + <div id="blob">No, this is an error.</div> + </body>`, + { headers: { "Content-Type": "text/html" }, status: 400 } + ) + ); + } +}); diff --git a/dom/workers/test/file_use_counter_service_worker.js b/dom/workers/test/file_use_counter_service_worker.js new file mode 100644 index 0000000000..8ee0d2e04a --- /dev/null +++ b/dom/workers/test/file_use_counter_service_worker.js @@ -0,0 +1,9 @@ +onmessage = async function (e) { + if (e.data === "RUN") { + console.log("worker runs"); + await clients.claim(); + clients.matchAll().then(res => { + res.forEach(client => client.postMessage("DONE")); + }); + } +}; diff --git a/dom/workers/test/file_use_counter_shared_worker.js b/dom/workers/test/file_use_counter_shared_worker.js new file mode 100644 index 0000000000..7e4f95af3b --- /dev/null +++ b/dom/workers/test/file_use_counter_shared_worker.js @@ -0,0 +1,10 @@ +onconnect = function (e) { + let port = e.ports[0]; + port.onmessage = function (m) { + if (m.data === "RUN") { + console.log("worker runs"); + port.postMessage("DONE"); + close(); + } + }; +}; diff --git a/dom/workers/test/file_use_counter_shared_worker_microtask.js b/dom/workers/test/file_use_counter_shared_worker_microtask.js new file mode 100644 index 0000000000..b219da2225 --- /dev/null +++ b/dom/workers/test/file_use_counter_shared_worker_microtask.js @@ -0,0 +1,12 @@ +onconnect = function (e) { + let port = e.ports[0]; + port.onmessage = function (m) { + if (m.data === "RUN") { + queueMicrotask(() => { + console.log("worker runs"); + }); + port.postMessage("DONE"); + close(); + } + }; +}; diff --git a/dom/workers/test/file_use_counter_worker.html b/dom/workers/test/file_use_counter_worker.html new file mode 100644 index 0000000000..d034ad6f5f --- /dev/null +++ b/dom/workers/test/file_use_counter_worker.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1202706 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1202706</title> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1202706">Mozilla Bug 1202706</a> +</body> +</html> diff --git a/dom/workers/test/file_use_counter_worker.js b/dom/workers/test/file_use_counter_worker.js new file mode 100644 index 0000000000..12046940a8 --- /dev/null +++ b/dom/workers/test/file_use_counter_worker.js @@ -0,0 +1,2 @@ +console.log("worker runs"); +postMessage("DONE"); diff --git a/dom/workers/test/file_worker.js b/dom/workers/test/file_worker.js new file mode 100644 index 0000000000..6ed8fe1816 --- /dev/null +++ b/dom/workers/test/file_worker.js @@ -0,0 +1,16 @@ +/** + * Expects a file. Returns an object containing the size, type, name and path. + */ +onmessage = function (event) { + var file = event.data; + + var rtnObj = new Object(); + + rtnObj.size = file.size; + rtnObj.type = file.type; + rtnObj.name = file.name; + rtnObj.path = file.path; + rtnObj.lastModified = file.lastModified; + + postMessage(rtnObj); +}; diff --git a/dom/workers/test/foreign.js b/dom/workers/test/foreign.js new file mode 100644 index 0000000000..33c982fa8f --- /dev/null +++ b/dom/workers/test/foreign.js @@ -0,0 +1 @@ +response = "bad"; diff --git a/dom/workers/test/head.js b/dom/workers/test/head.js new file mode 100644 index 0000000000..ce6cebbd06 --- /dev/null +++ b/dom/workers/test/head.js @@ -0,0 +1,75 @@ +/* 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"; + +/** + * Add a tab with given `url`. Returns a promise + * that will be resolved when the tab finished loading. + */ +function addTab(url) { + return BrowserTestUtils.openNewForegroundTab(gBrowser, TAB_URL); +} + +/** + * Remove the given `tab`. + */ +function removeTab(tab) { + gBrowser.removeTab(tab); +} + +/** + * Create a worker with the given `url` in the given `tab`. + */ +function createWorkerInTab(tab, url) { + info("Creating worker with url '" + url + "'\n"); + return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => { + if (!content._workers) { + content._workers = {}; + } + content._workers[urlChild] = new content.Worker(urlChild); + }); +} + +/** + * Terminate the worker with the given `url` in the given `tab`. + */ +function terminateWorkerInTab(tab, url) { + info("Terminating worker with url '" + url + "'\n"); + return SpecialPowers.spawn(tab.linkedBrowser, [url], urlChild => { + content._workers[urlChild].terminate(); + delete content._workers[urlChild]; + }); +} + +/** + * Post the given `message` to the worker with the given `url` in the given + * `tab`. + */ +function postMessageToWorkerInTab(tab, url, message) { + info("Posting message to worker with url '" + url + "'\n"); + return SpecialPowers.spawn( + tab.linkedBrowser, + [url, message], + (urlChild, messageChild) => { + let worker = content._workers[urlChild]; + worker.postMessage(messageChild); + return new Promise(function (resolve) { + worker.onmessage = function (event) { + worker.onmessage = null; + resolve(event.data); + }; + }); + } + ); +} + +/** + * Disable the cache in the given `tab`. + */ +function disableCacheInTab(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.docShell.defaultLoadFlags = + Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING; + }); +} diff --git a/dom/workers/test/importForeignScripts_worker.js b/dom/workers/test/importForeignScripts_worker.js new file mode 100644 index 0000000000..4e3d65483f --- /dev/null +++ b/dom/workers/test/importForeignScripts_worker.js @@ -0,0 +1,55 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var target = self; +var response; + +function runTests() { + response = "good"; + try { + importScripts("http://example.org/tests/dom/workers/test/foreign.js"); + } catch (e) { + dump("Got error " + e + " when calling importScripts"); + } + if (response === "good") { + try { + importScripts("redirect_to_foreign.sjs"); + } catch (e) { + dump("Got error " + e + " when calling importScripts"); + } + } + target.postMessage(response); + + // Now, test a nested worker. + if (location.search !== "?nested") { + var worker = new Worker("importForeignScripts_worker.js?nested"); + + worker.onmessage = function (e) { + target.postMessage(e.data); + target.postMessage("finish"); + }; + + worker.onerror = function () { + target.postMessage("nested worker error"); + }; + + worker.postMessage("start"); + } +} + +onmessage = function (e) { + if (e.data === "start") { + runTests(); + } +}; + +onconnect = function (e) { + target = e.ports[0]; + e.ports[0].onmessage = function (msg) { + if (msg.data === "start") { + runTests(); + } + }; +}; diff --git a/dom/workers/test/importScripts_3rdParty_worker.js b/dom/workers/test/importScripts_3rdParty_worker.js new file mode 100644 index 0000000000..326d48f77a --- /dev/null +++ b/dom/workers/test/importScripts_3rdParty_worker.js @@ -0,0 +1,88 @@ +const workerURL = + "http://mochi.test:8888/tests/dom/workers/test/importScripts_3rdParty_worker.js"; + +onmessage = function (a) { + if (a.data.nested) { + var worker = new Worker(workerURL); + worker.onmessage = function (event) { + postMessage(event.data); + }; + + worker.onerror = function (event) { + event.preventDefault(); + postMessage({ + error: event instanceof ErrorEvent && event.filename == workerURL, + }); + }; + + a.data.nested = false; + worker.postMessage(a.data); + return; + } + + // This first URL will use the same origin of this script. + var sameOriginURL = new URL(a.data.url); + var fileName1 = 42; + + // This is cross-origin URL. + var crossOriginURL = new URL(a.data.url); + crossOriginURL.host = "example.com"; + crossOriginURL.port = 80; + var fileName2 = 42; + + if (a.data.test == "none") { + importScripts(crossOriginURL.href); + return; + } + + try { + importScripts(sameOriginURL.href); + } catch (e) { + if (!(e instanceof SyntaxError)) { + postMessage({ result: false }); + return; + } + + fileName1 = e.fileName; + } + + if (fileName1 != sameOriginURL.href || !fileName1) { + postMessage({ result: false }); + return; + } + + if (a.data.test == "try") { + var exception; + try { + importScripts(crossOriginURL.href); + } catch (e) { + fileName2 = e.filename; + exception = e; + } + + postMessage({ + result: + fileName2 == workerURL && + exception.name == "NetworkError" && + exception.code == DOMException.NETWORK_ERR, + }); + return; + } + + if (a.data.test == "eventListener") { + addEventListener("error", function (event) { + event.preventDefault(); + postMessage({ + result: event instanceof ErrorEvent && event.filename == workerURL, + }); + }); + } + + if (a.data.test == "onerror") { + onerror = function (...args) { + postMessage({ result: args[1] == workerURL }); + }; + } + + importScripts(crossOriginURL.href); +}; diff --git a/dom/workers/test/importScripts_mixedcontent.html b/dom/workers/test/importScripts_mixedcontent.html new file mode 100644 index 0000000000..2955fdc46e --- /dev/null +++ b/dom/workers/test/importScripts_mixedcontent.html @@ -0,0 +1,46 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<script> + function ok(cond, msg) { + window.parent.postMessage({status: "ok", data: cond, msg}, "*"); + } + function finish() { + window.parent.postMessage({status: "done"}, "*"); + } + + function testSharedWorker() { + var sw = new SharedWorker("importForeignScripts_worker.js"); + sw.port.onmessage = function(e) { + if (e.data == "finish") { + finish(); + return; + } + ok(e.data === "good", "mixed content for shared workers is correctly blocked"); + }; + + sw.onerror = function() { + ok(false, "Error on shared worker "); + }; + + sw.port.postMessage("start"); + } + + var worker = new Worker("importForeignScripts_worker.js"); + + worker.onmessage = function(e) { + if (e.data == "finish") { + testSharedWorker(); + return; + } + ok(e.data === "good", "mixed content is correctly blocked"); + } + + worker.onerror = function() { + ok(false, "Error on worker"); + } + + worker.postMessage("start"); +</script> diff --git a/dom/workers/test/importScripts_worker.js b/dom/workers/test/importScripts_worker.js new file mode 100644 index 0000000000..ca46c949b9 --- /dev/null +++ b/dom/workers/test/importScripts_worker.js @@ -0,0 +1,62 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Try no args. This shouldn't do anything. +importScripts(); + +// This caused security exceptions in the past, make sure it doesn't! +var constructor = {}.constructor; + +importScripts("importScripts_worker_imported1.js"); + +// Try to call a function defined in the imported script. +importedScriptFunction(); + +function tryBadScripts() { + var badScripts = [ + // Has a syntax error + "importScripts_worker_imported3.js", + // Throws an exception + "importScripts_worker_imported4.js", + // Shouldn't exist! + "http://example.com/non-existing/importScripts_worker_foo.js", + // Not a valid url + "http://notadomain::notafile aword", + ]; + + for (var i = 0; i < badScripts.length; i++) { + var caughtException = false; + var url = badScripts[i]; + try { + importScripts(url); + } catch (e) { + caughtException = true; + } + if (!caughtException) { + throw "Bad script didn't throw exception: " + url; + } + } +} + +const url = "data:text/javascript,const startResponse = 'started';"; +importScripts(url); + +onmessage = function (event) { + switch (event.data) { + case "start": + importScripts("importScripts_worker_imported2.js"); + importedScriptFunction2(); + tryBadScripts(); + postMessage(startResponse); + break; + case "stop": + tryBadScripts(); + postMessage("stopped"); + break; + default: + throw new Error("Bad message: " + event.data); + } +}; + +tryBadScripts(); diff --git a/dom/workers/test/importScripts_worker_imported1.js b/dom/workers/test/importScripts_worker_imported1.js new file mode 100644 index 0000000000..2a1d28c44d --- /dev/null +++ b/dom/workers/test/importScripts_worker_imported1.js @@ -0,0 +1,9 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// This caused security exceptions in the past, make sure it doesn't! +var myConstructor = {}.constructor; + +// Try to call a function defined in the imported script. +function importedScriptFunction() {} diff --git a/dom/workers/test/importScripts_worker_imported2.js b/dom/workers/test/importScripts_worker_imported2.js new file mode 100644 index 0000000000..3f275e237b --- /dev/null +++ b/dom/workers/test/importScripts_worker_imported2.js @@ -0,0 +1,9 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// This caused security exceptions in the past, make sure it doesn't! +var myConstructor2 = {}.constructor; + +// Try to call a function defined in the imported script. +function importedScriptFunction2() {} diff --git a/dom/workers/test/importScripts_worker_imported3.js b/dom/workers/test/importScripts_worker_imported3.js new file mode 100644 index 0000000000..c54be3e5f7 --- /dev/null +++ b/dom/workers/test/importScripts_worker_imported3.js @@ -0,0 +1,6 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Deliberate syntax error, should generate a worker exception! +for (var index = 0; index < 100) {} diff --git a/dom/workers/test/importScripts_worker_imported4.js b/dom/workers/test/importScripts_worker_imported4.js new file mode 100644 index 0000000000..82f8708c59 --- /dev/null +++ b/dom/workers/test/importScripts_worker_imported4.js @@ -0,0 +1,6 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Deliberate throw, should generate a worker exception! +throw new Error("Bah!"); diff --git a/dom/workers/test/instanceof_worker.js b/dom/workers/test/instanceof_worker.js new file mode 100644 index 0000000000..a7f3ab418d --- /dev/null +++ b/dom/workers/test/instanceof_worker.js @@ -0,0 +1,16 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + postMessage({ + event: "XMLHttpRequest", + status: new XMLHttpRequest() instanceof XMLHttpRequest, + last: false, + }); + postMessage({ + event: "XMLHttpRequestUpload", + status: new XMLHttpRequest().upload instanceof XMLHttpRequestUpload, + last: true, + }); +}; diff --git a/dom/workers/test/invalid.js b/dom/workers/test/invalid.js new file mode 100644 index 0000000000..8912b7ee06 --- /dev/null +++ b/dom/workers/test/invalid.js @@ -0,0 +1 @@ +invalid> diff --git a/dom/workers/test/json_worker.js b/dom/workers/test/json_worker.js new file mode 100644 index 0000000000..229ac954c9 --- /dev/null +++ b/dom/workers/test/json_worker.js @@ -0,0 +1,354 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var cyclicalObject = {}; +cyclicalObject.foo = cyclicalObject; + +var cyclicalArray = []; +cyclicalArray.push(cyclicalArray); + +function makeNested(obj, count) { + var innermostobj; + for (var i = 0; i < count; i++) { + obj.foo = { bar: 5 }; + innermostobj = obj.foo; + obj = innermostobj; + } + return innermostobj; +} + +var nestedObject = {}; +makeNested(nestedObject, 100); + +var cyclicalObject = {}; +var innermost = makeNested(cyclicalObject, 1000); +innermost.baz = cyclicalObject; + +var objectWithSaneGetter = {}; +objectWithSaneGetter.__defineGetter__("foo", function () { + return 5; +}); + +// We don't walk prototype chains for cloning so this won't actually do much... +function objectWithSaneGetter2() {} +objectWithSaneGetter2.prototype = { + get foo() { + return 5; + }, +}; + +const throwingGetterThrownString = "bad"; + +var objectWithThrowingGetter = {}; +objectWithThrowingGetter.__defineGetter__("foo", function () { + throw throwingGetterThrownString; +}); + +var typedArrayWithValues = new Int8Array(5); +for (let index in typedArrayWithValues) { + typedArrayWithValues[index] = index; +} + +var typedArrayWithFunBuffer = new Int8Array(4); +for (let index in typedArrayWithFunBuffer) { + typedArrayWithFunBuffer[index] = 255; +} + +var typedArrayWithFunBuffer2 = new Int32Array(typedArrayWithFunBuffer.buffer); + +var xhr = new XMLHttpRequest(); + +var messages = [ + { + type: "object", + value: {}, + jsonValue: "{}", + }, + { + type: "object", + value: { foo: "bar" }, + jsonValue: '{"foo":"bar"}', + }, + { + type: "object", + value: { foo: "bar", foo2: { bee: "bop" } }, + jsonValue: '{"foo":"bar","foo2":{"bee":"bop"}}', + }, + { + type: "object", + value: { foo: "bar", foo2: { bee: "bop" }, foo3: "baz" }, + jsonValue: '{"foo":"bar","foo2":{"bee":"bop"},"foo3":"baz"}', + }, + { + type: "object", + value: { foo: "bar", foo2: [1, 2, 3] }, + jsonValue: '{"foo":"bar","foo2":[1,2,3]}', + }, + { + type: "object", + value: cyclicalObject, + }, + { + type: "object", + value: [null, 2, false, cyclicalObject], + }, + { + type: "object", + value: cyclicalArray, + }, + { + type: "object", + value: { foo: 1, bar: cyclicalArray }, + }, + { + type: "object", + value: nestedObject, + jsonValue: JSON.stringify(nestedObject), + }, + { + type: "object", + value: cyclicalObject, + }, + { + type: "object", + value: objectWithSaneGetter, + jsonValue: '{"foo":5}', + }, + { + type: "object", + value: new objectWithSaneGetter2(), + jsonValue: "{}", + }, + { + type: "object", + value: objectWithThrowingGetter, + exception: true, + }, + { + type: "object", + array: true, + value: [9, 8, 7], + jsonValue: "[9,8,7]", + }, + { + type: "object", + array: true, + value: [9, false, 10.5, { foo: "bar" }], + jsonValue: '[9,false,10.5,{"foo":"bar"}]', + }, + { + type: "object", + shouldEqual: true, + value: null, + }, + { + type: "undefined", + shouldEqual: true, + value: undefined, + }, + { + type: "string", + shouldEqual: true, + value: "Hello", + }, + { + type: "string", + shouldEqual: true, + value: JSON.stringify({ foo: "bar" }), + compareValue: '{"foo":"bar"}', + }, + { + type: "number", + shouldEqual: true, + value: 1, + }, + { + type: "number", + shouldEqual: true, + value: 0, + }, + { + type: "number", + shouldEqual: true, + value: -1, + }, + { + type: "number", + shouldEqual: true, + value: 12345678901234567000, + }, + { + type: "number", + shouldEqual: true, + value: -12345678901234567000, + }, + { + type: "number", + shouldEqual: true, + value: 0.25, + }, + { + type: "number", + shouldEqual: true, + value: -0.25, + }, + { + type: "boolean", + shouldEqual: true, + value: true, + }, + { + type: "boolean", + shouldEqual: true, + value: false, + }, + { + type: "object", + value(foo) { + return "Bad!"; + }, + exception: true, + }, + { + type: "number", + isNaN: true, + value: NaN, + }, + { + type: "number", + isInfinity: true, + value: Infinity, + }, + { + type: "number", + isNegativeInfinity: true, + value: -Infinity, + }, + { + type: "object", + value: new Int32Array(10), + jsonValue: '{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0}', + }, + { + type: "object", + value: new Float32Array(5), + jsonValue: '{"0":0,"1":0,"2":0,"3":0,"4":0}', + }, + { + type: "object", + value: typedArrayWithValues, + jsonValue: '{"0":0,"1":1,"2":2,"3":3,"4":4}', + }, + { + type: "number", + value: typedArrayWithValues[2], + compareValue: 2, + shouldEqual: true, + }, + { + type: "object", + value: typedArrayWithValues.buffer, + jsonValue: "{}", + }, + { + type: "object", + value: typedArrayWithFunBuffer2, + jsonValue: '{"0":-1}', + }, + { + type: "object", + value: { foo: typedArrayWithFunBuffer2 }, + jsonValue: '{"foo":{"0":-1}}', + }, + { + type: "object", + value: [typedArrayWithFunBuffer2], + jsonValue: '[{"0":-1}]', + }, + { + type: "object", + value: { + foo(a) { + alert(b); + }, + }, + exception: true, + }, + { + type: "object", + value: xhr, + exception: true, + }, + { + type: "number", + value: xhr.readyState, + shouldEqual: true, + }, + { + type: "object", + value: { xhr }, + exception: true, + }, + { + type: "object", + value: self, + exception: true, + }, + { + type: "object", + value: { p: ArrayBuffer.prototype }, + exception: true, + }, + { + type: "string", + shouldEqual: true, + value: "testFinished", + }, +]; + +for (let index = 0; index < messages.length; index++) { + var message = messages[index]; + if (message.hasOwnProperty("compareValue")) { + continue; + } + if ( + message.hasOwnProperty("shouldEqual") || + message.hasOwnProperty("shouldCompare") + ) { + message.compareValue = message.value; + } +} + +onmessage = function (event) { + for (let index = 0; index < messages.length; index++) { + var exception = undefined; + + try { + postMessage(messages[index].value); + } catch (e) { + if (e instanceof DOMException) { + if (e.code != DOMException.DATA_CLONE_ERR) { + throw "DOMException with the wrong code: " + e.code; + } + } else if (e != throwingGetterThrownString) { + throw "Exception of the wrong type: " + e; + } + exception = e; + } + + if ( + (exception !== undefined && !messages[index].exception) || + (exception === undefined && messages[index].exception) + ) { + throw ( + "Exception inconsistency [index = " + + index + + ", " + + messages[index].toSource() + + "]: " + + exception + ); + } + } +}; diff --git a/dom/workers/test/loadEncoding_worker.js b/dom/workers/test/loadEncoding_worker.js new file mode 100644 index 0000000000..5e40478445 --- /dev/null +++ b/dom/workers/test/loadEncoding_worker.js @@ -0,0 +1,7 @@ +/* + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ +// Bug 484305 - Load workers as UTF-8. +postMessage({ encoding: "KOI8-R", text: "ðÒÉ×ÅÔ" }); +postMessage({ encoding: "UTF-8", text: "Привет" }); diff --git a/dom/workers/test/location_worker.js b/dom/workers/test/location_worker.js new file mode 100644 index 0000000000..2f16364e1f --- /dev/null +++ b/dom/workers/test/location_worker.js @@ -0,0 +1,12 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +for (var string in self.location) { + var value = + typeof self.location[string] === "function" + ? self.location[string]() + : self.location[string]; + postMessage({ string, value }); +} +postMessage({ string: "testfinished" }); diff --git a/dom/workers/test/longThread_worker.js b/dom/workers/test/longThread_worker.js new file mode 100644 index 0000000000..4096354aca --- /dev/null +++ b/dom/workers/test/longThread_worker.js @@ -0,0 +1,14 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + switch (event.data) { + case "start": + for (var i = 0; i < 10000000; i++) {} + postMessage("done"); + break; + default: + throw "Bad message: " + event.data; + } +}; diff --git a/dom/workers/test/marionette/manifest.toml b/dom/workers/test/marionette/manifest.toml new file mode 100644 index 0000000000..b801ae5719 --- /dev/null +++ b/dom/workers/test/marionette/manifest.toml @@ -0,0 +1,5 @@ +[DEFAULT] + +["test_service_workers_at_startup.py"] + +["test_service_workers_disabled.py"] diff --git a/dom/workers/test/marionette/service_worker_utils.py b/dom/workers/test/marionette/service_worker_utils.py new file mode 100644 index 0000000000..b29789b6f0 --- /dev/null +++ b/dom/workers/test/marionette/service_worker_utils.py @@ -0,0 +1,63 @@ +# 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/. + +import os + +from marionette_driver import Wait +from marionette_harness import MarionetteTestCase + + +class MarionetteServiceWorkerTestCase(MarionetteTestCase): + def install_service_worker(self, path): + install_url = self.marionette.absolute_url(path) + self.marionette.navigate(install_url) + Wait(self.marionette).until( + lambda _: self.is_service_worker_registered, + message="Service worker not successfully installed", + ) + + # Wait for the registered service worker to be stored in the Firefox + # profile before restarting the instance to prevent intermittent + # failures (Bug 1665184). + Wait(self.marionette, timeout=10).until( + lambda _: self.profile_serviceworker_txt_exists, + message="Service worker not stored in profile", + ) + + # self.marionette.restart(in_app=True) will restore service workers if + # we don't navigate away before restarting. + self.marionette.navigate("about:blank") + + # Using @property helps avoid the case where missing parens at the call site + # yields an unvarying 'true' value. + @property + def profile_serviceworker_txt_exists(self): + return "serviceworker.txt" in os.listdir(self.marionette.profile_path) + + @property + def is_service_worker_registered(self): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService( + Ci.nsIServiceWorkerManager + ); + let ssm = Services.scriptSecurityManager; + + let principal = ssm.createContentPrincipalFromOrigin(arguments[0]); + + let serviceWorkers = swm.getAllRegistrations(); + for (let i = 0; i < serviceWorkers.length; i++) { + let sw = serviceWorkers.queryElementAt( + i, + Ci.nsIServiceWorkerRegistrationInfo + ); + if (sw.principal.origin == principal.origin) { + return true; + } + } + return false; + """, + script_args=(self.marionette.absolute_url(""),), + ) diff --git a/dom/workers/test/marionette/test_service_workers_at_startup.py b/dom/workers/test/marionette/test_service_workers_at_startup.py new file mode 100644 index 0000000000..14ff32f87f --- /dev/null +++ b/dom/workers/test/marionette/test_service_workers_at_startup.py @@ -0,0 +1,31 @@ +# 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/. + +import os +import sys + +# Add this directory to the import path. +sys.path.append(os.path.dirname(__file__)) + +from marionette_driver import Wait +from service_worker_utils import MarionetteServiceWorkerTestCase + + +class ServiceWorkerAtStartupTestCase(MarionetteServiceWorkerTestCase): + def setUp(self): + super(ServiceWorkerAtStartupTestCase, self).setUp() + self.install_service_worker("serviceworker/install_serviceworker.html") + + def tearDown(self): + self.marionette.restart(in_app=False, clean=True) + super(ServiceWorkerAtStartupTestCase, self).tearDown() + + def test_registered_service_worker_after_restart(self): + self.marionette.restart() + + Wait(self.marionette).until( + lambda _: self.is_service_worker_registered, + message="Service worker not registered after restart", + ) + self.assertTrue(self.is_service_worker_registered) diff --git a/dom/workers/test/marionette/test_service_workers_disabled.py b/dom/workers/test/marionette/test_service_workers_disabled.py new file mode 100644 index 0000000000..deed164242 --- /dev/null +++ b/dom/workers/test/marionette/test_service_workers_disabled.py @@ -0,0 +1,37 @@ +# 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/. + +import os +import sys + +# Add this directory to the import path. +sys.path.append(os.path.dirname(__file__)) + +from service_worker_utils import MarionetteServiceWorkerTestCase + + +class ServiceWorkersDisabledTestCase(MarionetteServiceWorkerTestCase): + def setUp(self): + super(ServiceWorkersDisabledTestCase, self).setUp() + self.install_service_worker("serviceworker/install_serviceworker.html") + + def tearDown(self): + self.marionette.restart(in_app=False, clean=True) + super(ServiceWorkersDisabledTestCase, self).tearDown() + + def test_service_workers_disabled_at_startup(self): + # self.marionette.set_pref sets preferences after startup. Using it + # here causes intermittent failures. + self.marionette.instance.profile.set_preferences( + { + "dom.serviceWorkers.enabled": False, + } + ) + + self.marionette.restart() + + self.assertFalse( + self.is_service_worker_registered, + "Service worker registration should have been purged", + ) diff --git a/dom/workers/test/mochitest.toml b/dom/workers/test/mochitest.toml new file mode 100644 index 0000000000..5ae8094b58 --- /dev/null +++ b/dom/workers/test/mochitest.toml @@ -0,0 +1,365 @@ +[DEFAULT] +support-files = [ + "WorkerTest_badworker.js", + "atob_worker.js", + "blank.html", + "bug978260_worker.js", + "bug1014466_data1.txt", + "bug1014466_data2.txt", + "bug1014466_worker.js", + "bug1020226_worker.js", + "bug1020226_frame.html", + "bug998474_worker.js", + "bug1063538_worker.js", + "bug1063538.sjs", + "clearTimeouts_worker.js", + "clearTimeoutsImplicit_worker.js", + "content_worker.js", + "console_worker.js", + "consoleReplaceable_worker.js", + "csp_worker.js", + "csp_worker.js^headers^", + "dynamicImport_nested.mjs", + "dynamicImport_postMessage.mjs", + "dynamicImport_worker.js", + "404_server.sjs", + "errorPropagation_iframe.html", + "errorPropagation_worker.js", + "errorwarning_worker.js", + "eventDispatch_worker.js", + "fibonacci_worker.js", + "file_bug1010784_worker.js", + "foreign.js", + "importForeignScripts_worker.js", + "importScripts_mixedcontent.html", + "importScripts_worker.js", + "importScripts_worker_imported1.js", + "importScripts_worker_imported2.js", + "importScripts_worker_imported3.js", + "importScripts_worker_imported4.js", + "instanceof_worker.js", + "json_worker.js", + "loadEncoding_worker.js", + "location_worker.js", + "longThread_worker.js", + "multi_sharedWorker_frame.html", + "multi_sharedWorker_sharedWorker.js", + "navigator_languages_worker.js", + "navigator_worker.js", + "newError_worker.js", + "notification_worker.js", + "notification_worker_child-child.js", + "notification_worker_child-parent.js", + "notification_permission_worker.js", + "onLine_worker.js", + "onLine_worker_child.js", + "onLine_worker_head.js", + "promise_worker.js", + "recursion_worker.js", + "recursiveOnerror_worker.js", + "redirect_to_foreign.sjs", + "rvals_worker.js", + "sharedWorker_sharedWorker.js", + "simpleThread_worker.js", + "suspend_window.html", + "suspend_worker.js", + "terminate_worker.js", + "test_csp.html^headers^", + "test_csp.js", + "referrer_worker.html", + "sourcemap_header_iframe.html", + "sourcemap_header_worker.js", + "sourcemap_header_worker.js^headers^", + "threadErrors_worker1.js", + "threadErrors_worker2.js", + "threadErrors_worker3.js", + "threadErrors_worker4.js", + "threadTimeouts_worker.js", + "throwingOnerror_worker.js", + "timeoutTracing_worker.js", + "transferable_worker.js", + "test_worker_interfaces.js", + "worker_driver.js", + "worker_wrapper.js", + "bug1060621_worker.js", + "bug1062920_worker.js", + "bug1104064_worker.js", + "worker_consoleAndBlobs.js", + "bug1132395_sharedWorker.js", + "bug1132924_worker.js", + "empty.html", + "referrer.sjs", + "referrer_test_server.sjs", + "sharedWorker_ports.js", + "sharedWorker_lifetime.js", + "worker_referrer.js", + "importScripts_3rdParty_worker.js", + "invalid.js", + "worker_bug1278777.js", + "worker_setTimeoutWith0.js", + "worker_bug1301094.js", + "script_createFile.js", + "worker_suspended.js", + "window_suspended.html", + "suspend_blank.html", + "multi_sharedWorker_manager.js", + "multi_sharedWorker_frame_nobfcache.html", + "multi_sharedWorker_frame_nobfcache.html^headers^", + "multi_sharedWorker_frame_bfcache.html", + "navigate.html", + "!/dom/notification/test/mochitest/MockServices.js", + "!/dom/notification/test/mochitest/NotificationTest.js", + "!/dom/xhr/tests/relativeLoad_import.js", + "!/dom/xhr/tests/relativeLoad_worker.js", + "!/dom/xhr/tests/relativeLoad_worker2.js", + "!/dom/xhr/tests/subdir/relativeLoad_sub_worker.js", + "!/dom/xhr/tests/subdir/relativeLoad_sub_worker2.js", + "!/dom/xhr/tests/subdir/relativeLoad_sub_import.js", + "!/dom/events/test/event_leak_utils.js", +] + +["test_404.html"] + +["test_atob.html"] + +["test_blobConstructor.html"] + +["test_blobWorkers.html"] + +["test_bug949946.html"] + +["test_bug978260.html"] + +["test_bug998474.html"] + +["test_bug1002702.html"] + +["test_bug1010784.html"] + +["test_bug1014466.html"] + +["test_bug1020226.html"] + +["test_bug1036484.html"] + +["test_bug1060621.html"] + +["test_bug1062920.html"] + +["test_bug1063538.html"] +skip-if = [ + "http3", + "http2", +] + +["test_bug1104064.html"] + +["test_bug1132395.html"] +skip-if = ["true"] # bug 1176225 + +["test_bug1132924.html"] + +["test_bug1278777.html"] + +["test_bug1301094.html"] + +["test_bug1317725.html"] +support-files = ["test_bug1317725.js"] + +["test_bug1824498.html"] +support-files = ["worker_bug1824498.mjs"] + +["test_chromeWorker.html"] + +["test_clearTimeouts.html"] + +["test_clearTimeoutsImplicit.html"] + +["test_console.html"] + +["test_consoleAndBlobs.html"] + +["test_consoleReplaceable.html"] + +["test_contentWorker.html"] + +["test_csp.html"] + +["test_dataURLWorker.html"] + +["test_dynamicImport.html"] + +["test_dynamicImport_and_terminate.html"] +support-files = ["worker_dynamicImport.mjs"] + +["test_dynamicImport_early_termination.html"] + +["test_errorPropagation.html"] +skip-if = [ + "http3", + "http2", +] + +["test_errorwarning.html"] + +["test_eventDispatch.html"] + +["test_fibonacci.html"] + +["test_fileReaderSync_when_closing.html"] + +["test_importScripts.html"] + +["test_importScripts_1.html"] + +["test_importScripts_2.html"] + +["test_importScripts_3rdparty.html"] +skip-if = [ + "http3", + "http2", +] + +["test_importScripts_mixedcontent.html"] +tags = "mcb" + +["test_instanceof.html"] + +["test_json.html"] + +["test_loadEncoding.html"] + +["test_loadError.html"] + +["test_location.html"] +skip-if = [ + "http3", + "http2", +] + +["test_longThread.html"] + +["test_multi_sharedWorker.html"] +skip-if = [ + "http3", + "http2", +] + +["test_multi_sharedWorker_lifetimes_bfcache.html"] + +["test_multi_sharedWorker_lifetimes_nobfcache.html"] + +["test_navigator.html"] +support-files = [ + "test_navigator.js", + "test_navigator_iframe.html", + "test_navigator_iframe.js", +] +skip-if = [ + "http3", + "http2", +] + +["test_navigator_languages.html"] + +["test_navigator_secureContext.html"] +scheme = "https" +support-files = [ + "test_navigator.js", + "test_navigator_iframe.html", + "test_navigator_iframe.js", +] + +["test_navigator_workers_hardwareConcurrency.html"] + +["test_newError.html"] + +["test_notification.html"] + +["test_notification_child.html"] + +["test_notification_permission.html"] + +["test_onLine.html"] + +["test_promise.html"] + +["test_promise_resolved_with_string.html"] + +["test_recursion.html"] + +["test_recursiveOnerror.html"] +skip-if = [ + "http3", + "http2", +] + +["test_referrer.html"] + +["test_referrer_header_worker.html"] +skip-if = [ + "http3", + "http2", +] + +["test_resolveWorker-assignment.html"] + +["test_resolveWorker.html"] + +["test_rvals.html"] + +["test_setTimeoutWith0.html"] + +["test_sharedWorker.html"] + +["test_sharedWorker_lifetime.html"] + +["test_sharedWorker_ports.html"] + +["test_sharedWorker_thirdparty.html"] +support-files = [ + "sharedWorker_thirdparty_frame.html", + "sharedWorker_thirdparty_window.html", +] +skip-if = [ + "http3", + "http2", +] + +["test_sharedworker_event_listener_leaks.html"] +skip-if = [ + "bits == 64 && os == 'linux' && asan", # Disabled on Linux64 opt asan, bug 1493563 + "os == 'win' && debug && xorigin", # high frequency intermittent +] + +["test_simpleThread.html"] +skip-if = [ + "http3", + "http2", +] + +["test_subworkers_suspended.html"] +scheme = "https" + +["test_suspend.html"] + +["test_terminate.html"] + +["test_threadErrors.html"] + +["test_threadTimeouts.html"] + +["test_throwingOnerror.html"] + +["test_timeoutTracing.html"] + +["test_transferable.html"] + +["test_worker_interfaces.html"] +skip-if = [ + "http3", + "http2", +] + +["test_worker_interfaces_secureContext.html"] +scheme = "https" diff --git a/dom/workers/test/multi_sharedWorker_frame.html b/dom/workers/test/multi_sharedWorker_frame.html new file mode 100644 index 0000000000..94866b918d --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_frame.html @@ -0,0 +1,58 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + </head> + <body> + <script type="text/javascript"> + "use strict"; + + function postMessageToParentOrOpener(message) { + if (parent != window) { + parent.postMessage(message, "*"); + } + } + + function debug(message) { + if (typeof(message) != "string") { + throw new Error("debug() only accepts strings!"); + } + postMessageToParentOrOpener(message); + } + + let worker; + + window.addEventListener("message", function(event) { + if (!worker) { + worker = new SharedWorker("multi_sharedWorker_sharedWorker.js", + "FrameWorker"); + worker.onerror = function(error) { + debug("Worker error: " + error.message); + error.preventDefault(); + + let data = { + type: "error", + message: error.message, + filename: error.filename, + lineno: error.lineno, + isErrorEvent: error instanceof ErrorEvent + }; + postMessageToParentOrOpener(data); + }; + + worker.port.onmessage = function(msg) { + debug("Worker message: " + JSON.stringify(msg.data)); + postMessageToParentOrOpener(msg.data); + }; + } + + debug("Posting message: " + JSON.stringify(event.data)); + worker.port.postMessage(event.data); + }); + </script> + </body> +</html> diff --git a/dom/workers/test/multi_sharedWorker_frame_bfcache.html b/dom/workers/test/multi_sharedWorker_frame_bfcache.html new file mode 100644 index 0000000000..dea14a8f78 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_frame_bfcache.html @@ -0,0 +1,13 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + </head> + <body> + <script type="text/javascript" src=multi_sharedWorker_manager.js></script> + </body> +</html> diff --git a/dom/workers/test/multi_sharedWorker_frame_nobfcache.html b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html new file mode 100644 index 0000000000..dea14a8f78 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html @@ -0,0 +1,13 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + </head> + <body> + <script type="text/javascript" src=multi_sharedWorker_manager.js></script> + </body> +</html> diff --git a/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^ new file mode 100644 index 0000000000..2567dc2fe5 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_frame_nobfcache.html^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
\ No newline at end of file diff --git a/dom/workers/test/multi_sharedWorker_manager.js b/dom/workers/test/multi_sharedWorker_manager.js new file mode 100644 index 0000000000..c0a9455ef1 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_manager.js @@ -0,0 +1,58 @@ +var query = window.location.search; +var bc = new BroadcastChannel("bugSharedWorkerLiftetime" + query); +bc.onmessage = msgEvent => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "postToWorker") { + postToWorker(msg.workerMessage); + } else if (command == "navigate") { + window.location = msg.location; + } else if (command == "finish") { + bc.postMessage({ command: "finished" }); + bc.close(); + window.close(); + } +}; + +window.onload = () => { + bc.postMessage({ command: "loaded" }); +}; + +function debug(message) { + if (typeof message != "string") { + throw new Error("debug() only accepts strings!"); + } + bc.postMessage({ command: "debug", message }); +} + +let worker; + +function postToWorker(msg) { + if (!worker) { + worker = new SharedWorker( + "multi_sharedWorker_sharedWorker.js", + "FrameWorker" + ); + worker.onerror = function (error) { + debug("Worker error: " + error.message); + error.preventDefault(); + + let data = { + type: "error", + message: error.message, + filename: error.filename, + lineno: error.lineno, + isErrorEvent: error instanceof ErrorEvent, + }; + bc.postMessage({ command: "fromWorker", workerMessage: data }); + }; + + worker.port.onmessage = function (message) { + debug("Worker message: " + JSON.stringify(message.data)); + bc.postMessage({ command: "fromWorker", workerMessage: message.data }); + }; + } + + debug("Posting message: " + JSON.stringify(msg)); + worker.port.postMessage(msg); +} diff --git a/dom/workers/test/multi_sharedWorker_sharedWorker.js b/dom/workers/test/multi_sharedWorker_sharedWorker.js new file mode 100644 index 0000000000..3c3e4c5780 --- /dev/null +++ b/dom/workers/test/multi_sharedWorker_sharedWorker.js @@ -0,0 +1,73 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +if (self.name != "FrameWorker") { + throw new Error("Bad worker name: " + self.name); +} + +var registeredPorts = []; +var errorCount = 0; +var storedData; + +self.onconnect = function (event) { + var port = event.ports[0]; + + if (registeredPorts.length) { + let data = { + type: "connect", + }; + + registeredPorts.forEach(function (registeredPort) { + registeredPort.postMessage(data); + }); + } + + port.onmessage = function (msg) { + switch (msg.data.command) { + case "start": + break; + + case "error": + throw new Error("Expected"); + + case "store": + storedData = msg.data.data; + break; + + case "retrieve": + var data = { + type: "result", + data: storedData, + }; + port.postMessage(data); + break; + + default: + throw new Error("Unknown command '" + error.data.command + "'"); + } + }; + + registeredPorts.push(port); +}; + +self.onerror = function (message, filename, lineno) { + if (!errorCount++) { + var data = { + type: "worker-error", + message, + filename, + lineno, + }; + + registeredPorts.forEach(function (registeredPort) { + registeredPort.postMessage(data); + }); + + // Prevent the error from propagating the first time only. + return true; + } + return undefined; +}; diff --git a/dom/workers/test/navigate.html b/dom/workers/test/navigate.html new file mode 100644 index 0000000000..e38ab4540a --- /dev/null +++ b/dom/workers/test/navigate.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<script> + var bc = new BroadcastChannel("navigate"); + bc.onmessage = (event) => { + if (event.data.command == "navigate") { + window.location = event.data.location; + bc.close(); + } + } + window.onload = () => { + bc.postMessage({command: "loaded"}); + } +</script> diff --git a/dom/workers/test/navigator_languages_worker.js b/dom/workers/test/navigator_languages_worker.js new file mode 100644 index 0000000000..22ece09ef2 --- /dev/null +++ b/dom/workers/test/navigator_languages_worker.js @@ -0,0 +1,11 @@ +var active = true; +onmessage = function (e) { + if (e.data == "finish") { + active = false; + return; + } + + if (active) { + postMessage(navigator.languages); + } +}; diff --git a/dom/workers/test/navigator_worker.js b/dom/workers/test/navigator_worker.js new file mode 100644 index 0000000000..a9cd4f29d5 --- /dev/null +++ b/dom/workers/test/navigator_worker.js @@ -0,0 +1,87 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// IMPORTANT: Do not change the list below without review from a DOM peer! +var supportedProps = [ + "appCodeName", + "appName", + "appVersion", + "globalPrivacyControl", + "platform", + "product", + "userAgent", + "onLine", + "language", + "languages", + { name: "locks", isSecureContext: true }, + "mediaCapabilities", + "hardwareConcurrency", + { name: "storage", isSecureContext: true }, + "connection", +]; + +self.onmessage = function (event) { + if (!event || !event.data) { + return; + } + + startTest(event.data); +}; + +function startTest(channelData) { + // Prepare the interface map showing if a propery should exist in this build. + // For example, if interfaceMap[foo] = true means navigator.foo should exist. + var interfaceMap = {}; + + for (var prop of supportedProps) { + if (typeof prop === "string") { + interfaceMap[prop] = true; + continue; + } + + if ( + prop.isNightly === !channelData.isNightly || + prop.release === !channelData.isRelease || + prop.isSecureContext === !isSecureContext || + prop.isAndroid === !channelData.isAndroid + ) { + interfaceMap[prop.name] = false; + continue; + } + + interfaceMap[prop.name] = true; + } + + for (var prop in navigator) { + // Make sure the list is current! + if (!interfaceMap[prop]) { + throw "Navigator has the '" + prop + "' property that isn't in the list!"; + } + } + + var obj; + + for (var prop in interfaceMap) { + // Skip the property that is not supposed to exist in this build. + if (!interfaceMap[prop]) { + continue; + } + + if (typeof navigator[prop] == "undefined") { + throw "Navigator has no '" + prop + "' property!"; + } + + obj = { name: prop }; + obj.value = navigator[prop]; + + postMessage(JSON.stringify(obj)); + } + + obj = { + name: "testFinished", + }; + + postMessage(JSON.stringify(obj)); +} diff --git a/dom/workers/test/newError_worker.js b/dom/workers/test/newError_worker.js new file mode 100644 index 0000000000..46e6226f73 --- /dev/null +++ b/dom/workers/test/newError_worker.js @@ -0,0 +1,5 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +throw new Error("foo!"); diff --git a/dom/workers/test/notification_permission_worker.js b/dom/workers/test/notification_permission_worker.js new file mode 100644 index 0000000000..0e6b96d975 --- /dev/null +++ b/dom/workers/test/notification_permission_worker.js @@ -0,0 +1,59 @@ +function info(message) { + dump("INFO: " + message + "\n"); +} + +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +function is(a, b, message) { + postMessage({ type: "is", test1: a, test2: b, message }); +} + +if (self.Notification) { + var steps = [ + function (done) { + info("Test notification permission"); + ok(typeof Notification === "function", "Notification constructor exists"); + ok( + Notification.permission === "denied", + "Notification.permission is denied." + ); + + var n = new Notification("Hello"); + n.onerror = function (e) { + ok(true, "error called due to permission denied."); + done(); + }; + }, + ]; + + onmessage = function (e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({ type: "finish" }); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (ex) { + ok(false, "Test threw exception! " + nextTest + " " + ex); + finishTest(); + } + })(steps); + }; +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: "finish" }); +} diff --git a/dom/workers/test/notification_worker.js b/dom/workers/test/notification_worker.js new file mode 100644 index 0000000000..87aa02ac05 --- /dev/null +++ b/dom/workers/test/notification_worker.js @@ -0,0 +1,104 @@ +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +function is(a, b, message) { + postMessage({ type: "is", test1: a, test2: b, message }); +} + +if (self.Notification) { + var steps = [ + function () { + ok(typeof Notification === "function", "Notification constructor exists"); + ok(Notification.permission, "Notification.permission exists"); + ok( + typeof Notification.requestPermission === "undefined", + "Notification.requestPermission should not exist" + ); + }, + + function (done) { + var options = { + dir: "auto", + lang: "", + body: "This is a notification body", + tag: "sometag", + icon: "icon.png", + data: ["a complex object that should be", { structured: "cloned" }], + mozbehavior: { vibrationPattern: [30, 200, 30] }, + }; + var notification = new Notification("This is a title", options); + + ok(notification !== undefined, "Notification exists"); + is(notification.onclick, null, "onclick() should be null"); + is(notification.onshow, null, "onshow() should be null"); + is(notification.onerror, null, "onerror() should be null"); + is(notification.onclose, null, "onclose() should be null"); + is(typeof notification.close, "function", "close() should exist"); + + is(notification.dir, options.dir, "auto should get set"); + is(notification.lang, options.lang, "lang should get set"); + is(notification.body, options.body, "body should get set"); + is(notification.tag, options.tag, "tag should get set"); + is(notification.icon, options.icon, "icon should get set"); + is( + notification.data[0], + "a complex object that should be", + "data item 0 should be a matching string" + ); + is( + notification.data[1].structured, + "cloned", + "data item 1 should be a matching object literal" + ); + + // store notification in test context + this.notification = notification; + + notification.onshow = function () { + ok(true, "onshow handler should be called"); + done(); + }; + }, + + function (done) { + var notification = this.notification; + + notification.onclose = function () { + ok(true, "onclose handler should be called"); + done(); + }; + + notification.close(); + }, + ]; + + onmessage = function (e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({ type: "finish" }); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (ex) { + ok(false, "Test threw exception! " + nextTest + " " + ex); + finishTest(); + } + })(steps); + }; +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: "finish" }); +} diff --git a/dom/workers/test/notification_worker_child-child.js b/dom/workers/test/notification_worker_child-child.js new file mode 100644 index 0000000000..236e314e47 --- /dev/null +++ b/dom/workers/test/notification_worker_child-child.js @@ -0,0 +1,93 @@ +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +function is(a, b, message) { + postMessage({ type: "is", test1: a, test2: b, message }); +} + +if (self.Notification) { + var steps = [ + function () { + ok(typeof Notification === "function", "Notification constructor exists"); + ok(Notification.permission, "Notification.permission exists"); + ok( + typeof Notification.requestPermission === "undefined", + "Notification.requestPermission should not exist" + ); + //ok(typeof Notification.get === "function", "Notification.get exists"); + }, + + function (done) { + var options = { + dir: "auto", + lang: "", + body: "This is a notification body", + tag: "sometag", + icon: "icon.png", + }; + var notification = new Notification("This is a title", options); + + ok(notification !== undefined, "Notification exists"); + is(notification.onclick, null, "onclick() should be null"); + is(notification.onshow, null, "onshow() should be null"); + is(notification.onerror, null, "onerror() should be null"); + is(notification.onclose, null, "onclose() should be null"); + is(typeof notification.close, "function", "close() should exist"); + + is(notification.dir, options.dir, "auto should get set"); + is(notification.lang, options.lang, "lang should get set"); + is(notification.body, options.body, "body should get set"); + is(notification.tag, options.tag, "tag should get set"); + is(notification.icon, options.icon, "icon should get set"); + + // store notification in test context + this.notification = notification; + + notification.onshow = function () { + ok(true, "onshow handler should be called"); + done(); + }; + }, + + function (done) { + var notification = this.notification; + + notification.onclose = function () { + ok(true, "onclose handler should be called"); + done(); + }; + + notification.close(); + }, + ]; + + onmessage = function (e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({ type: "finish" }); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (ex) { + ok(false, "Test threw exception! " + nextTest + " " + ex); + finishTest(); + } + })(steps); + }; +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: "finish" }); +} diff --git a/dom/workers/test/notification_worker_child-parent.js b/dom/workers/test/notification_worker_child-parent.js new file mode 100644 index 0000000000..45dd061993 --- /dev/null +++ b/dom/workers/test/notification_worker_child-parent.js @@ -0,0 +1,26 @@ +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +function is(a, b, message) { + postMessage({ type: "is", test1: a, test2: b, message }); +} + +if (self.Notification) { + var child = new Worker("notification_worker_child-child.js"); + child.onerror = function (e) { + ok(false, "Error loading child worker " + e); + postMessage({ type: "finish" }); + }; + + child.onmessage = function (e) { + postMessage(e.data); + }; + + onmessage = function (e) { + child.postMessage("start"); + }; +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: "finish" }); +} diff --git a/dom/workers/test/onLine_worker.js b/dom/workers/test/onLine_worker.js new file mode 100644 index 0000000000..94c10c699c --- /dev/null +++ b/dom/workers/test/onLine_worker.js @@ -0,0 +1,70 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +importScripts("onLine_worker_head.js"); + +var N_CHILDREN = 3; +var children = []; +var finishedChildrenCount = 0; +var lastTest = false; + +for (var event of ["online", "offline"]) { + addEventListener( + event, + makeHandler( + "addEventListener('%1', ..., false)", + event, + 1, + "Parent Worker" + ), + false + ); +} + +onmessage = function (e) { + if (e.data === "lastTest") { + children.forEach(function (w) { + w.postMessage({ type: "lastTest" }); + }); + lastTest = true; + } +}; + +function setupChildren(cb) { + var readyCount = 0; + for (var i = 0; i < N_CHILDREN; ++i) { + var w = new Worker("onLine_worker_child.js"); + children.push(w); + + w.onerror = function (e) { + info("Error creating child " + e.message); + }; + + w.onmessage = function (e) { + if (e.data.type === "ready") { + info("Got ready from child"); + readyCount++; + if (readyCount === N_CHILDREN) { + cb(); + } + } else if (e.data.type === "finished") { + finishedChildrenCount++; + + if (lastTest && finishedChildrenCount === N_CHILDREN) { + postMessage({ type: "finished" }); + children = []; + close(); + } + } else if (e.data.type === "ok") { + // Pass on test to page. + postMessage(e.data); + } + }; + } +} + +setupChildren(function () { + postMessage({ type: "ready" }); +}); diff --git a/dom/workers/test/onLine_worker_child.js b/dom/workers/test/onLine_worker_child.js new file mode 100644 index 0000000000..92542c018f --- /dev/null +++ b/dom/workers/test/onLine_worker_child.js @@ -0,0 +1,91 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +function info(text) { + dump("Test for Bug 925437: worker: " + text + "\n"); +} + +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +/** + * Returns a handler function for an online/offline event. The returned handler + * ensures the passed event object has expected properties and that the handler + * is called at the right moment (according to the gState variable). + * @param nameTemplate The string identifying the hanlder. '%1' in that + * string will be replaced with the event name. + * @param eventName 'online' or 'offline' + * @param expectedState value of gState at the moment the handler is called. + * The handler increases gState by one before checking + * if it matches expectedState. + */ +function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) { + prefix += ": "; + return function (e) { + var name = nameTemplate.replace(/%1/, eventName); + ok(e.constructor == Event, prefix + "event should be an Event"); + ok(e.type == eventName, prefix + "event type should be " + eventName); + ok(!e.bubbles, prefix + "event should not bubble"); + ok(!e.cancelable, prefix + "event should not be cancelable"); + ok( + e.target == self, + prefix + "the event target should be the worker scope" + ); + ok( + eventName == "online" ? navigator.onLine : !navigator.onLine, + prefix + + "navigator.onLine " + + navigator.onLine + + " should reflect event " + + eventName + ); + + if (custom) { + custom(); + } + }; +} + +var lastTest = false; + +function lastTestTest() { + if (lastTest) { + postMessage({ type: "finished" }); + close(); + } +} + +for (var event of ["online", "offline"]) { + addEventListener( + event, + makeHandler( + "addEventListener('%1', ..., false)", + event, + 1, + "Child Worker", + lastTestTest + ), + false + ); +} + +onmessage = function (e) { + if (e.data.type === "lastTest") { + lastTest = true; + } else if (e.data.type === "navigatorState") { + ok( + e.data.state === navigator.onLine, + "Child and parent navigator state should match" + ); + } +}; + +postMessage({ type: "ready" }); diff --git a/dom/workers/test/onLine_worker_head.js b/dom/workers/test/onLine_worker_head.js new file mode 100644 index 0000000000..632821b1f4 --- /dev/null +++ b/dom/workers/test/onLine_worker_head.js @@ -0,0 +1,50 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +function info(text) { + dump("Test for Bug 925437: worker: " + text + "\n"); +} + +function ok(test, message) { + postMessage({ type: "ok", test, message }); +} + +/** + * Returns a handler function for an online/offline event. The returned handler + * ensures the passed event object has expected properties and that the handler + * is called at the right moment (according to the gState variable). + * @param nameTemplate The string identifying the hanlder. '%1' in that + * string will be replaced with the event name. + * @param eventName 'online' or 'offline' + * @param expectedState value of gState at the moment the handler is called. + * The handler increases gState by one before checking + * if it matches expectedState. + */ +function makeHandler(nameTemplate, eventName, expectedState, prefix, custom) { + prefix += ": "; + return function (e) { + var name = nameTemplate.replace(/%1/, eventName); + ok(e.constructor == Event, prefix + "event should be an Event"); + ok(e.type == eventName, prefix + "event type should be " + eventName); + ok(!e.bubbles, prefix + "event should not bubble"); + ok(!e.cancelable, prefix + "event should not be cancelable"); + ok( + e.target == self, + prefix + "the event target should be the worker scope" + ); + ok( + eventName == "online" ? navigator.onLine : !navigator.onLine, + prefix + + "navigator.onLine " + + navigator.onLine + + " should reflect event " + + eventName + ); + + if (custom) { + custom(); + } + }; +} diff --git a/dom/workers/test/promise_worker.js b/dom/workers/test/promise_worker.js new file mode 100644 index 0000000000..fd4a9177d6 --- /dev/null +++ b/dom/workers/test/promise_worker.js @@ -0,0 +1,1007 @@ +"use strict"; + +function ok(a, msg) { + dump("OK: " + !!a + " => " + a + " " + msg + "\n"); + postMessage({ type: "status", status: !!a, msg: a + ": " + msg }); +} + +function is(a, b, msg) { + dump("IS: " + (a === b) + " => " + a + " | " + b + " " + msg + "\n"); + postMessage({ + type: "status", + status: a === b, + msg: a + " === " + b + ": " + msg, + }); +} + +function isnot(a, b, msg) { + dump("ISNOT: " + (a !== b) + " => " + a + " | " + b + " " + msg + "\n"); + postMessage({ + type: "status", + status: a !== b, + msg: a + " !== " + b + ": " + msg, + }); +} + +function promiseResolve() { + ok(Promise, "Promise object should exist"); + + var promise = new Promise(function (resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); + + resolve(42); + }).then( + function (what) { + ok(true, "Then - resolveCb has been called"); + is(what, 42, "ResolveCb received 42"); + runTest(); + }, + function () { + ok(false, "Then - rejectCb has been called"); + runTest(); + } + ); +} + +function promiseResolveNoArg() { + var promise = new Promise(function (resolve, reject) { + ok(resolve, "Promise.resolve exists"); + ok(reject, "Promise.reject exists"); + + resolve(); + }).then( + function (what) { + ok(true, "Then - resolveCb has been called"); + is(what, undefined, "ResolveCb received undefined"); + runTest(); + }, + function () { + ok(false, "Then - rejectCb has been called"); + runTest(); + } + ); +} + +function promiseRejectNoHandler() { + // This test only checks that the code that reports unhandled errors in the + // Promises implementation does not crash or leak. + var promise = new Promise(function (res, rej) { + noSuchMethod(); + }); + runTest(); +} + +function promiseReject() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }).then( + function (what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, + function (what) { + ok(true, "Then - rejectCb has been called"); + is(what, 42, "RejectCb received 42"); + runTest(); + } + ); +} + +function promiseRejectNoArg() { + var promise = new Promise(function (resolve, reject) { + reject(); + }).then( + function (what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, + function (what) { + ok(true, "Then - rejectCb has been called"); + is(what, undefined, "RejectCb received undefined"); + runTest(); + } + ); +} + +function promiseException() { + var promise = new Promise(function (resolve, reject) { + throw 42; + }).then( + function (what) { + ok(false, "Then - resolveCb has been called"); + runTest(); + }, + function (what) { + ok(true, "Then - rejectCb has been called"); + is(what, 42, "RejectCb received 42"); + runTest(); + } + ); +} + +function promiseAsync_TimeoutResolveThen() { + var handlerExecuted = false; + + setTimeout(function () { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + Promise.resolve().then(function () { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveTimeoutThen() { + var handlerExecuted = false; + + var promise = Promise.resolve(); + + setTimeout(function () { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + promise.then(function () { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveThenTimeout() { + var handlerExecuted = false; + + Promise.resolve().then(function () { + handlerExecuted = true; + }); + + setTimeout(function () { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_SyncXHRAndImportScripts() { + var handlerExecuted = false; + + Promise.resolve().then(function () { + handlerExecuted = true; + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }); + + ok(!handlerExecuted, "Handlers are not called until the next microtask."); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "testXHR.txt", false); + xhr.send(null); + + ok(!handlerExecuted, "Sync XHR should not trigger microtask execution."); + + importScripts("../../../dom/xhr/tests/relativeLoad_import.js"); + + ok(!handlerExecuted, "importScripts should not trigger microtask execution."); +} + +function promiseDoubleThen() { + var steps = 0; + var promise = new Promise(function (r1, r2) { + r1(42); + }); + + promise.then( + function (what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + steps++; + }, + function (what) { + ok(false, "Then.reject has been called"); + } + ); + + promise.then( + function (what) { + ok(true, "Then.resolve has been called"); + is(steps, 1, "Then.resolve - step == 1"); + is(what, 42, "Value == 42"); + runTest(); + }, + function (what) { + ok(false, "Then.reject has been called"); + } + ); +} + +function promiseThenException() { + var promise = new Promise(function (resolve, reject) { + resolve(42); + }); + + promise + .then(function (what) { + ok(true, "Then.resolve has been called"); + throw "booh"; + }) + .catch(function (e) { + ok(true, "Catch has been called!"); + runTest(); + }); +} + +function promiseThenCatchThen() { + var promise = new Promise(function (resolve, reject) { + resolve(42); + }); + + var promise2 = promise.then( + function (what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + return what + 1; + }, + function (what) { + ok(false, "Then.reject has been called"); + } + ); + + isnot(promise, promise2, "These 2 promise objs are different"); + + promise2 + .then( + function (what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + return what + 1; + }, + function (what) { + ok(false, "Then.reject has been called"); + } + ) + .catch(function () { + ok(false, "Catch has been called"); + }) + .then( + function (what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }, + function (what) { + ok(false, "Then.reject has been called"); + } + ); +} + +function promiseRejectThenCatchThen() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + var promise2 = promise.then( + function (what) { + ok(false, "Then.resolve has been called"); + }, + function (what) { + ok(true, "Then.reject has been called"); + is(what, 42, "Value == 42"); + return what + 1; + } + ); + + isnot(promise, promise2, "These 2 promise objs are different"); + + promise2 + .then(function (what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + return what + 1; + }) + .catch(function (what) { + ok(false, "Catch has been called"); + }) + .then(function (what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }); +} + +function promiseRejectThenCatchThen2() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + promise + .then(function (what) { + ok(true, "Then.resolve has been called"); + is(what, 42, "Value == 42"); + return what + 1; + }) + .catch(function (what) { + is(what, 42, "Value == 42"); + ok(true, "Catch has been called"); + return what + 1; + }) + .then(function (what) { + ok(true, "Then.resolve has been called"); + is(what, 43, "Value == 43"); + runTest(); + }); +} + +function promiseRejectThenCatchExceptionThen() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + promise + .then( + function (what) { + ok(false, "Then.resolve has been called"); + }, + function (what) { + ok(true, "Then.reject has been called"); + is(what, 42, "Value == 42"); + throw what + 1; + } + ) + .catch(function (what) { + ok(true, "Catch has been called"); + is(what, 43, "Value == 43"); + return what + 1; + }) + .then(function (what) { + ok(true, "Then.resolve has been called"); + is(what, 44, "Value == 44"); + runTest(); + }); +} + +function promiseThenCatchOrderingResolve() { + var global = 0; + var f = new Promise(function (r1, r2) { + r1(42); + }); + + f.then(function () { + f.then(function () { + global++; + }); + f.catch(function () { + global++; + }); + f.then(function () { + global++; + }); + setTimeout(function () { + is(global, 2, "Many steps... should return 2"); + runTest(); + }, 0); + }); +} + +function promiseThenCatchOrderingReject() { + var global = 0; + var f = new Promise(function (r1, r2) { + r2(42); + }); + + f.then( + function () {}, + function () { + f.then(function () { + global++; + }); + f.catch(function () { + global++; + }); + f.then( + function () {}, + function () { + global++; + } + ); + setTimeout(function () { + is(global, 2, "Many steps... should return 2"); + runTest(); + }, 0); + } + ); +} + +function promiseThenNoArg() { + var promise = new Promise(function (resolve, reject) { + resolve(42); + }); + + var clone = promise.then(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.then(function (v) { + clone.then(function (cv) { + is(v, cv, "Both resolve to the same value"); + runTest(); + }); + }); +} + +function promiseThenUndefinedResolveFunction() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + try { + promise.then(undefined, function (v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on undefined resolve function"); + } +} + +function promiseThenNullResolveFunction() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + try { + promise.then(null, function (v) { + is(v, 42, "Promise rejected with 42"); + runTest(); + }); + } catch (e) { + ok(false, "then should not throw on null resolve function"); + } +} + +function promiseCatchNoArg() { + var promise = new Promise(function (resolve, reject) { + reject(42); + }); + + var clone = promise.catch(); + isnot(promise, clone, "These 2 promise objs are different"); + promise.catch(function (v) { + clone.catch(function (cv) { + is(v, cv, "Both reject to the same value"); + runTest(); + }); + }); +} + +function promiseNestedPromise() { + new Promise(function (resolve, reject) { + resolve( + new Promise(function (r) { + ok(true, "Nested promise is executed"); + r(42); + }) + ); + }).then(function (value) { + is(value, 42, "Nested promise is executed and then == 42"); + runTest(); + }); +} + +function promiseNestedNestedPromise() { + new Promise(function (resolve, reject) { + resolve( + new Promise(function (r) { + ok(true, "Nested promise is executed"); + r(42); + }).then(function (what) { + return what + 1; + }) + ); + }).then(function (value) { + is(value, 43, "Nested promise is executed and then == 43"); + runTest(); + }); +} + +function promiseWrongNestedPromise() { + new Promise(function (resolve, reject) { + resolve( + new Promise(function (r, r2) { + ok(true, "Nested promise is executed"); + r(42); + }) + ); + reject(42); + }).then( + function (value) { + is(value, 42, "Nested promise is executed and then == 42"); + runTest(); + }, + function (value) { + ok(false, "This is wrong"); + } + ); +} + +function promiseLoop() { + new Promise(function (resolve, reject) { + resolve( + new Promise(function (r1, r2) { + ok(true, "Nested promise is executed"); + r1( + new Promise(function (r3, r4) { + ok(true, "Nested nested promise is executed"); + r3(42); + }) + ); + }) + ); + }).then( + function (value) { + is(value, 42, "Nested nested promise is executed and then == 42"); + runTest(); + }, + function (value) { + ok(false, "This is wrong"); + } + ); +} + +function promiseStaticReject() { + var promise = Promise.reject(42).then( + function (what) { + ok(false, "This should not be called"); + }, + function (what) { + is(what, 42, "Value == 42"); + runTest(); + } + ); +} + +function promiseStaticResolve() { + var promise = Promise.resolve(42).then( + function (what) { + is(what, 42, "Value == 42"); + runTest(); + }, + function () { + ok(false, "This should not be called"); + } + ); +} + +function promiseResolveNestedPromise() { + var promise = Promise.resolve( + new Promise( + function (r, r2) { + ok(true, "Nested promise is executed"); + r(42); + }, + function () { + ok(false, "This should not be called"); + } + ) + ).then( + function (what) { + is(what, 42, "Value == 42"); + runTest(); + }, + function () { + ok(false, "This should not be called"); + } + ); +} + +function promiseUtilitiesDefined() { + ok(Promise.all, "Promise.all must be defined when Promise is enabled."); + ok(Promise.race, "Promise.race must be defined when Promise is enabled."); + runTest(); +} + +function promiseAllArray() { + var p = Promise.all([1, new Date(), Promise.resolve("firefox")]); + ok(p instanceof Promise, "Return value of Promise.all should be a Promise."); + p.then( + function (values) { + ok(Array.isArray(values), "Resolved value should be an array."); + is( + values.length, + 3, + "Resolved array length should match iterable's length." + ); + is(values[0], 1, "Array values should match."); + ok(values[1] instanceof Date, "Array values should match."); + is(values[2], "firefox", "Array values should match."); + runTest(); + }, + function () { + ok( + false, + "Promise.all shouldn't fail when iterable has no rejected Promises." + ); + runTest(); + } + ); +} + +function promiseAllWaitsForAllPromises() { + var arr = [ + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 1), 50); + }), + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 2), 10); + }), + new Promise(function (resolve) { + setTimeout( + resolve.bind( + undefined, + new Promise(function (resolve2) { + resolve2(3); + }) + ), + 10 + ); + }), + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 4), 20); + }), + ]; + + var p = Promise.all(arr); + p.then( + function (values) { + ok(Array.isArray(values), "Resolved value should be an array."); + is( + values.length, + 4, + "Resolved array length should match iterable's length." + ); + is(values[0], 1, "Array values should match."); + is(values[1], 2, "Array values should match."); + is(values[2], 3, "Array values should match."); + is(values[3], 4, "Array values should match."); + runTest(); + }, + function () { + ok( + false, + "Promise.all shouldn't fail when iterable has no rejected Promises." + ); + runTest(); + } + ); +} + +function promiseAllRejectFails() { + var arr = [ + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 1), 50); + }), + new Promise(function (resolve, reject) { + setTimeout(reject.bind(undefined, 2), 10); + }), + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 3), 10); + }), + new Promise(function (resolve) { + setTimeout(resolve.bind(undefined, 4), 20); + }), + ]; + + var p = Promise.all(arr); + p.then( + function (values) { + ok( + false, + "Promise.all shouldn't resolve when iterable has rejected Promises." + ); + runTest(); + }, + function (e) { + ok( + true, + "Promise.all should reject when iterable has rejected Promises." + ); + is(e, 2, "Rejection value should match."); + runTest(); + } + ); +} + +function promiseRaceEmpty() { + var p = Promise.race([]); + ok(p instanceof Promise, "Should return a Promise."); + // An empty race never resolves! + runTest(); +} + +function promiseRaceValuesArray() { + var p = Promise.race([true, new Date(), 3]); + ok(p instanceof Promise, "Should return a Promise."); + p.then( + function (winner) { + is(winner, true, "First value should win."); + runTest(); + }, + function (err) { + ok(false, "Should not fail " + err + "."); + runTest(); + } + ); +} + +function promiseRacePromiseArray() { + var arr = [ + new Promise(function (resolve) { + resolve("first"); + }), + Promise.resolve("second"), + new Promise(function () {}), + new Promise(function (resolve) { + setTimeout(function () { + setTimeout(function () { + resolve("fourth"); + }, 0); + }, 0); + }), + ]; + + var p = Promise.race(arr); + p.then(function (winner) { + is(winner, "first", "First queued resolution should win the race."); + runTest(); + }); +} + +function promiseRaceReject() { + var p = Promise.race([ + Promise.reject(new Error("Fail bad!")), + new Promise(function (resolve) { + setTimeout(resolve, 0); + }), + ]); + + p.then( + function () { + ok(false, "Should not resolve when winning Promise rejected."); + runTest(); + }, + function (e) { + ok(true, "Should be rejected"); + ok(e instanceof Error, "Should reject with Error."); + ok(e.message == "Fail bad!", "Message should match."); + runTest(); + } + ); +} + +function promiseRaceThrow() { + var p = Promise.race([ + new Promise(function (resolve) { + nonExistent(); + }), + new Promise(function (resolve) { + setTimeout(resolve, 0); + }), + ]); + + p.then( + function () { + ok(false, "Should not resolve when winning Promise had an error."); + runTest(); + }, + function (e) { + ok(true, "Should be rejected"); + ok( + e instanceof ReferenceError, + "Should reject with ReferenceError for function nonExistent()." + ); + runTest(); + } + ); +} + +function promiseResolveArray() { + var p = Promise.resolve([1, 2, 3]); + ok(p instanceof Promise, "Should return a Promise."); + p.then(function (v) { + ok(Array.isArray(v), "Resolved value should be an Array"); + is(v.length, 3, "Length should match"); + is(v[0], 1, "Resolved value should match original"); + is(v[1], 2, "Resolved value should match original"); + is(v[2], 3, "Resolved value should match original"); + runTest(); + }); +} + +function promiseResolveThenable() { + var p = Promise.resolve({ + then(onFulfill, onReject) { + onFulfill(2); + }, + }); + ok(p instanceof Promise, "Should cast to a Promise."); + p.then( + function (v) { + is(v, 2, "Should resolve to 2."); + runTest(); + }, + function (e) { + ok(false, "promiseResolveThenable should've resolved"); + runTest(); + } + ); +} + +function promiseResolvePromise() { + var original = Promise.resolve(true); + var cast = Promise.resolve(original); + + ok(cast instanceof Promise, "Should cast to a Promise."); + is(cast, original, "Should return original Promise."); + cast.then(function (v) { + is(v, true, "Should resolve to true."); + runTest(); + }); +} + +// Bug 1009569. +// Ensure that thenables are run on a clean stack asynchronously. +// Test case adopted from +// https://gist.github.com/getify/d64bb01751b50ed6b281#file-bug1-js. +function promiseResolveThenableCleanStack() { + function immed(s) { + x++; + s(); + } + function incX() { + x++; + } + + var x = 0; + var thenable = { then: immed }; + var results = []; + + var p = Promise.resolve(thenable).then(incX); + results.push(x); + + // check what happens after all "next cycle" steps + // have had a chance to complete + setTimeout(function () { + // Result should be [0, 2] since `thenable` will be called async. + is(results[0], 0, "Expected thenable to be called asynchronously"); + // See Bug 1023547 comment 13 for why this check has to be gated on p. + p.then(function () { + results.push(x); + is(results[1], 2, "Expected thenable to be called asynchronously"); + runTest(); + }); + }, 1000); +} + +// Bug 1062323 +function promiseWrapperAsyncResolution() { + var p = new Promise(function (resolve, reject) { + resolve(); + }); + + var results = []; + var q = p + .then(function () { + results.push("1-1"); + }) + .then(function () { + results.push("1-2"); + }) + .then(function () { + results.push("1-3"); + }); + + var r = p + .then(function () { + results.push("2-1"); + }) + .then(function () { + results.push("2-2"); + }) + .then(function () { + results.push("2-3"); + }); + + Promise.all([q, r]).then( + function () { + var match = + results[0] == "1-1" && + results[1] == "2-1" && + results[2] == "1-2" && + results[3] == "2-2" && + results[4] == "1-3" && + results[5] == "2-3"; + ok(match, "Chained promises should resolve asynchronously."); + runTest(); + }, + function () { + ok(false, "promiseWrapperAsyncResolution: One of the promises failed."); + runTest(); + } + ); +} + +var tests = [ + promiseResolve, + promiseReject, + promiseException, + promiseAsync_TimeoutResolveThen, + promiseAsync_ResolveTimeoutThen, + promiseAsync_ResolveThenTimeout, + promiseAsync_SyncXHRAndImportScripts, + promiseDoubleThen, + promiseThenException, + promiseThenCatchThen, + promiseRejectThenCatchThen, + promiseRejectThenCatchThen2, + promiseRejectThenCatchExceptionThen, + promiseThenCatchOrderingResolve, + promiseThenCatchOrderingReject, + promiseNestedPromise, + promiseNestedNestedPromise, + promiseWrongNestedPromise, + promiseLoop, + promiseStaticReject, + promiseStaticResolve, + promiseResolveNestedPromise, + promiseResolveNoArg, + promiseRejectNoArg, + + promiseThenNoArg, + promiseThenUndefinedResolveFunction, + promiseThenNullResolveFunction, + promiseCatchNoArg, + promiseRejectNoHandler, + + promiseUtilitiesDefined, + + promiseAllArray, + promiseAllWaitsForAllPromises, + promiseAllRejectFails, + + promiseRaceEmpty, + promiseRaceValuesArray, + promiseRacePromiseArray, + promiseRaceReject, + promiseRaceThrow, + + promiseResolveArray, + promiseResolveThenable, + promiseResolvePromise, + + promiseResolveThenableCleanStack, + + promiseWrapperAsyncResolution, +]; + +function runTest() { + if (!tests.length) { + postMessage({ type: "finish" }); + return; + } + + var test = tests.shift(); + test(); +} + +onmessage = function () { + runTest(); +}; diff --git a/dom/workers/test/recursion_worker.js b/dom/workers/test/recursion_worker.js new file mode 100644 index 0000000000..84eb6d9740 --- /dev/null +++ b/dom/workers/test/recursion_worker.js @@ -0,0 +1,47 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This function should never run on a too much recursion error. +onerror = function (event) { + postMessage(event.message); +}; + +// Pure JS recursion +function recurse() { + recurse(); +} + +// JS -> C++ -> JS -> C++ recursion +function recurse2() { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + xhr.open("GET", "nonexistent.file"); + }; + xhr.open("GET", "nonexistent.file"); +} + +var messageCount = 0; +onmessage = function (event) { + switch (++messageCount) { + case 2: + recurse2(); + + // An exception thrown from an event handler like xhr.onreadystatechange + // should not leave an exception pending in the code that generated the + // event. + postMessage("Done"); + return; + + case 1: + recurse(); + throw "Exception should have prevented us from getting here!"; + + default: + throw "Weird number of messages: " + messageCount; + } + + // eslint-disable-next-line no-unreachable + throw "Impossible to get here!"; +}; diff --git a/dom/workers/test/recursiveOnerror_worker.js b/dom/workers/test/recursiveOnerror_worker.js new file mode 100644 index 0000000000..35d2e2b80d --- /dev/null +++ b/dom/workers/test/recursiveOnerror_worker.js @@ -0,0 +1,11 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onerror = function (message, filename, lineno) { + throw new Error("2"); +}; + +onmessage = function (event) { + throw new Error("1"); +}; diff --git a/dom/workers/test/redirect_to_foreign.sjs b/dom/workers/test/redirect_to_foreign.sjs new file mode 100644 index 0000000000..06fd12052b --- /dev/null +++ b/dom/workers/test/redirect_to_foreign.sjs @@ -0,0 +1,7 @@ +function handleRequest(request, response) { + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + "http://example.org/tests/dom/workers/test/foreign.js" + ); +} diff --git a/dom/workers/test/referrer.sjs b/dom/workers/test/referrer.sjs new file mode 100644 index 0000000000..f91c434c16 --- /dev/null +++ b/dom/workers/test/referrer.sjs @@ -0,0 +1,14 @@ +function handleRequest(request, response) { + if (request.queryString == "result") { + response.write(getState("referer")); + setState("referer", "INVALID"); + } else if (request.queryString == "worker") { + response.setHeader("Content-Type", "text/javascript", false); + response.write("onmessage = function() { postMessage(42); }"); + setState("referer", request.getHeader("referer")); + } else if (request.queryString == "import") { + setState("referer", request.getHeader("referer")); + response.setHeader("Content-Type", "text/javascript", false); + response.write("'hello world'"); + } +} diff --git a/dom/workers/test/referrer_test_server.sjs b/dom/workers/test/referrer_test_server.sjs new file mode 100644 index 0000000000..80bd2bee54 --- /dev/null +++ b/dom/workers/test/referrer_test_server.sjs @@ -0,0 +1,97 @@ +const SJS = "referrer_test_server.sjs?"; +const SHARED_KEY = SJS; + +var SAME_ORIGIN = "https://example.com/tests/dom/workers/test/" + SJS; +var CROSS_ORIGIN = "https://test2.example.com/tests/dom/workers/test/" + SJS; +var DOWNGRADE = "http://example.com/tests/dom/workers/test/" + SJS; + +function createUrl(aRequestType, aPolicy) { + var searchParams = new URLSearchParams(); + searchParams.append("ACTION", "request-worker"); + searchParams.append("Referrer-Policy", aPolicy); + searchParams.append("TYPE", aRequestType); + + var url = SAME_ORIGIN; + + if (aRequestType === "cross-origin") { + url = CROSS_ORIGIN; + } else if (aRequestType === "downgrade") { + url = DOWNGRADE; + } + + return url + searchParams.toString(); +} +function createWorker(aRequestType, aPolicy) { + return ` + onmessage = function() { + fetch("${createUrl(aRequestType, aPolicy)}").then(function () { + postMessage(42); + close(); + }); + } + `; +} + +function handleRequest(request, response) { + var params = new URLSearchParams(request.queryString); + var policy = params.get("Referrer-Policy"); + var type = params.get("TYPE"); + var action = params.get("ACTION"); + response.setHeader("Content-Security-Policy", "default-src *", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + + if (policy) { + response.setHeader("Referrer-Policy", policy, false); + } + + if (action === "test") { + response.setHeader("Content-Type", "text/javascript", false); + response.write(createWorker(type, policy)); + return; + } + + if (action === "resetState") { + setSharedState(SHARED_KEY, "{}"); + response.write(""); + return; + } + + if (action === "get-test-results") { + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/plain", false); + response.write(getSharedState(SHARED_KEY)); + return; + } + + if (action === "request-worker") { + var result = getSharedState(SHARED_KEY); + result = result ? JSON.parse(result) : {}; + var referrerLevel = "none"; + var test = {}; + + if (request.hasHeader("Referer")) { + var referrer = request.getHeader("Referer"); + if (referrer.indexOf("referrer_test_server") > 0) { + referrerLevel = "full"; + } else if (referrer.indexOf("https://example.com") == 0) { + referrerLevel = "origin"; + } else { + // this is never supposed to happen + referrerLevel = "other-origin"; + } + test.referrer = referrer; + } else { + test.referrer = ""; + } + + test.policy = referrerLevel; + test.expected = policy; + + // test id equals type + "-" + policy + // Ex: same-origin-default + result[type + "-" + policy] = test; + setSharedState(SHARED_KEY, JSON.stringify(result)); + + response.write("'hello world'"); + } +} diff --git a/dom/workers/test/referrer_worker.html b/dom/workers/test/referrer_worker.html new file mode 100644 index 0000000000..f4c6719912 --- /dev/null +++ b/dom/workers/test/referrer_worker.html @@ -0,0 +1,144 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body onload="tests.next();"> +<script type="text/javascript"> +const SJS = "referrer_test_server.sjs?"; +const BASE_URL = "https://example.com/tests/dom/workers/test/" + SJS; +const GET_RESULT = BASE_URL + 'ACTION=get-test-results'; +const RESET_STATE = BASE_URL + 'ACTION=resetState'; + +function ok(val, message) { + val = val ? "true" : "false"; + window.parent.postMessage("SimpleTest.ok(" + val + ", '" + message + "');", "*"); +} + +function info(val) { + window.parent.postMessage("SimpleTest.info(" + val + ");", "*"); +} + +function is(a, b, message) { + ok(a == b, message); +} + +function finish() { + // Let window.onerror have a chance to fire + setTimeout(function() { + setTimeout(function() { + tests.return(); + window.parent.postMessage("SimpleTest.finish();", "*"); + }, 0); + }, 0); +} + +var testCases = { + 'same-origin': { 'Referrer-Policy' : { 'default' : 'full', + 'origin' : 'origin', + 'origin-when-cross-origin' : 'full', + 'unsafe-url' : 'full', + 'same-origin' : 'full', + 'strict-origin' : 'origin', + 'strict-origin-when-cross-origin' : 'full', + 'no-referrer' : 'none', + 'unsafe-url, no-referrer' : 'none', + 'invalid' : 'full' }}, + + 'cross-origin': { 'Referrer-Policy' : { 'default' : 'origin', + 'origin' : 'origin', + 'origin-when-cross-origin' : 'origin', + 'unsafe-url' : 'full', + 'same-origin' : 'none', + 'strict-origin' : 'origin', + 'strict-origin-when-cross-origin' : 'origin', + 'no-referrer' : 'none', + 'unsafe-url, no-referrer' : 'none', + 'invalid' : 'origin' }}, + + // Downgrading in worker is blocked entirely without unblock option + // https://bugzilla.mozilla.org/show_bug.cgi?id=1198078#c17 + // Skip the downgrading test + /* 'downgrade': { 'Referrer-Policy' : { 'default' : 'full', + 'origin' : 'full', + 'origin-when-cross-origin"' : 'full', + 'unsafe-url' : 'full', + 'same-origin' : 'none', + 'strict-origin' : 'none', + 'strict-origin-when-cross-origin' : 'none', + 'no-referrer' : 'full', + 'unsafe-url, no-referrer' : 'none', + 'invalid' : 'full' }}, */ + + +}; + +var advance = function() { tests.next(); }; + +/** + * helper to perform an XHR + * to do checkIndividualResults and resetState + */ +function doXHR(aUrl, onSuccess, onFail) { + var xhr = new XMLHttpRequest({mozSystem: true}); + xhr.responseType = "json"; + xhr.onload = function () { + onSuccess(xhr); + }; + xhr.onerror = function () { + onFail(xhr); + }; + xhr.open('GET', aUrl, true); + xhr.send(null); +} + + +function resetState() { + doXHR(RESET_STATE, + advance, + function(xhr) { + ok(false, "error in reset state"); + finish(); + }); +} + +function checkIndividualResults(aType, aPolicy, aExpected) { + var onload = xhr => { + var results = xhr.response; + dump(JSON.stringify(xhr.response)); + // test id equals type + "-" + policy + // Ex: same-origin-default + var id = aType + "-" + aPolicy; + ok(id in results, id + " tests have to be performed."); + is(results[id].policy, aExpected, id + ' --- ' + results[id].policy + ' (' + results[id].referrer + ')'); + advance(); + }; + var onerror = xhr => { + ok(false, "Can't get results from the counter server."); + finish(); + }; + doXHR(GET_RESULT, onload, onerror); +} + +var tests = (function*() { + + for (var type in testCases) { + for (var policy in testCases[type]['Referrer-Policy']) { + yield resetState(); + var searchParams = new URLSearchParams(); + searchParams.append("TYPE", type); + searchParams.append("ACTION", "test"); + searchParams.append("Referrer-Policy", policy); + var worker = new Worker(BASE_URL + searchParams.toString()); + worker.onmessage = function () { + advance(); + }; + yield worker.postMessage(42); + yield checkIndividualResults(type, policy, escape(testCases[type]['Referrer-Policy'][policy])); + } + } + + finish(); +})(); +</script> +</body> +</html> diff --git a/dom/workers/test/rvals_worker.js b/dom/workers/test/rvals_worker.js new file mode 100644 index 0000000000..221d701443 --- /dev/null +++ b/dom/workers/test/rvals_worker.js @@ -0,0 +1,13 @@ +onmessage = function (evt) { + postMessage(postMessage("ignore") == undefined); + + var id = setInterval(function () {}, 200); + postMessage(clearInterval(id) == undefined); + + id = setTimeout(function () {}, 200); + postMessage(clearTimeout(id) == undefined); + + postMessage(dump(42 + "\n") == undefined); + + postMessage("finished"); +}; diff --git a/dom/workers/test/script_createFile.js b/dom/workers/test/script_createFile.js new file mode 100644 index 0000000000..09f7be509e --- /dev/null +++ b/dom/workers/test/script_createFile.js @@ -0,0 +1,43 @@ +/* eslint-env mozilla/chrome-script */ + +// eslint-disable-next-line mozilla/reject-importGlobalProperties +Cu.importGlobalProperties(["File"]); + +addMessageListener("file.open", function (e) { + var tmpFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + tmpFile.append("file.txt"); + tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + File.createFromNsIFile(tmpFile).then(function (file) { + sendAsyncMessage("file.opened", { data: file }); + }); +}); + +addMessageListener("nonEmptyFile.open", function (e) { + var tmpFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIDirectoryService) + .QueryInterface(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + tmpFile.append("file.txt"); + tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + var outStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + outStream.init( + tmpFile, + 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, + 0 + ); + var fileData = "Hello world!"; + outStream.write(fileData, fileData.length); + outStream.close(); + + File.createFromNsIFile(tmpFile).then(function (file) { + sendAsyncMessage("nonEmptyFile.opened", { data: file }); + }); +}); diff --git a/dom/workers/test/server_fetch_synthetic.sjs b/dom/workers/test/server_fetch_synthetic.sjs new file mode 100644 index 0000000000..703b26d0d2 --- /dev/null +++ b/dom/workers/test/server_fetch_synthetic.sjs @@ -0,0 +1,50 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function log(str) { + //dump(`SJS LOG: ${str}\n`); +} + +/** + * Given a multipart/form-data encoded string that we know to have only a single + * part, return the contents of the part. (MIME multipart encoding is too + * exciting to delve into.) + */ +function extractBlobFromMultipartFormData(text) { + const lines = text.split(/\r\n/g); + const firstBlank = lines.indexOf(""); + const foo = lines.slice(firstBlank + 1, -2).join("\n"); + return foo; +} + +async function handleRequest(request, response) { + let blobContents = ""; + if (request.method !== "POST") { + } else { + var body = new BinaryInputStream(request.bodyInputStream); + + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + let requestBodyContents = String.fromCharCode.apply(null, bytes); + log(requestBodyContents); + blobContents = extractBlobFromMultipartFormData(requestBodyContents); + } + + log("Setting Headers"); + response.setHeader("Content-Type", "text/html", false); + response.setStatusLine(request.httpVersion, "200", "OK"); + response.write(`<!DOCTYPE HTML><head><meta charset="utf-8"/></head><body> + <h1 id="url">${request.scheme}${request.host}${request.port}${request.path}</h1> + <div id="source">ServerJS</div> + <div id="blob">${blobContents}</div> + </body>`); + log("Done"); +} diff --git a/dom/workers/test/sharedWorker_console.js b/dom/workers/test/sharedWorker_console.js new file mode 100644 index 0000000000..d78fca94c6 --- /dev/null +++ b/dom/workers/test/sharedWorker_console.js @@ -0,0 +1,12 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +onconnect = function (evt) { + console.profile("Hello profiling from a SharedWorker!"); + console.log("Hello world from a SharedWorker!"); + console.log("Here is a SAB", new SharedArrayBuffer(1024)); + evt.ports[0].postMessage("ok!"); +}; diff --git a/dom/workers/test/sharedWorker_lifetime.js b/dom/workers/test/sharedWorker_lifetime.js new file mode 100644 index 0000000000..594bd5833d --- /dev/null +++ b/dom/workers/test/sharedWorker_lifetime.js @@ -0,0 +1,5 @@ +onconnect = function (e) { + setTimeout(function () { + e.ports[0].postMessage("Still alive!"); + }, 500); +}; diff --git a/dom/workers/test/sharedWorker_ports.js b/dom/workers/test/sharedWorker_ports.js new file mode 100644 index 0000000000..6bdb5695db --- /dev/null +++ b/dom/workers/test/sharedWorker_ports.js @@ -0,0 +1,30 @@ +var port; +onconnect = function (evt) { + evt.source.postMessage({ type: "connected" }); + + if (!port) { + port = evt.source; + evt.source.onmessage = function (evtFromPort) { + port.postMessage({ + type: "status", + test: "Port from the main-thread!" == evtFromPort.data, + msg: "The message is coming from the main-thread", + }); + port.postMessage({ + type: "status", + test: evtFromPort.ports.length == 1, + msg: "1 port transferred", + }); + + evtFromPort.ports[0].onmessage = function (evtFromPort2) { + port.postMessage({ + type: "status", + test: evtFromPort2.data.type == "connected", + msg: "The original message received", + }); + port.postMessage({ type: "finish" }); + close(); + }; + }; + } +}; diff --git a/dom/workers/test/sharedWorker_privateBrowsing.js b/dom/workers/test/sharedWorker_privateBrowsing.js new file mode 100644 index 0000000000..c16bd209f0 --- /dev/null +++ b/dom/workers/test/sharedWorker_privateBrowsing.js @@ -0,0 +1,4 @@ +var counter = 0; +onconnect = function (evt) { + evt.ports[0].postMessage(++counter); +}; diff --git a/dom/workers/test/sharedWorker_sharedWorker.js b/dom/workers/test/sharedWorker_sharedWorker.js new file mode 100644 index 0000000000..a7f859919f --- /dev/null +++ b/dom/workers/test/sharedWorker_sharedWorker.js @@ -0,0 +1,100 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +if (!("self" in this)) { + throw new Error("No 'self' exists on SharedWorkerGlobalScope!"); +} +if (this !== self) { + throw new Error("'self' not equal to global object!"); +} +if (!(self instanceof SharedWorkerGlobalScope)) { + throw new Error("self not a SharedWorkerGlobalScope instance!"); +} + +var propsToCheck = [ + "location", + "navigator", + "close", + "importScripts", + "setTimeout", + "clearTimeout", + "setInterval", + "clearInterval", + "dump", + "atob", + "btoa", +]; + +for (var index = 0; index < propsToCheck.length; index++) { + var prop = propsToCheck[index]; + if (!(prop in self)) { + throw new Error("SharedWorkerGlobalScope has no '" + prop + "' property!"); + } +} + +onconnect = function (event) { + if (!("SharedWorkerGlobalScope" in self)) { + throw new Error("SharedWorkerGlobalScope should be visible!"); + } + if (!(self instanceof SharedWorkerGlobalScope)) { + throw new Error("The global should be a SharedWorkerGlobalScope!"); + } + if (!(self instanceof WorkerGlobalScope)) { + throw new Error("The global should be a WorkerGlobalScope!"); + } + if ("DedicatedWorkerGlobalScope" in self) { + throw new Error("DedicatedWorkerGlobalScope should not be visible!"); + } + if (!(event instanceof MessageEvent)) { + throw new Error("'connect' event is not a MessageEvent!"); + } + if (!("ports" in event)) { + throw new Error("'connect' event doesn't have a 'ports' property!"); + } + if (event.ports.length != 1) { + throw new Error( + "'connect' event has a 'ports' property with length '" + + event.ports.length + + "'!" + ); + } + if (!event.ports[0]) { + throw new Error("'connect' event has a null 'ports[0]' property!"); + } + if (!(event.ports[0] instanceof MessagePort)) { + throw new Error( + "'connect' event has a 'ports[0]' property that isn't a " + "MessagePort!" + ); + } + if (!(event.ports[0] == event.source)) { + throw new Error("'connect' event source property is incorrect!"); + } + if (event.data) { + throw new Error("'connect' event has data: " + event.data); + } + + // Statement after return should trigger a warning, but NOT fire error events + // at us. + (function () { + return; + // eslint-disable-next-line no-unreachable + 1; + }); + + event.ports[0].onmessage = function (msg) { + if (!(msg instanceof MessageEvent)) { + throw new Error("'message' event is not a MessageEvent!"); + } + if (!("ports" in msg)) { + throw new Error("'message' event doesn't have a 'ports' property!"); + } + if (msg.ports === null) { + throw new Error("'message' event has a null 'ports' property!"); + } + msg.target.postMessage(msg.data); + throw new Error(msg.data); + }; +}; diff --git a/dom/workers/test/sharedWorker_thirdparty_frame.html b/dom/workers/test/sharedWorker_thirdparty_frame.html new file mode 100644 index 0000000000..ebd78412a6 --- /dev/null +++ b/dom/workers/test/sharedWorker_thirdparty_frame.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<script> + let params = new URLSearchParams(document.location.search.substring(1)); + let name = params.get('name'); + try { + let worker = new SharedWorker('sharedWorker_sharedWorker.js', + { name }); + worker.port.addEventListener('message', evt => { + parent.postMessage( { name, result: 'allowed' }, '*'); + }, { once: true }); + worker.port.start(); + worker.port.postMessage('ping'); + } catch(e) { + parent.postMessage({ name, result: 'blocked' }, '*'); + } +</script> diff --git a/dom/workers/test/sharedWorker_thirdparty_window.html b/dom/workers/test/sharedWorker_thirdparty_window.html new file mode 100644 index 0000000000..f5f01066c2 --- /dev/null +++ b/dom/workers/test/sharedWorker_thirdparty_window.html @@ -0,0 +1,26 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SharedWorker in 3rd Party Iframes</title> +</head> +<body> + <script> + + let url = new URL(window.location); + + let frame = document.createElement('iframe'); + frame.src = + 'http://example.org/tests/dom/workers/test/sharedWorker_thirdparty_frame.html?name=' + url.searchParams.get('name'); + document.body.appendChild(frame); + window.addEventListener('message', evt => { + frame.remove(); + opener.postMessage(evt.data, "*"); + }, {once: true}); + + </script> +</body> +</html> diff --git a/dom/workers/test/simpleThread_worker.js b/dom/workers/test/simpleThread_worker.js new file mode 100644 index 0000000000..9d6fb30f2f --- /dev/null +++ b/dom/workers/test/simpleThread_worker.js @@ -0,0 +1,52 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +function messageListener(event) { + var exception; + try { + event.bubbles = true; + } catch (e) { + exception = e; + } + + if (!(exception instanceof TypeError)) { + throw exception; + } + + switch (event.data) { + case "no-op": + break; + case "components": + postMessage(Components.toString()); + break; + case "start": + for (var i = 0; i < 1000; i++) {} + postMessage("started"); + break; + case "stop": + self.postMessage("no-op"); + postMessage("stopped"); + self.removeEventListener("message", messageListener); + break; + default: + throw "Bad message: " + event.data; + } +} + +if (!("DedicatedWorkerGlobalScope" in self)) { + throw new Error("DedicatedWorkerGlobalScope should be visible!"); +} +if (!(self instanceof DedicatedWorkerGlobalScope)) { + throw new Error("The global should be a SharedWorkerGlobalScope!"); +} +if (!(self instanceof WorkerGlobalScope)) { + throw new Error("The global should be a WorkerGlobalScope!"); +} +if ("SharedWorkerGlobalScope" in self) { + throw new Error("SharedWorkerGlobalScope should not be visible!"); +} + +addEventListener("message", { handleEvent: messageListener }); diff --git a/dom/workers/test/sourcemap_header.js b/dom/workers/test/sourcemap_header.js new file mode 100644 index 0000000000..9f10b35ed9 --- /dev/null +++ b/dom/workers/test/sourcemap_header.js @@ -0,0 +1,65 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +(async () => { + SimpleTest.waitForExplicitFinish(); + + const HTTP_BASE_URL = "http://mochi.test:8888/tests/dom/workers/test/"; + const IFRAME_URL = HTTP_BASE_URL + "sourcemap_header_iframe.html"; + const WORKER_URL = HTTP_BASE_URL + "sourcemap_header_worker.js"; + const DEBUGGER_URL = BASE_URL + "sourcemap_header_debugger.js"; + + const workerFrame = document.getElementById("worker-frame"); + ok(workerFrame, "has frame"); + + await new Promise(r => { + workerFrame.onload = r; + workerFrame.src = IFRAME_URL; + }); + + info("Start worker and watch for registration"); + const workerLoadedChannel = new MessageChannel(); + + const loadDebuggerAndWorker = Promise.all([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + // We need to wait for the worker to load so a Debugger.Source will be + // guaranteed to exist. + new Promise(r => { + workerLoadedChannel.port1.onmessage = r; + }), + ]); + workerFrame.contentWindow.postMessage(WORKER_URL, "*", [ + workerLoadedChannel.port2, + ]); + const [dbg] = await loadDebuggerAndWorker; + + // Wait for the debugger server to reply with the sourceMapURL of the + // loaded worker scripts. + info("Querying for the sourceMapURL of the worker script"); + const urls = await new Promise(res => { + dbg.addListener({ + onMessage(msg) { + const data = JSON.parse(msg); + if (data.type !== "response-sourceMapURL") { + return; + } + dbg.removeListener(this); + res(data.value); + }, + }); + dbg.postMessage( + JSON.stringify({ + type: "request-sourceMapURL", + url: WORKER_URL, + }) + ); + }); + + ok(Array.isArray(urls) && urls.length === 1, "has a single source actor"); + is(urls[0], "worker-header.js.map", "has the right map URL"); + + SimpleTest.finish(); +})(); diff --git a/dom/workers/test/sourcemap_header_debugger.js b/dom/workers/test/sourcemap_header_debugger.js new file mode 100644 index 0000000000..bb8ed0c1f7 --- /dev/null +++ b/dom/workers/test/sourcemap_header_debugger.js @@ -0,0 +1,29 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +addEventListener("message", function (event) { + let data; + try { + data = JSON.parse(event.data); + } catch {} + + switch (data.type) { + case "request-sourceMapURL": + const dbg = new Debugger(global); + const sourceMapURLs = dbg + .findSources() + .filter(source => source.url === data.url) + .map(source => source.sourceMapURL); + + postMessage( + JSON.stringify({ + type: "response-sourceMapURL", + value: sourceMapURLs, + }) + ); + break; + } +}); diff --git a/dom/workers/test/sourcemap_header_iframe.html b/dom/workers/test/sourcemap_header_iframe.html new file mode 100644 index 0000000000..82278f41a1 --- /dev/null +++ b/dom/workers/test/sourcemap_header_iframe.html @@ -0,0 +1,19 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <script> + self.onmessage = (msg) => { + const workerLoadedPort = msg.ports[0]; + const worker = new Worker(msg.data); + worker.onmessage = () => { + workerLoadedPort.postMessage("worker loaded"); + }; + }; + </script> +</head> +<body></body> +</html> diff --git a/dom/workers/test/sourcemap_header_worker.js b/dom/workers/test/sourcemap_header_worker.js new file mode 100644 index 0000000000..ca094686d3 --- /dev/null +++ b/dom/workers/test/sourcemap_header_worker.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Send a message pack so the test knows that the source has loaded before +// it tries to search for the Debugger.Source. +postMessage("loaded"); diff --git a/dom/workers/test/sourcemap_header_worker.js^headers^ b/dom/workers/test/sourcemap_header_worker.js^headers^ new file mode 100644 index 0000000000..833288ef02 --- /dev/null +++ b/dom/workers/test/sourcemap_header_worker.js^headers^ @@ -0,0 +1 @@ +X-SourceMap: worker-header.js.map diff --git a/dom/workers/test/suspend_blank.html b/dom/workers/test/suspend_blank.html new file mode 100644 index 0000000000..b6dd0075e6 --- /dev/null +++ b/dom/workers/test/suspend_blank.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<script> + var interval; + var finish = false; + var bc = new BroadcastChannel("suspendBlank"); + bc.onmessage = (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "navigateBack") { + finish = true; + history.back(); + } + } + window.onpagehide = () => { + bc.postMessage({command: "pagehide"}); + if (finish) { + bc.close(); + } + } + window.onload = () => { + bc.postMessage({command: "loaded"}); + } +</script> diff --git a/dom/workers/test/suspend_window.html b/dom/workers/test/suspend_window.html new file mode 100644 index 0000000000..9bede3aaa6 --- /dev/null +++ b/dom/workers/test/suspend_window.html @@ -0,0 +1,82 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for DOM Worker Threads Suspending</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="output"></div> +<script class="testbody" type="text/javascript"> + + var worker; + var finish = false; + var bc = new BroadcastChannel("suspendWindow"); + bc.onmessage = (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "startWorker") { + startWorker(); + } else if (command == "navigate") { + window.location = "suspend_blank.html"; + } else if (command == "finish") { + finish = true; + terminateWorker(); + bc.postMessage({command: "finished"}); + bc.close(); + window.close(); + } + } + + function messageCallback(data) { + if (finish) { + return; + } + bc.postMessage({command: "messageCallback", data}); + } + + function errorCallback(msg) { + if (finish) { + return; + } + bc.postMessage({command: "errorCallback", data: msg}); + } + + var output = document.getElementById("output"); + + function terminateWorker() { + if (worker) { + worker.postMessage("stop"); + worker = null; + } + } + + function startWorker() { + var lastData; + worker = new Worker("suspend_worker.js"); + + worker.onmessage = function(event) { + output.textContent = (lastData ? lastData + " -> " : "") + event.data; + lastData = event.data; + messageCallback(event.data); + }; + + worker.onerror = function(event) { + this.terminate(); + errorCallback(event.message); + }; + } + + window.onload = () => { + bc.postMessage({command: "loaded"}); + } + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/suspend_worker.js b/dom/workers/test/suspend_worker.js new file mode 100644 index 0000000000..e024972737 --- /dev/null +++ b/dom/workers/test/suspend_worker.js @@ -0,0 +1,13 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var counter = 0; + +var interval = setInterval(function () { + postMessage(++counter); +}, 100); + +onmessage = function (event) { + clearInterval(interval); +}; diff --git a/dom/workers/test/terminate_worker.js b/dom/workers/test/terminate_worker.js new file mode 100644 index 0000000000..7b9984e869 --- /dev/null +++ b/dom/workers/test/terminate_worker.js @@ -0,0 +1,11 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onmessage = function (event) { + throw "No messages should reach me!"; +}; + +setInterval(function () { + postMessage("Still alive!"); +}, 100); diff --git a/dom/workers/test/test_404.html b/dom/workers/test/test_404.html new file mode 100644 index 0000000000..59ab691e02 --- /dev/null +++ b/dom/workers/test/test_404.html @@ -0,0 +1,39 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads +--> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("nonexistent_worker.js"); + + worker.onmessage = function(event) { + ok(false, "Shouldn't ever get a message!"); + SimpleTest.finish(); + } + + worker.onerror = function(event) { + is(event.target, worker); + event.preventDefault(); + SimpleTest.finish(); + }; + + worker.postMessage("dummy"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_WorkerDebugger.initialize.xhtml b/dom/workers/test/test_WorkerDebugger.initialize.xhtml new file mode 100644 index 0000000000..9e7ec2e9a6 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.initialize.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger.initialize" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger.initialize_worker.js"; + const CHILD_WORKER_URL = "WorkerDebugger.initialize_childWorker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebugger.initialize_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker that creates a child worker, wait for their " + + "debuggers to be registered, and initialize them."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL) + ]); + let worker = new Worker(WORKER_URL); + await promise; + + info("Check that the debuggers are initialized before the workers " + + "start running."); + await waitForMultiple([ + waitForWorkerMessage(worker, "debugger"), + waitForWorkerMessage(worker, "worker"), + waitForWorkerMessage(worker, "child:debugger"), + waitForWorkerMessage(worker, "child:worker") + ]); + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger.postMessage.xhtml b/dom/workers/test/test_WorkerDebugger.postMessage.xhtml new file mode 100644 index 0000000000..58e15b3a05 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.postMessage.xhtml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger.postMessage" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger.postMessage_worker.js"; + const CHILD_WORKER_URL = "WorkerDebugger.postMessage_childWorker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebugger.postMessage_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker that creates a child worker, wait for their " + + "debuggers to be registered, and initialize them."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL) + ]); + let worker = new Worker(WORKER_URL); + let [dbg, childDbg] = await promise; + + info("Send a request to the worker debugger. This should cause the " + + "the worker debugger to send a response."); + promise = waitForDebuggerMessage(dbg, "pong"); + dbg.postMessage("ping"); + await promise; + + info("Send a request to the child worker debugger. This should cause " + + "the child worker debugger to send a response."); + promise = waitForDebuggerMessage(childDbg, "pong"); + childDbg.postMessage("ping"); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger.xhtml b/dom/workers/test/test_WorkerDebugger.xhtml new file mode 100644 index 0000000000..d0810b851a --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger.xhtml @@ -0,0 +1,145 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger_worker.js"; + const CHILD_WORKER_URL = "WorkerDebugger_childWorker.js"; + const SHARED_WORKER_URL = "WorkerDebugger_sharedWorker.js"; + + add_task( + async function runTest() { + info("Create a top-level chrome worker that creates a non-top-level " + + "content worker and wait for their debuggers to be registered."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL), + waitForRegister(CHILD_WORKER_URL) + ]); + worker = new ChromeWorker(WORKER_URL); + let [dbg, childDbg] = await promise; + + info("Check that the top-level chrome worker debugger has the " + + "correct properties."); + is(dbg.isChrome, true, + "Chrome worker debugger should be chrome."); + is(dbg.parent, null, + "Top-level debugger should not have parent."); + is(dbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED, + "Chrome worker debugger should be dedicated."); + is(dbg.window, window, + "Top-level dedicated worker debugger should have window."); + + info("Check that the non-top-level content worker debugger has the " + + "correct properties."); + is(childDbg.isChrome, false, + "Content worker debugger should be content."); + is(childDbg.parent, dbg, + "Non-top-level worker debugger should have parent."); + is(childDbg.type, Ci.nsIWorkerDebugger.TYPE_DEDICATED, + "Content worker debugger should be dedicated."); + is(childDbg.window, window, + "Non-top-level worker debugger should have window."); + + info("Terminate the top-level chrome worker and the non-top-level " + + "content worker, and wait for their debuggers to be " + + "unregistered and closed."); + promise = waitForMultiple([ + waitForUnregister(CHILD_WORKER_URL), + waitForDebuggerClose(childDbg), + waitForUnregister(WORKER_URL), + waitForDebuggerClose(dbg), + ]); + worker.terminate(); + await promise; + + info("Create a shared worker and wait for its debugger to be " + + "registered"); + promise = waitForRegister(SHARED_WORKER_URL); + worker = new SharedWorker(SHARED_WORKER_URL); + let sharedDbg = await promise; + + info("Check that the shared worker debugger has the correct " + + "properties."); + is(sharedDbg.isChrome, false, + "Shared worker debugger should be content."); + is(sharedDbg.parent, null, + "Shared worker debugger should not have parent."); + is(sharedDbg.type, Ci.nsIWorkerDebugger.TYPE_SHARED, + "Shared worker debugger should be shared."); + is(sharedDbg.window, null, + "Shared worker debugger should not have window."); + + info("Create a shared worker with the same URL and check that its " + + "debugger is not registered again."); + let listener = { + onRegistered () { + ok(false, + "Shared worker debugger should not be registered again."); + }, + }; + wdm.addListener(listener); + + let secondWorker = new SharedWorker(SHARED_WORKER_URL); + + info("Send a message to the shared worker to tell it to close " + + "itself, and wait for its debugger to be closed."); + promise = waitForMultiple([ + waitForUnregister(SHARED_WORKER_URL), + waitForDebuggerClose(sharedDbg) + ]); + secondWorker.port.start(); + secondWorker.port.postMessage("close"); + await promise; + worker = null; + secondWorker = null; + + info("Create a SharedWorker again for the infinite loop test.") + promise = waitForRegister(SHARED_WORKER_URL); + // Give it an explicit name so we don't reuse the above SharedWorker. + worker = new SharedWorker(SHARED_WORKER_URL, "loopy"); + sharedDbg = await promise; + + info("Send a message to the shared worker to tell it to close " + + "itself, then loop forever, and wait for its debugger to be closed."); + promise = waitForMultiple([ + waitForUnregister(SHARED_WORKER_URL), + waitForDebuggerClose(sharedDbg) + ]); + + // When the closing process begins, we schedule a timer to terminate + // the worker in case it's in an infinite loop, which is exactly what + // we do in this test. The default delay is 30 seconds. This test + // previously waited 15 seconds for reasons that were poorly justified. + // We now set it to 100ms because we just want to make sure that the + // timeout mechanism to force cancellation from the parent properly + // works (as long as the parent thread isn't blocked). + await SpecialPowers.pushPrefEnv({"set": [[ "dom.worker.canceling.timeoutMilliseconds", 100 ]]}); + + worker.port.start(); + worker.port.postMessage("close_loop"); + await promise; + + wdm.removeListener(listener); + } + ); + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml new file mode 100644 index 0000000000..89114f5e49 --- /dev/null +++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.createSandbox.xhtml @@ -0,0 +1,49 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerGlobalScope.createSandbox" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebuggerGlobalScope.createSandbox_worker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.createSandbox_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker, wait for its debugger to be registered, and " + + "initialize it."); + let promise = waitForRegister(WORKER_URL, DEBUGGER_URL); + let worker = new Worker(WORKER_URL); + let dbg = await promise; + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to send a response from within a sandbox."); + promise = waitForDebuggerMessage(dbg, "pong"); + dbg.postMessage("ping"); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml new file mode 100644 index 0000000000..d5cff95d39 --- /dev/null +++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.enterEventLoop.xhtml @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerGlobalScope.enterEventLoop" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebuggerGlobalScope.enterEventLoop_worker.js"; + const CHILD_WORKER_URL = "WorkerDebuggerGlobalScope.enterEventLoop_childWorker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.enterEventLoop_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker that creates a child worker, wait for their " + + "debuggers to be registered, and initialize them."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL) + ]); + let worker = new Worker(WORKER_URL); + let [dbg, childDbg] = await promise; + + info("Send a request to the child worker. This should cause the " + + "child worker debugger to enter a nested event loop."); + promise = waitForDebuggerMessage(childDbg, "paused"); + worker.postMessage("child:ping"); + await promise; + + info("Send a request to the child worker debugger. This should cause " + + "the child worker debugger to enter a second nested event loop."); + promise = waitForDebuggerMessage(childDbg, "paused"); + childDbg.postMessage("eval"); + await promise; + + info("Send a request to the child worker debugger. This should cause " + + "the child worker debugger to leave its second nested event " + + "loop. The child worker debugger should not send a response " + + "for its previous request until after it has left the nested " + + "event loop."); + promise = waitForMultiple([ + waitForDebuggerMessage(childDbg, "resumed"), + waitForDebuggerMessage(childDbg, "evalled") + ]); + childDbg.postMessage("resume"); + await promise; + + info("Send a request to the child worker debugger. This should cause " + + "the child worker debugger to leave its first nested event loop." + + "The child worker should not send a response for its earlier " + + "request until after the child worker debugger has left the " + + "nested event loop."); + promise = waitForMultiple([ + waitForDebuggerMessage(childDbg, "resumed"), + waitForWorkerMessage(worker, "child:pong") + ]); + childDbg.postMessage("resume"); + await promise; + + info("Send a request to the worker. This should cause the worker " + + "debugger to enter a nested event loop."); + promise = waitForDebuggerMessage(dbg, "paused"); + worker.postMessage("ping"); + await promise; + + info("Terminate the worker. This should not cause the worker " + + "debugger to terminate as well."); + worker.terminate(); + + worker.onmessage = function () { + ok(false, "Worker should have been terminated."); + }; + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to enter a second nested event loop."); + promise = waitForDebuggerMessage(dbg, "paused"); + dbg.postMessage("eval"); + await promise; + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to leave its second nested event loop. The " + + "worker debugger should not send a response for the previous " + + "request until after leaving the nested event loop."); + promise = waitForMultiple([ + waitForDebuggerMessage(dbg, "resumed"), + waitForDebuggerMessage(dbg, "evalled") + ]); + dbg.postMessage("resume"); + await promise; + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to leave its first nested event loop. The " + + "worker should not send a response for its earlier request, " + + "since it has been terminated."); + promise = waitForMultiple([ + waitForDebuggerMessage(dbg, "resumed"), + ]); + dbg.postMessage("resume"); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml new file mode 100644 index 0000000000..20e731c1cf --- /dev/null +++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.reportError.xhtml @@ -0,0 +1,95 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerGlobalScope.reportError" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebuggerGlobalScope.reportError_worker.js"; + const CHILD_WORKER_URL = "WorkerDebuggerGlobalScope.reportError_childWorker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.reportError_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker that creates a child worker, wait for their " + + "debuggers to be registered, and initialize them."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL) + ]); + let worker = new Worker(WORKER_URL); + let [dbg, childDbg] = await promise; + + worker.onmessage = function () { + ok(false, "Debugger error events should not be fired at workers."); + }; + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to report an error."); + promise = waitForDebuggerError(dbg); + dbg.postMessage("report"); + let error = await promise; + is(error.fileName, DEBUGGER_URL, + "fileName should be name of file from which error is reported."); + is(error.lineNumber, 6, + "lineNumber should be line number from which error is reported."); + is(error.message, "reported", "message should be reported."); + + info("Send a request to the worker debugger. This should cause the " + + "worker debugger to throw an error."); + promise = waitForDebuggerError(dbg); + dbg.postMessage("throw"); + error = await promise; + is(error.fileName, DEBUGGER_URL, + "fileName should be name of file from which error is thrown"); + is(error.lineNumber, 9, + "lineNumber should be line number from which error is thrown"); + is(error.message, "Error: thrown", "message should be Error: thrown"); + + info("Send a reqeust to the child worker debugger. This should cause " + + "the child worker debugger to report an error."); + promise = waitForDebuggerError(childDbg); + childDbg.postMessage("report"); + error = await promise; + is(error.fileName, DEBUGGER_URL, + "fileName should be name of file from which error is reported."); + is(error.lineNumber, 6, + "lineNumber should be line number from which error is reported."); + is(error.message, "reported", "message should be reported."); + + info("Send a message to the child worker debugger. This should cause " + + "the child worker debugger to throw an error."); + promise = waitForDebuggerError(childDbg); + childDbg.postMessage("throw"); + error = await promise; + is(error.fileName, DEBUGGER_URL, + "fileName should be name of file from which error is thrown"); + is(error.lineNumber, 9, + "lineNumber should be line number from which error is thrown"); + is(error.message, "Error: thrown", "message should be Error: thrown"); + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml new file mode 100644 index 0000000000..4b09f01708 --- /dev/null +++ b/dom/workers/test/test_WorkerDebuggerGlobalScope.setImmediate.xhtml @@ -0,0 +1,52 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerGlobalScope.setImmediate" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebuggerGlobalScope.setImmediate_worker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebuggerGlobalScope.setImmediate_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + let promise = waitForRegister(WORKER_URL, DEBUGGER_URL); + let worker = new Worker(WORKER_URL); + let dbg = await promise; + + info("Send a request to the worker debugger. This should cause a " + + "the worker debugger to send two responses. The worker debugger " + + "should send the second response before the first one, since " + + "the latter is delayed until the next tick of the event loop."); + promise = waitForMultiple([ + waitForDebuggerMessage(dbg, "pong2"), + waitForDebuggerMessage(dbg, "pong1") + ]); + dbg.postMessage("ping"); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebuggerManager.xhtml b/dom/workers/test/test_WorkerDebuggerManager.xhtml new file mode 100644 index 0000000000..1ed09563de --- /dev/null +++ b/dom/workers/test/test_WorkerDebuggerManager.xhtml @@ -0,0 +1,99 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerManager" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebuggerManager_worker.js"; + const CHILD_WORKER_URL = "WorkerDebuggerManager_childWorker.js"; + + add_task( + async function runTest() { + info("Check that worker debuggers are not enumerated before they are " + + "registered."); + ok(!findDebugger(WORKER_URL), + "Worker debugger should not be enumerated before it is registered."); + ok(!findDebugger(CHILD_WORKER_URL), + "Child worker debugger should not be enumerated before it is " + + "registered."); + + info("Create a worker that creates a child worker, and wait for " + + "their debuggers to be registered."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL), + waitForRegister(CHILD_WORKER_URL) + ]); + let worker = new Worker(WORKER_URL); + let [dbg, childDbg] = await promise; + + info("Check that worker debuggers are enumerated after they are " + + "registered."); + ok(findDebugger(WORKER_URL), + "Worker debugger should be enumerated after it is registered."); + ok(findDebugger(CHILD_WORKER_URL), + "Child worker debugger should be enumerated after it is " + + "registered."); + + info("Check that worker debuggers are not closed before they are " + + "unregistered."); + is(dbg.isClosed, false, + "Worker debugger should not be closed before it is unregistered."); + is(childDbg.isClosed, false, + "Child worker debugger should not be closed before it is " + + "unregistered"); + + info("Terminate the worker and the child worker, and wait for their " + + "debuggers to be unregistered."); + promise = waitForMultiple([ + waitForUnregister(CHILD_WORKER_URL), + waitForUnregister(WORKER_URL), + ]); + worker.terminate(); + await promise; + + info("Check that worker debuggers are not enumerated after they are " + + "unregistered."); + ok(!findDebugger(WORKER_URL), + "Worker debugger should not be enumerated after it is " + + "unregistered."); + ok(!findDebugger(CHILD_WORKER_URL), + "Child worker debugger should not be enumerated after it is " + + "unregistered."); + + info("Check that worker debuggers are closed after they are " + + "unregistered."); + is(dbg.isClosed, true, + "Worker debugger should be closed after it is unregistered."); + is(childDbg.isClosed, true, + "Child worker debugger should be closed after it is unregistered."); + + info("Check that property accesses on worker debuggers throws " + + "after they are closed."); + assertThrows(() => dbg.url, + "Property accesses on worker debugger should throw " + + "after it is closed."); + assertThrows(() => childDbg.url, + "Property accesses on child worker debugger should " + + "throw after it is closed."); + } + ); + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger_console.xhtml b/dom/workers/test/test_WorkerDebugger_console.xhtml new file mode 100644 index 0000000000..b38ccdac45 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger_console.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebuggerGlobalScope.console methods" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger.console_worker.js"; + const CHILD_WORKER_URL = "WorkerDebugger.console_childWorker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebugger.console_debugger.js"; + + consoleMessagesReceived = 0; + function test() { + const ConsoleAPIStorage = SpecialPowers.Cc[ + "@mozilla.org/consoleAPI-storage;1" + ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage); + + function consoleListener() { + this.observe = this.observe.bind(this); + ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal); + } + + consoleListener.prototype = { + observe(aSubject) { + var obj = aSubject.wrappedJSObject; + if (obj.arguments[0] == "Hello from the debugger script!" && + !consoleMessagesReceived) { + consoleMessagesReceived++; + ok(true, "Something has been received"); + ConsoleAPIStorage.removeLogEventListener(this.observe); + } + } + } + + var cl = new consoleListener(); + + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker that creates a child worker, wait for their " + + "debuggers to be registered, and initialize them."); + let promise = waitForMultiple([ + waitForRegister(WORKER_URL, DEBUGGER_URL), + waitForRegister(CHILD_WORKER_URL, DEBUGGER_URL) + ]); + let worker = new Worker(WORKER_URL); + let [dbg, childDbg] = await promise; + + info("Send a request to the worker debugger. This should cause the " + + "the worker debugger to send a response."); + dbg.addListener({ + onMessage(msg) { + try { + msg = JSON.parse(msg); + } catch(e) { + ok(false, "Something went wrong"); + return; + } + + if (msg.type == 'finish') { + ok(consoleMessagesReceived, "We received something via debugger console!"); + dbg.removeListener(this); + SimpleTest.finish(); + return; + } + + if (msg.type == 'status') { + ok(msg.what, msg.msg); + return; + } + + ok(false, "Something went wrong"); + } + }); + + dbg.postMessage("do magic"); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger_frozen.xhtml b/dom/workers/test/test_WorkerDebugger_frozen.xhtml new file mode 100644 index 0000000000..f29def78ce --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger_frozen.xhtml @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger with frozen workers" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WINDOW1_URL = "WorkerDebugger_frozen_window1.html"; + const WINDOW2_URL = "WorkerDebugger_frozen_window2.html"; + + const WORKER1_URL = "WorkerDebugger_frozen_worker1.js"; + const WORKER2_URL = "WorkerDebugger_frozen_worker2.js"; + + add_task( + async function runTest() { + await SpecialPowers.pushPrefEnv({set: + [["browser.sessionhistory.max_total_viewers", 10]]}); + + let promise = waitForMultiple([ + waitForRegister(WORKER1_URL), + waitForWindowMessage(window, "ready"), + ]); + let testWin = window.open(WINDOW1_URL, "testWin");; + let [dbg1] = await promise; + is(dbg1.isClosed, false, + "debugger for worker on page 1 should not be closed"); + + promise = waitForMultiple([ + waitForUnregister(WORKER1_URL), + waitForDebuggerClose(dbg1), + waitForRegister(WORKER2_URL), + waitForWindowMessage(window, "ready"), + ]); + testWin.location = WINDOW2_URL; + let [,, dbg2] = await promise; + is(dbg1.isClosed, true, + "debugger for worker on page 1 should be closed"); + is(dbg2.isClosed, false, + "debugger for worker on page 2 should not be closed"); + + promise = Promise.all([ + waitForUnregister(WORKER2_URL), + waitForDebuggerClose(dbg2), + waitForRegister(WORKER1_URL) + ]); + testWin.history.back(); + [,, dbg1] = await promise; + is(dbg1.isClosed, false, + "debugger for worker on page 1 should not be closed"); + is(dbg2.isClosed, true, + "debugger for worker on page 2 should be closed"); + } + ); + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger_promise.xhtml b/dom/workers/test/test_WorkerDebugger_promise.xhtml new file mode 100644 index 0000000000..14d50969b5 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger_promise.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. ++ http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger with DOM Promises" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger_promise_worker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebugger_promise_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + let promise = waitForRegister(WORKER_URL, DEBUGGER_URL); + let worker = new Worker(WORKER_URL); + let dbg = await promise; + + info("Send a request to the worker. This should cause the worker " + + "to send a response."); + promise = waitForWorkerMessage(worker, "resolved"); + worker.postMessage("resolve"); + await promise; + + info("Send a request to the debugger. This should cause the debugger " + + "to send a response."); + promise = waitForDebuggerMessage(dbg, "resolved"); + dbg.postMessage("resolve"); + await promise; + + info("Send a request to the worker. This should cause the debugger " + + "to enter a nested event loop."); + promise = waitForDebuggerMessage(dbg, "paused"); + worker.postMessage("pause"); + await promise; + + info("Send a request to the debugger. This should cause the debugger " + + "to leave the nested event loop."); + promise = waitForMultiple([ + waitForDebuggerMessage(dbg, "resumed"), + waitForWorkerMessage(worker, "resumed") + ]); + dbg.postMessage("resume"); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_WorkerDebugger_suspended.xhtml b/dom/workers/test/test_WorkerDebugger_suspended.xhtml new file mode 100644 index 0000000000..d0c9bff552 --- /dev/null +++ b/dom/workers/test/test_WorkerDebugger_suspended.xhtml @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="Test for WorkerDebugger with suspended workers" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + const WORKER_URL = "WorkerDebugger_suspended_worker.js"; + const DEBUGGER_URL = BASE_URL + "WorkerDebugger_suspended_debugger.js"; + + function test() { + (async function() { + SimpleTest.waitForExplicitFinish(); + + info("Create a worker, wait for its debugger to be registered, and " + + "initialize it."); + let promise = waitForRegister(WORKER_URL, DEBUGGER_URL); + let worker = new Worker(WORKER_URL); + let dbg = await promise; + + info("Send a request to the worker. This should cause both the " + + "worker and the worker debugger to send a response."); + promise = waitForMultiple([ + waitForWorkerMessage(worker, "worker"), + waitForDebuggerMessage(dbg, "debugger") + ]); + worker.postMessage("ping"); + await promise; + + info("Suspend the workers for this window, and send another request " + + "to the worker. This should cause only the worker debugger to " + + "send a response."); + let windowUtils = window.windowUtils; + windowUtils.suspendTimeouts(); + function onmessage() { + ok(false, "The worker should not send a response."); + }; + worker.addEventListener("message", onmessage); + promise = waitForDebuggerMessage(dbg, "debugger"); + worker.postMessage("ping"); + await promise; + worker.removeEventListener("message", onmessage); + + info("Resume the workers for this window. This should cause the " + + "worker to send a response to the previous request."); + promise = waitForWorkerMessage(worker, "worker"); + windowUtils.resumeTimeouts(); + await promise; + + SimpleTest.finish(); + })(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_atob.html b/dom/workers/test/test_atob.html new file mode 100644 index 0000000000..0e82029b46 --- /dev/null +++ b/dom/workers/test/test_atob.html @@ -0,0 +1,57 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script src="atob_worker.js" language="javascript"></script> +<script class="testbody" type="text/javascript"> + + var dataIndex = 0; + + var worker = new Worker("atob_worker.js"); + worker.onmessage = function(event) { + switch (event.data.type) { + case "done": + is(dataIndex, data.length, "Saw all values"); + SimpleTest.finish(); + return; + case "btoa": + is(btoa(data[dataIndex]), event.data.value, + "Good btoa value " + dataIndex); + break; + case "atob": + is(atob(btoa(data[dataIndex])) + "", event.data.value, + "Good round trip value " + dataIndex); + dataIndex++; + break; + default: + ok(false, "Worker posted a bad message: " + event.message); + worker.terminate(); + SimpleTest.finish(); + } + } + + worker.onerror = function(event) { + ok(false, "Worker threw an error: " + event.message); + worker.terminate(); + SimpleTest.finish(); + } + + worker.postMessage("go"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_blobConstructor.html b/dom/workers/test/test_blobConstructor.html new file mode 100644 index 0000000000..4aff5b545b --- /dev/null +++ b/dom/workers/test/test_blobConstructor.html @@ -0,0 +1,60 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE html> +<html> +<!-- +Tests of DOM Worker Blob constructor +--> +<head> + <title>Test for DOM Worker Blob constructor</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +(function() { + onerror = function(e) { + ok(false, "Main Thread had an error: " + event.data); + SimpleTest.finish(); + }; + function f() { + onmessage = function(e) { + var b = new Blob([e.data, "World"],{type: "text/plain"}); + var fr = new FileReaderSync(); + postMessage({text: fr.readAsText(b), type: b.type}); + }; + } + var b = new Blob([f,"f();"]); + var u = URL.createObjectURL(b); + var w = new Worker(u); + w.onmessage = function(e) { + URL.revokeObjectURL(u); + is(e.data.text, fr.result); + is(e.data.type, "text/plain"); + SimpleTest.finish(); + }; + w.onerror = function(e) { + is(e.target, w); + ok(false, "Worker had an error: " + e.message); + SimpleTest.finish(); + }; + + b = new Blob(["Hello, "]); + var fr = new FileReader(); + fr.readAsText(new Blob([b, "World"],{})); + fr.onload = function() { + w.postMessage(b); + }; + SimpleTest.waitForExplicitFinish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_blobWorkers.html b/dom/workers/test/test_blobWorkers.html new file mode 100644 index 0000000000..6ecd6c6f4b --- /dev/null +++ b/dom/workers/test/test_blobWorkers.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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="text/javascript"> + const message = "hi"; + + const workerScript = + "onmessage = function(event) {" + + " postMessage(event.data);" + + "};"; + + var worker = new Worker(URL.createObjectURL(new Blob([workerScript]))); + worker.onmessage = function(event) { + is(event.data, message, "Got correct message"); + SimpleTest.finish(); + }; + worker.postMessage(message); + + SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_bug1002702.html b/dom/workers/test/test_bug1002702.html new file mode 100644 index 0000000000..c020dcfd05 --- /dev/null +++ b/dom/workers/test/test_bug1002702.html @@ -0,0 +1,26 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 1002702</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + +var port = new SharedWorker('data:application/javascript,1').port; +port.close(); +SpecialPowers.forceGC(); +ok(true, "No crash \\o/"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1010784.html b/dom/workers/test/test_bug1010784.html new file mode 100644 index 0000000000..3e2f62971d --- /dev/null +++ b/dom/workers/test/test_bug1010784.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1010784 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1010784</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1010784">Mozilla Bug 1010784</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + + var worker = new Worker("file_bug1010784_worker.js"); + + worker.onmessage = function(event) { + is(event.data, "done", "Got correct result"); + SimpleTest.finish(); + } + + worker.postMessage("testXHR.txt"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1014466.html b/dom/workers/test/test_bug1014466.html new file mode 100644 index 0000000000..26ef2fb316 --- /dev/null +++ b/dom/workers/test/test_bug1014466.html @@ -0,0 +1,42 @@ +<!-- +2 Any copyright is dedicated to the Public Domain. +3 http://creativecommons.org/publicdomain/zero/1.0/ +4 --> +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1014466 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1014466</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1014466">Mozilla Bug 1014466</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + + var worker = new Worker("bug1014466_worker.js"); + + worker.onmessage = function(event) { + if (event.data.type == 'finish') { + SimpleTest.finish(); + } else if (event.data.type == 'status') { + ok(event.data.status, event.data.msg); + } + }; + + worker.postMessage(true); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1020226.html b/dom/workers/test/test_bug1020226.html new file mode 100644 index 0000000000..1ed69db41e --- /dev/null +++ b/dom/workers/test/test_bug1020226.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1020226 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1020226</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020226">Mozilla Bug 1020226</a> +<p id="display"></p> +<div id="content" style="display: none"> + +<iframe id="iframe" src="bug1020226_frame.html" onload="finishTest();"> +</iframe> +</div> +<pre id="test"> +<script type="application/javascript"> +function finishTest() { + document.getElementById("iframe").onload = null; + window.onmessage = function(e) { + info("Got message"); + document.getElementById("iframe").src = "about:blank"; + // We aren't really interested in the test, it shouldn't crash when the + // worker is GCed later. + ok(true, "Should not crash"); + SimpleTest.finish(); + }; +} + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1036484.html b/dom/workers/test/test_bug1036484.html new file mode 100644 index 0000000000..feada50f5a --- /dev/null +++ b/dom/workers/test/test_bug1036484.html @@ -0,0 +1,52 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads: bug 1036484 +--> +<head> + <title>Test for DOM Worker Threads: bug 1036484</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function test(script) { + var worker = new Worker(script); + + worker.onmessage = function(event) { + ok(false, "Shouldn't ever get a message!"); + } + + worker.onerror = function(event) { + is(event.target, worker); + event.preventDefault(); + runTests(); + }; + + worker.postMessage("dummy"); +} + +var tests = [ '404_server.sjs', '404_server.sjs?js' ]; +function runTests() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var script = tests.shift(); + test(script); +} + +SimpleTest.waitForExplicitFinish(); +runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1060621.html b/dom/workers/test/test_bug1060621.html new file mode 100644 index 0000000000..d2af81c93e --- /dev/null +++ b/dom/workers/test/test_bug1060621.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for URLSearchParams object in workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("bug1060621_worker.js"); + + worker.onmessage = function(event) { + ok(true, "The operation is done. We should not leak."); + SimpleTest.finish(); + }; + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1062920.html b/dom/workers/test/test_bug1062920.html new file mode 100644 index 0000000000..bea2b7f461 --- /dev/null +++ b/dom/workers/test/test_bug1062920.html @@ -0,0 +1,69 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for navigator property override</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + function checkValues() { + var worker = new Worker("bug1062920_worker.js"); + + worker.onmessage = function(event) { + var ifr = document.createElement('IFRAME'); + ifr.src = "about:blank"; + + ifr.addEventListener('load', function() { + var nav = ifr.contentWindow.navigator; + is(event.data.appCodeName, nav.appCodeName, "appCodeName should match"); + is(event.data.appName, nav.appName, "appName should match"); + is(event.data.appVersion, nav.appVersion, "appVersion should match"); + is(event.data.platform, nav.platform, "platform should match"); + is(event.data.userAgent, nav.userAgent, "userAgent should match"); + is(event.data.product, nav.product, "product should match"); + runTests(); + }); + + document.getElementById('content').appendChild(ifr); + }; + } + + function replaceAndCheckValues() { + SpecialPowers.pushPrefEnv({"set": [ + ["general.appversion.override", "appVersion overridden"], + ["general.platform.override", "platform overridden"], + ["general.useragent.override", "userAgent overridden"] + ]}, checkValues); + } + + var tests = [ + checkValues, + replaceAndCheckValues + ]; + + function runTests() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); + } + + SimpleTest.waitForExplicitFinish(); + runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1062920.xhtml b/dom/workers/test/test_bug1062920.xhtml new file mode 100644 index 0000000000..0dfeccfb77 --- /dev/null +++ b/dom/workers/test/test_bug1062920.xhtml @@ -0,0 +1,64 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="DOM Worker Threads Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + + function checkValues() { + var worker = new Worker("bug1062920_worker.js"); + + worker.onmessage = function(event) { + is(event.data.appCodeName, navigator.appCodeName, "appCodeName should match"); + is(event.data.appVersion, navigator.appVersion, "appVersion should match"); + isnot(event.data.appVersion, "appVersion overridden", "appVersion is not overridden"); + is(event.data.platform, navigator.platform, "platform should match"); + isnot(event.data.platform, "platform overridden", "platform is not overridden"); + is(event.data.userAgent, navigator.userAgent, "userAgent should match"); + is(event.data.product, navigator.product, "product should match"); + runTests(); + }; + } + + function replaceAndCheckValues() { + SpecialPowers.pushPrefEnv({"set": [ + ["general.appversion.override", "appVersion overridden"], + ["general.platform.override", "platform overridden"], + ["general.useragent.override", "userAgent overridden"] + ]}, checkValues); + } + + var tests = [ + replaceAndCheckValues, + checkValues + ]; + + function runTests() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); + } + + SimpleTest.waitForExplicitFinish(); + runTests(); + + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_bug1063538.html b/dom/workers/test/test_bug1063538.html new file mode 100644 index 0000000000..a1dc6624b9 --- /dev/null +++ b/dom/workers/test/test_bug1063538.html @@ -0,0 +1,47 @@ +<!-- +2 Any copyright is dedicated to the Public Domain. +3 http://creativecommons.org/publicdomain/zero/1.0/ +4 --> +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1063538 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1063538</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1063538">Mozilla Bug 1063538</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +function runTest() { + var worker = new Worker("bug1063538_worker.js"); + + worker.onmessage = function(e) { + if (e.data.type == 'finish') { + ok(e.data.progressFired, "Progress was fired."); + SimpleTest.finish(); + } + }; + + worker.postMessage(true); +} + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTest); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1104064.html b/dom/workers/test/test_bug1104064.html new file mode 100644 index 0000000000..1c8b3ac92c --- /dev/null +++ b/dom/workers/test/test_bug1104064.html @@ -0,0 +1,27 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 1104064</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +var worker = new Worker("bug1104064_worker.js"); +worker.onmessage = function() { + ok(true, "setInterval has been called twice."); + SimpleTest.finish(); +} +worker.postMessage("go"); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1132395.html b/dom/workers/test/test_bug1132395.html new file mode 100644 index 0000000000..8d424e5ad4 --- /dev/null +++ b/dom/workers/test/test_bug1132395.html @@ -0,0 +1,40 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for 1132395</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script class="testbody" type="text/javascript"> + +// This test is full of dummy debug messages. This is because I need to follow +// an hard-to-reproduce timeout failure. + +info("test started"); +var sw = new SharedWorker('bug1132395_sharedWorker.js'); +sw.port.onmessage = function(event) { + info("sw.onmessage received"); + ok(true, "We didn't crash."); + SimpleTest.finish(); +} + +sw.onerror = function(event) { + ok(false, "Failed to create a ServiceWorker"); + SimpleTest.finish(); +} + +info("sw.postmessage called"); +sw.port.postMessage('go'); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1132924.html b/dom/workers/test/test_bug1132924.html new file mode 100644 index 0000000000..b5b952e908 --- /dev/null +++ b/dom/workers/test/test_bug1132924.html @@ -0,0 +1,28 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for 1132924</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +var w = new Worker('bug1132924_worker.js'); +w.onmessage = function(event) { + ok(true, "We are still alive."); + SimpleTest.finish(); +} + +w.postMessage('go'); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1278777.html b/dom/workers/test/test_bug1278777.html new file mode 100644 index 0000000000..c995212bd0 --- /dev/null +++ b/dom/workers/test/test_bug1278777.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1278777 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1278777</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1278777">Mozilla Bug 1278777</a> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var worker = new Worker('worker_bug1278777.js'); +worker.onerror = function() { + ok(false, "We should not see any error."); + SimpleTest.finish(); +} + +worker.onmessage = function(e) { + ok(e.data, "Everything seems ok."); + SimpleTest.finish(); +} + + </script> +</body> +</html> diff --git a/dom/workers/test/test_bug1301094.html b/dom/workers/test/test_bug1301094.html new file mode 100644 index 0000000000..efe25fa3a9 --- /dev/null +++ b/dom/workers/test/test_bug1301094.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1301094 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1301094</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1301094">Mozilla Bug 1301094</a> + <input id="file" type="file"></input> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var url = SimpleTest.getTestFileURL("script_createFile.js"); +script = SpecialPowers.loadChromeScript(url); + +var mainThreadOk, workerOk; + +function maybeFinish() { + if (mainThreadOk & workerOk) { + SimpleTest.finish(); + } +} + +function onOpened(message) { + var input = document.getElementById('file'); + SpecialPowers.wrap(input).mozSetDndFilesAndDirectories([message.data]); + + var worker = new Worker('worker_bug1301094.js'); + worker.onerror = function() { + ok(false, "We should not see any error."); + SimpleTest.finish(); + } + + worker.onmessage = function(e) { + ok(e.data, "Everything seems OK on the worker-side."); + + workerOk = true; + maybeFinish(); + } + + is(input.files.length, 1, "We have something"); + ok(input.files[0] instanceof Blob, "We have one Blob"); + worker.postMessage(input.files[0]); + + var xhr = new XMLHttpRequest(); + xhr.open("POST", 'worker_bug1301094.js', false); + xhr.onload = function() { + ok(xhr.responseText, "Everything seems OK on the main-thread-side."); + mainThreadOk = true; + maybeFinish(); + }; + + var fd = new FormData(); + fd.append('file', input.files[0]); + xhr.send(fd); +} + +script.addMessageListener("file.opened", onOpened); +script.sendAsyncMessage("file.open"); + + </script> +</body> +</html> diff --git a/dom/workers/test/test_bug1317725.html b/dom/workers/test/test_bug1317725.html new file mode 100644 index 0000000000..7da3fc2644 --- /dev/null +++ b/dom/workers/test/test_bug1317725.html @@ -0,0 +1,49 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 1317725</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<input type="file" id="file" /> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var url = SimpleTest.getTestFileURL("script_createFile.js"); +script = SpecialPowers.loadChromeScript(url); + +function onOpened(message) { + var input = document.getElementById('file'); + SpecialPowers.wrap(input).mozSetFileArray([message.data]); + + var worker = new Worker("test_bug1317725.js"); + worker.onerror = function(e) { + ok(false, "We should not see any error."); + SimpleTest.finish(); + } + + worker.onmessage = function(e) { + ok(e.data, "Everything seems OK on the worker-side."); + SimpleTest.finish(); + } + + is(input.files.length, 1, "We have something"); + ok(input.files[0] instanceof Blob, "We have one Blob"); + worker.postMessage(input.files[0]); +} + +script.addMessageListener("file.opened", onOpened); +script.sendAsyncMessage("file.open"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug1317725.js b/dom/workers/test/test_bug1317725.js new file mode 100644 index 0000000000..858488fac3 --- /dev/null +++ b/dom/workers/test/test_bug1317725.js @@ -0,0 +1,8 @@ +onmessage = function (e) { + var data = new FormData(); + data.append("Filedata", e.data.slice(0, 127), encodeURI(e.data.name)); + var xhr = new XMLHttpRequest(); + xhr.open("POST", location.href, false); + xhr.send(data); + postMessage("No crash \\o/"); +}; diff --git a/dom/workers/test/test_bug1824498.html b/dom/workers/test/test_bug1824498.html new file mode 100644 index 0000000000..ea01cabe1b --- /dev/null +++ b/dom/workers/test/test_bug1824498.html @@ -0,0 +1,38 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Worker Import failure (Bug 1824498)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1824498"> +Worker Import failure test: Bug 1824498 +</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +document.addEventListener("DOMContentLoaded", async () => { + await SpecialPowers.pushPrefEnv( + { set: [["dom.workers.modules.enabled", true ]] }); + + const worker = new Worker("worker_bug1824498.mjs", {"type": "module"}) + worker.onerror = function(event) { + ok(true, "not assert"); + SimpleTest.finish(); + }; +}); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug949946.html b/dom/workers/test/test_bug949946.html new file mode 100644 index 0000000000..41b021a098 --- /dev/null +++ b/dom/workers/test/test_bug949946.html @@ -0,0 +1,26 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 949946</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + +new SharedWorker('sharedWorker_sharedWorker.js'); +new SharedWorker('sharedWorker_sharedWorker.js', ':'); +new SharedWorker('sharedWorker_sharedWorker.js', '|||'); +ok(true, "3 SharedWorkers created!"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug978260.html b/dom/workers/test/test_bug978260.html new file mode 100644 index 0000000000..056b8b6c72 --- /dev/null +++ b/dom/workers/test/test_bug978260.html @@ -0,0 +1,35 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + SimpleTest.waitForExplicitFinish(); + + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + var worker = new Worker("bug978260_worker.js"); + worker.onmessage = function(event) { + is(event.data, "loaded"); + SimpleTest.finish(); + } + } + + xhr.open('GET', '/', false); + xhr.send(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_bug998474.html b/dom/workers/test/test_bug998474.html new file mode 100644 index 0000000000..93632a5de4 --- /dev/null +++ b/dom/workers/test/test_bug998474.html @@ -0,0 +1,40 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 998474</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="boom();"> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + +function boom() +{ + var worker = new SharedWorker("bug998474_worker.js"); + + setTimeout(function() { + port = worker.port; + port.postMessage(""); + + setTimeout(function() { + port.start(); + ok(true, "Still alive!"); + SimpleTest.finish(); + }, 150); + }, 150); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_chromeWorker.html b/dom/workers/test/test_chromeWorker.html new file mode 100644 index 0000000000..35d5c08928 --- /dev/null +++ b/dom/workers/test/test_chromeWorker.html @@ -0,0 +1,26 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + try { + var worker = new ChromeWorker("simpleThread_worker.js"); + ok(false, "ChromeWorker constructor should be blocked!"); + } + catch (e) { + ok(true, "ChromeWorker constructor wasn't blocked!"); + } + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_chromeWorker.xhtml b/dom/workers/test/test_chromeWorker.xhtml new file mode 100644 index 0000000000..65d14f8851 --- /dev/null +++ b/dom/workers/test/test_chromeWorker.xhtml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="DOM Worker Threads Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + add_task(async function classic_worker_test() { + let worker = window.classicWorker = new ChromeWorker("chromeWorker_worker.js"); + await new Promise((resolve, reject) => { + worker.onmessage = function(event) { + is(event.data, "Done!", "Got the done message!"); + resolve(); + }; + worker.onerror = function(event) { + ok(false, "Classic Worker had an error: " + event.message); + worker.terminate(); + reject(); + }; + worker.postMessage("go"); + }); + }); + + add_task(async function module_worker_test() { + waitForWorkerFinish(); + + let worker = window.moduleWorker = new ChromeWorker("chromeWorker_worker.sys.mjs", { type: "module" }); + await new Promise((resolve, reject) => { + worker.onmessage = function(event) { + is(event.data, "Done!", "Got the done message!"); + resolve(); + }; + worker.onerror = function(event) { + ok(false, "Module Worker had an error: " + event.message); + worker.terminate(); + reject(); + }; + worker.postMessage("go"); + }); + }); + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_chromeWorkerJSM.xhtml b/dom/workers/test/test_chromeWorkerJSM.xhtml new file mode 100644 index 0000000000..6341737815 --- /dev/null +++ b/dom/workers/test/test_chromeWorkerJSM.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<window title="DOM Worker Threads Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test();"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <script type="application/javascript"> + <![CDATA[ + + function test() + { + waitForWorkerFinish(); + + var worker; + + function done() + { + worker = null; + finish(); + } + + function messageCallback(event) { + is(event.data, "Done", "Correct message"); + done(); + } + + function errorCallback(event) { + ok(false, "Worker had an error: " + event.message); + done(); + } + + const {WorkerTest} = ChromeUtils.import("chrome://mochitests/content/chrome/dom/workers/test/WorkerTest.jsm"); + + worker = WorkerTest.go(window.location.href, messageCallback, + errorCallback); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display:none;"></div> + <pre id="test"></pre> + </body> + <label id="test-result"/> +</window> diff --git a/dom/workers/test/test_clearTimeouts.html b/dom/workers/test/test_clearTimeouts.html new file mode 100644 index 0000000000..caa87fbf56 --- /dev/null +++ b/dom/workers/test/test_clearTimeouts.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + new Worker("clearTimeouts_worker.js").onmessage = function(event) { + event.target.terminate(); + + is(event.data, "ready", "Correct message"); + setTimeout(function() { SimpleTest.finish(); }, 1000); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_clearTimeoutsImplicit.html b/dom/workers/test/test_clearTimeoutsImplicit.html new file mode 100644 index 0000000000..59a37974ca --- /dev/null +++ b/dom/workers/test/test_clearTimeoutsImplicit.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + new Worker("clearTimeoutsImplicit_worker.js").onmessage = function(event) { + event.target.terminate(); + + is(event.data, "ready", "Correct message"); + setTimeout(function() { SimpleTest.finish(); }, 1000); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_console.html b/dom/workers/test/test_console.html new file mode 100644 index 0000000000..b8c0f189ab --- /dev/null +++ b/dom/workers/test/test_console.html @@ -0,0 +1,44 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Console +--> +<head> + <title>Test for DOM Worker Console</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + var worker = new Worker("console_worker.js"); + + worker.onmessage = function(event) { + is(event.target, worker, "Worker and target match!"); + ok(event.data.status, event.data.event); + + if (!event.data.status || event.data.last) + SimpleTest.finish(); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + } + + worker.postMessage(true); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_consoleAndBlobs.html b/dom/workers/test/test_consoleAndBlobs.html new file mode 100644 index 0000000000..e07cfe5dca --- /dev/null +++ b/dom/workers/test/test_consoleAndBlobs.html @@ -0,0 +1,46 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for console API and blobs</title> + <script src="/tests/SimpleTest/SimpleTest.js"> + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + </head> + <body> + <script type="text/javascript"> + const ConsoleAPIStorage = SpecialPowers.Cc[ + "@mozilla.org/consoleAPI-storage;1" + ].getService(SpecialPowers.Ci.nsIConsoleAPIStorage); + + function consoleListener() { + this.observe = this.observe.bind(this); + ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal); + } + + var order = 0; + consoleListener.prototype = { + observe(aSubject) { + ok(true, "Something has been received"); + + var obj = aSubject.wrappedJSObject; + if (obj.arguments[0] && obj.arguments[0].msg === 'consoleAndBlobs') { + ConsoleAPIStorage.removeLogEventListener(this.observe); + is(obj.arguments[0].blob.size, 3, "The size is correct"); + is(obj.arguments[0].blob.type, 'foo/bar', "The type is correct"); + SimpleTest.finish(); + } + } + } + + var cl = new consoleListener(); + + new Worker('worker_consoleAndBlobs.js'); + SimpleTest.waitForExplicitFinish(); + + </script> + </body> +</html> diff --git a/dom/workers/test/test_consoleReplaceable.html b/dom/workers/test/test_consoleReplaceable.html new file mode 100644 index 0000000000..b8a60411e4 --- /dev/null +++ b/dom/workers/test/test_consoleReplaceable.html @@ -0,0 +1,44 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Console +--> +<head> + <title>Test for DOM Worker Console</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + var worker = new Worker("consoleReplaceable_worker.js"); + + worker.onmessage = function(event) { + is(event.target, worker, "Worker and target match!"); + ok(event.data.status, event.data.event); + + if (event.data.last) + SimpleTest.finish(); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + } + + worker.postMessage(true); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_contentWorker.html b/dom/workers/test/test_contentWorker.html new file mode 100644 index 0000000000..38891a88c5 --- /dev/null +++ b/dom/workers/test/test_contentWorker.html @@ -0,0 +1,48 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker privileged properties</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + var workerFilename = "content_worker.js"; + var worker = new Worker(workerFilename); + + var props = { + 'ctypes': 1, + 'OS': 1 + }; + + worker.onmessage = function(event) { + if (event.data.testfinished) { + SimpleTest.finish(); + return; + } + var prop = event.data.prop; + ok(prop in props, "checking " + prop); + is(event.data.value, undefined, prop + " should be undefined"); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_csp.html b/dom/workers/test/test_csp.html new file mode 100644 index 0000000000..f3ef747372 --- /dev/null +++ b/dom/workers/test/test_csp.html @@ -0,0 +1,18 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker + CSP</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +<script type="text/javascript" src="test_csp.js"></script> +</html> diff --git a/dom/workers/test/test_csp.html^headers^ b/dom/workers/test/test_csp.html^headers^ new file mode 100644 index 0000000000..1c93210799 --- /dev/null +++ b/dom/workers/test/test_csp.html^headers^ @@ -0,0 +1,2 @@ +Cache-Control: no-cache +Content-Security-Policy: default-src 'self' blob: diff --git a/dom/workers/test/test_csp.js b/dom/workers/test/test_csp.js new file mode 100644 index 0000000000..8c2b53586a --- /dev/null +++ b/dom/workers/test/test_csp.js @@ -0,0 +1,54 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var tests = 3; + +SimpleTest.waitForExplicitFinish(); + +testDone = function (event) { + if (!--tests) { + SimpleTest.finish(); + } +}; + +// Workers don't inherit CSP +worker = new Worker("csp_worker.js"); +worker.postMessage({ do: "eval" }); +worker.onmessage = function (event) { + is(event.data, 42, "Eval succeeded!"); + testDone(); +}; + +// blob: workers *do* inherit CSP +xhr = new XMLHttpRequest(); +xhr.open("GET", "csp_worker.js"); +xhr.responseType = "blob"; +xhr.send(); +xhr.onload = e => { + uri = URL.createObjectURL(e.target.response); + worker = new Worker(uri); + worker.postMessage({ do: "eval" }); + worker.onmessage = function (event) { + is(event.data, "EvalError: call to eval() blocked by CSP", "Eval threw"); + testDone(); + }; +}; + +xhr = new XMLHttpRequest(); +xhr.open("GET", "csp_worker.js"); +xhr.responseType = "blob"; +xhr.send(); +xhr.onload = e => { + uri = URL.createObjectURL(e.target.response); + worker = new Worker(uri); + worker.postMessage({ do: "nest", uri, level: 3 }); + worker.onmessage = function (event) { + is( + event.data, + "EvalError: call to eval() blocked by CSP", + "Eval threw in nested worker" + ); + testDone(); + }; +}; diff --git a/dom/workers/test/test_dataURLWorker.html b/dom/workers/test/test_dataURLWorker.html new file mode 100644 index 0000000000..145b0e43f1 --- /dev/null +++ b/dom/workers/test/test_dataURLWorker.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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="text/javascript"> + const message = "hi"; + const url = "DATA:text/plain," + + "onmessage = function(event) {" + + " postMessage(event.data);" + + "};"; + + var worker = new Worker(url); + worker.onmessage = function(event) { + is(event.data, message, "Got correct message"); + SimpleTest.finish(); + }; + worker.postMessage(message); + + SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_dynamicImport.html b/dom/workers/test/test_dynamicImport.html new file mode 100644 index 0000000000..bffef87d2d --- /dev/null +++ b/dom/workers/test/test_dynamicImport.html @@ -0,0 +1,76 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of Worker Dynamic Import (Bug 1540913) +Ensure that the script loader doesn't accidentally reorder events due to async work +done by dynamic import +--> +<head> + <title>Test for Worker Dynamic Import (Bug 1540913)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="onLoad()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540913">Worker Dynamic Import + Bug 1540913</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +async function onLoad() { + await SpecialPowers.pushPrefEnv( + { set: [["dom.workers.modules.enabled", true ]] }); + + const workers = [ + new Worker("dynamicImport_worker.js", {type: "classic"}), + new Worker("dynamicImport_worker.js", {type: "module"}) + ]; + + let successCount = 0; + + for (const worker of workers) { + const events = []; + worker.onmessage = function(event) { + switch (event.data) { + case "first": + ok(events.length === 1 && events[0] === "second", + "first dynamic import returned"); + events.push(event.data); + successCount++; + // Cheap way to make sure we only finish successfully after + // both the module and classic test is finished. + if (successCount == 2) { + SimpleTest.finish(); + } + break; + case "second": + ok(events.length === 0, + "second dynamic import returned"); + events.push(event.data); + break; + default: + ok(false, "Unexpected message:" + event.data); + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error:" + event.message); + SimpleTest.finish(); + } + + worker.postMessage("start"); + } +} +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_dynamicImport_and_terminate.html b/dom/workers/test/test_dynamicImport_and_terminate.html new file mode 100644 index 0000000000..cf355572e6 --- /dev/null +++ b/dom/workers/test/test_dynamicImport_and_terminate.html @@ -0,0 +1,34 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Worker create script loader failure</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +document.addEventListener("DOMContentLoaded", () => { + const worker = new Worker("worker_dynamicImport.mjs", {"type": "module"}); + setTimeout(() => { + worker.terminate(); + ok(true, "done"); + SimpleTest.finish(); + }, 0); +}); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_dynamicImport_early_termination.html b/dom/workers/test/test_dynamicImport_early_termination.html new file mode 100644 index 0000000000..fb9096df14 --- /dev/null +++ b/dom/workers/test/test_dynamicImport_early_termination.html @@ -0,0 +1,79 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of Worker Dynamic Import (Bug 1540913) +Ensure that the script loader doesn't fail if requests are terminated early. +--> +<head> + <title>Test for Worker Dynamic Import (Bug 1540913)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="onLoad()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1540913">Worker Dynamic Import + Bug 1540913</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +async function onLoad() { + await SpecialPowers.pushPrefEnv( + { set: [["dom.workers.modules.enabled", true ]] }); + + const workers = [ + new Worker("dynamicImport_worker.js", {type: "classic"}), + new Worker("dynamicImport_worker.js", {type: "module"}) + ] + + let successCount = 0; + + // In the implementation of dynamic import, every dynamic import has + // it's own ScriptLoader. To ensure that this is working correctly, + // this tests that if we re-order the dynamic import order, + // worker termination works as expected. + for (const worker of workers) { + const events = []; + worker.onmessage = function(event) { + switch (event.data) { + case "first": + ok(false, "first dynamic import returned"); + SimpleTest.finish(); + break; + case "second": + ok(events.length === 0, + "second dynamic import returned"); + events.push(event.data); + worker.terminate() + successCount++; + // Cheap way to make sure we only finish successfully after + // both the module and classic test is finished. + if (successCount == 2) { + SimpleTest.finish(); + } + break; + default: + ok(false, "Unexpected message:" + event.data); + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error:" + event.message); + SimpleTest.finish(); + } + + worker.postMessage("start"); + } +} +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_errorPropagation.html b/dom/workers/test/test_errorPropagation.html new file mode 100644 index 0000000000..8eb899fe7e --- /dev/null +++ b/dom/workers/test/test_errorPropagation.html @@ -0,0 +1,65 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <meta charset="utf-8"> + <script src="/tests/SimpleTest/SimpleTest.js"> + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + </head> + <body> + <iframe id="workerFrame" src="errorPropagation_iframe.html" + onload="workerFrameLoaded();"></iframe> + <script type="text/javascript"> + const workerCount = 3; + + const errorMessage = "Error: expectedError"; + const errorFilename = "http://mochi.test:8888/tests/dom/workers/test/" + + "errorPropagation_worker.js"; + const errorLineno = 49; + + var workerFrame; + + scopeErrorCount = 0; + workerErrorCount = 0; + windowErrorCount = 0; + + function messageListener(event) { + if (event.type == "scope") { + scopeErrorCount++; + } + else if (event.type == "worker") { + workerErrorCount++; + } + else if (event.type == "window") { + windowErrorCount++; + } + else { + ok(false, "Bad event type: " + event.type); + } + + is(event.data.message, errorMessage, "Correct message event.message"); + is(event.data.filename, errorFilename, + "Correct message event.filename"); + is(event.data.lineno, errorLineno, "Correct message event.lineno"); + + if (windowErrorCount == 1) { + is(scopeErrorCount, workerCount, "Good number of scope errors"); + is(workerErrorCount, workerCount, "Good number of worker errors"); + workerFrame.stop(); + SimpleTest.finish(); + } + } + + function workerFrameLoaded() { + workerFrame = document.getElementById("workerFrame").contentWindow; + workerFrame.start(workerCount, messageListener); + } + + SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_errorwarning.html b/dom/workers/test/test_errorwarning.html new file mode 100644 index 0000000000..282b46ec20 --- /dev/null +++ b/dom/workers/test/test_errorwarning.html @@ -0,0 +1,93 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Test javascript.options.strict in Workers +--> +<head> + <title>Test javascript.options.strict in Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + var errors = 0; + function errorHandler(e) { + ok(true, "An error has been received!"); + errors++; + } + + function test_noErrors() { + errors = 0; + + var worker = new Worker('errorwarning_worker.js'); + worker.onerror = errorHandler; + worker.onmessage = function(e) { + if (e.data.type == 'ignore') + return; + + if (e.data.type == 'error') { + errorHandler(); + return; + } + + if (e.data.type == 'finish') { + ok(errors == 0, "Here we are with 0 errors!"); + runTests(); + } + } + + onerror = errorHandler; + worker.postMessage({ loop: 5, errors: false }); + } + + function test_errors() { + errors = 0; + + var worker = new Worker('errorwarning_worker.js'); + worker.onerror = errorHandler; + worker.onmessage = function(e) { + if (e.data.type == 'ignore') + return; + + if (e.data.type == 'error') { + errorHandler(); + return; + } + + if (e.data.type == 'finish') { + ok(errors != 0, "Here we are with errors!"); + runTests(); + } + } + + onerror = errorHandler; + worker.postMessage({ loop: 5, errors: true }); + } + + var tests = [ test_noErrors, test_errors ]; + function runTests() { + var test = tests.shift(); + if (test) { + test(); + } else { + SimpleTest.finish(); + } + } + + runTests(); + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_eventDispatch.html b/dom/workers/test/test_eventDispatch.html new file mode 100644 index 0000000000..e6bbd7e2d1 --- /dev/null +++ b/dom/workers/test/test_eventDispatch.html @@ -0,0 +1,32 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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="text/javascript"> + const message = "Hi"; + + var messageCount = 0; + + var worker = new Worker("eventDispatch_worker.js"); + worker.onmessage = function(event) { + is(event.data, message, "Got correct data."); + if (!messageCount++) { + event.target.postMessage(event.data); + return; + } + SimpleTest.finish(); + } + worker.postMessage(message); + + SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_fibonacci.html b/dom/workers/test/test_fibonacci.html new file mode 100644 index 0000000000..c3e3e98574 --- /dev/null +++ b/dom/workers/test/test_fibonacci.html @@ -0,0 +1,51 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads with Fibonacci +--> +<head> + <title>Test for DOM Worker Threads with Fibonacci</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads Fibonacci</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + const seqNum = 5; + + function recursivefib(n) { + return n < 2 ? n : recursivefib(n - 1) + recursivefib(n - 2); + } + + var worker = new Worker("fibonacci_worker.js"); + + worker.onmessage = function(event) { + is(event.target, worker); + is(event.data, recursivefib(seqNum)); + SimpleTest.finish(); + }; + + worker.onerror = function(event) { + is(event.target, worker); + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + }; + + worker.postMessage(seqNum); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_file.xhtml b/dom/workers/test/test_file.xhtml new file mode 100644 index 0000000000..2b628e7f4d --- /dev/null +++ b/dom/workers/test/test_file.xhtml @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=123456 +--> +<window title="Mozilla Bug 123456" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=123456" + target="_blank">Mozilla Bug 123456</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 123456 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data and optionally adds the specified file extension. + */ + function createFileWithData(fileData, /** optional */ extension) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + var fileExtension = (extension == undefined) ? "" : "." + extension; + testFile.append("workerFile" + fileNum++ + fileExtension); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Create a worker to access file properties. + */ + function accessFileProperties(file, expectedSize, expectedType) { + waitForWorkerFinish(); + + var worker = new Worker("file_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread."); + is(event.data.type, expectedType, "type proproperty accessed from worker is incorrect."); + is(event.data.name, file.name, "name proproperty accessed from worker is incorrect."); + is(event.data.lastModified, file.lastModified, "lastModified proproperty accessed from worker is incorrect."); + finish(); + }; + + worker.postMessage(file); + } + + // Empty file. + accessFileProperties(createFileWithData(""), 0, ""); + + // Typical use case. + accessFileProperties(createFileWithData("Hello"), 5, ""); + + // Longish file. + var text = ""; + for (var i = 0; i < 10000; ++i) { + text += "long"; + } + accessFileProperties(createFileWithData(text), 40000, ""); + + // Type detection based on extension. + accessFileProperties(createFileWithData("text", "txt"), 4, "text/plain"); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileBlobPosting.xhtml b/dom/workers/test/test_fileBlobPosting.xhtml new file mode 100644 index 0000000000..61f4a8e909 --- /dev/null +++ b/dom/workers/test/test_fileBlobPosting.xhtml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data. + */ + function createFileWithData(fileData) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("workerBlobPosting" + fileNum++); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Create a worker which posts the same blob given. Used to test cloning of blobs. + * Checks the size, type, name and path of the file posted from the worker to ensure it + * is the same as the original. + */ + function postBlob(file) { + var worker = new Worker("filePosting_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + console.log(event.data); + is(event.data.size, file.size, "size of file posted from worker does not match file posted to worker."); + finish(); + }; + + var blob = file.slice(); + worker.postMessage(blob); + waitForWorkerFinish(); + } + + // Empty file. + postBlob(createFileWithData("")); + + // Typical use case. + postBlob(createFileWithData("Hello")); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileBlobSubWorker.xhtml b/dom/workers/test/test_fileBlobSubWorker.xhtml new file mode 100644 index 0000000000..8b67552788 --- /dev/null +++ b/dom/workers/test/test_fileBlobSubWorker.xhtml @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data and optionally adds the specified file extension. + */ + function createFileWithData(fileData, /** optional */ extension) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + var fileExtension = (extension == undefined) ? "" : "." + extension; + testFile.append("workerBlobSubWorker" + fileNum++ + fileExtension); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Create a worker to access blob properties. + */ + function accessFileProperties(file, expectedSize) { + var worker = new Worker("fileBlobSubWorker_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + if (event.data == undefined) { + ok(false, "Worker had an error."); + } else { + is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread."); + } + finish(); + }; + + var blob = file.slice(); + worker.postMessage(blob); + waitForWorkerFinish(); + } + + // Empty file. + accessFileProperties(createFileWithData(""), 0); + + // Typical use case. + accessFileProperties(createFileWithData("Hello"), 5); + + // Longish file. + var text = ""; + for (var i = 0; i < 10000; ++i) { + text += "long"; + } + accessFileProperties(createFileWithData(text), 40000); + + // Type detection based on extension. + accessFileProperties(createFileWithData("text", "txt"), 4); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_filePosting.xhtml b/dom/workers/test/test_filePosting.xhtml new file mode 100644 index 0000000000..3ffa219516 --- /dev/null +++ b/dom/workers/test/test_filePosting.xhtml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data. + */ + function createFileWithData(fileData) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("workerFilePosting" + fileNum++); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Create a worker which posts the same file given. Used to test cloning of files. + * Checks the size, type, name and path of the file posted from the worker to ensure it + * is the same as the original. + */ + function postFile(file) { + var worker = new Worker("file_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + is(event.data.size, file.size, "size of file posted from worker does not match file posted to worker."); + is(event.data.type, file.type, "type of file posted from worker does not match file posted to worker."); + is(event.data.name, file.name, "name of file posted from worker does not match file posted to worker."); + finish(); + }; + + worker.postMessage(file); + waitForWorkerFinish(); + } + + // Empty file. + postFile(createFileWithData("")); + + // Typical use case. + postFile(createFileWithData("Hello")); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileReadSlice.xhtml b/dom/workers/test/test_fileReadSlice.xhtml new file mode 100644 index 0000000000..fa396e88e8 --- /dev/null +++ b/dom/workers/test/test_fileReadSlice.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + if (navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); + } + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data. + */ + function createFileWithData(fileData) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("workerReadSlice" + fileNum++); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Creates a worker which slices a blob to the given start and end offset and + * reads the content as text. + */ + function readSlice(blob, start, end, expectedText) { + var worker = new Worker("fileReadSlice_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + is(event.data, expectedText, "Text from sliced blob in worker is incorrect."); + finish(); + }; + + var params = {blob, start, end}; + worker.postMessage(params); + waitForWorkerFinish(); + } + + // Empty file. + readSlice(createFileWithData(""), 0, 0, ""); + + // Typical use case. + readSlice(createFileWithData("HelloBye"), 5, 8, "Bye"); + + // End offset too large. + readSlice(createFileWithData("HelloBye"), 5, 9, "Bye"); + + // Start of file. + readSlice(createFileWithData("HelloBye"), 0, 5, "Hello"); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileReaderSync.xhtml b/dom/workers/test/test_fileReaderSync.xhtml new file mode 100644 index 0000000000..bba2890b5e --- /dev/null +++ b/dom/workers/test/test_fileReaderSync.xhtml @@ -0,0 +1,198 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data and optionally adds the specified file extension. + */ + function createFileWithData(fileData, /** optional */ extension) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + var fileExtension = (extension == undefined) ? "" : "." + extension; + testFile.append("workerFileReaderSync" + fileNum++ + fileExtension); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + function convertToUTF16(s) { + res = ""; + for (var i = 0; i < s.length; ++i) { + c = s.charCodeAt(i); + res += String.fromCharCode(c & 255, c >>> 8); + } + return res; + } + + /** + * Converts the given string to a data URL of the specified mime type. + */ + function convertToDataURL(mime, s) { + return "data:" + mime + ";base64," + btoa(s); + } + + /** + * Create a worker to read a file containing fileData using FileReaderSync and + * checks the return type against the expected type. Optionally set an encoding + * for reading the file as text. + */ + function readFileData(fileData, expectedText, /** optional */ encoding) { + var worker = new Worker("fileReaderSync_worker.js"); + + worker.onmessage = function(event) { + is(event.data.text, expectedText, "readAsText in worker returned incorrect result."); + is(event.data.bin, fileData, "readAsBinaryString in worker returned incorrect result."); + is(event.data.url, convertToDataURL("application/octet-stream", fileData), "readAsDataURL in worker returned incorrect result."); + is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length."); + finish(); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + var params = {file: createFileWithData(fileData), encoding}; + + worker.postMessage(params); + + waitForWorkerFinish(); + } + + /** + * Create a worker which reuses a FileReaderSync to read multiple files as DataURLs. + */ + function reuseReaderForURL(files, expected) { + var worker = new Worker("fileReaderSync_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + var k = 0; + worker.onmessage = function(event) { + is(event.data.url, expected[k], "readAsDataURL in worker returned incorrect result when reusing FileReaderSync."); + k++; + finish(); + }; + + for (var i = 0; i < files.length; ++i) { + var params = {file: files[i], encoding: undefined}; + worker.postMessage(params); + waitForWorkerFinish(); + } + } + + /** + * Create a worker which reuses a FileReaderSync to read multiple files as text. + */ + function reuseReaderForText(fileData, expected) { + var worker = new Worker("fileReaderSync_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + var k = 0; + worker.onmessage = function(event) { + is(event.data.text, expected[k++], "readAsText in worker returned incorrect result when reusing FileReaderSync."); + finish(); + }; + + for (var i = 0; i < fileData.length; ++i) { + var params = {file: createFileWithData(fileData[i]), encoding: undefined}; + worker.postMessage(params); + waitForWorkerFinish(); + } + } + + + /** + * Creates a a worker which reads a file containing fileData as an ArrayBuffer. + * Verifies that the ArrayBuffer when interpreted as a string matches the original data. + */ + function readArrayBuffer(fileData) { + var worker = new Worker("fileReaderSync_worker.js"); + + worker.onmessage = function(event) { + var view = new Uint8Array(event.data.arrayBuffer); + is(event.data.arrayBuffer.byteLength, fileData.length, "readAsArrayBuffer returned buffer of incorrect length."); + is(String.fromCharCode.apply(String, view), fileData, "readAsArrayBuffer returned buffer containing incorrect data."); + finish(); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + var params = {file: createFileWithData(fileData), encoding: undefined}; + + worker.postMessage(params); + + waitForWorkerFinish(); + } + + // Empty file. + readFileData("", ""); + + // Typical use case. + readFileData("text", "text"); + + // Test reading UTF-16 characters. + readFileData(convertToUTF16("text"), "text", "UTF-16"); + + // First read a file of type "text/plain", then read a file of type "application/octet-stream". + reuseReaderForURL([createFileWithData("text", "txt"), createFileWithData("text")], + [convertToDataURL("text/plain", "text"), + convertToDataURL("application/octet-stream", "text")]); + + // First read UTF-16 characters marked using BOM, then read UTF-8 characters. + reuseReaderForText([convertToUTF16("\ufefftext"), "text"], + ["text", "text"]); + + // Reading data as ArrayBuffer. + readArrayBuffer(""); + readArrayBuffer("text"); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileReaderSyncErrors.xhtml b/dom/workers/test/test_fileReaderSyncErrors.xhtml new file mode 100644 index 0000000000..626b67e1ba --- /dev/null +++ b/dom/workers/test/test_fileReaderSyncErrors.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data. + */ + function createFileWithData(fileData) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + testFile.append("workerFileReaderSyncErrors" + fileNum++); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Creates a worker which runs errors cases. + */ + function runWorkerErrors(file) { + var worker = new Worker("fileReaderSyncErrors_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + if(event.data == undefined) { + // Worker returns undefined when tests have finished running. + finish(); + } else { + // Otherwise worker will return results of tests to be evaluated. + is(event.data.actual, event.data.expected, event.data.message); + } + }; + + worker.postMessage(file); + waitForWorkerFinish(); + } + + // Run worker which creates exceptions. + runWorkerErrors(createFileWithData("text")); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileReaderSync_when_closing.html b/dom/workers/test/test_fileReaderSync_when_closing.html new file mode 100644 index 0000000000..d5fadbaa65 --- /dev/null +++ b/dom/workers/test/test_fileReaderSync_when_closing.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for FileReaderSync when the worker is closing</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + +// In order to exercise FileReaderSync::SyncRead's syncLoop-using AsyncWait() +// path, we need to provide a stream that will both 1) not have all the data +// immediately available (eliminating memory-backed Blobs) and 2) return +// NS_BASE_STREAM_WOULD_BLOCK. Under e10s, any Blob/File sourced from the +// parent process (as loadChromeScript performs) will be backed by an +// RemoteLazyInputStream and will behave this way on first use (when it is in +// the eInit state). For ease of testing, we reuse script_createFile.js which +// involves a file on disk, but a memory-backed Blob from the parent process +// would be equally fine. Under non-e10s, this File will not do the right +// thing because a synchronous nsFileInputStream will be made directly +// available and the AsyncWait path won't be taken, but the test will still +// pass. + +var url = SimpleTest.getTestFileURL("script_createFile.js"); +var script = SpecialPowers.loadChromeScript(url); + +function onOpened(message) { + function workerCode() { + onmessage = function(e) { + self.close(); + var fr = new FileReaderSync(); + self.postMessage(fr.readAsText(e.data)); + } + } + + var b = new Blob([workerCode+'workerCode();']); + var w = new Worker(URL.createObjectURL(b)); + w.onmessage = function(e) { + is(e.data, "Hello world!", "The blob content is OK!"); + SimpleTest.finish(); + } + + w.postMessage(message.data); +} + +script.addMessageListener("nonEmptyFile.opened", onOpened); +script.sendAsyncMessage("nonEmptyFile.open"); + +SimpleTest.waitForExplicitFinish(); + + </script> +</body> +</html> diff --git a/dom/workers/test/test_fileSlice.xhtml b/dom/workers/test/test_fileSlice.xhtml new file mode 100644 index 0000000000..c352404cc1 --- /dev/null +++ b/dom/workers/test/test_fileSlice.xhtml @@ -0,0 +1,105 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data and optionally adds the specified file extension. + */ + function createFileWithData(fileData, /** optional */ extension) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + var fileExtension = (extension == undefined) ? "" : "." + extension; + testFile.append("workerSlice" + fileNum++ + fileExtension); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Starts a worker which slices the blob to the given start offset and optional end offset and + * content type. It then verifies that the size and type of the sliced blob is correct. + */ + function createSlice(blob, start, expectedLength, /** optional */ end, /** optional */ contentType) { + var worker = new Worker("fileSlice_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + is(event.data.size, expectedLength, "size property of slice is incorrect."); + is(event.data.type, contentType ? contentType : blob.type, "type property of slice is incorrect."); + finish(); + }; + + var params = {blob, start, end, contentType}; + worker.postMessage(params); + waitForWorkerFinish(); + } + + // Empty file. + createSlice(createFileWithData(""), 0, 0, 0); + + // Typical use case. + createSlice(createFileWithData("Hello"), 1, 1, 2); + + // Longish file. + var text = ""; + for (var i = 0; i < 10000; ++i) { + text += "long"; + } + createSlice(createFileWithData(text), 2000, 2000, 4000); + + // Slice to different type. + createSlice(createFileWithData("text", "txt"), 0, 2, 2, "image/png"); + + // Length longer than blob. + createSlice(createFileWithData("text"), 0, 4, 20); + + // Start longer than blob. + createSlice(createFileWithData("text"), 20, 0, 4); + + // No optional arguments + createSlice(createFileWithData("text"), 0, 4); + createSlice(createFileWithData("text"), 2, 2); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_fileSubWorker.xhtml b/dom/workers/test/test_fileSubWorker.xhtml new file mode 100644 index 0000000000..92aa3b1a17 --- /dev/null +++ b/dom/workers/test/test_fileSubWorker.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=664783 +--> +<window title="Mozilla Bug 664783" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="dom_worker_helper.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=664783" + target="_blank">Mozilla Bug 664783</a> + + <div id="content" style="display: none"> + <input id="fileList" type="file"></input> + </div> + + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 664783 **/ + + var fileNum = 0; + + /** + * Create a file which contains the given data and optionally adds the specified file extension. + */ + function createFileWithData(fileData, /** optional */ extension) { + var testFile = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("ProfD", Ci.nsIFile); + var fileExtension = (extension == undefined) ? "" : "." + extension; + testFile.append("workerSubWorker" + fileNum++ + fileExtension); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; + } + + /** + * Create a worker to access file properties. + */ + function accessFileProperties(file, expectedSize, expectedType) { + var worker = new Worker("fileSubWorker_worker.js"); + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + finish(); + }; + + worker.onmessage = function(event) { + if (event.data == undefined) { + ok(false, "Worker had an error."); + } else { + is(event.data.size, expectedSize, "size proproperty accessed from worker is not the same as on main thread."); + is(event.data.type, expectedType, "type proproperty accessed from worker is incorrect."); + is(event.data.name, file.name, "name proproperty accessed from worker is incorrect."); + } + finish(); + }; + + worker.postMessage(file); + waitForWorkerFinish(); + } + + // Empty file. + accessFileProperties(createFileWithData(""), 0, ""); + + // Typical use case. + accessFileProperties(createFileWithData("Hello"), 5, ""); + + // Longish file. + var text = ""; + for (var i = 0; i < 10000; ++i) { + text += "long"; + } + accessFileProperties(createFileWithData(text), 40000, ""); + + // Type detection based on extension. + accessFileProperties(createFileWithData("text", "txt"), 4, "text/plain"); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_importScripts.html b/dom/workers/test/test_importScripts.html new file mode 100644 index 0000000000..0e6a3dde5e --- /dev/null +++ b/dom/workers/test/test_importScripts.html @@ -0,0 +1,53 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads (Bug 437152) +--> +<head> + <title>Test for DOM Worker Threads (Bug 437152)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("importScripts_worker.js"); + + worker.onmessage = function(event) { + switch (event.data) { + case "started": + worker.postMessage("stop"); + break; + case "stopped": + ok(true, "worker correctly stopped"); + SimpleTest.finish(); + break; + default: + ok(false, "Unexpected message:" + event.data); + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error:" + event.message); + SimpleTest.finish(); + } + + worker.postMessage("start"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_importScripts_1.html b/dom/workers/test/test_importScripts_1.html new file mode 100644 index 0000000000..2c75a3ed91 --- /dev/null +++ b/dom/workers/test/test_importScripts_1.html @@ -0,0 +1,35 @@ +<head> + <title>Test for ServiceWorker importScripts</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script id="worker" type="javascript/worker"> +onconnect = async function(e) { + e.ports[0].onmessage = async function(msg) { + try { + self.importScripts("N:", ""); + } catch (ex) { + e.source.postMessage("done"); + } + }; +}; +</script> +<script> +SimpleTest.waitForExplicitFinish(); +document.addEventListener("DOMContentLoaded", async () => { + const blob = new Blob([document.querySelector('#worker').textContent], + {type: "text/javascript"}); + const sw = new SharedWorker(window.URL.createObjectURL(blob)); + sw.port.postMessage([], []); + sw.port.onmessage = function(e) { + if (e.data == "done") { + ok(true); + SimpleTest.finish(); + } + }; +}); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_importScripts_2.html b/dom/workers/test/test_importScripts_2.html new file mode 100644 index 0000000000..f40b9a64ed --- /dev/null +++ b/dom/workers/test/test_importScripts_2.html @@ -0,0 +1,34 @@ +<head> + <title>Test for Worker importScripts</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script id="worker" type="javascript/worker"> +onmessage = async function(msg) { + try { + self.importScripts("N:", ""); + } catch (ex) { + postMessage("done"); + } +}; + +</script> +<script> +SimpleTest.waitForExplicitFinish(); +document.addEventListener("DOMContentLoaded", async () => { + const blob = new Blob([document.querySelector('#worker').textContent], + {type: "text/javascript"}); + const worker = new Worker(window.URL.createObjectURL(blob)); + worker.postMessage([], []); + worker.onmessage = function(e) { + if (e.data == "done") { + ok(true); + SimpleTest.finish(); + } + }; +}); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_importScripts_3rdparty.html b/dom/workers/test/test_importScripts_3rdparty.html new file mode 100644 index 0000000000..7f10f23faf --- /dev/null +++ b/dom/workers/test/test_importScripts_3rdparty.html @@ -0,0 +1,136 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for 3rd party imported script and muted errors</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript"> + +const workerURL = 'http://mochi.test:8888/tests/dom/workers/test/importScripts_3rdParty_worker.js'; + +const sameOriginURL = 'http://mochi.test:8888/tests/dom/workers/test/invalid.js' + +var tests = [ + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onmessage = function(event) { + ok("result" in event.data && event.data.result, "It seems we don't share data!"); + next(); + }; + + worker.postMessage({ url: sameOriginURL, test: 'try', nested: false }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onmessage = function(event) { + ok("result" in event.data && event.data.result, "It seems we don't share data in nested workers!"); + next(); + }; + + worker.postMessage({ url: sameOriginURL, test: 'try', nested: true }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onmessage = function(event) { + ok("result" in event.data && event.data.result, "It seems we don't share data via eventListener!"); + next(); + }; + + worker.postMessage({ url: sameOriginURL, test: 'eventListener', nested: false }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onmessage = function(event) { + ok("result" in event.data && event.data.result, "It seems we don't share data in nested workers via eventListener!"); + next(); + }; + + worker.postMessage({ url: sameOriginURL, test: 'eventListener', nested: true }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onmessage = function(event) { + ok("result" in event.data && event.data.result, "It seems we don't share data via onerror!"); + next(); + }; + worker.onerror = function(event) { + event.preventDefault(); + } + + worker.postMessage({ url: sameOriginURL, test: 'onerror', nested: false }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onerror = function(event) { + event.preventDefault(); + ok(event instanceof ErrorEvent, "ErrorEvent received."); + is(event.filename, workerURL, "ErrorEvent.filename is correct"); + next(); + }; + + worker.postMessage({ url: sameOriginURL, test: 'none', nested: false }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.addEventListener("error", function(event) { + event.preventDefault(); + ok(event instanceof ErrorEvent, "ErrorEvent received."); + is(event.filename, workerURL, "ErrorEvent.filename is correct"); + next(); + }); + + worker.postMessage({ url: sameOriginURL, test: 'none', nested: false }); + }, + + function() { + var worker = new Worker("importScripts_3rdParty_worker.js"); + worker.onerror = function(event) { + ok(false, "No error should be received!"); + }; + + worker.onmessage = function(event) { + ok("error" in event.data && event.data.error, "The error has been fully received from a nested worker"); + next(); + }; + worker.postMessage({ url: sameOriginURL, test: 'none', nested: true }); + }, + + function() { + var url = URL.createObjectURL(new Blob(["%&%^&%^"])); + var worker = new Worker(url); + worker.onerror = function(event) { + event.preventDefault(); + ok(event instanceof Event, "Event received."); + next(); + }; + } +]; + +function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); +} + +SimpleTest.waitForExplicitFinish(); +next(); + +</script> +</body> +</html> diff --git a/dom/workers/test/test_importScripts_mixedcontent.html b/dom/workers/test/test_importScripts_mixedcontent.html new file mode 100644 index 0000000000..04281deaea --- /dev/null +++ b/dom/workers/test/test_importScripts_mixedcontent.html @@ -0,0 +1,50 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1198078 - test that we respect mixed content blocking in importScript() inside workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198078">DOM Worker Threads Bug 1198078</a> +<iframe></iframe> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + onmessage = function(event) { + switch (event.data.status) { + case "done": + SimpleTest.finish(); + break; + case "ok": + ok(event.data.data, event.data.msg); + break; + default: + ok(false, "Unexpected message:" + event.data); + SimpleTest.finish(); + } + }; + + SimpleTest.waitForExplicitFinish(); + onload = function() { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.workers.sharedWorkers.enabled", true], + ["security.mixed_content.block_active_content", false], + ]}, function() { + var iframe = document.querySelector("iframe"); + iframe.src = "https://example.com/tests/dom/workers/test/importScripts_mixedcontent.html"; + }); + }; + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_instanceof.html b/dom/workers/test/test_instanceof.html new file mode 100644 index 0000000000..3a66674877 --- /dev/null +++ b/dom/workers/test/test_instanceof.html @@ -0,0 +1,40 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker JSON messages +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script src="json_worker.js" language="javascript"></script> +<script class="testbody" language="javascript"> + + var worker = new Worker("instanceof_worker.js"); + + worker.onmessage = function(event) { + ok(event.data.status, event.data.event); + + if (event.data.last) + SimpleTest.finish(); + }; + + worker.postMessage(42); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_json.html b/dom/workers/test/test_json.html new file mode 100644 index 0000000000..26e951aa63 --- /dev/null +++ b/dom/workers/test/test_json.html @@ -0,0 +1,89 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker JSON messages +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script src="json_worker.js" language="javascript"></script> +<script class="testbody" language="javascript"> + + ok(messages.length, "No messages to test!"); + + var worker = new Worker("json_worker.js"); + + var index = 0; + worker.onmessage = function(event) { + var key = messages[index++]; + + // Loop for the ones we shouldn't receive. + while (key.exception) { + key = messages[index++]; + } + + is(typeof event.data, key.type, "Bad type! " + messages.indexOf(key)); + + if (key.array) { + is(event.data instanceof Array, key.array, + "Array mismatch! " + messages.indexOf(key)); + } + + if (key.isNaN) { + ok(isNaN(event.data), "Should be NaN!" + messages.indexOf(key)); + } + + if (key.isInfinity) { + is(event.data, Infinity, "Should be Infinity!" + messages.indexOf(key)); + } + + if (key.isNegativeInfinity) { + is(event.data, -Infinity, "Should be -Infinity!" + messages.indexOf(key)); + } + + if (key.shouldCompare || key.shouldEqual) { + ok(event.data == key.compareValue, + "Values don't compare! " + messages.indexOf(key)); + } + + if (key.shouldEqual) { + ok(event.data === key.compareValue, + "Values don't equal! " + messages.indexOf(key)); + } + + if (key.jsonValue) { + is(JSON.stringify(event.data), key.jsonValue, + "Object stringification inconsistent!" + messages.indexOf(key)); + } + + if (event.data == "testFinished") { + is(index, messages.length, "Didn't see the right number of messages!"); + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + } + + worker.postMessage("start"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_loadEncoding.html b/dom/workers/test/test_loadEncoding.html new file mode 100644 index 0000000000..e7afb2fc82 --- /dev/null +++ b/dom/workers/test/test_loadEncoding.html @@ -0,0 +1,50 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 484305 - Load workers as UTF-8</title> + <meta http-equiv="content-type" content="text/html; charset=KOI8-R"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=484305">Bug 484305 - Load workers as UTF-8</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var canonical = String.fromCharCode(0x41F, 0x440, 0x438, 0x432, 0x435, 0x442); +ok(document.inputEncoding === "KOI8-R", "Document encoding is KOI8-R"); + +// Worker sends two strings, one with `canonical` encoded in KOI8-R and one as UTF-8. +// Since Worker scripts should always be decoded using UTF-8, even if the owning document's charset is different, the UTF-8 decode should match, while KOI8-R should fail. +var counter = 0; +var worker = new Worker("loadEncoding_worker.js"); +worker.onmessage = function(e) { + if (e.data.encoding === "KOI8-R") { + ok(e.data.text !== canonical, "KOI8-R decoded text should not match"); + } else if (e.data.encoding === "UTF-8") { + ok(e.data.text === canonical, "UTF-8 decoded text should match"); + } + counter++; + if (counter === 2) + SimpleTest.finish(); +} + +worker.onerror = function(e) { + ok(false, "Worker error"); + SimpleTest.finish(); +} +</script> + +</pre> +</body> +</html> diff --git a/dom/workers/test/test_loadError.html b/dom/workers/test/test_loadError.html new file mode 100644 index 0000000000..dec29d00c4 --- /dev/null +++ b/dom/workers/test/test_loadError.html @@ -0,0 +1,67 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> +"use strict"; + +function nextTest() { + (function(){ + function workerfunc() { + var subworker = new Worker("about:blank"); + subworker.onerror = function(e) { + e.preventDefault(); + postMessage("ERROR"); + } + } + var b = new Blob([workerfunc+'workerfunc();']); + var u = URL.createObjectURL(b); + function callworker(i) { + var w = new Worker(u); + URL.revokeObjectURL(u); + w.onmessage = function(e) { + is(i, 0, 'Message received'); + is(e.data, "ERROR", + "Should catch the error when loading inner script"); + if (++i < 2) callworker(i); + else SimpleTest.finish(); + }; + w.onerror = function(e) { + is(i, 1, 'OnError received'); + SimpleTest.finish(); + } + } + callworker(0); + })(); +} + +try { + var worker = new Worker("about:blank"); + worker.onerror = function(e) { + e.preventDefault(); + nextTest(); + } + + worker.onmessage = function(event) { + ok(false, "Shouldn't get a message!"); + SimpleTest.finish(); + } +} catch (e) { + ok(false, "This should not happen."); +} + + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_location.html b/dom/workers/test/test_location.html new file mode 100644 index 0000000000..55c94ae1cf --- /dev/null +++ b/dom/workers/test/test_location.html @@ -0,0 +1,72 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Location +--> +<head> + <title>Test for DOM Worker Location</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + var thisFilename = "test_location.html"; + var workerFilename = "location_worker.js"; + + var href = window.location.href + var queryPos = href.lastIndexOf(window.location.search); + var baseHref = href.substr(0, href.substr(0, queryPos).lastIndexOf("/") + 1); + + var path = window.location.pathname; + var basePath = path.substr(0, path.lastIndexOf("/") + 1); + + var strings = { + "toString": baseHref + workerFilename, + "href": baseHref + workerFilename, + "protocol": window.location.protocol, + "host": window.location.host, + "hostname": window.location.hostname, + "port": window.location.port, + "pathname": basePath + workerFilename, + "search": "", + "hash": "", + "origin": "http://mochi.test:8888" + }; + + var lastSlash = href.substr(0, queryPos).lastIndexOf("/") + 1; + is(thisFilename, + href.substr(lastSlash, queryPos - lastSlash), + "Correct filename "); + + var worker = new Worker(workerFilename); + + worker.onmessage = function(event) { + if (event.data.string == "testfinished") { + SimpleTest.finish(); + return; + } + ok(event.data.string in strings, event.data.string); + is(event.data.value, strings[event.data.string], event.data.string); + }; + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_longThread.html b/dom/workers/test/test_longThread.html new file mode 100644 index 0000000000..80602419ca --- /dev/null +++ b/dom/workers/test/test_longThread.html @@ -0,0 +1,58 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads (Bug 437152) +--> +<head> + <title>Test for DOM Worker Threads (Bug 437152)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + const numThreads = 5; + var doneThreads = 0; + + function onmessage(event) { + switch (event.data) { + case "done": + if (++doneThreads == numThreads) { + ok(true, "All messages received from workers"); + SimpleTest.finish(); + } + break; + default: + ok(false, "Unexpected message"); + SimpleTest.finish(); + } + } + + function onerror(event) { + ok(false, "Worker had an error"); + SimpleTest.finish(); + } + + for (var i = 0; i < numThreads; i++) { + var worker = new Worker("longThread_worker.js"); + worker.onmessage = onmessage; + worker.onerror = onerror; + worker.postMessage("start"); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_multi_sharedWorker.html b/dom/workers/test/test_multi_sharedWorker.html new file mode 100644 index 0000000000..7bfbeaa9e9 --- /dev/null +++ b/dom/workers/test/test_multi_sharedWorker.html @@ -0,0 +1,241 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script class="testbody" type="text/javascript"> + "use strict"; + + const basePath = + location.pathname.substring(0, + location.pathname.lastIndexOf("/") + 1); + const baseURL = location.origin + basePath; + + const frameRelativeURL = "multi_sharedWorker_frame.html"; + const frameAbsoluteURL = baseURL + frameRelativeURL; + const workerAbsoluteURL = + baseURL + "multi_sharedWorker_sharedWorker.js"; + + const storedData = "0123456789abcdefghijklmnopqrstuvwxyz"; + const errorMessage = "Error: Expected"; + const errorLineno = 34; + + let testGenerator = (function*() { + SimpleTest.waitForExplicitFinish(); + + window.addEventListener("message", function(event) { + if (typeof(event.data) == "string") { + info(event.data); + } else { + sendToGenerator(event); + } + }); + + let frame1 = document.getElementById("frame1"); + frame1.src = frameRelativeURL; + frame1.onload = sendToGenerator; + + yield undefined; + + frame1 = frame1.contentWindow; + + let frame2 = document.getElementById("frame2"); + frame2.src = frameAbsoluteURL; + frame2.onload = sendToGenerator; + + yield undefined; + + frame2 = frame2.contentWindow; + + let data = { + command: "start" + }; + + frame1.postMessage(data, "*"); + frame2.postMessage(data, "*"); + + let event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame1, "First window got the event"); + is(event.data.type, "connect", "Got a connect message"); + + data = { + command: "retrieve" + }; + frame1.postMessage(data, "*"); + + event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame1, "First window got the event"); + is(event.data.type, "result", "Got a result message"); + is(event.data.data, undefined, "No data stored yet"); + + frame2.postMessage(data, "*"); + + event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame2, "Second window got the event"); + is(event.data.type, "result", "Got a result message"); + is(event.data.data, undefined, "No data stored yet"); + + data = { + command: "store", + data: storedData + }; + frame2.postMessage(data, "*"); + + data = { + command: "retrieve" + }; + frame1.postMessage(data, "*"); + + event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame1, "First window got the event"); + is(event.data.type, "result", "Got a result message"); + is(event.data.data, storedData, "Got stored data"); + + // This will generate two MessageEvents, one for each window. + let sawFrame1Error = false; + let sawFrame2Error = false; + + data = { + command: "error" + }; + frame1.postMessage(data, "*"); + + // First event. + event = yield undefined; + + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.data.type, "worker-error", "Got an error message"); + is(event.data.message, errorMessage, "Got correct error message"); + is(event.data.filename, workerAbsoluteURL, "Got correct filename"); + is(event.data.lineno, errorLineno, "Got correct lineno"); + if (event.source == frame1) { + is(sawFrame1Error, false, "Haven't seen error for frame1 yet"); + sawFrame1Error = true; + } else if (event.source == frame2) { + is(sawFrame2Error, false, "Haven't seen error for frame1 yet"); + sawFrame2Error = true; + } else { + ok(false, "Saw error from unknown window"); + } + + // Second event + event = yield undefined; + + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.data.type, "worker-error", "Got an error message"); + is(event.data.message, errorMessage, "Got correct error message"); + is(event.data.filename, workerAbsoluteURL, "Got correct filename"); + is(event.data.lineno, errorLineno, "Got correct lineno"); + if (event.source == frame1) { + is(sawFrame1Error, false, "Haven't seen error for frame1 yet"); + sawFrame1Error = true; + } else if (event.source == frame2) { + is(sawFrame2Error, false, "Haven't seen error for frame1 yet"); + sawFrame2Error = true; + } else { + ok(false, "Saw error from unknown window"); + } + + is(sawFrame1Error, true, "Saw error for frame1"); + is(sawFrame2Error, true, "Saw error for frame2"); + + // This will generate two MessageEvents, one for each window. + sawFrame1Error = false; + sawFrame2Error = false; + + data = { + command: "error" + }; + frame1.postMessage(data, "*"); + + // First event. + event = yield undefined; + + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.data.type, "error", "Got an error message"); + is(event.data.message, errorMessage, "Got correct error message"); + is(event.data.filename, workerAbsoluteURL, "Got correct filename"); + is(event.data.lineno, errorLineno, "Got correct lineno"); + is(event.data.isErrorEvent, true, "Frame got an ErrorEvent"); + if (event.source == frame1) { + is(sawFrame1Error, false, "Haven't seen error for frame1 yet"); + sawFrame1Error = true; + } else if (event.source == frame2) { + is(sawFrame2Error, false, "Haven't seen error for frame1 yet"); + sawFrame2Error = true; + } else { + ok(false, "Saw error from unknown window"); + } + + // Second event + event = yield undefined; + + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.data.type, "error", "Got an error message"); + is(event.data.message, errorMessage, "Got correct error message"); + is(event.data.filename, workerAbsoluteURL, "Got correct filename"); + is(event.data.lineno, errorLineno, "Got correct lineno"); + is(event.data.isErrorEvent, true, "Frame got an ErrorEvent"); + if (event.source == frame1) { + is(sawFrame1Error, false, "Haven't seen error for frame1 yet"); + sawFrame1Error = true; + } else if (event.source == frame2) { + is(sawFrame2Error, false, "Haven't seen error for frame1 yet"); + sawFrame2Error = true; + } else { + ok(false, "Saw error from unknown window"); + } + + is(sawFrame1Error, true, "Saw error for frame1"); + is(sawFrame2Error, true, "Saw error for frame2"); + + // Try a shared worker in a different origin. + frame1 = document.getElementById("frame1"); + frame1.src = "http://example.org" + basePath + frameRelativeURL; + frame1.onload = sendToGenerator; + yield undefined; + + frame1 = frame1.contentWindow; + + data = { + command: "retrieve" + }; + frame1.postMessage(data, "*"); + + event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame1, "First window got the event"); + is(event.data.type, "result", "Got a result message"); + is(event.data.data, undefined, "No data stored yet"); + + frame2.postMessage(data, "*"); + + event = yield undefined; + ok(event instanceof MessageEvent, "Got a MessageEvent"); + is(event.source, frame2, "First window got the event"); + is(event.data.type, "result", "Got a result message"); + is(event.data.data, storedData, "Got stored data"); + + window.removeEventListener("message", sendToGenerator); + + SimpleTest.finish(); + })(); + + let sendToGenerator = testGenerator.next.bind(testGenerator); + + </script> + </head> + <body onload="testGenerator.next();"> + <iframe id="frame1"></iframe> + <iframe id="frame2"></iframe> + </body> +</html> diff --git a/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html b/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html new file mode 100644 index 0000000000..346950020f --- /dev/null +++ b/dom/workers/test/test_multi_sharedWorker_lifetimes_bfcache.html @@ -0,0 +1,151 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script class="testbody" type="text/javascript"> + "use strict"; + const windowRelativeURL = "multi_sharedWorker_frame_bfcache.html"; + // This suffix will be used as a search query parameter when we are + // navigating from navigate.html to the multi_sharedWorker_frame_bfcache + // page again. Since we are not using history.back() we are not loading + // that page from bfcache, but instead loading it anew, while the page + // we loaded initially stays in bfcache. In order to not kick out the + // page that is currently in the bfcache, we need to use a different + // BroadcastChannel. So we use search query param as part of + // BroadcastChannel's name. + const suffix = "?3"; + const storedData = "0123456789abcdefghijklmnopqrstuvwxyz"; + var bc, bc2, bc3; + SimpleTest.waitForExplicitFinish(); + + + function postToWorker(aBc, workerMessage) { + aBc.postMessage({command: "postToWorker", workerMessage}); + } + + let testGenerator = (function*() { + + bc = new BroadcastChannel("bugSharedWorkerLiftetime"); + bc3 = new BroadcastChannel("bugSharedWorkerLiftetime" + suffix); + bc.onmessage = (event) => { + var msg = event.data; + var command = msg.command; + if (command == "debug") { + info(msg.message); + } else if (command == "fromWorker" || command == "loaded") { + sendToGenerator(msg.workerMessage); + } + } + bc3.onmessage = (event) => { + var msg = event.data; + var command = msg.command; + if (command == "finished") { + bc.close(); + bc3.close(); + bc2.close(); + SimpleTest.finish(); + } else if (command == "debug") { + info(msg.message); + } else if (command == "fromWorker" || command == "loaded") { + sendToGenerator(msg.workerMessage); + } + } + bc2 = new BroadcastChannel("navigate"); + bc2.onmessage = (event) => { + if (event.data.command == "loaded") { + sendToGenerator(); + } + } + + // Open the window + window.open(windowRelativeURL, "testWin", "noopener"); + yield undefined; + + postToWorker(bc, { command: "retrieve" }); + + var msg = yield undefined; + is(msg.type, "result", "Got a result message"); + is(msg.data, undefined, "No data stored"); + + postToWorker(bc, { command: "store", data: storedData }); + postToWorker(bc, { command: "retrieve" }); + + msg = yield undefined; + is(msg.type, "result", "Got a result message"); + is(msg.data, storedData, "Got stored data"); + + + // Navigate when the bfcache is enabled. + info("Navigating to a different page"); + bc.postMessage({command: "navigate", location: "navigate.html"}); + yield undefined; + + for (let i = 0; i < 3; i++) { + info("Running GC"); + SpecialPowers.exactGC(sendToGenerator); + yield undefined; + + // It seems using SpecialPowers.executeSoon() would make the + // entryGlobal being the BrowserChildGlobal (and that would make the + // baseURI in the location assignment below being incorrect); + // setTimeout on the otherhand ensures the entryGlobal is this + // window. + info("Waiting the event queue to clear"); + setTimeout(sendToGenerator, 0); + yield undefined; + } + + info("Navigating to " + windowRelativeURL); + bc2.postMessage({command: "navigate", location: windowRelativeURL + suffix}); + yield undefined; + + postToWorker(bc3, { command: "retrieve" }); + + msg = yield undefined; + is(msg.type, "result", "Got a result message"); + is(msg.data, storedData, "Still have data stored"); + + bc3.postMessage({command: "finish"}); + })(); + + let sendToGenerator = testGenerator.next.bind(testGenerator); + + function runTest() { + if (isXOrigin) { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the BroadcastChannel used to + // communicate with the opened windows works in xorigin tests. Otherwise, + // the iframe containing this page is isolated from first-party storage access, + // which isolates BroadcastChannel communication. + SpecialPowers.wrap(document).notifyUserGestureActivation(); + SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{ + SpecialPowers.wrap(document).requestStorageAccess().then(() => { + // If Fission is disabled, the pref is no-op. + SpecialPowers.pushPrefEnv({set: [ + ["fission.bfcacheInParent", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false], + ]}, () => { + testGenerator.next(); + }); + }).then(() => { + SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888"); + }); + }); + } else { + // If Fission is disabled, the pref is no-op. + SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => { + testGenerator.next(); + }); + } + } + </script> + </head> + <body onload="runTest()"> + </body> +</html> diff --git a/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html b/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html new file mode 100644 index 0000000000..5049ead1a9 --- /dev/null +++ b/dom/workers/test/test_multi_sharedWorker_lifetimes_nobfcache.html @@ -0,0 +1,126 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script class="testbody" type="text/javascript"> + "use strict"; + + function runTest() { + const windowRelativeURL = "multi_sharedWorker_frame_nobfcache.html"; + const storedData = "0123456789abcdefghijklmnopqrstuvwxyz"; + var bc, bc2; + bc = new BroadcastChannel("bugSharedWorkerLiftetime"); + bc.onmessage = (event) => { + var msg = event.data; + var command = msg.command; + if (command == "finished") { + bc.close(); + bc2.close(); + SimpleTest.finish(); + } else if (command == "debug") { + info(msg.message); + } else if (command == "fromWorker" || command == "loaded") { + sendToGenerator(msg.workerMessage); + } + } + bc2 = new BroadcastChannel("navigate"); + bc2.onmessage = (event) => { + if (event.data.command == "loaded") { + sendToGenerator(); + } + } + + function postToWorker(workerMessage) { + bc.postMessage({command: "postToWorker", workerMessage}); + } + + let testGenerator = (function*() { + SimpleTest.waitForExplicitFinish(); + + // Open the window + window.open(windowRelativeURL, "testWin", "noopener"); + yield undefined; + + // Retrieve data from worker + postToWorker({ command: "retrieve" }); + + let msg = yield undefined; + + // Verify there is no data stored + is(msg.type, "result", "Got a result message"); + is(msg.data, undefined, "No data stored yet"); + + // Store data, and retrieve it + postToWorker({ command: "store", data: storedData }); + postToWorker({ command: "retrieve" }); + + msg = yield undefined; + // Verify there is data stored + is(msg.type, "result", "Got a result message"); + is(msg.data, storedData, "Got stored data"); + + + info("Navigating to a different page"); + // Current subpage should not go into bfcache because of the Cache-Control + // headers we have set. + bc.postMessage({command: "navigate", location: "navigate.html"}); + yield undefined; + + info("Navigating to " + windowRelativeURL); + bc2.postMessage({command: "navigate", location: windowRelativeURL }); + yield undefined; + + postToWorker({ command: "retrieve" }); + + msg = yield undefined; + is(msg.type, "result", "Got a result message"); + is(msg.data, undefined, "No data stored"); + + postToWorker({ command: "store", data: storedData }); + postToWorker({ command: "retrieve" }); + + msg = yield undefined; + is(msg.type, "result", "Got a result message"); + is(msg.data, storedData, "Got stored data"); + + bc.postMessage({command: "finish"}); + })(); + + let sendToGenerator = testGenerator.next.bind(testGenerator); + testGenerator.next(); + } + + SimpleTest.waitForExplicitFinish(); + if (isXOrigin) { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the BroadcastChannel used to + // communicate with the opened windows works in xorigin tests. Otherwise, + // the iframe containing this page is isolated from first-party storage access, + // which isolates BroadcastChannel communication. + SpecialPowers.wrap(document).notifyUserGestureActivation(); + SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], + }).then(() => { + SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{ + SpecialPowers.wrap(document).requestStorageAccess().then(() => { + runTest(); + }).then(() => { + SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888"); + }); + }); + }); + } else { + runTest(); + } + + </script> + </head> + <body> + </body> +</html> diff --git a/dom/workers/test/test_navigator.html b/dom/workers/test/test_navigator.html new file mode 100644 index 0000000000..a9ca9cad66 --- /dev/null +++ b/dom/workers/test/test_navigator.html @@ -0,0 +1,27 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Navigator +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script> + ok(!self.isSecureContext, "This test should not be running in a secure context"); +</script> +<script type="text/javascript" src="test_navigator.js"></script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_navigator.js b/dom/workers/test/test_navigator.js new file mode 100644 index 0000000000..c1eab3aa89 --- /dev/null +++ b/dom/workers/test/test_navigator.js @@ -0,0 +1,10 @@ +SimpleTest.waitForExplicitFinish(); + +// This test loads in an iframe, to ensure that the navigator instance is +// loaded with the correct value of the preference. +SpecialPowers.pushPrefEnv({ set: [["dom.netinfo.enabled", true]] }, () => { + let iframe = document.createElement("iframe"); + iframe.id = "f1"; + iframe.src = "test_navigator_iframe.html"; + document.body.appendChild(iframe); +}); diff --git a/dom/workers/test/test_navigator_iframe.html b/dom/workers/test/test_navigator_iframe.html new file mode 100644 index 0000000000..907739e90a --- /dev/null +++ b/dom/workers/test/test_navigator_iframe.html @@ -0,0 +1,24 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Sub-tests of DOM Worker Navigator tests. +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript" src="test_navigator_iframe.js"></script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_navigator_iframe.js b/dom/workers/test/test_navigator_iframe.js new file mode 100644 index 0000000000..248d7ed2cd --- /dev/null +++ b/dom/workers/test/test_navigator_iframe.js @@ -0,0 +1,65 @@ +var worker = new Worker("navigator_worker.js"); + +var is = window.parent.is; +var ok = window.parent.ok; +var SimpleTest = window.parent.SimpleTest; + +worker.onmessage = function (event) { + var args = JSON.parse(event.data); + + if (args.name == "testFinished") { + SimpleTest.finish(); + return; + } + + if (typeof navigator[args.name] == "undefined") { + ok(false, "Navigator has no '" + args.name + "' property!"); + return; + } + + if (args.name === "languages") { + is( + navigator.languages.toString(), + args.value.toString(), + "languages matches" + ); + return; + } + + const objectProperties = [ + "connection", + "gpu", + "locks", + "mediaCapabilities", + "storage", + ]; + + if (objectProperties.includes(args.name)) { + is( + typeof navigator[args.name], + typeof args.value, + `${args.name} type matches` + ); + return; + } + + is( + navigator[args.name], + args.value, + "Mismatched navigator string for " + args.name + "!" + ); +}; + +worker.onerror = function (event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); +}; + +var { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +var isNightly = AppConstants.NIGHTLY_BUILD; +var isRelease = AppConstants.RELEASE_OR_BETA; +var isAndroid = AppConstants.platform == "android"; + +worker.postMessage({ isNightly, isRelease, isAndroid }); diff --git a/dom/workers/test/test_navigator_languages.html b/dom/workers/test/test_navigator_languages.html new file mode 100644 index 0000000000..e4b6fec9a6 --- /dev/null +++ b/dom/workers/test/test_navigator_languages.html @@ -0,0 +1,58 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Navigator +--> +<head> + <title>Test for DOM Worker Navigator.languages</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + var tests = [ + { expectedLanguages: 'en,it', inputLanguages: 'en,it' }, + { expectedLanguages: 'it,en,fr', inputLanguages: 'it,en,fr' }, + { expectedLanguages: SpecialPowers.Services.locale.webExposedLocales[0], inputLanguages: '' }, + { expectedLanguages: 'en,it', inputLanguages: 'en,it' }, + ]; + var test; + function runTests() { + if (!tests.length) { + worker.postMessage('finish'); + SimpleTest.finish(); + return; + } + + test = tests.shift(); + SpecialPowers.pushPrefEnv({"set": [["intl.accept_languages", test.inputLanguages]]}, function() { + worker.postMessage(true); + }); + } + + SimpleTest.waitForExplicitFinish(); + + var worker = new Worker("navigator_languages_worker.js"); + + worker.onmessage = function(event) { + is(event.data.toString(), navigator.languages.toString(), "The languages match."); + is(event.data.toString(), test.expectedLanguages, "This is the correct result."); + runTests(); + } + + runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_navigator_secureContext.html b/dom/workers/test/test_navigator_secureContext.html new file mode 100644 index 0000000000..ac5ceb6628 --- /dev/null +++ b/dom/workers/test/test_navigator_secureContext.html @@ -0,0 +1,27 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Navigator +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script> + ok(self.isSecureContext, "This test should be running in a secure context"); +</script> +<script type="text/javascript" src="test_navigator.js"></script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_navigator_workers_hardwareConcurrency.html b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html new file mode 100644 index 0000000000..e80c211ce7 --- /dev/null +++ b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for Navigator.hardwareConcurrency</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + + function getWorkerHardwareConcurrency(onmessage) { + var script = "postMessage(navigator.hardwareConcurrency)"; + var url = URL.createObjectURL(new Blob([script])); + var w = new Worker(url); + w.onmessage = onmessage; + } + + function resistFingerprinting(value) { + return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]}); + } + + getWorkerHardwareConcurrency(e => { + var x = e.data; + is(typeof x, "number", "hardwareConcurrency should be a number."); + ok(x > 0, "hardwareConcurrency should be greater than 0."); + + resistFingerprinting(true).then(() => { + getWorkerHardwareConcurrency(msg => { + const y = msg.data; + ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting."); + + resistFingerprinting(false).then(() => { + getWorkerHardwareConcurrency(msg1 => { + const z = msg1.data; + ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting."); + + SimpleTest.finish(); + }); + }); + }); + }); + }); + + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_newError.html b/dom/workers/test/test_newError.html new file mode 100644 index 0000000000..9dd0889df8 --- /dev/null +++ b/dom/workers/test/test_newError.html @@ -0,0 +1,33 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("newError_worker.js"); + + worker.onmessage = function(event) { + ok(false, "Shouldn't get a message!"); + SimpleTest.finish(); + } + + worker.onerror = function(event) { + is(event.message, "Error: foo!", "Got wrong error message!"); + event.preventDefault(); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_notification.html b/dom/workers/test/test_notification.html new file mode 100644 index 0000000000..0171dea0f2 --- /dev/null +++ b/dom/workers/test/test_notification.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show and click events."); + + function runTest() { + MockServices.register(); + var w = new Worker("notification_worker.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + runTest(); +</script> +</body> +</html> diff --git a/dom/workers/test/test_notification_child.html b/dom/workers/test/test_notification_child.html new file mode 100644 index 0000000000..4f9523a03f --- /dev/null +++ b/dom/workers/test/test_notification_child.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893 - Test Notifications in child workers.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event."); + function runTest() { + MockServices.register(); + var w = new Worker("notification_worker_child-parent.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + runTest(); +</script> +</body> +</html> diff --git a/dom/workers/test/test_notification_permission.html b/dom/workers/test/test_notification_permission.html new file mode 100644 index 0000000000..904cfcdef2 --- /dev/null +++ b/dom/workers/test/test_notification_permission.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893 - Make sure error is fired on Notification if permission is denied.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/notification/test/mochitest/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event."); + function runTest() { + MockServices.register(); + var w = new Worker("notification_permission_worker.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + SpecialPowers.setBoolPref("notification.prompt.testing.allow", true); + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + SpecialPowers.setBoolPref("notification.prompt.testing.allow", false); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + runTest(); +</script> +</body> +</html> diff --git a/dom/workers/test/test_onLine.html b/dom/workers/test/test_onLine.html new file mode 100644 index 0000000000..b0429e7d96 --- /dev/null +++ b/dom/workers/test/test_onLine.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +Bug 925437: online/offline events tests. + +Any copyright is dedicated to the Public Domain. +http://creativecommons.org/licenses/publicdomain/ +--> +<head> + <title>Test for Bug 925437 (worker online/offline events)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=925437">Mozilla Bug 925437</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script class="testbody" type="text/javascript"> + +addLoadEvent(function() { + var w = new Worker("onLine_worker.js"); + + w.onmessage = function(e) { + if (e.data.type === 'ready') { + // XXX Important trick here. + // + // Setting iosvc.offline would trigger a sync notifyObservers call, and if + // there exists a preloaded about:newtab (see tabbrowser._handleNewTab), + // that tab will be notified. + // + // This implies a sync call across different tabGroups, and will hit the + // assertion in SchedulerGroup::ValidateAccess(). So use executeSoon to + // re-dispatch an unlabeled runnable to the event queue. + SpecialPowers.executeSoon(doTest); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'finished') { + SimpleTest.finish(); + } + } + + function doTest() { + var iosvc = SpecialPowers.Cc["@mozilla.org/network/io-service;1"] + .getService(SpecialPowers.Ci.nsIIOService); + iosvc.manageOfflineStatus = false; + + info("setting iosvc.offline = true"); + iosvc.offline = true; + + info("setting iosvc.offline = false"); + iosvc.offline = false; + + info("setting iosvc.offline = true"); + iosvc.offline = true; + + for (var i = 0; i < 10; ++i) { + iosvc.offline = !iosvc.offline; + } + + info("setting iosvc.offline = false"); + w.postMessage('lastTest'); + iosvc.offline = false; + } +}); + +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/dom/workers/test/test_promise.html b/dom/workers/test/test_promise.html new file mode 100644 index 0000000000..7c3ef09a98 --- /dev/null +++ b/dom/workers/test/test_promise.html @@ -0,0 +1,43 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Promise object in workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + function runTest() { + var worker = new Worker("promise_worker.js"); + + worker.onmessage = function(event) { + + if (event.data.type == 'finish') { + SimpleTest.finish(); + } else if (event.data.type == 'status') { + ok(event.data.status, event.data.msg); + } + } + + worker.onerror = function(event) { + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + }; + + worker.postMessage(true); + } + + SimpleTest.waitForExplicitFinish(); + runTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_promise_resolved_with_string.html b/dom/workers/test/test_promise_resolved_with_string.html new file mode 100644 index 0000000000..8c0b0aca49 --- /dev/null +++ b/dom/workers/test/test_promise_resolved_with_string.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1027221 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1027221</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1027221 **/ + // Set up a permanent atom + SimpleTest.waitForExplicitFinish(); + var x = "x"; + // Trigger some incremental gc + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + + // Kick off a worker that uses this same atom + var w = new Worker("data:text/plain,Promise.resolve('x').then(function() { postMessage(1); });"); + // Maybe trigger some more incremental gc + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + + w.onmessage = function() { + ok(true, "Got here"); + SimpleTest.finish(); + }; + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027221">Mozilla Bug 1027221</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_readableStream_when_closing.html b/dom/workers/test/test_readableStream_when_closing.html new file mode 100644 index 0000000000..24d5bf3821 --- /dev/null +++ b/dom/workers/test/test_readableStream_when_closing.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for ReadableStream+fetch when the worker is closing</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +</head> +<body> + <script> + +function workerCode() { + onmessage = () => { + const BIG_BUFFER_SIZE = 1000000; + const fibStream = new ReadableStream({ + start(controller) {}, + + pull(controller) { + const buffer = new Uint8Array(BIG_BUFFER_SIZE); + buffer.fill(42); + controller.enqueue(buffer); + } + }); + + const r = new Response(fibStream); + + const p = r.blob(); + self.postMessage("reading"); + + p.then(() => { + // really? + }); + } +} + +SimpleTest.waitForExplicitFinish(); + +const b = new Blob([workerCode+'workerCode();']); +const url = URL.createObjectURL(b); +const w = new Worker(url); +w.onmessage = function(e) { + ok(true, 'Worker is reading'); + + const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"]. + getService(Ci.nsIWorkerDebuggerManager); + wdm.addListener({ + onUnregister (dbg) { + if (dbg.url == url) { + ok(true, "Debugger with url " + url + " should be unregistered."); + wdm.removeListener(this); + SimpleTest.finish(); + } + } + }); + + w.terminate(); +} +w.postMessage("start"); + </script> +</body> +</html> diff --git a/dom/workers/test/test_recursion.html b/dom/workers/test/test_recursion.html new file mode 100644 index 0000000000..8e38a80351 --- /dev/null +++ b/dom/workers/test/test_recursion.html @@ -0,0 +1,69 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads +--> +<head> + <title>Test for DOM Worker Threads Recursion</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + // Intermittently triggers one assertion on Mac (bug 848098). + if (navigator.platform.indexOf("Mac") == 0) { + SimpleTest.expectAssertions(0, 1); + } + + const testCount = 2; + var errorCount = 0; + + var worker = new Worker("recursion_worker.js"); + + function done() { + worker.terminate(); + SimpleTest.finish(); + } + + worker.onmessage = function(event) { + if (event.data == "Done") { + ok(true, "correct message"); + } + else { + ok(false, "Bad message: " + event.data); + } + done(); + } + + worker.onerror = function(event) { + event.preventDefault(); + if (event.message == "too much recursion") { + ok(true, "got correct error message"); + ++errorCount; + } + else { + ok(false, "got bad error message: " + event.message); + done(); + } + } + + for (var i = 0; i < testCount; i++) { + worker.postMessage(""); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_recursiveOnerror.html b/dom/workers/test/test_recursiveOnerror.html new file mode 100644 index 0000000000..e6c040439d --- /dev/null +++ b/dom/workers/test/test_recursiveOnerror.html @@ -0,0 +1,44 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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="text/javascript"> + const filename = "http://mochi.test:8888/tests/dom/workers/test/" + + "recursiveOnerror_worker.js"; + const errors = [ + { message: "Error: 2", lineno: 6 }, + { message: "Error: 1", lineno: 10 } + ] + + var errorCount = 0; + + var worker = new Worker("recursiveOnerror_worker.js"); + worker.postMessage("go"); + + worker.onerror = function(event) { + event.preventDefault(); + + ok(errorCount < errors.length, "Correct number of error events"); + const error = errors[errorCount++]; + + is(event.message, error.message, "Correct message"); + is(event.filename, filename, "Correct filename"); + is(event.lineno, error.lineno, "Correct lineno"); + + if (errorCount == errors.length) { + SimpleTest.finish(); + } + } + + SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_referrer.html b/dom/workers/test/test_referrer.html new file mode 100644 index 0000000000..a95fe91809 --- /dev/null +++ b/dom/workers/test/test_referrer.html @@ -0,0 +1,58 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the referrer of workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + + function test_mainScript() { + var worker = new Worker("referrer.sjs?worker"); + worker.onmessage = function() { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'referrer.sjs?result', true); + xhr.onload = function() { + is(xhr.responseText, location.href, "The referrer has been sent."); + next(); + } + xhr.send(); + } + worker.postMessage(42); + } + + function test_importScript() { + var worker = new Worker("worker_referrer.js"); + worker.onmessage = function(e) { + is(e.data, location.href.replace("test_referrer.html", "worker_referrer.js").split("?")[0], "The referrer has been sent."); + next(); + } + worker.postMessage(42); + } + + var tests = [ test_mainScript, test_importScript ]; + function next() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); + } + + SimpleTest.waitForExplicitFinish(); + next(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_referrer_header_worker.html b/dom/workers/test/test_referrer_header_worker.html new file mode 100644 index 0000000000..6d350801cb --- /dev/null +++ b/dom/workers/test/test_referrer_header_worker.html @@ -0,0 +1,39 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test the referrer of workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + <script class="testbody" type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv( + {"set": [ + ['security.mixed_content.block_display_content', false], + ['security.mixed_content.block_active_content', false] + ]}, + function() { + SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], test); + }); + + function test() { + function messageListener(event) { + // eslint-disable-next-line no-eval + eval(event.data); + } + window.addEventListener("message", messageListener); + + var ifr = document.createElement('iframe'); + ifr.setAttribute('src', 'https://example.com/tests/dom/workers/test/referrer_worker.html'); + document.body.appendChild(ifr); + } + </script> +</body> +</html> diff --git a/dom/workers/test/test_resolveWorker-assignment.html b/dom/workers/test/test_resolveWorker-assignment.html new file mode 100644 index 0000000000..2f3be19668 --- /dev/null +++ b/dom/workers/test/test_resolveWorker-assignment.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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"> + window.Worker = 17; // resolve through assignment + + var desc = Object.getOwnPropertyDescriptor(window, "Worker"); + ok(typeof desc === "object" && desc !== null, "Worker property must exist"); + + is(desc.value, 17, "Overwrite didn't work correctly"); + is(desc.enumerable, false, + "Initial descriptor was non-enumerable, and [[Put]] changes the " + + "property value but not its enumerability"); + is(desc.configurable, true, + "Initial descriptor was configurable, and [[Put]] changes the " + + "property value but not its configurability"); + is(desc.writable, true, + "Initial descriptor was writable, and [[Put]] changes the " + + "property value but not its writability"); + </script> + </body> +</html> diff --git a/dom/workers/test/test_resolveWorker.html b/dom/workers/test/test_resolveWorker.html new file mode 100644 index 0000000000..0d91a9f90a --- /dev/null +++ b/dom/workers/test/test_resolveWorker.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!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"> + window.Worker; // resolve not through assignment + Worker = 17; + + var desc = Object.getOwnPropertyDescriptor(window, "Worker"); + ok(typeof desc === "object" && desc !== null, "Worker property must exist"); + + is(desc.value, 17, "Overwrite didn't work correctly"); + is(desc.enumerable, false, + "Initial descriptor was non-enumerable, and [[Put]] changes the " + + "property value but not its enumerability"); + is(desc.configurable, true, + "Initial descriptor was configurable, and [[Put]] changes the " + + "property value but not its configurability"); + is(desc.writable, true, + "Initial descriptor was writable, and [[Put]] changes the " + + "property value but not its writability"); + </script> + </body> +</html> diff --git a/dom/workers/test/test_rvals.html b/dom/workers/test/test_rvals.html new file mode 100644 index 0000000000..799c42779d --- /dev/null +++ b/dom/workers/test/test_rvals.html @@ -0,0 +1,35 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for bug 911085</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("rvals_worker.js"); + + worker.onmessage = function(event) { + if (event.data == 'ignore') return; + + if (event.data == 'finished') { + is(worker.terminate(), undefined, "Terminate() returns 'undefined'"); + SimpleTest.finish(); + return; + } + + ok(event.data, "something good returns 'undefined' in workers"); + }; + + is(worker.postMessage(42), undefined, "PostMessage() returns 'undefined' on main thread"); + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_setTimeoutWith0.html b/dom/workers/test/test_setTimeoutWith0.html new file mode 100644 index 0000000000..8685eacfe5 --- /dev/null +++ b/dom/workers/test/test_setTimeoutWith0.html @@ -0,0 +1,19 @@ +<html> +<head> + <title>Test for DOM Worker setTimeout and strings containing 0</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> + +var a = new Worker('worker_setTimeoutWith0.js'); +a.onmessage = function(e) { + is(e.data, 2, "We want to see 2 here"); + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +</script> +</body> +</html> diff --git a/dom/workers/test/test_sharedWorker.html b/dom/workers/test/test_sharedWorker.html new file mode 100644 index 0000000000..0a3cde2d32 --- /dev/null +++ b/dom/workers/test/test_sharedWorker.html @@ -0,0 +1,71 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for SharedWorker</title> + <script src="/tests/SimpleTest/SimpleTest.js"> + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + </head> + <body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + <script class="testbody"> + "use strict"; + + const href = window.location.href; + const filename = "sharedWorker_sharedWorker.js"; + const sentMessage = "ping"; + const errorFilename = href.substring(0, href.lastIndexOf("/") + 1) + + filename; + const errorLine = 98; + const errorColumn = 11; + + var worker = new SharedWorker(filename); + + ok(worker instanceof SharedWorker, "Got SharedWorker instance"); + ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'"); + ok(worker.port instanceof MessagePort, + "Shared worker has MessagePort"); + + var receivedMessage; + var receivedError; + + worker.port.onmessage = function(event) { + ok(event instanceof MessageEvent, "Got a MessageEvent"); + ok(event.target === worker.port, + "MessageEvent has correct 'target' property"); + is(event.data, sentMessage, "Got correct message"); + ok(receivedMessage === undefined, "Haven't gotten message yet"); + receivedMessage = event.data; + if (receivedError) { + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + ok(event instanceof ErrorEvent, "Got an ErrorEvent"); + is(event.message, "Error: " + sentMessage, "Got correct error"); + is(event.filename, errorFilename, "Got correct filename"); + is(event.lineno, errorLine, "Got correct lineno"); + is(event.colno, errorColumn, "Got correct column"); + ok(receivedError === undefined, "Haven't gotten error yet"); + receivedError = event.message; + event.preventDefault(); + if (receivedMessage) { + SimpleTest.finish(); + } + }; + + worker.port.postMessage(sentMessage); + + SimpleTest.waitForExplicitFinish(); + + </script> + </pre> + </body> +</html> diff --git a/dom/workers/test/test_sharedWorker_lifetime.html b/dom/workers/test/test_sharedWorker_lifetime.html new file mode 100644 index 0000000000..b987f0d7cf --- /dev/null +++ b/dom/workers/test/test_sharedWorker_lifetime.html @@ -0,0 +1,30 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for MessagePort and SharedWorkers</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + </head> + <body> + <script class="testbody" type="text/javascript"> + +var gced = false; + +var sw = new SharedWorker('sharedWorker_lifetime.js'); +sw.port.onmessage = function(event) { + ok(gced, "The SW is still alive also after GC"); + SimpleTest.finish(); +} + +sw = null; +SpecialPowers.forceGC(); +gced = true; + +SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_sharedWorker_ports.html b/dom/workers/test/test_sharedWorker_ports.html new file mode 100644 index 0000000000..6befd4920c --- /dev/null +++ b/dom/workers/test/test_sharedWorker_ports.html @@ -0,0 +1,41 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> + <head> + <title>Test for MessagePort and SharedWorkers</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + </head> + <body> + <script class="testbody" type="text/javascript"> + +var sw1 = new SharedWorker('sharedWorker_ports.js'); +sw1.port.onmessage = function(event) { + if (event.data.type == "connected") { + ok(true, "The SharedWorker is alive."); + + var sw2 = new SharedWorker('sharedWorker_ports.js'); + sw1.port.postMessage("Port from the main-thread!", [sw2.port]); + return; + } + + if (event.data.type == "status") { + ok(event.data.test, event.data.msg); + return; + } + + if (event.data.type == "finish") { + info("Finished!"); + ok(sw1.port, "The port still exists"); + sw1.port.foo = sw1; // Just a test to see if we leak. + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); + </script> + </body> +</html> diff --git a/dom/workers/test/test_sharedWorker_privateBrowsing.html b/dom/workers/test/test_sharedWorker_privateBrowsing.html new file mode 100644 index 0000000000..e93d65b3b8 --- /dev/null +++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html @@ -0,0 +1,104 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Test for SharedWorker - Private Browsing</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +</head> +<body> + +<script type="application/javascript"> +const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" +); +var mainWindow; + +var contentPage = "http://mochi.test:8888/chrome/dom/workers/test/empty.html"; + +function testOnWindow(aIsPrivate, aCallback) { + var win = mainWindow.OpenBrowserWindow({private: aIsPrivate}); + win.addEventListener("load", function() { + win.addEventListener("DOMContentLoaded", function onInnerLoad() { + if (win.content.location.href != contentPage) { + BrowserTestUtils.startLoadingURIString(win.gBrowser, contentPage); + return; + } + + win.removeEventListener("DOMContentLoaded", onInnerLoad, true); + SimpleTest.executeSoon(function() { aCallback(win); }); + }, true); + }, {capture: true, once: true}); +} + +function setupWindow() { + mainWindow = window.browsingContext.topChromeWindow; + runTest(); +} + +var wN; +var wP; + +function doTests() { + testOnWindow(false, function(aWin) { + wN = aWin; + + testOnWindow(true, function(win) { + wP = win; + + var sharedWorker1 = new wP.content.SharedWorker('sharedWorker_privateBrowsing.js'); + sharedWorker1.port.onmessage = function(event) { + is(event.data, 1, "Only 1 sharedworker expected in the private window"); + + var sharedWorker2 = new wN.content.SharedWorker('sharedWorker_privateBrowsing.js'); + sharedWorker2.port.onmessage = function(event1) { + is(event1.data, 1, "Only 1 sharedworker expected in the normal window"); + + var sharedWorker3 = new wP.content.SharedWorker('sharedWorker_privateBrowsing.js'); + sharedWorker3.port.onmessage = function(event2) { + is(event2.data, 2, "Only 2 sharedworker expected in the private window"); + runTest(); + } + } + } + }); + }); +} + +function doSystemSharedWorkerTest() { + try { + let chromeShared = + new wP.SharedWorker("chrome://mochitests/content/dom/workers/test/sharedWorker_privateBrowsing.js"); + ok(true, "system SharedWorker created without throwing or crashing!"); + } catch (_ex) { + ok(false, "system SharedWorker should not throw or crash"); + } + runTest(); +} + +var steps = [ + setupWindow, + doTests, + doSystemSharedWorkerTest, +]; + +function runTest() { + if (!steps.length) { + wN.close(); + wP.close(); + + SimpleTest.finish(); + return; + } + + var step = steps.shift(); + step(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({"set": [ + ["browser.startup.page", 0], + ["browser.startup.homepage_override.mstone", "ignore"], +]}, runTest); + +</script> +</body> +</html> diff --git a/dom/workers/test/test_sharedWorker_thirdparty.html b/dom/workers/test/test_sharedWorker_thirdparty.html new file mode 100644 index 0000000000..caeb122bba --- /dev/null +++ b/dom/workers/test/test_sharedWorker_thirdparty.html @@ -0,0 +1,54 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for SharedWorker in 3rd Party Iframes</title> + <script src="/tests/SimpleTest/SimpleTest.js"> </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> +</head> +<body> + <script class="testbody"> + + function testThirdPartyFrame(name) { + return new Promise(resolve => { + // Let's use a window, loading the same origin, in order to have the new + // cookie-policy applied. + let w = window.open("sharedWorker_thirdparty_window.html?name=" + name); + window.addEventListener('message', function messageListener(evt) { + if (evt.data.name !== name) { + return; + } + w.close(); + window.removeEventListener('message', messageListener); + resolve(evt.data.result); + }); + }); + } + + const COOKIE_BEHAVIOR_ACCEPT = 0; + const COOKIE_BEHAVIOR_REJECTFOREIGN = 1; + + add_task(async function allowed() { + await SpecialPowers.pushPrefEnv({ set: [ + ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_ACCEPT] + ]}); + let result = await testThirdPartyFrame('allowed'); + ok(result === 'allowed', + 'SharedWorker should be allowed when 3rd party iframes can access storage'); + }); + + add_task(async function blocked() { + await SpecialPowers.pushPrefEnv({ set: [ + ["network.cookie.cookieBehavior", COOKIE_BEHAVIOR_REJECTFOREIGN] + ]}); + let result = await testThirdPartyFrame('blocked'); + ok(result === 'blocked', + 'SharedWorker should not be allowed when 3rd party iframes are denied storage'); + }); + + </script> +</body> +</html> diff --git a/dom/workers/test/test_sharedworker_event_listener_leaks.html b/dom/workers/test/test_sharedworker_event_listener_leaks.html new file mode 100644 index 0000000000..4016cdda48 --- /dev/null +++ b/dom/workers/test/test_sharedworker_event_listener_leaks.html @@ -0,0 +1,51 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1450358 - Test SharedWorker event listener leak conditions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +// Manipulate SharedWorker objects in the frame's context. +// Its important here that we create a listener callback from +// the DOM objects back to the frame's global in order to +// exercise the leak condition. +async function useSharedWorker(contentWindow) { + contentWindow.messageCount = 0; + + let sw = new contentWindow.SharedWorker("data:application/javascript,self.onconnect = e => e.ports[0].postMessage({})"); + sw.onerror = _ => { + contentWindow.errorCount += 1; + } + await new Promise(resolve => { + sw.port.onmessage = e => { + contentWindow.messageCount += 1; + resolve(); + }; + }); + + is(contentWindow.messageCount, 1, "message should be received"); +} + +async function runTest() { + try { + await checkForEventListenerLeaks("SharedWorker", useSharedWorker); + } catch (e) { + ok(false, e); + } finally { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +addEventListener("load", runTest, { once: true }); +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_shutdownCheck.xhtml b/dom/workers/test/test_shutdownCheck.xhtml new file mode 100644 index 0000000000..257b37fe32 --- /dev/null +++ b/dom/workers/test/test_shutdownCheck.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<window title="Worker shutdown check" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + +SimpleTest.waitForExplicitFinish() + +const URL = "worker_shutdownCheck.js"; + +function checkWorker() { + const wdm = Cc["@mozilla.org/dom/workers/workerdebuggermanager;1"]. + getService(Ci.nsIWorkerDebuggerManager); + + let e = wdm.getWorkerDebuggerEnumerator(); + while (e.hasMoreElements()) { + let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger); + if (dbg.url == URL) { + return true; + } + } + + return false; +} + +new Promise(resolve => { + var w = new Worker(URL); + ok(checkWorker(), "We have the worker"); + w.onmessage = () => { resolve(); } +}).then(() => { + info("Waiting..."); + + // We don't know if the worker thread is able to shutdown when calling + // CC/GC. Better to check again in case. + function checkGC() { + Cu.forceCC(); + Cu.forceGC(); + if (!checkWorker()) { + ok(true, "We don't have the worker"); + SimpleTest.finish(); + return; + } + setTimeout(checkGC, 200); + } + + checkGC(); +}); + + ]]> + </script> +</window> diff --git a/dom/workers/test/test_simpleThread.html b/dom/workers/test/test_simpleThread.html new file mode 100644 index 0000000000..aee5765170 --- /dev/null +++ b/dom/workers/test/test_simpleThread.html @@ -0,0 +1,74 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads (Bug 437152) +--> +<head> + <title>Test for DOM Worker Threads (Bug 437152)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("simpleThread_worker.js"); + + worker.addEventListener("message",function(event) { + is(event.target, worker); + switch (event.data) { + case "no-op": + break; + case "started": + is(gotErrors, true); + worker.postMessage("no-op"); + worker.postMessage("stop"); + break; + case "stopped": + worker.postMessage("no-op"); + SimpleTest.finish(); + break; + default: + ok(false, "Unexpected message:" + event.data); + SimpleTest.finish(); + } + }); + + var gotErrors = false; + worker.onerror = function(event) { + event.preventDefault(); + is(event.target, worker); + is(event.message, "uncaught exception: Bad message: asdf"); + + worker.onerror = function(otherEvent) { + otherEvent.preventDefault(); + is(otherEvent.target, worker); + is(otherEvent.message, "ReferenceError: Components is not defined"); + gotErrors = true; + + worker.onerror = function(oneMoreEvent) { + ok(false, "Worker had an error:" + oneMoreEvent.message); + SimpleTest.finish(); + }; + }; + }; + + worker.postMessage("asdf"); + worker.postMessage("components"); + worker.postMessage("start"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_sourcemap_header.html b/dom/workers/test/test_sourcemap_header.html new file mode 100644 index 0000000000..250b30f079 --- /dev/null +++ b/dom/workers/test/test_sourcemap_header.html @@ -0,0 +1,22 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Test for DOM Worker + SourceMap header</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script type="application/javascript" src="dom_worker_helper.js"></script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> + +<iframe id="worker-frame"></iframe> +</body> +<script type="text/javascript" src="sourcemap_header.js"></script> +</html> diff --git a/dom/workers/test/test_subworkers_suspended.html b/dom/workers/test/test_subworkers_suspended.html new file mode 100644 index 0000000000..01157671d7 --- /dev/null +++ b/dom/workers/test/test_subworkers_suspended.html @@ -0,0 +1,144 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for sub workers+bfcache behavior</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + + + + /** + * - main page opens testUrl1 + * - testUrl1 ---"onpageshow"---> to main page + * - main page ---"startWorker"---> testUrl1 + * - testUrl1 starts workers, also ---"verifyCacheData"---> main page + * - main page ---"changeLocation"---> testUrl1 + * - testUrl1 navigated to testUrl2 + * - testUrl2 ---"onpageshow"---> to main page + * - main page ---"startWorker"---> testUrl2 + * - testUrl2 starts workers, also ---"verifyCacheData"---> main page + * - main page ---"goBack"---> testUrl2 + * - testUrl2 navigates back to testUrl1 + * - testUrl1 ---"onpageshow"---> to main page + * - main page checks cache data and ---"finish"---> testUrl2 + * - testUrl1 ---"finished"---> to main page + */ + var testUrl1 = "window_suspended.html?page1Shown"; + var counter = 0; + const SUB_WORKERS = 3; + + function cacheData() { + return caches.open("test") + .then(function(cache) { + return cache.match("http://mochi.test:888/foo"); + }) + .then(function(response) { + return response.text(); + }); + } + + function runTest() { + var bc1 = new BroadcastChannel("page1Shown"); + bc1.onmessage = async (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + info(`Main page, received command=${command}`); + if (command == "onpageshow") { + info("Page1Shown: " + msg.location); + // First time this page is shown. + if (counter == 0) { + ok(!msg.persisted, "test page should have been persisted initially"); + var workerMessage = { type: "page1", count: SUB_WORKERS }; + bc1.postMessage({command: "startWorker", workerMessage}); + } else { + is(msg.persisted, true, "test page should have been persisted in pageshow"); + var promise = new Promise((resolve, reject) => { + info("Waiting a few seconds..."); + setTimeout(resolve, 10000); + }); + + promise.then(function() { + info("Retrieving data from cache..."); + return cacheData(); + }) + + .then(function(content) { + is(content.indexOf("page1-"), 0, "We have data from the worker"); + }) + .then(function() { + bc1.postMessage({command: "finish"}); + }); + } + counter++; + } else if (command == "workerMessage") { + is(msg.workerMessage, "ready", "We want to receive: -ready-"); + } else if (command == "verifyCacheData") { + var content = await cacheData(); + is(content.indexOf("page1-"), 0, "We have data from the worker"); + bc1.postMessage({command: "changeLocation"}); + } else if (command == "finished") { + bc1.close(); + bc2.close(); + SimpleTest.finish(); + } + } + var bc2 = new BroadcastChannel("page2Shown"); + bc2.onmessage = async (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "onpageshow") { + info("Page1Shown: " + msg.location); + var workerMessage = { type: "page2" }; + bc2.postMessage({command: "startWorker", workerMessage}); + } else if (command == "workerMessage") { + is(msg.workerMessage, "ready", "We want to receive: -ready-"); + } else if (command == "verifyCacheData") { + var content = await cacheData(); + is(content, "page2-0", "We have data from the second worker"); + bc2.postMessage({command: "goBack"}); + } + } + + SpecialPowers.pushPrefEnv({ set: [ + ["dom.caches.testing.enabled", true], + // If Fission is disabled, the pref is no-op. + ["fission.bfcacheInParent", true], + ] }, + function() { + window.open(testUrl1, "", "noopener"); + }); + + } + + if (isXOrigin) { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the BroadcastChannel used to + // communicate with the opened windows works in xorigin tests. Otherwise, + // the iframe containing this page is isolated from first-party storage access, + // which isolates BroadcastChannel communication. + SpecialPowers.wrap(document).notifyUserGestureActivation(); + SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], + }).then(() => { + SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{ + SpecialPowers.wrap(document).requestStorageAccess().then(() => { + runTest(); + }).then(() => { + SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888"); + }); + }); + }); + } else { + runTest(); + } + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + + </script> +</body> +</html> diff --git a/dom/workers/test/test_suspend.html b/dom/workers/test/test_suspend.html new file mode 100644 index 0000000000..9ab1a6a7ec --- /dev/null +++ b/dom/workers/test/test_suspend.html @@ -0,0 +1,188 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + SimpleTest.waitForExplicitFinish(); + + /** + * - main page tells subpage to call startWorker() + * - subpage starts worker + * - worker calls setInterval() and keeps calling postMessage() + * - onmessage(), as setup by the subpage, calls messageCallback + * - when messageCallback gets called more than 25 times + * - subpage gets navigated to blank.html + * - blank page posts message to main page, and main page calls suspendCallback() + * - suspendCallback() schedules waitInterval() to be fired off every second + * - after 5 times, it clears the interval and navigates subpage back + * - suspend_window subpage starts receiving messages again and + * does a final call to messageCallback() + * - finishTest() is called + */ + + var lastCount; + + var suspended = false; + var resumed = false; + var finished = false; + var suspendBlankPageCurrentlyShowing = false; + + var interval; + var oldMessageCount; + var waitCount = 0; + + var bcSuspendWindow, bcSuspendBlank; + + function runTest() { + bcSuspendWindow = new BroadcastChannel("suspendWindow"); + bcSuspendWindow.onmessage = (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + var data = msg.data; + if (command == "loaded") { + if (finished) { + return; + } + bcSuspendWindow.postMessage({command: "startWorker"}); + } else if (command == "messageCallback") { + messageCallback(data); + } else if (command == "errorCallback") { + errorCallback(data); + } else if (command == "finished") { + SimpleTest.finish(); + } + } + + bcSuspendBlank = new BroadcastChannel("suspendBlank"); + bcSuspendBlank.onmessage = (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "loaded") { + suspendBlankPageCurrentlyShowing = true; + if (suspended) { + badOnloadCallback(); + } else { + suspendCallback(); + } + } else if (command == "pagehide") { + suspendBlankPageCurrentlyShowing = false; + } + } + + // If Fission is disabled, the pref is no-op. + SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => { + window.open("suspend_window.html", "testWin", "noopener"); + }); + } + + function finishTest() { + if (finished) { + return; + } + finished = true; + bcSuspendWindow.postMessage({command: "finish"}); + } + + function waitInterval() { + if (finished) { + return; + } + ok(suspendBlankPageCurrentlyShowing, "correct page is showing"); + is(suspended, true, "Not suspended?"); + is(resumed, false, "Already resumed?!"); + is(lastCount, oldMessageCount, "Received a message while suspended!"); + if (++waitCount == 5) { + clearInterval(interval); + resumed = true; + bcSuspendBlank.postMessage({command: "navigateBack"}); + } + } + + function badOnloadCallback() { + if (finished) { + return; + } + ok(false, "We don't want suspend_window.html to fire a new load event, we want it to come out of the bfcache!"); + finishTest(); + } + + function suspendCallback() { + if (finished) { + return; + } + ok(suspendBlankPageCurrentlyShowing, "correct page is showing"); + is(suspended, false, "Already suspended?"); + is(resumed, false, "Already resumed?"); + suspended = true; + oldMessageCount = lastCount; + interval = setInterval(waitInterval, 1000); + } + + function messageCallback(data) { + if (finished) { + return; + } + + if (!suspended) { + ok(lastCount === undefined || lastCount == data - 1, + "Got good data, lastCount = " + lastCount + ", data = " + data); + lastCount = data; + if (lastCount == 25) { + bcSuspendWindow.postMessage({command: "navigate"}); + } + return; + } + + ok(!suspendBlankPageCurrentlyShowing, "correct page is showing"); + is(resumed, true, "Got message before resumed!"); + is(lastCount, data - 1, "Missed a message, suspend failed!"); + finishTest(); + } + + function errorCallback(data) { + if (finished) { + return; + } + ok(false, "testWin had an error: '" + data + "'"); + finishTest(); + } + + if (isXOrigin) { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the BroadcastChannel used to + // communicate with the opened windows works in xorigin tests. Otherwise, + // the iframe containing this page is isolated from first-party storage access, + // which isolates BroadcastChannel communication. + SpecialPowers.wrap(document).notifyUserGestureActivation(); + SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], + }).then(() => { + SpecialPowers.pushPermissions([{'type': 'storageAccessAPI', 'allow': 1, 'context': document}], () =>{ + SpecialPowers.wrap(document).requestStorageAccess().then(() => { + runTest(); + }).then(() => { + SpecialPowers.removePermission("3rdPartyStorage^http://mochi.test:8888", "http://mochi.xorigin-test:8888"); + }); + }); + }); + } else { + runTest(); + } + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_terminate.html b/dom/workers/test/test_terminate.html new file mode 100644 index 0000000000..c19d65770b --- /dev/null +++ b/dom/workers/test/test_terminate.html @@ -0,0 +1,100 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker terminate feature +--> +<head> + <title>Test for DOM Worker Navigator</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + + var messageCount = 0; + var intervalCount = 0; + + var interval; + + var worker; + + function messageListener(event) { + is(event.data, "Still alive!", "Correct message!"); + if (++messageCount == 20) { + ok(worker.onmessage === messageListener, + "Correct listener before terminate"); + + worker.terminate(); + + var exception = false; + try { + worker.addEventListener("message", messageListener); + } + catch (e) { + exception = true; + } + is(exception, false, "addEventListener didn't throw after terminate"); + + exception = false; + try { + worker.removeEventListener("message", messageListener); + } + catch (e) { + exception = true; + } + is(exception, false, "removeEventListener didn't throw after terminate"); + + exception = false; + try { + worker.postMessage("foo"); + } + catch (e) { + exception = true; + } + is(exception, false, "postMessage didn't throw after terminate"); + + exception = false; + try { + worker.terminate(); + } + catch (e) { + exception = true; + } + is(exception, false, "terminate didn't throw after terminate"); + + ok(worker.onmessage === messageListener, + "Correct listener after terminate"); + + worker.onmessage = function(msg) { } + + interval = setInterval(testCount, 1000); + } + } + + function testCount() { + is(messageCount, 20, "Received another message after terminated!"); + if (intervalCount++ == 5) { + clearInterval(interval); + SimpleTest.finish(); + } + } + + worker = new Worker("terminate_worker.js"); + worker.onmessage = messageListener; + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_threadErrors.html b/dom/workers/test/test_threadErrors.html new file mode 100644 index 0000000000..1eb33244ba --- /dev/null +++ b/dom/workers/test/test_threadErrors.html @@ -0,0 +1,64 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads (Bug 437152) +--> +<head> + <title>Test for DOM Worker Threads (Bug 437152)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + const expectedErrorCount = 4; + + function messageListener(event) { + ok(false, "Unexpected message: " + event.data); + SimpleTest.finish(); + }; + + var actualErrorCount = 0; + var failedWorkers = []; + + function errorListener(event) { + event.preventDefault(); + + if (failedWorkers.includes(event.target)) { + ok(false, "Seen an extra error from this worker"); + SimpleTest.finish(); + return; + } + + failedWorkers.push(event.target); + actualErrorCount++; + + if (actualErrorCount == expectedErrorCount) { + ok(true, "all errors correctly detected"); + SimpleTest.finish(); + } + }; + + for (var i = 1; i <= expectedErrorCount; i++) { + var worker = new Worker("threadErrors_worker" + i + ".js"); + worker.onmessage = messageListener; + worker.onerror = errorListener; + worker.postMessage("Hi"); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_threadTimeouts.html b/dom/workers/test/test_threadTimeouts.html new file mode 100644 index 0000000000..93a8d9243c --- /dev/null +++ b/dom/workers/test/test_threadTimeouts.html @@ -0,0 +1,60 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads (Bug 437152) +--> +<head> + <title>Test for DOM Worker Threads (Bug 437152)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=437152">DOM Worker Threads Bug 437152</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("threadTimeouts_worker.js"); + + worker.onmessage = function(event) { + is(event.target, worker); + switch (event.data) { + case "timeoutFinished": + event.target.postMessage("startInterval"); + break; + case "intervalFinished": + event.target.postMessage("cancelInterval"); + break; + case "intervalCanceled": + worker.postMessage("startExpression"); + break; + case "expressionFinished": + SimpleTest.finish(); + break; + default: + ok(false, "Unexpected message"); + SimpleTest.finish(); + } + }; + + worker.onerror = function(event) { + is(event.target, worker); + ok(false, "Worker had an error: " + event.message); + SimpleTest.finish(); + }; + + worker.postMessage("startTimeout"); + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_throwingOnerror.html b/dom/workers/test/test_throwingOnerror.html new file mode 100644 index 0000000000..0ed1f74247 --- /dev/null +++ b/dom/workers/test/test_throwingOnerror.html @@ -0,0 +1,54 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads +--> +<head> + <title>Test for DOM Worker Threads Recursion</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("throwingOnerror_worker.js"); + + var errors = ["foo", "bar"]; + + worker.onerror = function(event) { + event.preventDefault(); + var found = false; + for (var index in errors) { + if (event.message == "uncaught exception: " + errors[index]) { + errors.splice(index, 1); + found = true; + break; + } + } + is(found, true, "Unexpected error!"); + }; + + worker.onmessage = function(event) { + is(errors.length, 0, "Didn't see expected errors!"); + SimpleTest.finish(); + }; + + for (var i = 0; i < 2; i++) { + worker.postMessage(""); + } + + SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_timeoutTracing.html b/dom/workers/test/test_timeoutTracing.html new file mode 100644 index 0000000000..8e64564b72 --- /dev/null +++ b/dom/workers/test/test_timeoutTracing.html @@ -0,0 +1,47 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker Threads +--> +<head> + <title>Test for DOM Worker Threads</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var worker = new Worker("timeoutTracing_worker.js"); + + worker.onmessage = function(event) { + // begin + worker.onmessage = null; + + // 1 second should be enough to crash. + window.setTimeout(function() { + ok(true, "Didn't crash!"); + SimpleTest.finish(); + }, 1000); + + var os = SpecialPowers.Cc["@mozilla.org/observer-service;1"] + .getService(SpecialPowers.Ci.nsIObserverService); + os.notifyObservers(null, "memory-pressure", "heap-minimize"); + } + + worker.onerror = function(event) { + ok(false, "I was expecting a crash, not an error"); + SimpleTest.finish(); + }; + + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("untriaged"); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_transferable.html b/dom/workers/test/test_transferable.html new file mode 100644 index 0000000000..ac490f369a --- /dev/null +++ b/dom/workers/test/test_transferable.html @@ -0,0 +1,123 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests of DOM Worker transferable objects +--> +<head> + <title>Test for DOM Worker transferable objects</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" language="javascript"> + + function test1(sizes) { + if (!sizes.length) { + runTests(); + return; + } + + var size = sizes.pop(); + + var worker = new Worker("transferable_worker.js"); + worker.onmessage = function(event) { + ok(event.data.status, event.data.event); + if (!event.data.status) { + runTests(); + return; + } + + if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) { + ok(event.data.notEmpty.byteLength != 0, + "P: NotEmpty object received: " + event.data.notEmpty.byteLength); + } + + if (!event.data.last) + return; + + test1(sizes); + } + worker.onerror = function(event) { + ok(false, "No errors!"); + } + + try { + worker.postMessage(42, true); + ok(false, "P: PostMessage - Exception for wrong type"); + } catch(e) { + ok(true, "P: PostMessage - Exception for wrong type"); + } + + try { + ab = new ArrayBuffer(size); + worker.postMessage(42,[ab, ab]); + ok(false, "P: PostMessage - Exception for duplicate"); + } catch(e) { + ok(true, "P: PostMessage - Exception for duplicate"); + } + + var ab = new ArrayBuffer(size); + ok(ab.byteLength == size, "P: The size is: " + size + " == " + ab.byteLength); + worker.postMessage({ data: 0, timeout: 0, ab, cb: ab, size }, [ab]); + ok(ab.byteLength == 0, "P: PostMessage - The size is: 0 == " + ab.byteLength) + } + + function test2() { + var worker = new Worker("transferable_worker.js"); + worker.onmessage = function(event) { + ok(event.data.status, event.data.event); + if (!event.data.status) { + runTests(); + return; + } + + if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) { + ok(event.data.notEmpty.byteLength != 0, + "P: NotEmpty object received: " + event.data.notEmpty.byteLength); + } + + if (event.data.last) { + runTests(); + } + } + worker.onerror = function(event) { + ok(false, "No errors!"); + } + + var f = new Float32Array([0,1,2,3]); + ok(f.byteLength != 0, "P: The size is: " + f.byteLength + " is not 0"); + worker.postMessage({ event: "P: postMessage with Float32Array", status: true, + size: 4, notEmpty: f, bc: [ f, f, { dd: f } ] }, [f.buffer]); + ok(f.byteLength == 0, "P: The size is: " + f.byteLength + " is 0"); + } + + var tests = [ + function() { test1([1024 * 1024 * 32, 128, 4]); }, + test2 + ]; + function runTests() { + if (!tests.length) { + SimpleTest.finish(); + return; + } + + var test = tests.shift(); + test(); + } + + SimpleTest.waitForExplicitFinish(); + runTests(); + +</script> +</pre> +</body> +</html> diff --git a/dom/workers/test/test_worker_interfaces.html b/dom/workers/test/test_worker_interfaces.html new file mode 100644 index 0000000000..b051e01242 --- /dev/null +++ b/dom/workers/test/test_worker_interfaces.html @@ -0,0 +1,19 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="worker_driver.js"></script> +</head> +<body> +<script> + ok(!self.isSecureContext, "This test should not be running in a secure context"); +</script> +<script class="testbody" type="text/javascript"> +workerTestExec("test_worker_interfaces.js"); +</script> +</body> +</html> diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js new file mode 100644 index 0000000000..3ea89ad6b5 --- /dev/null +++ b/dom/workers/test/test_worker_interfaces.js @@ -0,0 +1,563 @@ +// This is a list of all interfaces that are exposed to workers. +// Please only add things to this list with great care and proper review +// from the associated module peers. + +// This file lists global interfaces we want exposed and verifies they +// are what we intend. Each entry in the arrays below can either be a +// simple string with the interface name, or an object with a 'name' +// property giving the interface name as a string, and additional +// properties which qualify the exposure of that interface. For example: +// +// [ +// "AGlobalInterface", // secure context only +// { name: "ExperimentalThing", release: false }, +// { name: "ReallyExperimentalThing", nightly: true }, +// { name: "DesktopOnlyThing", desktop: true }, +// { name: "FancyControl", xbl: true }, +// { name: "DisabledEverywhere", disabled: true }, +// ]; +// +// See createInterfaceMap() below for a complete list of properties. +// +// The values of the properties need to be literal true/false +// (e.g. indicating whether something is enabled on a particular +// channel/OS). If we ever end up in a situation where a propert +// value needs to depend on channel or OS, we will need to make sure +// we have that information before setting up the property lists. + +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +let wasmGlobalEntry = { + name: "WebAssembly", + insecureContext: true, + disabled: !getJSTestingFunctions().wasmIsSupportedByHardware(), +}; +let wasmGlobalInterfaces = [ + { name: "Module", insecureContext: true }, + { name: "Instance", insecureContext: true }, + { name: "Memory", insecureContext: true }, + { name: "Table", insecureContext: true }, + { name: "Global", insecureContext: true }, + { name: "CompileError", insecureContext: true }, + { name: "LinkError", insecureContext: true }, + { name: "RuntimeError", insecureContext: true }, + { name: "Function", insecureContext: true, nightly: true }, + { name: "Exception", insecureContext: true }, + { name: "Tag", insecureContext: true }, + { name: "compile", insecureContext: true }, + { name: "compileStreaming", insecureContext: true }, + { name: "instantiate", insecureContext: true }, + { name: "instantiateStreaming", insecureContext: true }, + { name: "validate", insecureContext: true }, +]; +// IMPORTANT: Do not change this list without review from +// a JavaScript Engine peer! +let ecmaGlobals = [ + { name: "AggregateError", insecureContext: true }, + { name: "Array", insecureContext: true }, + { name: "ArrayBuffer", insecureContext: true }, + { name: "Atomics", insecureContext: true }, + { name: "BigInt", insecureContext: true }, + { name: "BigInt64Array", insecureContext: true }, + { name: "BigUint64Array", insecureContext: true }, + { name: "Boolean", insecureContext: true }, + { name: "DataView", insecureContext: true }, + { name: "Date", insecureContext: true }, + { name: "Error", insecureContext: true }, + { name: "EvalError", insecureContext: true }, + { name: "FinalizationRegistry", insecureContext: true }, + { name: "Float32Array", insecureContext: true }, + { name: "Float64Array", insecureContext: true }, + { name: "Function", insecureContext: true }, + { name: "Infinity", insecureContext: true }, + { name: "Int16Array", insecureContext: true }, + { name: "Int32Array", insecureContext: true }, + { name: "Int8Array", insecureContext: true }, + { name: "InternalError", insecureContext: true }, + { name: "Intl", insecureContext: true }, + { name: "JSON", insecureContext: true }, + { name: "Map", insecureContext: true }, + { name: "MediaCapabilities", insecureContext: true }, + { name: "MediaCapabilitiesInfo", insecureContext: true }, + { name: "Math", insecureContext: true }, + { name: "NaN", insecureContext: true }, + { name: "Number", insecureContext: true }, + { name: "Object", insecureContext: true }, + { name: "Promise", insecureContext: true }, + { name: "Proxy", insecureContext: true }, + { name: "RangeError", insecureContext: true }, + { name: "ReferenceError", insecureContext: true }, + { name: "Reflect", insecureContext: true }, + { name: "RegExp", insecureContext: true }, + { name: "Set", insecureContext: true }, + { + name: "SharedArrayBuffer", + insecureContext: true, + crossOringinIsolated: true, + }, + { name: "String", insecureContext: true }, + { name: "Symbol", insecureContext: true }, + { name: "SyntaxError", insecureContext: true }, + { name: "TypeError", insecureContext: true }, + { name: "Uint16Array", insecureContext: true }, + { name: "Uint32Array", insecureContext: true }, + { name: "Uint8Array", insecureContext: true }, + { name: "Uint8ClampedArray", insecureContext: true }, + { name: "URIError", insecureContext: true }, + { name: "WeakMap", insecureContext: true }, + { name: "WeakRef", insecureContext: true }, + { name: "WeakSet", insecureContext: true }, + wasmGlobalEntry, + { name: "decodeURI", insecureContext: true }, + { name: "decodeURIComponent", insecureContext: true }, + { name: "encodeURI", insecureContext: true }, + { name: "encodeURIComponent", insecureContext: true }, + { name: "escape", insecureContext: true }, + { name: "eval", insecureContext: true }, + { name: "globalThis", insecureContext: true }, + { name: "isFinite", insecureContext: true }, + { name: "isNaN", insecureContext: true }, + { name: "parseFloat", insecureContext: true }, + { name: "parseInt", insecureContext: true }, + { name: "undefined", insecureContext: true }, + { name: "unescape", insecureContext: true }, +]; +// IMPORTANT: Do not change the list above without review from +// a JavaScript Engine peer! + +// IMPORTANT: Do not change the list below without review from a DOM peer! +let interfaceNamesInGlobalScope = [ + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AbortController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "AbortSignal", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Blob", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "BroadcastChannel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ByteLengthQueuingStrategy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "Cache", + // IMPORTANT: Do not change this list without review from a DOM peer! + "CacheStorage", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasGradient", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CanvasPattern", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CloseEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CompressionStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CountQueuingStrategy", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Crypto", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CryptoKey" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "CustomEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DecompressionStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DedicatedWorkerGlobalScope", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Directory", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMException", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMMatrix", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMMatrixReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMPoint", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMPointReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMQuad", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRect", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRectReadOnly", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMRequest", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "DOMStringList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EncodedVideoChunk", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ErrorEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Event", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EventSource", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "EventTarget", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "File", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileReaderSync", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemDirectoryHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemFileHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemSyncAccessHandle" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FileSystemWritableFileStream" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFace", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFaceSet", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FontFaceSetLoadEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "FormData", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Headers", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBCursor", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBCursorWithValue", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBDatabase", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBFactory", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBIndex", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBKeyRange", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBObjectStore", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBOpenDBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBTransaction", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "IDBVersionChangeEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageBitmap", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageBitmapRenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ImageData", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + "Lock", + // IMPORTANT: Do not change this list without review from a DOM peer! + "LockManager", + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessageChannel", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessageEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "MessagePort", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "NetworkInformation", insecureContext: true, disabled: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Notification", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OffscreenCanvas", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OffscreenCanvasRenderingContext2D", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Path2D", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Performance", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceEntry", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceMark", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceMeasure", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceObserver", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceObserverEntryList", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceResourceTiming", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PerformanceServerTiming", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ProgressEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "PromiseRejectionEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableByteStreamController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamBYOBReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamBYOBRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamDefaultController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "ReadableStreamDefaultReader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Request", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Response", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCEncodedAudioFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCEncodedVideoFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCRtpScriptTransformer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "RTCTransformEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Scheduler", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "StorageManager", fennec: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "SubtleCrypto" }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskController", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskPriorityChangeEvent", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TaskSignal", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextDecoder", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextDecoderStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextEncoder", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextEncoderStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TextMetrics", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "TransformStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { + name: "TransformStreamDefaultController", + insecureContext: true, + }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequest", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequestEventTarget", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "XMLHttpRequestUpload", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "URL", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "URLSearchParams", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VideoColorSpace", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VideoDecoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VideoEncoder", nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "VideoFrame", insecureContext: true, nightly: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGL2RenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLActiveInfo", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLBuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLContextEvent", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLFramebuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLProgram", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLQuery", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderbuffer", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderingContext", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLSampler", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShader", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShaderPrecisionFormat", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLSync", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLTexture", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLTransformFeedback", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLUniformLocation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLVertexArrayObject", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebSocket", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransport", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportBidirectionalStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportDatagramDuplexStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportError", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportReceiveStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebTransportSendStream", insecureContext: false }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "Worker", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WorkerGlobalScope", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WorkerLocation", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WorkerNavigator", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStream", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStreamDefaultController", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WritableStreamDefaultWriter", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "cancelAnimationFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "close", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "console", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "name", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmessage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onmessageerror", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "onrtctransform", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "postMessage", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! + { name: "requestAnimationFrame", insecureContext: true }, + // IMPORTANT: Do not change this list without review from a DOM peer! +]; +// IMPORTANT: Do not change the list above without review from a DOM peer! + +// List of functions defined on the global by the test harness or this test +// file. +let testFunctions = [ + "ok", + "is", + "workerTestArrayEquals", + "workerTestDone", + "workerTestGetPermissions", + "workerTestGetHelperData", + "entryDisabled", + "createInterfaceMap", + "runTest", +]; + +function entryDisabled( + entry, + { + isNightly, + isEarlyBetaOrEarlier, + isRelease, + isDesktop, + isAndroid, + isInsecureContext, + isFennec, + isCrossOringinIsolated, + } +) { + return ( + entry.nightly === !isNightly || + (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) || + entry.desktop === !isDesktop || + (entry.android === !isAndroid && !entry.nightlyAndroid) || + entry.fennecOrDesktop === (isAndroid && !isFennec) || + entry.fennec === !isFennec || + entry.release === !isRelease || + // The insecureContext test is very purposefully converting + // entry.insecureContext to boolean, so undefined will convert to + // false. That way entries without an insecureContext annotation + // will get treated as "insecureContext: false", which means exposed + // only in secure contexts. + (isInsecureContext && !entry.insecureContext) || + entry.earlyBetaOrEarlier === !isEarlyBetaOrEarlier || + entry.crossOringinIsolated === !isCrossOringinIsolated || + entry.disabled + ); +} + +function createInterfaceMap(data, ...interfaceGroups) { + var interfaceMap = {}; + + function addInterfaces(interfaces) { + for (var entry of interfaces) { + if (typeof entry === "string") { + ok(!(entry in interfaceMap), "duplicate entry for " + entry); + interfaceMap[entry] = !data.isInsecureContext; + } else { + ok(!(entry.name in interfaceMap), "duplicate entry for " + entry.name); + ok(!("pref" in entry), "Bogus pref annotation for " + entry.name); + interfaceMap[entry.name] = !entryDisabled(entry, data); + } + } + } + + for (let interfaceGroup of interfaceGroups) { + addInterfaces(interfaceGroup); + } + + return interfaceMap; +} + +function runTest(parentName, parent, data, ...interfaceGroups) { + var interfaceMap = createInterfaceMap(data, ...interfaceGroups); + for (var name of Object.getOwnPropertyNames(parent)) { + // Ignore functions on the global that are part of the test (harness). + if (parent === self && testFunctions.includes(name)) { + continue; + } + ok( + interfaceMap[name], + "If this is failing: DANGER, are you sure you want to expose the new interface " + + name + + " to all webpages as a property of " + + parentName + + "? Do not make a change to this file without a " + + " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)" + ); + delete interfaceMap[name]; + } + for (var name of Object.keys(interfaceMap)) { + ok( + name in parent === interfaceMap[name], + name + + " should " + + (interfaceMap[name] ? "" : " NOT") + + " be defined on " + + parentName + ); + if (!interfaceMap[name]) { + delete interfaceMap[name]; + } + } + is( + Object.keys(interfaceMap).length, + 0, + "The following interface(s) are not enumerated: " + + Object.keys(interfaceMap).join(", ") + ); +} + +workerTestGetHelperData(function (data) { + runTest("self", self, data, ecmaGlobals, interfaceNamesInGlobalScope); + if (WebAssembly && !entryDisabled(wasmGlobalEntry, data)) { + runTest("WebAssembly", WebAssembly, data, wasmGlobalInterfaces); + } + workerTestDone(); +}); diff --git a/dom/workers/test/test_worker_interfaces_secureContext.html b/dom/workers/test/test_worker_interfaces_secureContext.html new file mode 100644 index 0000000000..7d26cd2131 --- /dev/null +++ b/dom/workers/test/test_worker_interfaces_secureContext.html @@ -0,0 +1,19 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="worker_driver.js"></script> +</head> +<body> +<script> + ok(self.isSecureContext, "This test should be running in a secure context"); +</script> +<script class="testbody" type="text/javascript"> +workerTestExec("test_worker_interfaces.js"); +</script> +</body> +</html> diff --git a/dom/workers/test/threadErrors_worker1.js b/dom/workers/test/threadErrors_worker1.js new file mode 100644 index 0000000000..c0ddade82c --- /dev/null +++ b/dom/workers/test/threadErrors_worker1.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Syntax error +onmessage = function(event) { + for (var i = 0; i < 10) { } +} diff --git a/dom/workers/test/threadErrors_worker2.js b/dom/workers/test/threadErrors_worker2.js new file mode 100644 index 0000000000..da79569def --- /dev/null +++ b/dom/workers/test/threadErrors_worker2.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Bad function error +onmessage = function (event) { + foopy(); +}; diff --git a/dom/workers/test/threadErrors_worker3.js b/dom/workers/test/threadErrors_worker3.js new file mode 100644 index 0000000000..e470680981 --- /dev/null +++ b/dom/workers/test/threadErrors_worker3.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Unhandled exception in body +onmessage = function (event) {}; + +throw new Error("Bah!"); diff --git a/dom/workers/test/threadErrors_worker4.js b/dom/workers/test/threadErrors_worker4.js new file mode 100644 index 0000000000..88b089aa3b --- /dev/null +++ b/dom/workers/test/threadErrors_worker4.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +// Throwing message listener +onmessage = function (event) { + throw new Error("Bah!"); +}; diff --git a/dom/workers/test/threadTimeouts_worker.js b/dom/workers/test/threadTimeouts_worker.js new file mode 100644 index 0000000000..27e514b391 --- /dev/null +++ b/dom/workers/test/threadTimeouts_worker.js @@ -0,0 +1,45 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +var gTimeoutId; +var gTimeoutCount = 0; +var gIntervalCount = 0; + +function timeoutFunc() { + if (++gTimeoutCount > 1) { + throw new Error("Timeout called more than once!"); + } + postMessage("timeoutFinished"); +} + +function intervalFunc() { + if (++gIntervalCount == 2) { + postMessage("intervalFinished"); + } +} + +function messageListener(event) { + switch (event.data) { + case "startTimeout": + gTimeoutId = setTimeout(timeoutFunc, 2000); + clearTimeout(gTimeoutId); + gTimeoutId = setTimeout(timeoutFunc, 2000); + break; + case "startInterval": + gTimeoutId = setInterval(intervalFunc, 2000); + break; + case "cancelInterval": + clearInterval(gTimeoutId); + postMessage("intervalCanceled"); + break; + case "startExpression": + // eslint-disable-next-line no-implied-eval + setTimeout("this.postMessage('expressionFinished');", 2000); + break; + default: + throw "Bad message: " + event.data; + } +} + +addEventListener("message", messageListener, false); diff --git a/dom/workers/test/throwingOnerror_worker.js b/dom/workers/test/throwingOnerror_worker.js new file mode 100644 index 0000000000..47b727f56a --- /dev/null +++ b/dom/workers/test/throwingOnerror_worker.js @@ -0,0 +1,15 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +onerror = function (event) { + throw "bar"; +}; + +var count = 0; +onmessage = function (event) { + if (!count++) { + throw "foo"; + } + postMessage(""); +}; diff --git a/dom/workers/test/timeoutTracing_worker.js b/dom/workers/test/timeoutTracing_worker.js new file mode 100644 index 0000000000..2eac367535 --- /dev/null +++ b/dom/workers/test/timeoutTracing_worker.js @@ -0,0 +1,16 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function (event) { + throw "No messages should reach me!"; +}; + +setInterval(function () { + postMessage("Still alive!"); +}, 20); +// eslint-disable-next-line no-implied-eval +setInterval(";", 20); + +postMessage("Begin!"); diff --git a/dom/workers/test/transferable_worker.js b/dom/workers/test/transferable_worker.js new file mode 100644 index 0000000000..d0fa41cad1 --- /dev/null +++ b/dom/workers/test/transferable_worker.js @@ -0,0 +1,40 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +onmessage = function (event) { + if ("notEmpty" in event.data && "byteLength" in event.data.notEmpty) { + postMessage({ + event: "W: NotEmpty object received: " + event.data.notEmpty.byteLength, + status: event.data.notEmpty.byteLength != 0, + last: false, + }); + } + + var ab = new ArrayBuffer(event.data.size); + postMessage({ + event: "W: The size is: " + event.data.size + " == " + ab.byteLength, + status: ab.byteLength == event.data.size, + last: false, + }); + + postMessage( + { + event: "W: postMessage with arrayBuffer", + status: true, + notEmpty: ab, + ab, + bc: [ab, ab, { dd: ab }], + }, + [ab] + ); + + postMessage({ + event: "W: The size is: 0 == " + ab.byteLength, + status: ab.byteLength == 0, + last: false, + }); + + postMessage({ event: "W: last one!", status: true, last: true }); +}; diff --git a/dom/workers/test/window_suspended.html b/dom/workers/test/window_suspended.html new file mode 100644 index 0000000000..ae5d25df58 --- /dev/null +++ b/dom/workers/test/window_suspended.html @@ -0,0 +1,71 @@ +<script> +const WORKER_URL = "worker_suspended.js"; +var testUrl2 = "window_suspended.html?page2Shown"; + +let cacheDataPromise = {}; +cacheDataPromise.promise = new Promise(resolve => { + cacheDataPromise.resolve = resolve; +}); +var bcName = location.search.split('?')[1]; +var bc = new BroadcastChannel(bcName); +if (bcName == "page1Shown") { + bc.onmessage = async (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "startWorker") { + // Create a worker and subworkers + let { worker, promise } = postMessageWorker(msg.workerMessage); + promise.then(function() { + bc.postMessage({command: "verifyCacheData"}); + return cacheDataPromise.promise; + }) + .then(function() { + location.href = testUrl2; + }); + } else if (command == "changeLocation") { + cacheDataPromise.resolve(); + } else if (command == "finish") { + bc.postMessage({command: "finished"}); + bc.close(); + window.close(); + } + } +} else if (bcName == "page2Shown") { + bc.onmessage = (msgEvent) => { + var msg = msgEvent.data; + var command = msg.command; + if (command == "startWorker") { + let { worker, promise } = postMessageWorker(msg.workerMessage); + promise.then(function() { + bc.postMessage({command: "verifyCacheData"}); + return cacheDataPromise.promise; + }) + .then(function() { + bc.close(); + history.back(); + }); + } else if (command == "goBack") { + cacheDataPromise.resolve(); + } + } +} + +function postMessageWorker(message) { + let worker = new window.Worker(WORKER_URL); + + var promise = new Promise((resolve, reject) => { + // Waiting until workers are ready + worker.addEventListener("message", function onmessage(msg) { + bc.postMessage({command: "workerMessage", workerMessage: msg.data}); + worker.removeEventListener("message", onmessage); + resolve(); + }); + worker.postMessage(message); + }); + return { worker, promise }; +} + +onpageshow = function(e) { + bc.postMessage({command: "onpageshow", persisted: e.persisted, location: location.href}); +} +</script> diff --git a/dom/workers/test/worker_bug1278777.js b/dom/workers/test/worker_bug1278777.js new file mode 100644 index 0000000000..f596ee978b --- /dev/null +++ b/dom/workers/test/worker_bug1278777.js @@ -0,0 +1,9 @@ +var xhr = new XMLHttpRequest(); +xhr.responseType = "blob"; +xhr.open("GET", "worker_bug1278777.js"); + +xhr.onload = function () { + postMessage(xhr.response instanceof Blob); +}; + +xhr.send(); diff --git a/dom/workers/test/worker_bug1301094.js b/dom/workers/test/worker_bug1301094.js new file mode 100644 index 0000000000..90fbe178b5 --- /dev/null +++ b/dom/workers/test/worker_bug1301094.js @@ -0,0 +1,11 @@ +onmessage = function (e) { + var xhr = new XMLHttpRequest(); + xhr.open("POST", "worker_bug1301094.js", false); + xhr.onload = function () { + self.postMessage("OK"); + }; + + var fd = new FormData(); + fd.append("file", e.data); + xhr.send(fd); +}; diff --git a/dom/workers/test/worker_bug1824498.mjs b/dom/workers/test/worker_bug1824498.mjs new file mode 100644 index 0000000000..932bb530ac --- /dev/null +++ b/dom/workers/test/worker_bug1824498.mjs @@ -0,0 +1,4 @@ +/* eslint-disable import/no-unassigned-import */ +/* eslint-disable import/no-unresolved */ +import {} from "./foo"; +import {} from "./bar"; diff --git a/dom/workers/test/worker_consoleAndBlobs.js b/dom/workers/test/worker_consoleAndBlobs.js new file mode 100644 index 0000000000..e95a87fb83 --- /dev/null +++ b/dom/workers/test/worker_consoleAndBlobs.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ +"use strict"; + +var b = new Blob(["123"], { type: "foo/bar" }); +console.log({ msg: "consoleAndBlobs", blob: b }); diff --git a/dom/workers/test/worker_driver.js b/dom/workers/test/worker_driver.js new file mode 100644 index 0000000000..29a0d50025 --- /dev/null +++ b/dom/workers/test/worker_driver.js @@ -0,0 +1,84 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +// Utility script for writing worker tests. In your main document do: +// +// <script type="text/javascript" src="worker_driver.js"></script> +// <script type="text/javascript"> +// workerTestExec('myWorkerTestCase.js') +// </script> +// +// This will then spawn a worker, define some utility functions, and then +// execute the code in myWorkerTestCase.js. You can then use these +// functions in your worker-side test: +// +// ok() - like the SimpleTest assert +// is() - like the SimpleTest assert +// workerTestDone() - like SimpleTest.finish() indicating the test is complete +// +// There are also some functions for requesting information that requires +// SpecialPowers or other main-thread-only resources: +// +// workerTestGetVersion() - request the current version string from the MT +// workerTestGetUserAgent() - request the user agent string from the MT +// workerTestGetOSCPU() - request the navigator.oscpu string from the MT +// +// For an example see test_worker_interfaces.html and test_worker_interfaces.js. + +function workerTestExec(script) { + SimpleTest.waitForExplicitFinish(); + var worker = new Worker("worker_wrapper.js"); + worker.onmessage = function (event) { + if (event.data.type == "finish") { + SimpleTest.finish(); + } else if (event.data.type == "status") { + ok(event.data.status, event.data.msg); + } else if (event.data.type == "getHelperData") { + const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + const isNightly = AppConstants.NIGHTLY_BUILD; + const isEarlyBetaOrEarlier = AppConstants.EARLY_BETA_OR_EARLIER; + const isRelease = AppConstants.RELEASE_OR_BETA; + const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent); + const isMac = AppConstants.platform == "macosx"; + const isWindows = AppConstants.platform == "win"; + const isAndroid = AppConstants.platform == "android"; + const isLinux = AppConstants.platform == "linux"; + const isInsecureContext = !window.isSecureContext; + // Currently, MOZ_APP_NAME is always "fennec" for all mobile builds, so we can't use AppConstants for this + const isFennec = + isAndroid && + SpecialPowers.Cc["@mozilla.org/android/bridge;1"].getService( + SpecialPowers.Ci.nsIAndroidBridge + ).isFennec; + const isCrossOriginIsolated = window.crossOriginIsolated; + + const result = { + isNightly, + isEarlyBetaOrEarlier, + isRelease, + isDesktop, + isMac, + isWindows, + isAndroid, + isLinux, + isInsecureContext, + isFennec, + isCrossOriginIsolated, + }; + + worker.postMessage({ + type: "returnHelperData", + result, + }); + } + }; + + worker.onerror = function (event) { + ok(false, "Worker had an error: " + event.data); + SimpleTest.finish(); + }; + + worker.postMessage({ script }); +} diff --git a/dom/workers/test/worker_dynamicImport.mjs b/dom/workers/test/worker_dynamicImport.mjs new file mode 100644 index 0000000000..b5c50d30f7 --- /dev/null +++ b/dom/workers/test/worker_dynamicImport.mjs @@ -0,0 +1,2 @@ +/* eslint-disable import/no-unresolved */ +const { o } = await import("./404.js"); diff --git a/dom/workers/test/worker_referrer.js b/dom/workers/test/worker_referrer.js new file mode 100644 index 0000000000..ec9fb1f8a0 --- /dev/null +++ b/dom/workers/test/worker_referrer.js @@ -0,0 +1,9 @@ +onmessage = function () { + importScripts(["referrer.sjs?import"]); + var xhr = new XMLHttpRequest(); + xhr.open("GET", "referrer.sjs?result", true); + xhr.onload = function () { + postMessage(xhr.responseText); + }; + xhr.send(); +}; diff --git a/dom/workers/test/worker_setTimeoutWith0.js b/dom/workers/test/worker_setTimeoutWith0.js new file mode 100644 index 0000000000..91de9c5a73 --- /dev/null +++ b/dom/workers/test/worker_setTimeoutWith0.js @@ -0,0 +1,4 @@ +/* eslint-disable no-implied-eval */ +var x = 0; +setTimeout("x++; '\x00'; x++;"); +setTimeout("postMessage(x);"); diff --git a/dom/workers/test/worker_shutdownCheck.js b/dom/workers/test/worker_shutdownCheck.js new file mode 100644 index 0000000000..f51279daf6 --- /dev/null +++ b/dom/workers/test/worker_shutdownCheck.js @@ -0,0 +1 @@ +postMessage("Ok!"); diff --git a/dom/workers/test/worker_suspended.js b/dom/workers/test/worker_suspended.js new file mode 100644 index 0000000000..f2b4146cba --- /dev/null +++ b/dom/workers/test/worker_suspended.js @@ -0,0 +1,39 @@ +var count = 0; + +function do_magic(data) { + caches + .open("test") + .then(function (cache) { + return cache.put( + "http://mochi.test:888/foo", + new Response(data.type + "-" + count++) + ); + }) + .then(function () { + if (count == 1) { + postMessage("ready"); + } + + if (data.loop) { + setTimeout(function () { + do_magic(data); + }, 500); + } + }); +} + +onmessage = function (e) { + if (e.data.type == "page1") { + if (e.data.count > 0) { + var a = new Worker("worker_suspended.js"); + a.postMessage({ type: "page1", count: e.data - 1 }); + a.onmessage = function () { + postMessage("ready"); + }; + } else { + do_magic({ type: e.data.type, loop: true }); + } + } else if (e.data.type == "page2") { + do_magic({ type: e.data.type, loop: false }); + } +}; diff --git a/dom/workers/test/worker_wrapper.js b/dom/workers/test/worker_wrapper.js new file mode 100644 index 0000000000..6a630d92d0 --- /dev/null +++ b/dom/workers/test/worker_wrapper.js @@ -0,0 +1,79 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +// Worker-side wrapper script for the worker_driver.js helper code. See +// the comments at the top of worker_driver.js for more information. + +function ok(a, msg) { + dump("OK: " + !!a + " => " + a + ": " + msg + "\n"); + postMessage({ type: "status", status: !!a, msg: a + ": " + msg }); +} + +function is(a, b, msg) { + dump("IS: " + (a === b) + " => " + a + " | " + b + ": " + msg + "\n"); + postMessage({ + type: "status", + status: a === b, + msg: a + " === " + b + ": " + msg, + }); +} + +function workerTestArrayEquals(a, b) { + if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) { + return false; + } + for (var i = 0, n = a.length; i < n; ++i) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +function workerTestDone() { + postMessage({ type: "finish" }); +} + +function workerTestGetPermissions(permissions, cb) { + addEventListener("message", function workerTestGetPermissionsCB(e) { + if ( + e.data.type != "returnPermissions" || + !workerTestArrayEquals(permissions, e.data.permissions) + ) { + return; + } + removeEventListener("message", workerTestGetPermissionsCB); + cb(e.data.result); + }); + postMessage({ + type: "getPermissions", + permissions, + }); +} + +function workerTestGetHelperData(cb) { + addEventListener("message", function workerTestGetHelperDataCB(e) { + if (e.data.type !== "returnHelperData") { + return; + } + removeEventListener("message", workerTestGetHelperDataCB); + cb(e.data.result); + }); + postMessage({ + type: "getHelperData", + }); +} + +addEventListener("message", function workerWrapperOnMessage(e) { + removeEventListener("message", workerWrapperOnMessage); + var data = e.data; + try { + importScripts(data.script); + } catch (ex) { + postMessage({ + type: "status", + status: false, + msg: "worker failed to import " + data.script + "; error: " + ex.message, + }); + } +}); diff --git a/dom/workers/test/xpcshell/data/base_uri_module.mjs b/dom/workers/test/xpcshell/data/base_uri_module.mjs new file mode 100644 index 0000000000..7604baed82 --- /dev/null +++ b/dom/workers/test/xpcshell/data/base_uri_module.mjs @@ -0,0 +1,23 @@ +// This file is for testing the module loader's path handling. +// ESLint rules that modifies path shouldn't be applied. + +export const obj = {}; + +export async function doImport() { + // This file is loaded as resource://test/data/base_uri_module.mjs + // Relative/absolute paths should be resolved based on the URI, instead of + // file: path. + + const namespaceWithURI = await import( + "resource://test/data/base_uri_module2.mjs" + ); + const namespaceWithCurrentDir = await import("./base_uri_module2.mjs"); + const namespaceWithParentDir = await import("../data/base_uri_module2.mjs"); + const namespaceWithAbsoluteDir = await import("/data/base_uri_module2.mjs"); + + return { + equal1: namespaceWithURI.obj2 == namespaceWithCurrentDir.obj2, + equal2: namespaceWithURI.obj2 == namespaceWithParentDir.obj2, + equal3: namespaceWithURI.obj2 == namespaceWithAbsoluteDir.obj2, + }; +} diff --git a/dom/workers/test/xpcshell/data/base_uri_module2.mjs b/dom/workers/test/xpcshell/data/base_uri_module2.mjs new file mode 100644 index 0000000000..2358d27a83 --- /dev/null +++ b/dom/workers/test/xpcshell/data/base_uri_module2.mjs @@ -0,0 +1 @@ +export const obj2 = {}; diff --git a/dom/workers/test/xpcshell/data/base_uri_worker.js b/dom/workers/test/xpcshell/data/base_uri_worker.js new file mode 100644 index 0000000000..74137cc20b --- /dev/null +++ b/dom/workers/test/xpcshell/data/base_uri_worker.js @@ -0,0 +1,27 @@ +// This file is for testing the module loader's path handling. +// ESLint rules that modifies path shouldn't be applied. + +onmessage = async event => { + // This file is loaded as resource://test/data/base_uri_worker.js + // Relative/absolute paths should be resolved based on the URI, instead of + // file: path. + + const namespaceWithURI = await import( + "resource://test/data/base_uri_module.mjs" + ); + const namespaceWithCurrentDir = await import("./base_uri_module.mjs"); + const namespaceWithParentDir = await import("../data/base_uri_module.mjs"); + const namespaceWithAbsoluteDir = await import("/data/base_uri_module.mjs"); + + postMessage({ + scriptToModule: { + equal1: namespaceWithURI.obj == namespaceWithCurrentDir.obj, + equal2: namespaceWithURI.obj == namespaceWithParentDir.obj, + equal3: namespaceWithURI.obj == namespaceWithAbsoluteDir.obj, + }, + moduleToModuleURI: await namespaceWithURI.doImport(), + moduleToModuleCurrent: await namespaceWithCurrentDir.doImport(), + moduleToModuleParent: await namespaceWithParentDir.doImport(), + moduleToModuleAbsolute: await namespaceWithAbsoluteDir.doImport(), + }); +}; 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..0a455f51c3 --- /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..44e7e6499b --- /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..3028e5d539 --- /dev/null +++ b/dom/workers/test/xpcshell/test_ext_redirects_sw_scripts.js @@ -0,0 +1,558 @@ +/* -*- 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.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +const { ExtensionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" +); + +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 +); +Services.prefs.setBoolPref("extensions.dnr.enabled", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("dom.serviceWorkers.testing.enabled"); + Services.prefs.clearUserPref( + "extensions.filterResponseServiceWorkerScript.disabled" + ); + Services.prefs.clearUserPref("extensions.dnr.enabled"); +}); + +// 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 { MessageChannel } = this.content; + 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"], + // In this test task, the extension resource is not expected to be + // requested at all, so it does not really matter whether the file is + // listed in web_accessible_resources. Regardless, add it to make sure + // that any load failure is not caused by the lack of being listed here. + web_accessible_resources: ["sw-unexpected-redirect.js"], + }, + 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('main worker 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 after loading the test extension, which should not be + // able to intercept and redirect the importedScripts requests because of + // invalid destinations. + 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 { MessageChannel } = this.content; + 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 { MessageChannel } = this.content; + 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 { MessageChannel } = this.content; + 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(); +}); + +// Test cases for redirects with declarativeNetRequest instead of webRequest, +// testing the same scenarios as: +// - test_extension_invalid_sw_scripts_redirect_ignored +// i.e. fail to redirect SW main script, fail to redirect SW to about:blank. +// - test_extension_redirect_sw_imported_script +// i.e. allowed to redirect importScripts to moz-extension. +add_task(async function test_dnr_redirect_sw_script_or_import() { + const extension = ExtensionTestUtils.loadExtension({ + manifest: { + manifest_version: 3, + permissions: ["declarativeNetRequest"], + host_permissions: ["<all_urls>"], + granted_host_permissions: true, + web_accessible_resources: [ + { + resources: ["sw-bad-redirect.js", "sw-dnr-redirect.js", "sw-nest.js"], + matches: ["*://*/*"], + }, + ], + }, + temporarilyInstalled: true, // <-- for granted_host_permissions + background: async () => { + await browser.declarativeNetRequest.updateSessionRules({ + addRules: [ + { + id: 1, + condition: { urlFilter: "|http://localhost/sw.js?dnr_redir_bad" }, + action: { + type: "redirect", + redirect: { extensionPath: "/sw-bad-redirect.js" }, + }, + }, + { + id: 2, + condition: { urlFilter: "|http://localhost/sw-imported.js|" }, + action: { + type: "redirect", + redirect: { extensionPath: "/sw-dnr-redirect.js" }, + }, + }, + { + id: 3, + condition: { urlFilter: "|http://localhost/sw-nest.js|" }, + action: { + type: "redirect", + redirect: { extensionPath: "/sw-nest.js" }, + }, + }, + { + id: 4, + condition: { urlFilter: "|http://localhost/sw-imported.js?about|" }, + action: { + type: "redirect", + redirect: { url: "about:blank" }, + }, + }, + ], + }); + browser.test.sendMessage("dnr_registered"); + }, + files: { + "sw-bad-redirect.js": String.raw` + dump('main worker redirected to moz-extension://UUID/sw-bad-redirect.js\n'); + self.onmessage = evt => evt.ports[0].postMessage('sw-bad-redirect'); + `, + "sw-dnr-redirect.js": String.raw` + dump('importScript redirected to moz-extension://UUID/sw-dnr-redirect.js\n'); + self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-before-nest'); + + importScripts("/sw-nest.js"); + // ^ sw-nest.js does not exist on the server, so if importScripts() + // succeeded, then that means that the DNR-triggered redirect worked. + + self.onmessage = evt => evt.ports[0].postMessage('sw-before-about'); + try { + importScripts("/sw-imported.js?about"); + // ^ DNR redirects to about:blank, which should throw here. + self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-about-bad'); + } catch (e) { + // All is good. + self.onmessage = evt => evt.ports[0].postMessage('sw-dnr-redirect'); + } + `, + "sw-nest.js": String.raw` + dump('importScript redirected to moz-extension://UUID/sw-nest.js\n'); + // No other code here. The caller verifies success by confirming that + // the importScripts() call did not throw. + `, + }, + }); + + // Start the test extension to redirect importScripts requests. + await extension.startup(); + await extension.awaitMessage("dnr_registered"); + + await ensureDataCleanup(); + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://localhost/page.html" + ); + + // Register the worker after loading the test extension, which should not be + // able to intercept and redirect the importedScripts requests because of + // invalid destinations. + info("Register service worker from a content webpage (disallowed redirects)"); + await contentPage.spawn([], async () => { + await Assert.rejects( + this.content.navigator.serviceWorker.register("/sw.js?dnr_redir_bad1"), + /SecurityError: The operation is insecure/, + "Redirect of main service worker script is not allowed" + ); + }); + info("Register service worker from a content webpage (with import redirect)"); + let workerMessage = await contentPage.spawn([], async () => { + const reg = await this.content.navigator.serviceWorker.register("/sw.js"); + return new Promise(resolve => { + const { MessageChannel } = this.content; + 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, + "sw-dnr-redirect", + "Got expected worker reply (importScripts redirected to moz-extension:-URL)" + ); + + await extension.unload(); + await contentPage.close(); +}); diff --git a/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js b/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js new file mode 100644 index 0000000000..718093422b --- /dev/null +++ b/dom/workers/test/xpcshell/test_ext_worker_offline_fetch.js @@ -0,0 +1,112 @@ +/* -*- 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.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +const { ExtensionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" +); + +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: ["example.com"] }); +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html", false); + response.write("dummy page"); +}); + +add_setup(() => { + info("Making sure Services.io.offline is true"); + // Explicitly setting Services.io.offline to true makes this test able + // to hit on Desktop builds the same issue that test_ext_cache_api.js + // was hitting on Android builds (Bug 1844825). + Services.io.offline = true; +}); + +// Regression test derived from Bug 1844825. +add_task(async function test_fetch_request_from_ext_shared_worker() { + if (!WebExtensionPolicy.useRemoteWebExtensions) { + // Ensure RemoteWorkerService has been initialized in the main + // process. + Services.obs.notifyObservers(null, "profile-after-change"); + } + + const background = async function () { + const testUrl = `http://example.com/dummy`; + const worker = new SharedWorker("worker.js"); + const { data: result } = await new Promise(resolve => { + worker.port.onmessage = resolve; + worker.port.postMessage(["worker-fetch-test", testUrl]); + }); + + browser.test.sendMessage("test-sharedworker-fetch:done", result); + }; + + const extension = ExtensionTestUtils.loadExtension({ + background, + manifest: { permissions: ["http://example.com/*"] }, + files: { + "worker.js": function () { + self.onconnect = evt => { + const port = evt.ports[0]; + port.onmessage = async evt => { + let result = {}; + let message; + try { + const [msg, url] = evt.data; + message = msg; + const response = await fetch(url); + dump(`fetch call resolved: ${response}\n`); + result.fetchResolvesTo = `${response}`; + } catch (err) { + dump(`fetch call rejected: ${err}\n`); + result.error = err.name; + throw err; + } finally { + port.postMessage([`${message}:result`, result]); + } + }; + }; + }, + }, + }); + + await extension.startup(); + const result = await extension.awaitMessage("test-sharedworker-fetch:done"); + if (Services.io.offline && WebExtensionPolicy.useRemoteWebExtensions) { + // If the network is offline and the extensions are running in the + // child extension process, expect the fetch call to be rejected + // with an TypeError. + Assert.deepEqual( + ["worker-fetch-test:result", { error: "TypeError" }], + result, + "fetch should have been rejected with an TypeError" + ); + } else { + // If the network is not offline or the extension are running in the + // parent process, we expect the fetch call to resolve to a Response. + Assert.deepEqual( + ["worker-fetch-test:result", { fetchResolvesTo: "[object Response]" }], + result, + "fetch should have been resolved to a Response instance" + ); + } + await extension.unload(); +}); diff --git a/dom/workers/test/xpcshell/test_fileReader.js b/dom/workers/test/xpcshell/test_fileReader.js new file mode 100644 index 0000000000..02bc3fa667 --- /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_import_base_uri.js b/dom/workers/test/xpcshell/test_import_base_uri.js new file mode 100644 index 0000000000..7c88a946f4 --- /dev/null +++ b/dom/workers/test/xpcshell/test_import_base_uri.js @@ -0,0 +1,35 @@ +/* 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/. */ + +add_task(async function testSyncImportBeforeAsyncImportDependencyInWorker() { + const worker = new ChromeWorker("resource://test/data/base_uri_worker.js"); + + const { promise, resolve } = Promise.withResolvers(); + worker.onmessage = event => { + resolve(event.data); + }; + worker.postMessage(""); + + const result = await promise; + + Assert.ok(result.scriptToModule.equal1); + Assert.ok(result.scriptToModule.equal2); + Assert.ok(result.scriptToModule.equal3); + + Assert.ok(result.moduleToModuleURI.equal1); + Assert.ok(result.moduleToModuleURI.equal2); + Assert.ok(result.moduleToModuleURI.equal3); + + Assert.ok(result.moduleToModuleCurrent.equal1); + Assert.ok(result.moduleToModuleCurrent.equal2); + Assert.ok(result.moduleToModuleCurrent.equal3); + + Assert.ok(result.moduleToModuleParent.equal1); + Assert.ok(result.moduleToModuleParent.equal2); + Assert.ok(result.moduleToModuleParent.equal3); + + Assert.ok(result.moduleToModuleAbsolute.equal1); + Assert.ok(result.moduleToModuleAbsolute.equal2); + Assert.ok(result.moduleToModuleAbsolute.equal3); +}); 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..b95ad4bcaf --- /dev/null +++ b/dom/workers/test/xpcshell/test_remoteworker_launch_new_process.js @@ -0,0 +1,88 @@ +/* -*- 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.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); +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() { + // 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..5b768f69bb --- /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..f3fd430457 --- /dev/null +++ b/dom/workers/test/xpcshell/test_workers_clone_error.js @@ -0,0 +1,43 @@ +/* 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 { + // eslint-disable-next-line no-eval + 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.toml b/dom/workers/test/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..61b1eef0c2 --- /dev/null +++ b/dom/workers/test/xpcshell/xpcshell.toml @@ -0,0 +1,38 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "data/worker.js", + "data/worker_fileReader.js", + "data/chrome.manifest", + "data/base_uri_worker.js", + "data/base_uri_module.mjs", + "data/base_uri_module2.mjs", +] + +["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_ext_worker_offline_fetch.js"] +firefox-appdir = "browser" + +["test_fileReader.js"] + +["test_import_base_uri.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 + + +["test_workers.js"] + +["test_workers_clone_error.js"] |