1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/test/test-content-worker.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,940 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +// Skipping due to window creation being unsupported in Fennec 1.11 +module.metadata = { 1.12 + engines: { 1.13 + 'Firefox': '*' 1.14 + } 1.15 +}; 1.16 + 1.17 +const { Cc, Ci } = require("chrome"); 1.18 +const { on } = require("sdk/event/core"); 1.19 +const { setTimeout } = require("sdk/timers"); 1.20 +const { LoaderWithHookedConsole } = require("sdk/test/loader"); 1.21 +const { Worker } = require("sdk/content/worker"); 1.22 +const { close } = require("sdk/window/helpers"); 1.23 +const { set: setPref } = require("sdk/preferences/service"); 1.24 +const { isArray } = require("sdk/lang/type"); 1.25 +const { URL } = require('sdk/url'); 1.26 +const fixtures = require("./fixtures"); 1.27 + 1.28 +const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; 1.29 + 1.30 +const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo"; 1.31 + 1.32 +const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," + 1.33 + "<script>window.addEventListener('message', function (e) {" + 1.34 + " if (e.data === 'from -> content-script')" + 1.35 + " window.postMessage('from -> window', '*');" + 1.36 + "});</script>"; 1.37 + 1.38 +function makeWindow() { 1.39 + let content = 1.40 + "<?xml version=\"1.0\"?>" + 1.41 + "<window " + 1.42 + "xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" + 1.43 + "<script>var documentValue=true;</script>" + 1.44 + "</window>"; 1.45 + var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + 1.46 + encodeURIComponent(content); 1.47 + var features = ["chrome", "width=10", "height=10"]; 1.48 + 1.49 + return Cc["@mozilla.org/embedcomp/window-watcher;1"]. 1.50 + getService(Ci.nsIWindowWatcher). 1.51 + openWindow(null, url, null, features.join(","), null); 1.52 +} 1.53 + 1.54 +// Listen for only first one occurence of DOM event 1.55 +function listenOnce(node, eventName, callback) { 1.56 + node.addEventListener(eventName, function onevent(event) { 1.57 + node.removeEventListener(eventName, onevent, true); 1.58 + callback(node); 1.59 + }, true); 1.60 +} 1.61 + 1.62 +// Load a given url in a given browser and fires the callback when it is loaded 1.63 +function loadAndWait(browser, url, callback) { 1.64 + listenOnce(browser, "load", callback); 1.65 + // We have to wait before calling `loadURI` otherwise, if we call 1.66 + // `loadAndWait` during browser load event, the history will be broken 1.67 + setTimeout(function () { 1.68 + browser.loadURI(url); 1.69 + }, 0); 1.70 +} 1.71 + 1.72 +// Returns a test function that will automatically open a new chrome window 1.73 +// with a <browser> element loaded on a given content URL 1.74 +// The callback receive 3 arguments: 1.75 +// - test: reference to the jetpack test object 1.76 +// - browser: a reference to the <browser> xul node 1.77 +// - done: a callback to call when test is over 1.78 +function WorkerTest(url, callback) { 1.79 + return function testFunction(assert, done) { 1.80 + let chromeWindow = makeWindow(); 1.81 + chromeWindow.addEventListener("load", function onload() { 1.82 + chromeWindow.removeEventListener("load", onload, true); 1.83 + let browser = chromeWindow.document.createElement("browser"); 1.84 + browser.setAttribute("type", "content"); 1.85 + chromeWindow.document.documentElement.appendChild(browser); 1.86 + // Wait for about:blank load event ... 1.87 + listenOnce(browser, "load", function onAboutBlankLoad() { 1.88 + // ... before loading the expected doc and waiting for its load event 1.89 + loadAndWait(browser, url, function onDocumentLoaded() { 1.90 + callback(assert, browser, function onTestDone() { 1.91 + 1.92 + close(chromeWindow).then(done); 1.93 + }); 1.94 + }); 1.95 + }); 1.96 + }, true); 1.97 + }; 1.98 +} 1.99 + 1.100 +exports["test:sample"] = WorkerTest( 1.101 + DEFAULT_CONTENT_URL, 1.102 + function(assert, browser, done) { 1.103 + 1.104 + assert.notEqual(browser.contentWindow.location.href, "about:blank", 1.105 + "window is now on the right document"); 1.106 + 1.107 + let window = browser.contentWindow 1.108 + let worker = Worker({ 1.109 + window: window, 1.110 + contentScript: "new " + function WorkerScope() { 1.111 + // window is accessible 1.112 + let myLocation = window.location.toString(); 1.113 + self.on("message", function(data) { 1.114 + if (data == "hi!") 1.115 + self.postMessage("bye!"); 1.116 + }); 1.117 + }, 1.118 + contentScriptWhen: "ready", 1.119 + onMessage: function(msg) { 1.120 + assert.equal("bye!", msg); 1.121 + assert.equal(worker.url, window.location.href, 1.122 + "worker.url still works"); 1.123 + done(); 1.124 + } 1.125 + }); 1.126 + 1.127 + assert.equal(worker.url, window.location.href, 1.128 + "worker.url works"); 1.129 + assert.equal(worker.contentURL, window.location.href, 1.130 + "worker.contentURL works"); 1.131 + worker.postMessage("hi!"); 1.132 + } 1.133 +); 1.134 + 1.135 +exports["test:emit"] = WorkerTest( 1.136 + DEFAULT_CONTENT_URL, 1.137 + function(assert, browser, done) { 1.138 + 1.139 + let worker = Worker({ 1.140 + window: browser.contentWindow, 1.141 + contentScript: "new " + function WorkerScope() { 1.142 + // Validate self.on and self.emit 1.143 + self.port.on("addon-to-content", function (data) { 1.144 + self.port.emit("content-to-addon", data); 1.145 + }); 1.146 + 1.147 + // Check for global pollution 1.148 + //if (typeof on != "undefined") 1.149 + // self.postMessage("`on` is in globals"); 1.150 + if (typeof once != "undefined") 1.151 + self.postMessage("`once` is in globals"); 1.152 + if (typeof emit != "undefined") 1.153 + self.postMessage("`emit` is in globals"); 1.154 + 1.155 + }, 1.156 + onMessage: function(msg) { 1.157 + assert.fail("Got an unexpected message : "+msg); 1.158 + } 1.159 + }); 1.160 + 1.161 + // Validate worker.port 1.162 + worker.port.on("content-to-addon", function (data) { 1.163 + assert.equal(data, "event data"); 1.164 + done(); 1.165 + }); 1.166 + worker.port.emit("addon-to-content", "event data"); 1.167 + } 1.168 +); 1.169 + 1.170 +exports["test:emit hack message"] = WorkerTest( 1.171 + DEFAULT_CONTENT_URL, 1.172 + function(assert, browser, done) { 1.173 + let worker = Worker({ 1.174 + window: browser.contentWindow, 1.175 + contentScript: "new " + function WorkerScope() { 1.176 + // Validate self.port 1.177 + self.port.on("message", function (data) { 1.178 + self.port.emit("message", data); 1.179 + }); 1.180 + // We should not receive message on self, but only on self.port 1.181 + self.on("message", function (data) { 1.182 + self.postMessage("message", data); 1.183 + }); 1.184 + }, 1.185 + onError: function(e) { 1.186 + assert.fail("Got exception: "+e); 1.187 + } 1.188 + }); 1.189 + 1.190 + worker.port.on("message", function (data) { 1.191 + assert.equal(data, "event data"); 1.192 + done(); 1.193 + }); 1.194 + worker.on("message", function (data) { 1.195 + assert.fail("Got an unexpected message : "+msg); 1.196 + }); 1.197 + worker.port.emit("message", "event data"); 1.198 + } 1.199 +); 1.200 + 1.201 +exports["test:n-arguments emit"] = WorkerTest( 1.202 + DEFAULT_CONTENT_URL, 1.203 + function(assert, browser, done) { 1.204 + let repeat = 0; 1.205 + let worker = Worker({ 1.206 + window: browser.contentWindow, 1.207 + contentScript: "new " + function WorkerScope() { 1.208 + // Validate self.on and self.emit 1.209 + self.port.on("addon-to-content", function (a1, a2, a3) { 1.210 + self.port.emit("content-to-addon", a1, a2, a3); 1.211 + }); 1.212 + } 1.213 + }); 1.214 + 1.215 + // Validate worker.port 1.216 + worker.port.on("content-to-addon", function (arg1, arg2, arg3) { 1.217 + if (!repeat++) { 1.218 + this.emit("addon-to-content", "first argument", "second", "third"); 1.219 + } else { 1.220 + assert.equal(arg1, "first argument"); 1.221 + assert.equal(arg2, "second"); 1.222 + assert.equal(arg3, "third"); 1.223 + done(); 1.224 + } 1.225 + }); 1.226 + worker.port.emit("addon-to-content", "first argument", "second", "third"); 1.227 + } 1.228 +); 1.229 + 1.230 +exports["test:post-json-values-only"] = WorkerTest( 1.231 + DEFAULT_CONTENT_URL, 1.232 + function(assert, browser, done) { 1.233 + 1.234 + let worker = Worker({ 1.235 + window: browser.contentWindow, 1.236 + contentScript: "new " + function WorkerScope() { 1.237 + self.on("message", function (message) { 1.238 + self.postMessage([ message.fun === undefined, 1.239 + typeof message.w, 1.240 + message.w && "port" in message.w, 1.241 + message.w._url, 1.242 + Array.isArray(message.array), 1.243 + JSON.stringify(message.array)]); 1.244 + }); 1.245 + } 1.246 + }); 1.247 + 1.248 + // Validate worker.onMessage 1.249 + let array = [1, 2, 3]; 1.250 + worker.on("message", function (message) { 1.251 + assert.ok(message[0], "function becomes undefined"); 1.252 + assert.equal(message[1], "object", "object stays object"); 1.253 + assert.ok(message[2], "object's attributes are enumerable"); 1.254 + assert.equal(message[3], DEFAULT_CONTENT_URL, 1.255 + "jsonable attributes are accessible"); 1.256 + // See bug 714891, Arrays may be broken over compartements: 1.257 + assert.ok(message[4], "Array keeps being an array"); 1.258 + assert.equal(message[5], JSON.stringify(array), 1.259 + "Array is correctly serialized"); 1.260 + done(); 1.261 + }); 1.262 + // Add a new url property sa the Class function used by 1.263 + // Worker doesn't set enumerables to true for non-functions 1.264 + worker._url = DEFAULT_CONTENT_URL; 1.265 + 1.266 + worker.postMessage({ fun: function () {}, w: worker, array: array }); 1.267 + } 1.268 +); 1.269 + 1.270 +exports["test:emit-json-values-only"] = WorkerTest( 1.271 + DEFAULT_CONTENT_URL, 1.272 + function(assert, browser, done) { 1.273 + 1.274 + let worker = Worker({ 1.275 + window: browser.contentWindow, 1.276 + contentScript: "new " + function WorkerScope() { 1.277 + // Validate self.on and self.emit 1.278 + self.port.on("addon-to-content", function (fun, w, obj, array) { 1.279 + self.port.emit("content-to-addon", [ 1.280 + fun === null, 1.281 + typeof w, 1.282 + "port" in w, 1.283 + w._url, 1.284 + "fun" in obj, 1.285 + Object.keys(obj.dom).length, 1.286 + Array.isArray(array), 1.287 + JSON.stringify(array) 1.288 + ]); 1.289 + }); 1.290 + } 1.291 + }); 1.292 + 1.293 + // Validate worker.port 1.294 + let array = [1, 2, 3]; 1.295 + worker.port.on("content-to-addon", function (result) { 1.296 + assert.ok(result[0], "functions become null"); 1.297 + assert.equal(result[1], "object", "objects stay objects"); 1.298 + assert.ok(result[2], "object's attributes are enumerable"); 1.299 + assert.equal(result[3], DEFAULT_CONTENT_URL, 1.300 + "json attribute is accessible"); 1.301 + assert.ok(!result[4], "function as object attribute is removed"); 1.302 + assert.equal(result[5], 0, "DOM nodes are converted into empty object"); 1.303 + // See bug 714891, Arrays may be broken over compartments: 1.304 + assert.ok(result[6], "Array keeps being an array"); 1.305 + assert.equal(result[7], JSON.stringify(array), 1.306 + "Array is correctly serialized"); 1.307 + done(); 1.308 + }); 1.309 + 1.310 + let obj = { 1.311 + fun: function () {}, 1.312 + dom: browser.contentWindow.document.createElement("div") 1.313 + }; 1.314 + // Add a new url property sa the Class function used by 1.315 + // Worker doesn't set enumerables to true for non-functions 1.316 + worker._url = DEFAULT_CONTENT_URL; 1.317 + worker.port.emit("addon-to-content", function () {}, worker, obj, array); 1.318 + } 1.319 +); 1.320 + 1.321 +exports["test:content is wrapped"] = WorkerTest( 1.322 + "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", 1.323 + function(assert, browser, done) { 1.324 + 1.325 + let worker = Worker({ 1.326 + window: browser.contentWindow, 1.327 + contentScript: "new " + function WorkerScope() { 1.328 + self.postMessage(!window.documentValue); 1.329 + }, 1.330 + contentScriptWhen: "ready", 1.331 + onMessage: function(msg) { 1.332 + assert.ok(msg, 1.333 + "content script has a wrapped access to content document"); 1.334 + done(); 1.335 + } 1.336 + }); 1.337 + } 1.338 +); 1.339 + 1.340 +exports["test:chrome is unwrapped"] = function(assert, done) { 1.341 + let window = makeWindow(); 1.342 + 1.343 + listenOnce(window, "load", function onload() { 1.344 + 1.345 + let worker = Worker({ 1.346 + window: window, 1.347 + contentScript: "new " + function WorkerScope() { 1.348 + self.postMessage(window.documentValue); 1.349 + }, 1.350 + contentScriptWhen: "ready", 1.351 + onMessage: function(msg) { 1.352 + assert.ok(msg, 1.353 + "content script has an unwrapped access to chrome document"); 1.354 + close(window).then(done); 1.355 + } 1.356 + }); 1.357 + 1.358 + }); 1.359 +} 1.360 + 1.361 +exports["test:nothing is leaked to content script"] = WorkerTest( 1.362 + DEFAULT_CONTENT_URL, 1.363 + function(assert, browser, done) { 1.364 + 1.365 + let worker = Worker({ 1.366 + window: browser.contentWindow, 1.367 + contentScript: "new " + function WorkerScope() { 1.368 + self.postMessage([ 1.369 + "ContentWorker" in window, 1.370 + "UNWRAP_ACCESS_KEY" in window, 1.371 + "getProxyForObject" in window 1.372 + ]); 1.373 + }, 1.374 + contentScriptWhen: "ready", 1.375 + onMessage: function(list) { 1.376 + assert.ok(!list[0], "worker API contrustor isn't leaked"); 1.377 + assert.ok(!list[1], "Proxy API stuff isn't leaked 1/2"); 1.378 + assert.ok(!list[2], "Proxy API stuff isn't leaked 2/2"); 1.379 + done(); 1.380 + } 1.381 + }); 1.382 + } 1.383 +); 1.384 + 1.385 +exports["test:ensure console.xxx works in cs"] = WorkerTest( 1.386 + DEFAULT_CONTENT_URL, 1.387 + function(assert, browser, done) { 1.388 + let { loader } = LoaderWithHookedConsole(module, onMessage); 1.389 + 1.390 + // Intercept all console method calls 1.391 + let calls = []; 1.392 + function onMessage(type, msg) { 1.393 + assert.equal(type, msg, 1.394 + "console.xxx(\"xxx\"), i.e. message is equal to the " + 1.395 + "console method name we are calling"); 1.396 + calls.push(msg); 1.397 + } 1.398 + 1.399 + // Finally, create a worker that will call all console methods 1.400 + let worker = loader.require("sdk/content/worker").Worker({ 1.401 + window: browser.contentWindow, 1.402 + contentScript: "new " + function WorkerScope() { 1.403 + console.time("time"); 1.404 + console.log("log"); 1.405 + console.info("info"); 1.406 + console.warn("warn"); 1.407 + console.error("error"); 1.408 + console.debug("debug"); 1.409 + console.exception("exception"); 1.410 + console.timeEnd("timeEnd"); 1.411 + self.postMessage(); 1.412 + }, 1.413 + onMessage: function() { 1.414 + // Ensure that console methods are called in the same execution order 1.415 + const EXPECTED_CALLS = ["time", "log", "info", "warn", "error", 1.416 + "debug", "exception", "timeEnd"]; 1.417 + assert.equal(JSON.stringify(calls), 1.418 + JSON.stringify(EXPECTED_CALLS), 1.419 + "console methods have been called successfully, in expected order"); 1.420 + done(); 1.421 + } 1.422 + }); 1.423 + } 1.424 +); 1.425 + 1.426 +exports["test:setTimeout works with string argument"] = WorkerTest( 1.427 + "data:text/html;charset=utf-8,<script>var docVal=5;</script>", 1.428 + function(assert, browser, done) { 1.429 + let worker = Worker({ 1.430 + window: browser.contentWindow, 1.431 + contentScript: "new " + function ContentScriptScope() { 1.432 + // must use "window.scVal" instead of "var csVal" 1.433 + // since we are inside ContentScriptScope function. 1.434 + // i'm NOT putting code-in-string inside code-in-string </YO DAWG> 1.435 + window.csVal = 13; 1.436 + setTimeout("self.postMessage([" + 1.437 + "csVal, " + 1.438 + "window.docVal, " + 1.439 + "'ContentWorker' in window, " + 1.440 + "'UNWRAP_ACCESS_KEY' in window, " + 1.441 + "'getProxyForObject' in window, " + 1.442 + "])", 1); 1.443 + }, 1.444 + contentScriptWhen: "ready", 1.445 + onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) { 1.446 + // test timer code is executed in the correct context 1.447 + assert.equal(csVal, 13, "accessing content-script values"); 1.448 + assert.notEqual(docVal, 5, "can't access document values (directly)"); 1.449 + assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome"); 1.450 + done(); 1.451 + } 1.452 + }); 1.453 + } 1.454 +); 1.455 + 1.456 +exports["test:setInterval works with string argument"] = WorkerTest( 1.457 + DEFAULT_CONTENT_URL, 1.458 + function(assert, browser, done) { 1.459 + let count = 0; 1.460 + let worker = Worker({ 1.461 + window: browser.contentWindow, 1.462 + contentScript: "setInterval('self.postMessage(1)', 50)", 1.463 + contentScriptWhen: "ready", 1.464 + onMessage: function(one) { 1.465 + count++; 1.466 + assert.equal(one, 1, "got " + count + " message(s) from setInterval"); 1.467 + if (count >= 3) done(); 1.468 + } 1.469 + }); 1.470 + } 1.471 +); 1.472 + 1.473 +exports["test:setInterval async Errors passed to .onError"] = WorkerTest( 1.474 + DEFAULT_CONTENT_URL, 1.475 + function(assert, browser, done) { 1.476 + let count = 0; 1.477 + let worker = Worker({ 1.478 + window: browser.contentWindow, 1.479 + contentScript: "setInterval(() => { throw Error('ubik') }, 50)", 1.480 + contentScriptWhen: "ready", 1.481 + onError: function(err) { 1.482 + count++; 1.483 + assert.equal(err.message, "ubik", 1.484 + "error (corectly) propagated " + count + " time(s)"); 1.485 + if (count >= 3) done(); 1.486 + } 1.487 + }); 1.488 + } 1.489 +); 1.490 + 1.491 +exports["test:setTimeout throws array, passed to .onError"] = WorkerTest( 1.492 + DEFAULT_CONTENT_URL, 1.493 + function(assert, browser, done) { 1.494 + let worker = Worker({ 1.495 + window: browser.contentWindow, 1.496 + contentScript: "setTimeout(function() { throw ['array', 42] }, 1)", 1.497 + contentScriptWhen: "ready", 1.498 + onError: function(arr) { 1.499 + assert.ok(isArray(arr), 1.500 + "the type of thrown/propagated object is array"); 1.501 + assert.ok(arr.length==2, 1.502 + "the propagated thrown array is the right length"); 1.503 + assert.equal(arr[1], 42, 1.504 + "element inside the thrown array correctly propagated"); 1.505 + done(); 1.506 + } 1.507 + }); 1.508 + } 1.509 +); 1.510 + 1.511 +exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest( 1.512 + DEFAULT_CONTENT_URL, 1.513 + function(assert, browser, done) { 1.514 + let worker = Worker({ 1.515 + window: browser.contentWindow, 1.516 + contentScript: "setTimeout('syntax 123 error', 1)", 1.517 + contentScriptWhen: "ready", 1.518 + onError: function(err) { 1.519 + assert.equal(err.name, "SyntaxError", 1.520 + "received SyntaxError thrown from bad code in string argument to setTimeout"); 1.521 + assert.ok('fileName' in err, 1.522 + "propagated SyntaxError contains a fileName property"); 1.523 + assert.ok('stack' in err, 1.524 + "propagated SyntaxError contains a stack property"); 1.525 + assert.equal(err.message, "missing ; before statement", 1.526 + "propagated SyntaxError has the correct (helpful) message"); 1.527 + assert.equal(err.lineNumber, 1, 1.528 + "propagated SyntaxError was thrown on the right lineNumber"); 1.529 + done(); 1.530 + } 1.531 + }); 1.532 + } 1.533 +); 1.534 + 1.535 +exports["test:setTimeout can't be cancelled by content"] = WorkerTest( 1.536 + "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", 1.537 + function(assert, browser, done) { 1.538 + 1.539 + let worker = Worker({ 1.540 + window: browser.contentWindow, 1.541 + contentScript: "new " + function WorkerScope() { 1.542 + let id = setTimeout(function () { 1.543 + self.postMessage("timeout"); 1.544 + }, 100); 1.545 + unsafeWindow.eval("clearTimeout("+id+");"); 1.546 + }, 1.547 + contentScriptWhen: "ready", 1.548 + onMessage: function(msg) { 1.549 + assert.ok(msg, 1.550 + "content didn't managed to cancel our setTimeout"); 1.551 + done(); 1.552 + } 1.553 + }); 1.554 + } 1.555 +); 1.556 + 1.557 +exports["test:clearTimeout"] = WorkerTest( 1.558 + "data:text/html;charset=utf-8,clear timeout", 1.559 + function(assert, browser, done) { 1.560 + let worker = Worker({ 1.561 + window: browser.contentWindow, 1.562 + contentScript: "new " + function WorkerScope() { 1.563 + let id1 = setTimeout(function() { 1.564 + self.postMessage("failed"); 1.565 + }, 10); 1.566 + let id2 = setTimeout(function() { 1.567 + self.postMessage("done"); 1.568 + }, 100); 1.569 + clearTimeout(id1); 1.570 + }, 1.571 + contentScriptWhen: "ready", 1.572 + onMessage: function(msg) { 1.573 + if (msg === "failed") { 1.574 + assert.fail("failed to cancel timer"); 1.575 + } else { 1.576 + assert.pass("timer cancelled"); 1.577 + done(); 1.578 + } 1.579 + } 1.580 + }); 1.581 + } 1.582 +); 1.583 + 1.584 +exports["test:clearInterval"] = WorkerTest( 1.585 + "data:text/html;charset=utf-8,clear timeout", 1.586 + function(assert, browser, done) { 1.587 + let called = 0; 1.588 + let worker = Worker({ 1.589 + window: browser.contentWindow, 1.590 + contentScript: "new " + function WorkerScope() { 1.591 + let id = setInterval(function() { 1.592 + self.postMessage("intreval") 1.593 + clearInterval(id) 1.594 + setTimeout(function() { 1.595 + self.postMessage("done") 1.596 + }, 100) 1.597 + }, 10); 1.598 + }, 1.599 + contentScriptWhen: "ready", 1.600 + onMessage: function(msg) { 1.601 + if (msg === "intreval") { 1.602 + called = called + 1; 1.603 + if (called > 1) assert.fail("failed to cancel timer"); 1.604 + } else { 1.605 + assert.pass("interval cancelled"); 1.606 + done(); 1.607 + } 1.608 + } 1.609 + }); 1.610 + } 1.611 +) 1.612 + 1.613 +exports["test:setTimeout are unregistered on content unload"] = WorkerTest( 1.614 + DEFAULT_CONTENT_URL, 1.615 + function(assert, browser, done) { 1.616 + 1.617 + let originalWindow = browser.contentWindow; 1.618 + let worker = Worker({ 1.619 + window: browser.contentWindow, 1.620 + contentScript: "new " + function WorkerScope() { 1.621 + document.title = "ok"; 1.622 + let i = 0; 1.623 + setInterval(function () { 1.624 + document.title = i++; 1.625 + }, 10); 1.626 + }, 1.627 + contentScriptWhen: "ready" 1.628 + }); 1.629 + 1.630 + // Change location so that content script is destroyed, 1.631 + // and all setTimeout/setInterval should be unregistered. 1.632 + // Wait some cycles in order to execute some intervals. 1.633 + setTimeout(function () { 1.634 + // Bug 689621: Wait for the new document load so that we are sure that 1.635 + // previous document cancelled its intervals 1.636 + let url2 = "data:text/html;charset=utf-8,<title>final</title>"; 1.637 + loadAndWait(browser, url2, function onload() { 1.638 + let titleAfterLoad = originalWindow.document.title; 1.639 + // Wait additional cycles to verify that intervals are really cancelled 1.640 + setTimeout(function () { 1.641 + assert.equal(browser.contentDocument.title, "final", 1.642 + "New document has not been modified"); 1.643 + assert.equal(originalWindow.document.title, titleAfterLoad, 1.644 + "Nor previous one"); 1.645 + 1.646 + done(); 1.647 + }, 100); 1.648 + }); 1.649 + }, 100); 1.650 + } 1.651 +); 1.652 + 1.653 +exports['test:check window attribute in iframes'] = WorkerTest( 1.654 + DEFAULT_CONTENT_URL, 1.655 + function(assert, browser, done) { 1.656 + 1.657 + // Create a first iframe and wait for its loading 1.658 + let contentWin = browser.contentWindow; 1.659 + let contentDoc = contentWin.document; 1.660 + let iframe = contentDoc.createElement("iframe"); 1.661 + contentDoc.body.appendChild(iframe); 1.662 + 1.663 + listenOnce(iframe, "load", function onload() { 1.664 + 1.665 + // Create a second iframe inside the first one and wait for its loading 1.666 + let iframeDoc = iframe.contentWindow.document; 1.667 + let subIframe = iframeDoc.createElement("iframe"); 1.668 + iframeDoc.body.appendChild(subIframe); 1.669 + 1.670 + listenOnce(subIframe, "load", function onload() { 1.671 + subIframe.removeEventListener("load", onload, true); 1.672 + 1.673 + // And finally create a worker against this second iframe 1.674 + let worker = Worker({ 1.675 + window: subIframe.contentWindow, 1.676 + contentScript: 'new ' + function WorkerScope() { 1.677 + self.postMessage([ 1.678 + window.top !== window, 1.679 + frameElement, 1.680 + window.parent !== window, 1.681 + top.location.href, 1.682 + parent.location.href, 1.683 + ]); 1.684 + }, 1.685 + onMessage: function(msg) { 1.686 + assert.ok(msg[0], "window.top != window"); 1.687 + assert.ok(msg[1], "window.frameElement is defined"); 1.688 + assert.ok(msg[2], "window.parent != window"); 1.689 + assert.equal(msg[3], contentWin.location.href, 1.690 + "top.location refers to the toplevel content doc"); 1.691 + assert.equal(msg[4], iframe.contentWindow.location.href, 1.692 + "parent.location refers to the first iframe doc"); 1.693 + done(); 1.694 + } 1.695 + }); 1.696 + 1.697 + }); 1.698 + subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar"); 1.699 + 1.700 + }); 1.701 + iframe.setAttribute("src", "data:text/html;charset=utf-8,foo"); 1.702 + } 1.703 +); 1.704 + 1.705 +exports['test:check window attribute in toplevel documents'] = WorkerTest( 1.706 + DEFAULT_CONTENT_URL, 1.707 + function(assert, browser, done) { 1.708 + 1.709 + let worker = Worker({ 1.710 + window: browser.contentWindow, 1.711 + contentScript: 'new ' + function WorkerScope() { 1.712 + self.postMessage([ 1.713 + window.top === window, 1.714 + frameElement, 1.715 + window.parent === window 1.716 + ]); 1.717 + }, 1.718 + onMessage: function(msg) { 1.719 + assert.ok(msg[0], "window.top == window"); 1.720 + assert.ok(!msg[1], "window.frameElement is null"); 1.721 + assert.ok(msg[2], "window.parent == window"); 1.722 + done(); 1.723 + } 1.724 + }); 1.725 + } 1.726 +); 1.727 + 1.728 +exports["test:check worker API with page history"] = WorkerTest( 1.729 + DEFAULT_CONTENT_URL, 1.730 + function(assert, browser, done) { 1.731 + let url2 = "data:text/html;charset=utf-8,bar"; 1.732 + 1.733 + loadAndWait(browser, url2, function () { 1.734 + let worker = Worker({ 1.735 + window: browser.contentWindow, 1.736 + contentScript: "new " + function WorkerScope() { 1.737 + // Just before the content script is disable, we register a timeout 1.738 + // that will be disable until the page gets visible again 1.739 + self.on("pagehide", function () { 1.740 + setTimeout(function () { 1.741 + self.postMessage("timeout restored"); 1.742 + }, 0); 1.743 + }); 1.744 + }, 1.745 + contentScriptWhen: "start" 1.746 + }); 1.747 + 1.748 + // postMessage works correctly when the page is visible 1.749 + worker.postMessage("ok"); 1.750 + 1.751 + // We have to wait before going back into history, 1.752 + // otherwise `goBack` won't do anything. 1.753 + setTimeout(function () { 1.754 + browser.goBack(); 1.755 + }, 0); 1.756 + 1.757 + // Wait for the document to be hidden 1.758 + browser.addEventListener("pagehide", function onpagehide() { 1.759 + browser.removeEventListener("pagehide", onpagehide, false); 1.760 + // Now any event sent to this worker should throw 1.761 + 1.762 + assert.throws( 1.763 + function () { worker.postMessage("data"); }, 1.764 + /The page is currently hidden and can no longer be used/, 1.765 + "postMessage should throw when the page is hidden in history" 1.766 + ); 1.767 + 1.768 + assert.throws( 1.769 + function () { worker.port.emit("event"); }, 1.770 + /The page is currently hidden and can no longer be used/, 1.771 + "port.emit should throw when the page is hidden in history" 1.772 + ); 1.773 + 1.774 + // Display the page with attached content script back in order to resume 1.775 + // its timeout and receive the expected message. 1.776 + // We have to delay this in order to not break the history. 1.777 + // We delay for a non-zero amount of time in order to ensure that we 1.778 + // do not receive the message immediatly, so that the timeout is 1.779 + // actually disabled 1.780 + setTimeout(function () { 1.781 + worker.on("message", function (data) { 1.782 + assert.ok(data, "timeout restored"); 1.783 + done(); 1.784 + }); 1.785 + browser.goForward(); 1.786 + }, 500); 1.787 + 1.788 + }, false); 1.789 + }); 1.790 + 1.791 + } 1.792 +); 1.793 + 1.794 +exports['test:conentScriptFile as URL instance'] = WorkerTest( 1.795 + DEFAULT_CONTENT_URL, 1.796 + function(assert, browser, done) { 1.797 + 1.798 + let url = new URL(fixtures.url("test-contentScriptFile.js")); 1.799 + let worker = Worker({ 1.800 + window: browser.contentWindow, 1.801 + contentScriptFile: url, 1.802 + onMessage: function(msg) { 1.803 + assert.equal(msg, "msg from contentScriptFile", 1.804 + "received a wrong message from contentScriptFile"); 1.805 + done(); 1.806 + } 1.807 + }); 1.808 + } 1.809 +); 1.810 + 1.811 +exports["test:worker events"] = WorkerTest( 1.812 + DEFAULT_CONTENT_URL, 1.813 + function (assert, browser, done) { 1.814 + let window = browser.contentWindow; 1.815 + let events = []; 1.816 + let worker = Worker({ 1.817 + window: window, 1.818 + contentScript: 'new ' + function WorkerScope() { 1.819 + self.postMessage('start'); 1.820 + }, 1.821 + onAttach: win => { 1.822 + events.push('attach'); 1.823 + assert.pass('attach event called when attached'); 1.824 + assert.equal(window, win, 'attach event passes in attached window'); 1.825 + }, 1.826 + onError: err => { 1.827 + assert.equal(err.message, 'Custom', 1.828 + 'Error passed into error event'); 1.829 + worker.detach(); 1.830 + }, 1.831 + onMessage: msg => { 1.832 + assert.pass('`onMessage` handles postMessage') 1.833 + throw new Error('Custom'); 1.834 + }, 1.835 + onDetach: _ => { 1.836 + assert.pass('`onDetach` called when worker detached'); 1.837 + done(); 1.838 + } 1.839 + }); 1.840 + // `attach` event is called synchronously during instantiation, 1.841 + // so we can't listen to that, TODO FIX? 1.842 + // worker.on('attach', obj => console.log('attach', obj)); 1.843 + } 1.844 +); 1.845 + 1.846 +exports["test:onDetach in contentScript on destroy"] = WorkerTest( 1.847 + "data:text/html;charset=utf-8,foo#detach", 1.848 + function(assert, browser, done) { 1.849 + let worker = Worker({ 1.850 + window: browser.contentWindow, 1.851 + contentScript: 'new ' + function WorkerScope() { 1.852 + self.port.on('detach', function(reason) { 1.853 + window.location.hash += '!' + reason; 1.854 + }) 1.855 + }, 1.856 + }); 1.857 + browser.contentWindow.addEventListener('hashchange', _ => { 1.858 + assert.equal(browser.contentWindow.location.hash, '#detach!', 1.859 + "location.href is as expected"); 1.860 + done(); 1.861 + }) 1.862 + worker.destroy(); 1.863 + } 1.864 +); 1.865 + 1.866 +exports["test:onDetach in contentScript on unload"] = WorkerTest( 1.867 + "data:text/html;charset=utf-8,foo#detach", 1.868 + function(assert, browser, done) { 1.869 + let { loader } = LoaderWithHookedConsole(module); 1.870 + let worker = loader.require("sdk/content/worker").Worker({ 1.871 + window: browser.contentWindow, 1.872 + contentScript: 'new ' + function WorkerScope() { 1.873 + self.port.on('detach', function(reason) { 1.874 + window.location.hash += '!' + reason; 1.875 + }) 1.876 + }, 1.877 + }); 1.878 + browser.contentWindow.addEventListener('hashchange', _ => { 1.879 + assert.equal(browser.contentWindow.location.hash, '#detach!shutdown', 1.880 + "location.href is as expected"); 1.881 + done(); 1.882 + }) 1.883 + loader.unload('shutdown'); 1.884 + } 1.885 +); 1.886 + 1.887 +exports["test:console method log functions properly"] = WorkerTest( 1.888 + DEFAULT_CONTENT_URL, 1.889 + function(assert, browser, done) { 1.890 + let logs = []; 1.891 + 1.892 + let clean = message => 1.893 + message.trim(). 1.894 + replace(/[\r\n]/g, " "). 1.895 + replace(/ +/g, " "); 1.896 + 1.897 + let onMessage = (type, message) => logs.push(clean(message)); 1.898 + let { loader } = LoaderWithHookedConsole(module, onMessage); 1.899 + 1.900 + let worker = loader.require("sdk/content/worker").Worker({ 1.901 + window: browser.contentWindow, 1.902 + contentScript: "new " + function WorkerScope() { 1.903 + console.log(Function); 1.904 + console.log((foo) => foo * foo); 1.905 + console.log(function foo(bar) { return bar + bar }); 1.906 + 1.907 + self.postMessage(); 1.908 + }, 1.909 + onMessage: () => { 1.910 + assert.deepEqual(logs, [ 1.911 + "function Function() { [native code] }", 1.912 + "(foo) => foo * foo", 1.913 + "function foo(bar) { \"use strict\"; return bar + bar }" 1.914 + ]); 1.915 + 1.916 + done(); 1.917 + } 1.918 + }); 1.919 + } 1.920 +); 1.921 + 1.922 +exports["test:global postMessage"] = WorkerTest( 1.923 + WINDOW_SCRIPT_URL, 1.924 + function(assert, browser, done) { 1.925 + let contentScript = "window.addEventListener('message', function (e) {" + 1.926 + " if (e.data === 'from -> window')" + 1.927 + " self.port.emit('response', e.data, e.origin);" + 1.928 + "});" + 1.929 + "postMessage('from -> content-script', '*');"; 1.930 + let { loader } = LoaderWithHookedConsole(module); 1.931 + let worker = loader.require("sdk/content/worker").Worker({ 1.932 + window: browser.contentWindow, 1.933 + contentScriptWhen: "ready", 1.934 + contentScript: contentScript 1.935 + }); 1.936 + 1.937 + worker.port.on("response", (data, origin) => { 1.938 + assert.equal(data, "from -> window", "Communication from content-script to window completed"); 1.939 + done(); 1.940 + }); 1.941 +}); 1.942 + 1.943 +require("test").run(exports);