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