/* 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"; // Skipping due to window creation being unsupported in Fennec module.metadata = { engines: { 'Firefox': '*' } }; const { Cc, Ci } = require("chrome"); const { on } = require("sdk/event/core"); const { setTimeout } = require("sdk/timers"); const { LoaderWithHookedConsole, Loader } = require("sdk/test/loader"); const { Worker } = require("sdk/content/worker"); const { close } = require("sdk/window/helpers"); const { set: setPref } = require("sdk/preferences/service"); const { isArray } = require("sdk/lang/type"); const { URL } = require('sdk/url'); const fixtures = require("./fixtures"); const system = require("sdk/system/events"); const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo"; const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," + ""; function makeWindow() { let content = "" + "" + "" + ""; var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + encodeURIComponent(content); var features = ["chrome", "width=10", "height=10"]; return Cc["@mozilla.org/embedcomp/window-watcher;1"]. getService(Ci.nsIWindowWatcher). openWindow(null, url, null, features.join(","), null); } // Listen for only first one occurence of DOM event function listenOnce(node, eventName, callback) { node.addEventListener(eventName, function onevent(event) { node.removeEventListener(eventName, onevent, true); callback(node); }, true); } // Load a given url in a given browser and fires the callback when it is loaded function loadAndWait(browser, url, callback) { listenOnce(browser, "load", callback); // We have to wait before calling `loadURI` otherwise, if we call // `loadAndWait` during browser load event, the history will be broken setTimeout(function () { browser.loadURI(url); }, 0); } // Returns a test function that will automatically open a new chrome window // with a element loaded on a given content URL // The callback receive 3 arguments: // - test: reference to the jetpack test object // - browser: a reference to the xul node // - done: a callback to call when test is over function WorkerTest(url, callback) { return function testFunction(assert, done) { let chromeWindow = makeWindow(); chromeWindow.addEventListener("load", function onload() { chromeWindow.removeEventListener("load", onload, true); let browser = chromeWindow.document.createElement("browser"); browser.setAttribute("type", "content"); chromeWindow.document.documentElement.appendChild(browser); // Wait for about:blank load event ... listenOnce(browser, "load", function onAboutBlankLoad() { // ... before loading the expected doc and waiting for its load event loadAndWait(browser, url, function onDocumentLoaded() { callback(assert, browser, function onTestDone() { close(chromeWindow).then(done); }); }); }); }, true); }; } exports["test:sample"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { assert.notEqual(browser.contentWindow.location.href, "about:blank", "window is now on the right document"); let window = browser.contentWindow let worker = Worker({ window: window, contentScript: "new " + function WorkerScope() { // window is accessible let myLocation = window.location.toString(); self.on("message", function(data) { if (data == "hi!") self.postMessage("bye!"); }); }, contentScriptWhen: "ready", onMessage: function(msg) { assert.equal("bye!", msg); assert.equal(worker.url, window.location.href, "worker.url still works"); done(); }, onAttach: function() { assert.equal(worker.url, window.location.href, "worker.url works"); assert.equal(worker.contentURL, window.location.href, "worker.contentURL works"); worker.postMessage("hi!"); } }); } ); exports["test:emit"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { // Validate self.on and self.emit self.port.on("addon-to-content", function (data) { self.port.emit("content-to-addon", data); }); // Check for global pollution //if (typeof on != "undefined") // self.postMessage("`on` is in globals"); if (typeof once != "undefined") self.postMessage("`once` is in globals"); if (typeof emit != "undefined") self.postMessage("`emit` is in globals"); }, onMessage: function(msg) { assert.fail("Got an unexpected message : "+msg); } }); // Validate worker.port worker.port.on("content-to-addon", function (data) { assert.equal(data, "event data"); done(); }); worker.port.emit("addon-to-content", "event data"); } ); exports["test:emit hack message"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { // Validate self.port self.port.on("message", function (data) { self.port.emit("message", data); }); // We should not receive message on self, but only on self.port self.on("message", function (data) { self.postMessage("message", data); }); }, onError: function(e) { assert.fail("Got exception: "+e); } }); worker.port.on("message", function (data) { assert.equal(data, "event data"); done(); }); worker.on("message", function (data) { assert.fail("Got an unexpected message : "+msg); }); worker.port.emit("message", "event data"); } ); exports["test:n-arguments emit"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let repeat = 0; let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { // Validate self.on and self.emit self.port.on("addon-to-content", function (a1, a2, a3) { self.port.emit("content-to-addon", a1, a2, a3); }); } }); // Validate worker.port worker.port.on("content-to-addon", function (arg1, arg2, arg3) { if (!repeat++) { this.emit("addon-to-content", "first argument", "second", "third"); } else { assert.equal(arg1, "first argument"); assert.equal(arg2, "second"); assert.equal(arg3, "third"); done(); } }); worker.port.emit("addon-to-content", "first argument", "second", "third"); } ); exports["test:post-json-values-only"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.on("message", function (message) { self.postMessage([ message.fun === undefined, typeof message.w, message.w && "port" in message.w, message.w._url, Array.isArray(message.array), JSON.stringify(message.array)]); }); } }); // Validate worker.onMessage let array = [1, 2, 3]; worker.on("message", function (message) { assert.ok(message[0], "function becomes undefined"); assert.equal(message[1], "object", "object stays object"); assert.ok(message[2], "object's attributes are enumerable"); assert.equal(message[3], DEFAULT_CONTENT_URL, "jsonable attributes are accessible"); // See bug 714891, Arrays may be broken over compartements: assert.ok(message[4], "Array keeps being an array"); assert.equal(message[5], JSON.stringify(array), "Array is correctly serialized"); done(); }); // Add a new url property sa the Class function used by // Worker doesn't set enumerables to true for non-functions worker._url = DEFAULT_CONTENT_URL; worker.postMessage({ fun: function () {}, w: worker, array: array }); } ); exports["test:emit-json-values-only"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { // Validate self.on and self.emit self.port.on("addon-to-content", function (fun, w, obj, array) { self.port.emit("content-to-addon", [ fun === null, typeof w, "port" in w, w._url, "fun" in obj, Object.keys(obj.dom).length, Array.isArray(array), JSON.stringify(array) ]); }); } }); // Validate worker.port let array = [1, 2, 3]; worker.port.on("content-to-addon", function (result) { assert.ok(result[0], "functions become null"); assert.equal(result[1], "object", "objects stay objects"); assert.ok(result[2], "object's attributes are enumerable"); assert.equal(result[3], DEFAULT_CONTENT_URL, "json attribute is accessible"); assert.ok(!result[4], "function as object attribute is removed"); assert.equal(result[5], 0, "DOM nodes are converted into empty object"); // See bug 714891, Arrays may be broken over compartments: assert.ok(result[6], "Array keeps being an array"); assert.equal(result[7], JSON.stringify(array), "Array is correctly serialized"); done(); }); let obj = { fun: function () {}, dom: browser.contentWindow.document.createElement("div") }; // Add a new url property sa the Class function used by // Worker doesn't set enumerables to true for non-functions worker._url = DEFAULT_CONTENT_URL; worker.port.emit("addon-to-content", function () {}, worker, obj, array); } ); exports["test:content is wrapped"] = WorkerTest( "data:text/html;charset=utf-8,", function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.postMessage(!window.documentValue); }, contentScriptWhen: "ready", onMessage: function(msg) { assert.ok(msg, "content script has a wrapped access to content document"); done(); } }); } ); // ContentWorker is not for chrome /* exports["test:chrome is unwrapped"] = function(assert, done) { let window = makeWindow(); listenOnce(window, "load", function onload() { let worker = Worker({ window: window, contentScript: "new " + function WorkerScope() { self.postMessage(window.documentValue); }, contentScriptWhen: "ready", onMessage: function(msg) { assert.ok(msg, "content script has an unwrapped access to chrome document"); close(window).then(done); } }); }); } */ exports["test:nothing is leaked to content script"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.postMessage([ "ContentWorker" in window, "UNWRAP_ACCESS_KEY" in window, "getProxyForObject" in window ]); }, contentScriptWhen: "ready", onMessage: function(list) { assert.ok(!list[0], "worker API contrustor isn't leaked"); assert.ok(!list[1], "Proxy API stuff isn't leaked 1/2"); assert.ok(!list[2], "Proxy API stuff isn't leaked 2/2"); done(); } }); } ); exports["test:ensure console.xxx works in cs"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { const EXPECTED = ["time", "log", "info", "warn", "error", "error", "timeEnd"]; let calls = []; let levels = []; system.on('console-api-log-event', onMessage); function onMessage({ subject }) { calls.push(subject.wrappedJSObject.arguments[0]); levels.push(subject.wrappedJSObject.level); } let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { console.time("time"); console.log("log"); console.info("info"); console.warn("warn"); console.error("error"); console.debug("debug"); console.exception("error"); console.timeEnd("timeEnd"); self.postMessage(); }, onMessage: function() { system.off('console-api-log-event', onMessage); assert.equal(JSON.stringify(calls), JSON.stringify(EXPECTED), "console methods have been called successfully, in expected order"); assert.equal(JSON.stringify(levels), JSON.stringify(EXPECTED), "console messages have correct log levels, in expected order"); done(); } }); } ); exports["test:setTimeout works with string argument"] = WorkerTest( "data:text/html;charset=utf-8,", function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function ContentScriptScope() { // must use "window.scVal" instead of "var csVal" // since we are inside ContentScriptScope function. // i'm NOT putting code-in-string inside code-in-string window.csVal = 13; setTimeout("self.postMessage([" + "csVal, " + "window.docVal, " + "'ContentWorker' in window, " + "'UNWRAP_ACCESS_KEY' in window, " + "'getProxyForObject' in window, " + "])", 1); }, contentScriptWhen: "ready", onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) { // test timer code is executed in the correct context assert.equal(csVal, 13, "accessing content-script values"); assert.notEqual(docVal, 5, "can't access document values (directly)"); assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome"); done(); } }); } ); exports["test:setInterval works with string argument"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let count = 0; let worker = Worker({ window: browser.contentWindow, contentScript: "setInterval('self.postMessage(1)', 50)", contentScriptWhen: "ready", onMessage: function(one) { count++; assert.equal(one, 1, "got " + count + " message(s) from setInterval"); if (count >= 3) done(); } }); } ); exports["test:setInterval async Errors passed to .onError"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let count = 0; let worker = Worker({ window: browser.contentWindow, contentScript: "setInterval(() => { throw Error('ubik') }, 50)", contentScriptWhen: "ready", onError: function(err) { count++; assert.equal(err.message, "ubik", "error (correctly) propagated " + count + " time(s)"); if (count >= 3) done(); } }); } ); exports["test:setTimeout throws array, passed to .onError"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "setTimeout(function() { throw ['array', 42] }, 1)", contentScriptWhen: "ready", onError: function(arr) { assert.ok(isArray(arr), "the type of thrown/propagated object is array"); assert.ok(arr.length==2, "the propagated thrown array is the right length"); assert.equal(arr[1], 42, "element inside the thrown array correctly propagated"); done(); } }); } ); exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "setTimeout('syntax 123 error', 1)", contentScriptWhen: "ready", onError: function(err) { assert.equal(err.name, "SyntaxError", "received SyntaxError thrown from bad code in string argument to setTimeout"); assert.ok('fileName' in err, "propagated SyntaxError contains a fileName property"); assert.ok('stack' in err, "propagated SyntaxError contains a stack property"); assert.equal(err.message, "missing ; before statement", "propagated SyntaxError has the correct (helpful) message"); assert.equal(err.lineNumber, 1, "propagated SyntaxError was thrown on the right lineNumber"); done(); } }); } ); exports["test:setTimeout can't be cancelled by content"] = WorkerTest( "data:text/html;charset=utf-8,", function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { let id = setTimeout(function () { self.postMessage("timeout"); }, 100); unsafeWindow.eval("clearTimeout("+id+");"); }, contentScriptWhen: "ready", onMessage: function(msg) { assert.ok(msg, "content didn't managed to cancel our setTimeout"); done(); } }); } ); exports["test:clearTimeout"] = WorkerTest( "data:text/html;charset=utf-8,clear timeout", function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { let id1 = setTimeout(function() { self.postMessage("failed"); }, 10); let id2 = setTimeout(function() { self.postMessage("done"); }, 100); clearTimeout(id1); }, contentScriptWhen: "ready", onMessage: function(msg) { if (msg === "failed") { assert.fail("failed to cancel timer"); } else { assert.pass("timer cancelled"); done(); } } }); } ); exports["test:clearInterval"] = WorkerTest( "data:text/html;charset=utf-8,clear timeout", function(assert, browser, done) { let called = 0; let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { let id = setInterval(function() { self.postMessage("intreval") clearInterval(id) setTimeout(function() { self.postMessage("done") }, 100) }, 10); }, contentScriptWhen: "ready", onMessage: function(msg) { if (msg === "intreval") { called = called + 1; if (called > 1) assert.fail("failed to cancel timer"); } else { assert.pass("interval cancelled"); done(); } } }); } ) exports["test:setTimeout are unregistered on content unload"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let originalWindow = browser.contentWindow; let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { document.title = "ok"; let i = 0; setInterval(function () { document.title = i++; }, 10); }, contentScriptWhen: "ready" }); // Change location so that content script is destroyed, // and all setTimeout/setInterval should be unregistered. // Wait some cycles in order to execute some intervals. setTimeout(function () { // Bug 689621: Wait for the new document load so that we are sure that // previous document cancelled its intervals let url2 = "data:text/html;charset=utf-8,final"; loadAndWait(browser, url2, function onload() { let titleAfterLoad = originalWindow.document.title; // Wait additional cycles to verify that intervals are really cancelled setTimeout(function () { assert.equal(browser.contentDocument.title, "final", "New document has not been modified"); assert.equal(originalWindow.document.title, titleAfterLoad, "Nor previous one"); done(); }, 100); }); }, 100); } ); exports['test:check window attribute in iframes'] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { // Create a first iframe and wait for its loading let contentWin = browser.contentWindow; let contentDoc = contentWin.document; let iframe = contentDoc.createElement("iframe"); contentDoc.body.appendChild(iframe); listenOnce(iframe, "load", function onload() { // Create a second iframe inside the first one and wait for its loading let iframeDoc = iframe.contentWindow.document; let subIframe = iframeDoc.createElement("iframe"); iframeDoc.body.appendChild(subIframe); listenOnce(subIframe, "load", function onload() { subIframe.removeEventListener("load", onload, true); // And finally create a worker against this second iframe let worker = Worker({ window: subIframe.contentWindow, contentScript: 'new ' + function WorkerScope() { self.postMessage([ window.top !== window, frameElement, window.parent !== window, top.location.href, parent.location.href, ]); }, onMessage: function(msg) { assert.ok(msg[0], "window.top != window"); assert.ok(msg[1], "window.frameElement is defined"); assert.ok(msg[2], "window.parent != window"); assert.equal(msg[3], contentWin.location.href, "top.location refers to the toplevel content doc"); assert.equal(msg[4], iframe.contentWindow.location.href, "parent.location refers to the first iframe doc"); done(); } }); }); subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar"); }); iframe.setAttribute("src", "data:text/html;charset=utf-8,foo"); } ); exports['test:check window attribute in toplevel documents'] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: 'new ' + function WorkerScope() { self.postMessage([ window.top === window, frameElement, window.parent === window ]); }, onMessage: function(msg) { assert.ok(msg[0], "window.top == window"); assert.ok(!msg[1], "window.frameElement is null"); assert.ok(msg[2], "window.parent == window"); done(); } }); } ); exports["test:check worker API with page history"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let url2 = "data:text/html;charset=utf-8,bar"; loadAndWait(browser, url2, function () { let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { // Just before the content script is disable, we register a timeout // that will be disable until the page gets visible again self.on("pagehide", function () { setTimeout(function () { self.port.emit("timeout"); }, 0); }); self.on("message", function() { self.postMessage("saw message"); }); self.on("event", function() { self.port.emit("event", "saw event"); }); }, contentScriptWhen: "start" }); // postMessage works correctly when the page is visible worker.postMessage("ok"); // We have to wait before going back into history, // otherwise `goBack` won't do anything. setTimeout(function () { browser.goBack(); }, 0); // Wait for the document to be hidden browser.addEventListener("pagehide", function onpagehide() { browser.removeEventListener("pagehide", onpagehide, false); // Now any event sent to this worker should be cached worker.postMessage("message"); worker.port.emit("event"); // Display the page with attached content script back in order to resume // its timeout and receive the expected message. // We have to delay this in order to not break the history. // We delay for a non-zero amount of time in order to ensure that we // do not receive the message immediatly, so that the timeout is // actually disabled setTimeout(function () { worker.on("pageshow", function() { let promise = Promise.all([ new Promise(resolve => { worker.port.on("event", () => { assert.pass("Saw event"); resolve(); }); }), new Promise(resolve => { worker.on("message", () => { assert.pass("Saw message"); resolve(); }); }), new Promise(resolve => { worker.port.on("timeout", () => { assert.pass("Timer fired"); resolve(); }); }) ]); promise.then(done); }); browser.goForward(); }, 500); }, false); }); } ); exports['test:conentScriptFile as URL instance'] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let url = new URL(fixtures.url("test-contentScriptFile.js")); let worker = Worker({ window: browser.contentWindow, contentScriptFile: url, onMessage: function(msg) { assert.equal(msg, "msg from contentScriptFile", "received a wrong message from contentScriptFile"); done(); } }); } ); exports["test:worker events"] = WorkerTest( DEFAULT_CONTENT_URL, function (assert, browser, done) { let window = browser.contentWindow; let events = []; let worker = Worker({ window: window, contentScript: 'new ' + function WorkerScope() { self.postMessage('start'); }, onAttach: () => { events.push('attach'); assert.pass('attach event called when attached'); }, onError: err => { assert.equal(err.message, 'Custom', 'Error passed into error event'); worker.detach(); }, onMessage: msg => { assert.pass('`onMessage` handles postMessage') throw new Error('Custom'); }, onDetach: _ => { assert.pass('`onDetach` called when worker detached'); done(); } }); // `attach` event is called synchronously during instantiation, // so we can't listen to that, TODO FIX? // worker.on('attach', obj => console.log('attach', obj)); } ); exports["test:onDetach in contentScript on destroy"] = WorkerTest( "data:text/html;charset=utf-8,foo#detach", function(assert, browser, done) { let worker = Worker({ window: browser.contentWindow, contentScript: 'new ' + function WorkerScope() { self.port.on('detach', function(reason) { window.location.hash += '!' + reason; }) }, onAttach: function() { browser.contentWindow.addEventListener('hashchange', _ => { assert.equal(browser.contentWindow.location.hash, '#detach!', "location.href is as expected"); done(); }) worker.destroy(); } }); } ); exports["test:onDetach in contentScript on unload"] = WorkerTest( "data:text/html;charset=utf-8,foo#detach", function(assert, browser, done) { let { loader } = LoaderWithHookedConsole(module); let worker = loader.require("sdk/content/worker").Worker({ window: browser.contentWindow, contentScript: 'new ' + function WorkerScope() { self.port.on('detach', function(reason) { window.location.hash += '!' + reason; }) }, }); browser.contentWindow.addEventListener('hashchange', _ => { assert.equal(browser.contentWindow.location.hash, '#detach!shutdown', "location.href is as expected"); done(); }) loader.unload('shutdown'); } ); exports["test:console method log functions properly"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { let logs = []; system.on('console-api-log-event', onMessage); function onMessage({ subject }) { logs.push(clean(subject.wrappedJSObject.arguments[0])); } let clean = message => message.trim(). replace(/[\r\n]/g, " "). replace(/ +/g, " "); let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { console.log(Function); console.log((foo) => foo * foo); console.log(function foo(bar) { return bar + bar }); self.postMessage(); }, onMessage: () => { system.off('console-api-log-event', onMessage); assert.deepEqual(logs, [ "function Function() { [native code] }", "(foo) => foo * foo", "function foo(bar) { return bar + bar }" ]); done(); } }); } ); exports["test:global postMessage"] = WorkerTest( WINDOW_SCRIPT_URL, function(assert, browser, done) { let contentScript = "window.addEventListener('message', function (e) {" + " if (e.data === 'from -> window')" + " self.port.emit('response', e.data, e.origin);" + "});" + "postMessage('from -> content-script', '*');"; let { loader } = LoaderWithHookedConsole(module); let worker = loader.require("sdk/content/worker").Worker({ window: browser.contentWindow, contentScriptWhen: "ready", contentScript: contentScript }); worker.port.on("response", (data, origin) => { assert.equal(data, "from -> window", "Communication from content-script to window completed"); done(); }); }); exports["test:destroy unbinds listeners from port"] = WorkerTest( "data:text/html;charset=utf-8,portdestroyer", function(assert, browser, done) { let destroyed = false; let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.port.emit("destroy"); setInterval(self.port.emit, 10, "ping"); }, onDestroy: done }); worker.port.on("ping", () => { if (destroyed) { assert.fail("Should not call events on port after destroy."); } }); worker.port.on("destroy", () => { destroyed = true; worker.destroy(); assert.pass("Worker destroyed, waiting for no future listeners handling events."); setTimeout(done, 500); }); } ); exports["test:destroy kills child worker"] = WorkerTest( "data:text/html;charset=utf-8,

", function(assert, browser, done) { let worker1 = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.port.on("ping", detail => { let event = document.createEvent("CustomEvent"); event.initCustomEvent("Test:Ping", true, true, detail); document.dispatchEvent(event); self.port.emit("pingsent"); }); let listener = function(event) { self.port.emit("pong", event.detail); }; self.port.on("detach", () => { window.removeEventListener("Test:Pong", listener); }); window.addEventListener("Test:Pong", listener); }, onAttach: function() { let worker2 = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { let listener = function(event) { let newEvent = document.createEvent("CustomEvent"); newEvent.initCustomEvent("Test:Pong", true, true, event.detail); document.dispatchEvent(newEvent); }; self.port.on("detach", () => { window.removeEventListener("Test:Ping", listener); }) window.addEventListener("Test:Ping", listener); self.postMessage(); }, onMessage: function() { worker1.port.emit("ping", "test1"); worker1.port.once("pong", detail => { assert.equal(detail, "test1", "Saw the right message"); worker1.port.once("pingsent", () => { assert.pass("The message was sent"); worker2.destroy(); worker1.port.emit("ping", "test2"); worker1.port.once("pong", detail => { assert.fail("worker2 shouldn't have responded"); }) worker1.port.once("pingsent", () => { assert.pass("The message was sent"); worker1.destroy(); done(); }); }); }) } }); } }); } ); exports["test:unload kills child worker"] = WorkerTest( "data:text/html;charset=utf-8,

", function(assert, browser, done) { let loader = Loader(module); let worker1 = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { self.port.on("ping", detail => { let event = document.createEvent("CustomEvent"); event.initCustomEvent("Test:Ping", true, true, detail); document.dispatchEvent(event); self.port.emit("pingsent"); }); let listener = function(event) { self.port.emit("pong", event.detail); }; self.port.on("detach", () => { window.removeEventListener("Test:Pong", listener); }); window.addEventListener("Test:Pong", listener); }, onAttach: function() { let worker2 = loader.require("sdk/content/worker").Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { let listener = function(event) { let newEvent = document.createEvent("CustomEvent"); newEvent.initCustomEvent("Test:Pong", true, true, event.detail); document.dispatchEvent(newEvent); }; self.port.on("detach", () => { window.removeEventListener("Test:Ping", listener); }) window.addEventListener("Test:Ping", listener); self.postMessage(); }, onMessage: function() { worker1.port.emit("ping", "test1"); worker1.port.once("pong", detail => { assert.equal(detail, "test1", "Saw the right message"); worker1.port.once("pingsent", () => { assert.pass("The message was sent"); loader.unload(); worker1.port.emit("ping", "test2"); worker1.port.once("pong", detail => { assert.fail("worker2 shouldn't have responded"); }) worker1.port.once("pingsent", () => { assert.pass("The message was sent"); worker1.destroy(); done(); }); }); }) } }); } }); } ); require("sdk/test").run(exports);