mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
8db07a23be
By sending the CPOW to the child processes the one that owns it will create the child worker and then send back the url of the window to set up the parent side of the worker. There are two breaking changes here. Workers invoked in this way no longer attach synchronously. We no longer pass the window through the attach event.
1130 lines
37 KiB
JavaScript
1130 lines
37 KiB
JavaScript
/* 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," +
|
|
"<script>window.addEventListener('message', function (e) {" +
|
|
" if (e.data === 'from -> content-script')" +
|
|
" window.postMessage('from -> window', '*');" +
|
|
"});</script>";
|
|
|
|
function makeWindow() {
|
|
let content =
|
|
"<?xml version=\"1.0\"?>" +
|
|
"<window " +
|
|
"xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" +
|
|
"<script>var documentValue=true;</script>" +
|
|
"</window>";
|
|
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 <browser> element loaded on a given content URL
|
|
// The callback receive 3 arguments:
|
|
// - test: reference to the jetpack test object
|
|
// - browser: a reference to the <browser> 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,<script>var documentValue=true;</script>",
|
|
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,<script>var docVal=5;</script>",
|
|
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 </YO DAWG>
|
|
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,<script>var documentValue=true;</script>",
|
|
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,<title>final</title>";
|
|
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) { \"use strict\"; 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,<html><body><p id='detail'></p></body></html>",
|
|
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,<html><body><p id='detail'></p></body></html>",
|
|
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);
|