|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 // Skipping due to window creation being unsupported in Fennec |
|
8 module.metadata = { |
|
9 engines: { |
|
10 'Firefox': '*' |
|
11 } |
|
12 }; |
|
13 |
|
14 const { Cc, Ci } = require("chrome"); |
|
15 const { on } = require("sdk/event/core"); |
|
16 const { setTimeout } = require("sdk/timers"); |
|
17 const { LoaderWithHookedConsole } = require("sdk/test/loader"); |
|
18 const { Worker } = require("sdk/content/worker"); |
|
19 const { close } = require("sdk/window/helpers"); |
|
20 const { set: setPref } = require("sdk/preferences/service"); |
|
21 const { isArray } = require("sdk/lang/type"); |
|
22 const { URL } = require('sdk/url'); |
|
23 const fixtures = require("./fixtures"); |
|
24 |
|
25 const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings"; |
|
26 |
|
27 const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo"; |
|
28 |
|
29 const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," + |
|
30 "<script>window.addEventListener('message', function (e) {" + |
|
31 " if (e.data === 'from -> content-script')" + |
|
32 " window.postMessage('from -> window', '*');" + |
|
33 "});</script>"; |
|
34 |
|
35 function makeWindow() { |
|
36 let content = |
|
37 "<?xml version=\"1.0\"?>" + |
|
38 "<window " + |
|
39 "xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" + |
|
40 "<script>var documentValue=true;</script>" + |
|
41 "</window>"; |
|
42 var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + |
|
43 encodeURIComponent(content); |
|
44 var features = ["chrome", "width=10", "height=10"]; |
|
45 |
|
46 return Cc["@mozilla.org/embedcomp/window-watcher;1"]. |
|
47 getService(Ci.nsIWindowWatcher). |
|
48 openWindow(null, url, null, features.join(","), null); |
|
49 } |
|
50 |
|
51 // Listen for only first one occurence of DOM event |
|
52 function listenOnce(node, eventName, callback) { |
|
53 node.addEventListener(eventName, function onevent(event) { |
|
54 node.removeEventListener(eventName, onevent, true); |
|
55 callback(node); |
|
56 }, true); |
|
57 } |
|
58 |
|
59 // Load a given url in a given browser and fires the callback when it is loaded |
|
60 function loadAndWait(browser, url, callback) { |
|
61 listenOnce(browser, "load", callback); |
|
62 // We have to wait before calling `loadURI` otherwise, if we call |
|
63 // `loadAndWait` during browser load event, the history will be broken |
|
64 setTimeout(function () { |
|
65 browser.loadURI(url); |
|
66 }, 0); |
|
67 } |
|
68 |
|
69 // Returns a test function that will automatically open a new chrome window |
|
70 // with a <browser> element loaded on a given content URL |
|
71 // The callback receive 3 arguments: |
|
72 // - test: reference to the jetpack test object |
|
73 // - browser: a reference to the <browser> xul node |
|
74 // - done: a callback to call when test is over |
|
75 function WorkerTest(url, callback) { |
|
76 return function testFunction(assert, done) { |
|
77 let chromeWindow = makeWindow(); |
|
78 chromeWindow.addEventListener("load", function onload() { |
|
79 chromeWindow.removeEventListener("load", onload, true); |
|
80 let browser = chromeWindow.document.createElement("browser"); |
|
81 browser.setAttribute("type", "content"); |
|
82 chromeWindow.document.documentElement.appendChild(browser); |
|
83 // Wait for about:blank load event ... |
|
84 listenOnce(browser, "load", function onAboutBlankLoad() { |
|
85 // ... before loading the expected doc and waiting for its load event |
|
86 loadAndWait(browser, url, function onDocumentLoaded() { |
|
87 callback(assert, browser, function onTestDone() { |
|
88 |
|
89 close(chromeWindow).then(done); |
|
90 }); |
|
91 }); |
|
92 }); |
|
93 }, true); |
|
94 }; |
|
95 } |
|
96 |
|
97 exports["test:sample"] = WorkerTest( |
|
98 DEFAULT_CONTENT_URL, |
|
99 function(assert, browser, done) { |
|
100 |
|
101 assert.notEqual(browser.contentWindow.location.href, "about:blank", |
|
102 "window is now on the right document"); |
|
103 |
|
104 let window = browser.contentWindow |
|
105 let worker = Worker({ |
|
106 window: window, |
|
107 contentScript: "new " + function WorkerScope() { |
|
108 // window is accessible |
|
109 let myLocation = window.location.toString(); |
|
110 self.on("message", function(data) { |
|
111 if (data == "hi!") |
|
112 self.postMessage("bye!"); |
|
113 }); |
|
114 }, |
|
115 contentScriptWhen: "ready", |
|
116 onMessage: function(msg) { |
|
117 assert.equal("bye!", msg); |
|
118 assert.equal(worker.url, window.location.href, |
|
119 "worker.url still works"); |
|
120 done(); |
|
121 } |
|
122 }); |
|
123 |
|
124 assert.equal(worker.url, window.location.href, |
|
125 "worker.url works"); |
|
126 assert.equal(worker.contentURL, window.location.href, |
|
127 "worker.contentURL works"); |
|
128 worker.postMessage("hi!"); |
|
129 } |
|
130 ); |
|
131 |
|
132 exports["test:emit"] = WorkerTest( |
|
133 DEFAULT_CONTENT_URL, |
|
134 function(assert, browser, done) { |
|
135 |
|
136 let worker = Worker({ |
|
137 window: browser.contentWindow, |
|
138 contentScript: "new " + function WorkerScope() { |
|
139 // Validate self.on and self.emit |
|
140 self.port.on("addon-to-content", function (data) { |
|
141 self.port.emit("content-to-addon", data); |
|
142 }); |
|
143 |
|
144 // Check for global pollution |
|
145 //if (typeof on != "undefined") |
|
146 // self.postMessage("`on` is in globals"); |
|
147 if (typeof once != "undefined") |
|
148 self.postMessage("`once` is in globals"); |
|
149 if (typeof emit != "undefined") |
|
150 self.postMessage("`emit` is in globals"); |
|
151 |
|
152 }, |
|
153 onMessage: function(msg) { |
|
154 assert.fail("Got an unexpected message : "+msg); |
|
155 } |
|
156 }); |
|
157 |
|
158 // Validate worker.port |
|
159 worker.port.on("content-to-addon", function (data) { |
|
160 assert.equal(data, "event data"); |
|
161 done(); |
|
162 }); |
|
163 worker.port.emit("addon-to-content", "event data"); |
|
164 } |
|
165 ); |
|
166 |
|
167 exports["test:emit hack message"] = WorkerTest( |
|
168 DEFAULT_CONTENT_URL, |
|
169 function(assert, browser, done) { |
|
170 let worker = Worker({ |
|
171 window: browser.contentWindow, |
|
172 contentScript: "new " + function WorkerScope() { |
|
173 // Validate self.port |
|
174 self.port.on("message", function (data) { |
|
175 self.port.emit("message", data); |
|
176 }); |
|
177 // We should not receive message on self, but only on self.port |
|
178 self.on("message", function (data) { |
|
179 self.postMessage("message", data); |
|
180 }); |
|
181 }, |
|
182 onError: function(e) { |
|
183 assert.fail("Got exception: "+e); |
|
184 } |
|
185 }); |
|
186 |
|
187 worker.port.on("message", function (data) { |
|
188 assert.equal(data, "event data"); |
|
189 done(); |
|
190 }); |
|
191 worker.on("message", function (data) { |
|
192 assert.fail("Got an unexpected message : "+msg); |
|
193 }); |
|
194 worker.port.emit("message", "event data"); |
|
195 } |
|
196 ); |
|
197 |
|
198 exports["test:n-arguments emit"] = WorkerTest( |
|
199 DEFAULT_CONTENT_URL, |
|
200 function(assert, browser, done) { |
|
201 let repeat = 0; |
|
202 let worker = Worker({ |
|
203 window: browser.contentWindow, |
|
204 contentScript: "new " + function WorkerScope() { |
|
205 // Validate self.on and self.emit |
|
206 self.port.on("addon-to-content", function (a1, a2, a3) { |
|
207 self.port.emit("content-to-addon", a1, a2, a3); |
|
208 }); |
|
209 } |
|
210 }); |
|
211 |
|
212 // Validate worker.port |
|
213 worker.port.on("content-to-addon", function (arg1, arg2, arg3) { |
|
214 if (!repeat++) { |
|
215 this.emit("addon-to-content", "first argument", "second", "third"); |
|
216 } else { |
|
217 assert.equal(arg1, "first argument"); |
|
218 assert.equal(arg2, "second"); |
|
219 assert.equal(arg3, "third"); |
|
220 done(); |
|
221 } |
|
222 }); |
|
223 worker.port.emit("addon-to-content", "first argument", "second", "third"); |
|
224 } |
|
225 ); |
|
226 |
|
227 exports["test:post-json-values-only"] = WorkerTest( |
|
228 DEFAULT_CONTENT_URL, |
|
229 function(assert, browser, done) { |
|
230 |
|
231 let worker = Worker({ |
|
232 window: browser.contentWindow, |
|
233 contentScript: "new " + function WorkerScope() { |
|
234 self.on("message", function (message) { |
|
235 self.postMessage([ message.fun === undefined, |
|
236 typeof message.w, |
|
237 message.w && "port" in message.w, |
|
238 message.w._url, |
|
239 Array.isArray(message.array), |
|
240 JSON.stringify(message.array)]); |
|
241 }); |
|
242 } |
|
243 }); |
|
244 |
|
245 // Validate worker.onMessage |
|
246 let array = [1, 2, 3]; |
|
247 worker.on("message", function (message) { |
|
248 assert.ok(message[0], "function becomes undefined"); |
|
249 assert.equal(message[1], "object", "object stays object"); |
|
250 assert.ok(message[2], "object's attributes are enumerable"); |
|
251 assert.equal(message[3], DEFAULT_CONTENT_URL, |
|
252 "jsonable attributes are accessible"); |
|
253 // See bug 714891, Arrays may be broken over compartements: |
|
254 assert.ok(message[4], "Array keeps being an array"); |
|
255 assert.equal(message[5], JSON.stringify(array), |
|
256 "Array is correctly serialized"); |
|
257 done(); |
|
258 }); |
|
259 // Add a new url property sa the Class function used by |
|
260 // Worker doesn't set enumerables to true for non-functions |
|
261 worker._url = DEFAULT_CONTENT_URL; |
|
262 |
|
263 worker.postMessage({ fun: function () {}, w: worker, array: array }); |
|
264 } |
|
265 ); |
|
266 |
|
267 exports["test:emit-json-values-only"] = WorkerTest( |
|
268 DEFAULT_CONTENT_URL, |
|
269 function(assert, browser, done) { |
|
270 |
|
271 let worker = Worker({ |
|
272 window: browser.contentWindow, |
|
273 contentScript: "new " + function WorkerScope() { |
|
274 // Validate self.on and self.emit |
|
275 self.port.on("addon-to-content", function (fun, w, obj, array) { |
|
276 self.port.emit("content-to-addon", [ |
|
277 fun === null, |
|
278 typeof w, |
|
279 "port" in w, |
|
280 w._url, |
|
281 "fun" in obj, |
|
282 Object.keys(obj.dom).length, |
|
283 Array.isArray(array), |
|
284 JSON.stringify(array) |
|
285 ]); |
|
286 }); |
|
287 } |
|
288 }); |
|
289 |
|
290 // Validate worker.port |
|
291 let array = [1, 2, 3]; |
|
292 worker.port.on("content-to-addon", function (result) { |
|
293 assert.ok(result[0], "functions become null"); |
|
294 assert.equal(result[1], "object", "objects stay objects"); |
|
295 assert.ok(result[2], "object's attributes are enumerable"); |
|
296 assert.equal(result[3], DEFAULT_CONTENT_URL, |
|
297 "json attribute is accessible"); |
|
298 assert.ok(!result[4], "function as object attribute is removed"); |
|
299 assert.equal(result[5], 0, "DOM nodes are converted into empty object"); |
|
300 // See bug 714891, Arrays may be broken over compartments: |
|
301 assert.ok(result[6], "Array keeps being an array"); |
|
302 assert.equal(result[7], JSON.stringify(array), |
|
303 "Array is correctly serialized"); |
|
304 done(); |
|
305 }); |
|
306 |
|
307 let obj = { |
|
308 fun: function () {}, |
|
309 dom: browser.contentWindow.document.createElement("div") |
|
310 }; |
|
311 // Add a new url property sa the Class function used by |
|
312 // Worker doesn't set enumerables to true for non-functions |
|
313 worker._url = DEFAULT_CONTENT_URL; |
|
314 worker.port.emit("addon-to-content", function () {}, worker, obj, array); |
|
315 } |
|
316 ); |
|
317 |
|
318 exports["test:content is wrapped"] = WorkerTest( |
|
319 "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", |
|
320 function(assert, browser, done) { |
|
321 |
|
322 let worker = Worker({ |
|
323 window: browser.contentWindow, |
|
324 contentScript: "new " + function WorkerScope() { |
|
325 self.postMessage(!window.documentValue); |
|
326 }, |
|
327 contentScriptWhen: "ready", |
|
328 onMessage: function(msg) { |
|
329 assert.ok(msg, |
|
330 "content script has a wrapped access to content document"); |
|
331 done(); |
|
332 } |
|
333 }); |
|
334 } |
|
335 ); |
|
336 |
|
337 exports["test:chrome is unwrapped"] = function(assert, done) { |
|
338 let window = makeWindow(); |
|
339 |
|
340 listenOnce(window, "load", function onload() { |
|
341 |
|
342 let worker = Worker({ |
|
343 window: window, |
|
344 contentScript: "new " + function WorkerScope() { |
|
345 self.postMessage(window.documentValue); |
|
346 }, |
|
347 contentScriptWhen: "ready", |
|
348 onMessage: function(msg) { |
|
349 assert.ok(msg, |
|
350 "content script has an unwrapped access to chrome document"); |
|
351 close(window).then(done); |
|
352 } |
|
353 }); |
|
354 |
|
355 }); |
|
356 } |
|
357 |
|
358 exports["test:nothing is leaked to content script"] = WorkerTest( |
|
359 DEFAULT_CONTENT_URL, |
|
360 function(assert, browser, done) { |
|
361 |
|
362 let worker = Worker({ |
|
363 window: browser.contentWindow, |
|
364 contentScript: "new " + function WorkerScope() { |
|
365 self.postMessage([ |
|
366 "ContentWorker" in window, |
|
367 "UNWRAP_ACCESS_KEY" in window, |
|
368 "getProxyForObject" in window |
|
369 ]); |
|
370 }, |
|
371 contentScriptWhen: "ready", |
|
372 onMessage: function(list) { |
|
373 assert.ok(!list[0], "worker API contrustor isn't leaked"); |
|
374 assert.ok(!list[1], "Proxy API stuff isn't leaked 1/2"); |
|
375 assert.ok(!list[2], "Proxy API stuff isn't leaked 2/2"); |
|
376 done(); |
|
377 } |
|
378 }); |
|
379 } |
|
380 ); |
|
381 |
|
382 exports["test:ensure console.xxx works in cs"] = WorkerTest( |
|
383 DEFAULT_CONTENT_URL, |
|
384 function(assert, browser, done) { |
|
385 let { loader } = LoaderWithHookedConsole(module, onMessage); |
|
386 |
|
387 // Intercept all console method calls |
|
388 let calls = []; |
|
389 function onMessage(type, msg) { |
|
390 assert.equal(type, msg, |
|
391 "console.xxx(\"xxx\"), i.e. message is equal to the " + |
|
392 "console method name we are calling"); |
|
393 calls.push(msg); |
|
394 } |
|
395 |
|
396 // Finally, create a worker that will call all console methods |
|
397 let worker = loader.require("sdk/content/worker").Worker({ |
|
398 window: browser.contentWindow, |
|
399 contentScript: "new " + function WorkerScope() { |
|
400 console.time("time"); |
|
401 console.log("log"); |
|
402 console.info("info"); |
|
403 console.warn("warn"); |
|
404 console.error("error"); |
|
405 console.debug("debug"); |
|
406 console.exception("exception"); |
|
407 console.timeEnd("timeEnd"); |
|
408 self.postMessage(); |
|
409 }, |
|
410 onMessage: function() { |
|
411 // Ensure that console methods are called in the same execution order |
|
412 const EXPECTED_CALLS = ["time", "log", "info", "warn", "error", |
|
413 "debug", "exception", "timeEnd"]; |
|
414 assert.equal(JSON.stringify(calls), |
|
415 JSON.stringify(EXPECTED_CALLS), |
|
416 "console methods have been called successfully, in expected order"); |
|
417 done(); |
|
418 } |
|
419 }); |
|
420 } |
|
421 ); |
|
422 |
|
423 exports["test:setTimeout works with string argument"] = WorkerTest( |
|
424 "data:text/html;charset=utf-8,<script>var docVal=5;</script>", |
|
425 function(assert, browser, done) { |
|
426 let worker = Worker({ |
|
427 window: browser.contentWindow, |
|
428 contentScript: "new " + function ContentScriptScope() { |
|
429 // must use "window.scVal" instead of "var csVal" |
|
430 // since we are inside ContentScriptScope function. |
|
431 // i'm NOT putting code-in-string inside code-in-string </YO DAWG> |
|
432 window.csVal = 13; |
|
433 setTimeout("self.postMessage([" + |
|
434 "csVal, " + |
|
435 "window.docVal, " + |
|
436 "'ContentWorker' in window, " + |
|
437 "'UNWRAP_ACCESS_KEY' in window, " + |
|
438 "'getProxyForObject' in window, " + |
|
439 "])", 1); |
|
440 }, |
|
441 contentScriptWhen: "ready", |
|
442 onMessage: function([csVal, docVal, chrome1, chrome2, chrome3]) { |
|
443 // test timer code is executed in the correct context |
|
444 assert.equal(csVal, 13, "accessing content-script values"); |
|
445 assert.notEqual(docVal, 5, "can't access document values (directly)"); |
|
446 assert.ok(!chrome1 && !chrome2 && !chrome3, "nothing is leaked from chrome"); |
|
447 done(); |
|
448 } |
|
449 }); |
|
450 } |
|
451 ); |
|
452 |
|
453 exports["test:setInterval works with string argument"] = WorkerTest( |
|
454 DEFAULT_CONTENT_URL, |
|
455 function(assert, browser, done) { |
|
456 let count = 0; |
|
457 let worker = Worker({ |
|
458 window: browser.contentWindow, |
|
459 contentScript: "setInterval('self.postMessage(1)', 50)", |
|
460 contentScriptWhen: "ready", |
|
461 onMessage: function(one) { |
|
462 count++; |
|
463 assert.equal(one, 1, "got " + count + " message(s) from setInterval"); |
|
464 if (count >= 3) done(); |
|
465 } |
|
466 }); |
|
467 } |
|
468 ); |
|
469 |
|
470 exports["test:setInterval async Errors passed to .onError"] = WorkerTest( |
|
471 DEFAULT_CONTENT_URL, |
|
472 function(assert, browser, done) { |
|
473 let count = 0; |
|
474 let worker = Worker({ |
|
475 window: browser.contentWindow, |
|
476 contentScript: "setInterval(() => { throw Error('ubik') }, 50)", |
|
477 contentScriptWhen: "ready", |
|
478 onError: function(err) { |
|
479 count++; |
|
480 assert.equal(err.message, "ubik", |
|
481 "error (corectly) propagated " + count + " time(s)"); |
|
482 if (count >= 3) done(); |
|
483 } |
|
484 }); |
|
485 } |
|
486 ); |
|
487 |
|
488 exports["test:setTimeout throws array, passed to .onError"] = WorkerTest( |
|
489 DEFAULT_CONTENT_URL, |
|
490 function(assert, browser, done) { |
|
491 let worker = Worker({ |
|
492 window: browser.contentWindow, |
|
493 contentScript: "setTimeout(function() { throw ['array', 42] }, 1)", |
|
494 contentScriptWhen: "ready", |
|
495 onError: function(arr) { |
|
496 assert.ok(isArray(arr), |
|
497 "the type of thrown/propagated object is array"); |
|
498 assert.ok(arr.length==2, |
|
499 "the propagated thrown array is the right length"); |
|
500 assert.equal(arr[1], 42, |
|
501 "element inside the thrown array correctly propagated"); |
|
502 done(); |
|
503 } |
|
504 }); |
|
505 } |
|
506 ); |
|
507 |
|
508 exports["test:setTimeout string arg with SyntaxError to .onError"] = WorkerTest( |
|
509 DEFAULT_CONTENT_URL, |
|
510 function(assert, browser, done) { |
|
511 let worker = Worker({ |
|
512 window: browser.contentWindow, |
|
513 contentScript: "setTimeout('syntax 123 error', 1)", |
|
514 contentScriptWhen: "ready", |
|
515 onError: function(err) { |
|
516 assert.equal(err.name, "SyntaxError", |
|
517 "received SyntaxError thrown from bad code in string argument to setTimeout"); |
|
518 assert.ok('fileName' in err, |
|
519 "propagated SyntaxError contains a fileName property"); |
|
520 assert.ok('stack' in err, |
|
521 "propagated SyntaxError contains a stack property"); |
|
522 assert.equal(err.message, "missing ; before statement", |
|
523 "propagated SyntaxError has the correct (helpful) message"); |
|
524 assert.equal(err.lineNumber, 1, |
|
525 "propagated SyntaxError was thrown on the right lineNumber"); |
|
526 done(); |
|
527 } |
|
528 }); |
|
529 } |
|
530 ); |
|
531 |
|
532 exports["test:setTimeout can't be cancelled by content"] = WorkerTest( |
|
533 "data:text/html;charset=utf-8,<script>var documentValue=true;</script>", |
|
534 function(assert, browser, done) { |
|
535 |
|
536 let worker = Worker({ |
|
537 window: browser.contentWindow, |
|
538 contentScript: "new " + function WorkerScope() { |
|
539 let id = setTimeout(function () { |
|
540 self.postMessage("timeout"); |
|
541 }, 100); |
|
542 unsafeWindow.eval("clearTimeout("+id+");"); |
|
543 }, |
|
544 contentScriptWhen: "ready", |
|
545 onMessage: function(msg) { |
|
546 assert.ok(msg, |
|
547 "content didn't managed to cancel our setTimeout"); |
|
548 done(); |
|
549 } |
|
550 }); |
|
551 } |
|
552 ); |
|
553 |
|
554 exports["test:clearTimeout"] = WorkerTest( |
|
555 "data:text/html;charset=utf-8,clear timeout", |
|
556 function(assert, browser, done) { |
|
557 let worker = Worker({ |
|
558 window: browser.contentWindow, |
|
559 contentScript: "new " + function WorkerScope() { |
|
560 let id1 = setTimeout(function() { |
|
561 self.postMessage("failed"); |
|
562 }, 10); |
|
563 let id2 = setTimeout(function() { |
|
564 self.postMessage("done"); |
|
565 }, 100); |
|
566 clearTimeout(id1); |
|
567 }, |
|
568 contentScriptWhen: "ready", |
|
569 onMessage: function(msg) { |
|
570 if (msg === "failed") { |
|
571 assert.fail("failed to cancel timer"); |
|
572 } else { |
|
573 assert.pass("timer cancelled"); |
|
574 done(); |
|
575 } |
|
576 } |
|
577 }); |
|
578 } |
|
579 ); |
|
580 |
|
581 exports["test:clearInterval"] = WorkerTest( |
|
582 "data:text/html;charset=utf-8,clear timeout", |
|
583 function(assert, browser, done) { |
|
584 let called = 0; |
|
585 let worker = Worker({ |
|
586 window: browser.contentWindow, |
|
587 contentScript: "new " + function WorkerScope() { |
|
588 let id = setInterval(function() { |
|
589 self.postMessage("intreval") |
|
590 clearInterval(id) |
|
591 setTimeout(function() { |
|
592 self.postMessage("done") |
|
593 }, 100) |
|
594 }, 10); |
|
595 }, |
|
596 contentScriptWhen: "ready", |
|
597 onMessage: function(msg) { |
|
598 if (msg === "intreval") { |
|
599 called = called + 1; |
|
600 if (called > 1) assert.fail("failed to cancel timer"); |
|
601 } else { |
|
602 assert.pass("interval cancelled"); |
|
603 done(); |
|
604 } |
|
605 } |
|
606 }); |
|
607 } |
|
608 ) |
|
609 |
|
610 exports["test:setTimeout are unregistered on content unload"] = WorkerTest( |
|
611 DEFAULT_CONTENT_URL, |
|
612 function(assert, browser, done) { |
|
613 |
|
614 let originalWindow = browser.contentWindow; |
|
615 let worker = Worker({ |
|
616 window: browser.contentWindow, |
|
617 contentScript: "new " + function WorkerScope() { |
|
618 document.title = "ok"; |
|
619 let i = 0; |
|
620 setInterval(function () { |
|
621 document.title = i++; |
|
622 }, 10); |
|
623 }, |
|
624 contentScriptWhen: "ready" |
|
625 }); |
|
626 |
|
627 // Change location so that content script is destroyed, |
|
628 // and all setTimeout/setInterval should be unregistered. |
|
629 // Wait some cycles in order to execute some intervals. |
|
630 setTimeout(function () { |
|
631 // Bug 689621: Wait for the new document load so that we are sure that |
|
632 // previous document cancelled its intervals |
|
633 let url2 = "data:text/html;charset=utf-8,<title>final</title>"; |
|
634 loadAndWait(browser, url2, function onload() { |
|
635 let titleAfterLoad = originalWindow.document.title; |
|
636 // Wait additional cycles to verify that intervals are really cancelled |
|
637 setTimeout(function () { |
|
638 assert.equal(browser.contentDocument.title, "final", |
|
639 "New document has not been modified"); |
|
640 assert.equal(originalWindow.document.title, titleAfterLoad, |
|
641 "Nor previous one"); |
|
642 |
|
643 done(); |
|
644 }, 100); |
|
645 }); |
|
646 }, 100); |
|
647 } |
|
648 ); |
|
649 |
|
650 exports['test:check window attribute in iframes'] = WorkerTest( |
|
651 DEFAULT_CONTENT_URL, |
|
652 function(assert, browser, done) { |
|
653 |
|
654 // Create a first iframe and wait for its loading |
|
655 let contentWin = browser.contentWindow; |
|
656 let contentDoc = contentWin.document; |
|
657 let iframe = contentDoc.createElement("iframe"); |
|
658 contentDoc.body.appendChild(iframe); |
|
659 |
|
660 listenOnce(iframe, "load", function onload() { |
|
661 |
|
662 // Create a second iframe inside the first one and wait for its loading |
|
663 let iframeDoc = iframe.contentWindow.document; |
|
664 let subIframe = iframeDoc.createElement("iframe"); |
|
665 iframeDoc.body.appendChild(subIframe); |
|
666 |
|
667 listenOnce(subIframe, "load", function onload() { |
|
668 subIframe.removeEventListener("load", onload, true); |
|
669 |
|
670 // And finally create a worker against this second iframe |
|
671 let worker = Worker({ |
|
672 window: subIframe.contentWindow, |
|
673 contentScript: 'new ' + function WorkerScope() { |
|
674 self.postMessage([ |
|
675 window.top !== window, |
|
676 frameElement, |
|
677 window.parent !== window, |
|
678 top.location.href, |
|
679 parent.location.href, |
|
680 ]); |
|
681 }, |
|
682 onMessage: function(msg) { |
|
683 assert.ok(msg[0], "window.top != window"); |
|
684 assert.ok(msg[1], "window.frameElement is defined"); |
|
685 assert.ok(msg[2], "window.parent != window"); |
|
686 assert.equal(msg[3], contentWin.location.href, |
|
687 "top.location refers to the toplevel content doc"); |
|
688 assert.equal(msg[4], iframe.contentWindow.location.href, |
|
689 "parent.location refers to the first iframe doc"); |
|
690 done(); |
|
691 } |
|
692 }); |
|
693 |
|
694 }); |
|
695 subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar"); |
|
696 |
|
697 }); |
|
698 iframe.setAttribute("src", "data:text/html;charset=utf-8,foo"); |
|
699 } |
|
700 ); |
|
701 |
|
702 exports['test:check window attribute in toplevel documents'] = WorkerTest( |
|
703 DEFAULT_CONTENT_URL, |
|
704 function(assert, browser, done) { |
|
705 |
|
706 let worker = Worker({ |
|
707 window: browser.contentWindow, |
|
708 contentScript: 'new ' + function WorkerScope() { |
|
709 self.postMessage([ |
|
710 window.top === window, |
|
711 frameElement, |
|
712 window.parent === window |
|
713 ]); |
|
714 }, |
|
715 onMessage: function(msg) { |
|
716 assert.ok(msg[0], "window.top == window"); |
|
717 assert.ok(!msg[1], "window.frameElement is null"); |
|
718 assert.ok(msg[2], "window.parent == window"); |
|
719 done(); |
|
720 } |
|
721 }); |
|
722 } |
|
723 ); |
|
724 |
|
725 exports["test:check worker API with page history"] = WorkerTest( |
|
726 DEFAULT_CONTENT_URL, |
|
727 function(assert, browser, done) { |
|
728 let url2 = "data:text/html;charset=utf-8,bar"; |
|
729 |
|
730 loadAndWait(browser, url2, function () { |
|
731 let worker = Worker({ |
|
732 window: browser.contentWindow, |
|
733 contentScript: "new " + function WorkerScope() { |
|
734 // Just before the content script is disable, we register a timeout |
|
735 // that will be disable until the page gets visible again |
|
736 self.on("pagehide", function () { |
|
737 setTimeout(function () { |
|
738 self.postMessage("timeout restored"); |
|
739 }, 0); |
|
740 }); |
|
741 }, |
|
742 contentScriptWhen: "start" |
|
743 }); |
|
744 |
|
745 // postMessage works correctly when the page is visible |
|
746 worker.postMessage("ok"); |
|
747 |
|
748 // We have to wait before going back into history, |
|
749 // otherwise `goBack` won't do anything. |
|
750 setTimeout(function () { |
|
751 browser.goBack(); |
|
752 }, 0); |
|
753 |
|
754 // Wait for the document to be hidden |
|
755 browser.addEventListener("pagehide", function onpagehide() { |
|
756 browser.removeEventListener("pagehide", onpagehide, false); |
|
757 // Now any event sent to this worker should throw |
|
758 |
|
759 assert.throws( |
|
760 function () { worker.postMessage("data"); }, |
|
761 /The page is currently hidden and can no longer be used/, |
|
762 "postMessage should throw when the page is hidden in history" |
|
763 ); |
|
764 |
|
765 assert.throws( |
|
766 function () { worker.port.emit("event"); }, |
|
767 /The page is currently hidden and can no longer be used/, |
|
768 "port.emit should throw when the page is hidden in history" |
|
769 ); |
|
770 |
|
771 // Display the page with attached content script back in order to resume |
|
772 // its timeout and receive the expected message. |
|
773 // We have to delay this in order to not break the history. |
|
774 // We delay for a non-zero amount of time in order to ensure that we |
|
775 // do not receive the message immediatly, so that the timeout is |
|
776 // actually disabled |
|
777 setTimeout(function () { |
|
778 worker.on("message", function (data) { |
|
779 assert.ok(data, "timeout restored"); |
|
780 done(); |
|
781 }); |
|
782 browser.goForward(); |
|
783 }, 500); |
|
784 |
|
785 }, false); |
|
786 }); |
|
787 |
|
788 } |
|
789 ); |
|
790 |
|
791 exports['test:conentScriptFile as URL instance'] = WorkerTest( |
|
792 DEFAULT_CONTENT_URL, |
|
793 function(assert, browser, done) { |
|
794 |
|
795 let url = new URL(fixtures.url("test-contentScriptFile.js")); |
|
796 let worker = Worker({ |
|
797 window: browser.contentWindow, |
|
798 contentScriptFile: url, |
|
799 onMessage: function(msg) { |
|
800 assert.equal(msg, "msg from contentScriptFile", |
|
801 "received a wrong message from contentScriptFile"); |
|
802 done(); |
|
803 } |
|
804 }); |
|
805 } |
|
806 ); |
|
807 |
|
808 exports["test:worker events"] = WorkerTest( |
|
809 DEFAULT_CONTENT_URL, |
|
810 function (assert, browser, done) { |
|
811 let window = browser.contentWindow; |
|
812 let events = []; |
|
813 let worker = Worker({ |
|
814 window: window, |
|
815 contentScript: 'new ' + function WorkerScope() { |
|
816 self.postMessage('start'); |
|
817 }, |
|
818 onAttach: win => { |
|
819 events.push('attach'); |
|
820 assert.pass('attach event called when attached'); |
|
821 assert.equal(window, win, 'attach event passes in attached window'); |
|
822 }, |
|
823 onError: err => { |
|
824 assert.equal(err.message, 'Custom', |
|
825 'Error passed into error event'); |
|
826 worker.detach(); |
|
827 }, |
|
828 onMessage: msg => { |
|
829 assert.pass('`onMessage` handles postMessage') |
|
830 throw new Error('Custom'); |
|
831 }, |
|
832 onDetach: _ => { |
|
833 assert.pass('`onDetach` called when worker detached'); |
|
834 done(); |
|
835 } |
|
836 }); |
|
837 // `attach` event is called synchronously during instantiation, |
|
838 // so we can't listen to that, TODO FIX? |
|
839 // worker.on('attach', obj => console.log('attach', obj)); |
|
840 } |
|
841 ); |
|
842 |
|
843 exports["test:onDetach in contentScript on destroy"] = WorkerTest( |
|
844 "data:text/html;charset=utf-8,foo#detach", |
|
845 function(assert, browser, done) { |
|
846 let worker = Worker({ |
|
847 window: browser.contentWindow, |
|
848 contentScript: 'new ' + function WorkerScope() { |
|
849 self.port.on('detach', function(reason) { |
|
850 window.location.hash += '!' + reason; |
|
851 }) |
|
852 }, |
|
853 }); |
|
854 browser.contentWindow.addEventListener('hashchange', _ => { |
|
855 assert.equal(browser.contentWindow.location.hash, '#detach!', |
|
856 "location.href is as expected"); |
|
857 done(); |
|
858 }) |
|
859 worker.destroy(); |
|
860 } |
|
861 ); |
|
862 |
|
863 exports["test:onDetach in contentScript on unload"] = WorkerTest( |
|
864 "data:text/html;charset=utf-8,foo#detach", |
|
865 function(assert, browser, done) { |
|
866 let { loader } = LoaderWithHookedConsole(module); |
|
867 let worker = loader.require("sdk/content/worker").Worker({ |
|
868 window: browser.contentWindow, |
|
869 contentScript: 'new ' + function WorkerScope() { |
|
870 self.port.on('detach', function(reason) { |
|
871 window.location.hash += '!' + reason; |
|
872 }) |
|
873 }, |
|
874 }); |
|
875 browser.contentWindow.addEventListener('hashchange', _ => { |
|
876 assert.equal(browser.contentWindow.location.hash, '#detach!shutdown', |
|
877 "location.href is as expected"); |
|
878 done(); |
|
879 }) |
|
880 loader.unload('shutdown'); |
|
881 } |
|
882 ); |
|
883 |
|
884 exports["test:console method log functions properly"] = WorkerTest( |
|
885 DEFAULT_CONTENT_URL, |
|
886 function(assert, browser, done) { |
|
887 let logs = []; |
|
888 |
|
889 let clean = message => |
|
890 message.trim(). |
|
891 replace(/[\r\n]/g, " "). |
|
892 replace(/ +/g, " "); |
|
893 |
|
894 let onMessage = (type, message) => logs.push(clean(message)); |
|
895 let { loader } = LoaderWithHookedConsole(module, onMessage); |
|
896 |
|
897 let worker = loader.require("sdk/content/worker").Worker({ |
|
898 window: browser.contentWindow, |
|
899 contentScript: "new " + function WorkerScope() { |
|
900 console.log(Function); |
|
901 console.log((foo) => foo * foo); |
|
902 console.log(function foo(bar) { return bar + bar }); |
|
903 |
|
904 self.postMessage(); |
|
905 }, |
|
906 onMessage: () => { |
|
907 assert.deepEqual(logs, [ |
|
908 "function Function() { [native code] }", |
|
909 "(foo) => foo * foo", |
|
910 "function foo(bar) { \"use strict\"; return bar + bar }" |
|
911 ]); |
|
912 |
|
913 done(); |
|
914 } |
|
915 }); |
|
916 } |
|
917 ); |
|
918 |
|
919 exports["test:global postMessage"] = WorkerTest( |
|
920 WINDOW_SCRIPT_URL, |
|
921 function(assert, browser, done) { |
|
922 let contentScript = "window.addEventListener('message', function (e) {" + |
|
923 " if (e.data === 'from -> window')" + |
|
924 " self.port.emit('response', e.data, e.origin);" + |
|
925 "});" + |
|
926 "postMessage('from -> content-script', '*');"; |
|
927 let { loader } = LoaderWithHookedConsole(module); |
|
928 let worker = loader.require("sdk/content/worker").Worker({ |
|
929 window: browser.contentWindow, |
|
930 contentScriptWhen: "ready", |
|
931 contentScript: contentScript |
|
932 }); |
|
933 |
|
934 worker.port.on("response", (data, origin) => { |
|
935 assert.equal(data, "from -> window", "Communication from content-script to window completed"); |
|
936 done(); |
|
937 }); |
|
938 }); |
|
939 |
|
940 require("test").run(exports); |