addon-sdk/source/test/test-content-worker.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     5 "use strict";
     7 // Skipping due to window creation being unsupported in Fennec
     8 module.metadata = {
     9   engines: {
    10     'Firefox': '*'
    11   }
    12 };
    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");
    25 const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
    27 const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
    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>";
    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"];
    46   return Cc["@mozilla.org/embedcomp/window-watcher;1"].
    47          getService(Ci.nsIWindowWatcher).
    48          openWindow(null, url, null, features.join(","), null);
    49 }
    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 }
    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 }
    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() {
    89             close(chromeWindow).then(done);
    90           });
    91         });
    92       });
    93     }, true);
    94   };
    95 }
    97 exports["test:sample"] = WorkerTest(
    98   DEFAULT_CONTENT_URL,
    99   function(assert, browser, done) {
   101     assert.notEqual(browser.contentWindow.location.href, "about:blank",
   102                         "window is now on the right document");
   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     });
   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 );
   132 exports["test:emit"] = WorkerTest(
   133   DEFAULT_CONTENT_URL,
   134   function(assert, browser, done) {
   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           });
   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");
   152         },
   153         onMessage: function(msg) {
   154           assert.fail("Got an unexpected message : "+msg);
   155         }
   156       });
   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 );
   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       });
   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 );
   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       });
   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 );
   227 exports["test:post-json-values-only"] = WorkerTest(
   228   DEFAULT_CONTENT_URL,
   229   function(assert, browser, done) {
   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       });
   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;
   263     worker.postMessage({ fun: function () {}, w: worker, array: array });
   264   }
   265 );
   267 exports["test:emit-json-values-only"] = WorkerTest(
   268   DEFAULT_CONTENT_URL,
   269   function(assert, browser, done) {
   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       });
   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     });
   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 );
   318 exports["test:content is wrapped"] = WorkerTest(
   319   "data:text/html;charset=utf-8,<script>var documentValue=true;</script>",
   320   function(assert, browser, done) {
   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 );
   337 exports["test:chrome is unwrapped"] = function(assert, done) {
   338   let window = makeWindow();
   340   listenOnce(window, "load", function onload() {
   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     });
   355   });
   356 }
   358 exports["test:nothing is leaked to content script"] = WorkerTest(
   359   DEFAULT_CONTENT_URL,
   360   function(assert, browser, done) {
   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 );
   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);
   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     }
   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 );
   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 );
   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 );
   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 );
   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 );
   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 );
   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) {
   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 );
   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 );
   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 )
   610 exports["test:setTimeout are unregistered on content unload"] = WorkerTest(
   611   DEFAULT_CONTENT_URL,
   612   function(assert, browser, done) {
   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     });
   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");
   643           done();
   644         }, 100);
   645       });
   646     }, 100);
   647   }
   648 );
   650 exports['test:check window attribute in iframes'] = WorkerTest(
   651   DEFAULT_CONTENT_URL,
   652   function(assert, browser, done) {
   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);
   660     listenOnce(iframe, "load", function onload() {
   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);
   667       listenOnce(subIframe, "load", function onload() {
   668         subIframe.removeEventListener("load", onload, true);
   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         });
   694       });
   695       subIframe.setAttribute("src", "data:text/html;charset=utf-8,bar");
   697     });
   698     iframe.setAttribute("src", "data:text/html;charset=utf-8,foo");
   699   }
   700 );
   702 exports['test:check window attribute in toplevel documents'] = WorkerTest(
   703   DEFAULT_CONTENT_URL,
   704   function(assert, browser, done) {
   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 );
   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";
   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       });
   745       // postMessage works correctly when the page is visible
   746       worker.postMessage("ok");
   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);
   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
   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             );
   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             );
   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);
   785       }, false);
   786     });
   788   }
   789 );
   791 exports['test:conentScriptFile as URL instance'] = WorkerTest(
   792   DEFAULT_CONTENT_URL,
   793   function(assert, browser, done) {
   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 );
   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 );
   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 );
   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 );
   884 exports["test:console method log functions properly"] = WorkerTest(
   885   DEFAULT_CONTENT_URL,
   886   function(assert, browser, done) {
   887     let logs = [];
   889     let clean = message =>
   890           message.trim().
   891           replace(/[\r\n]/g, " ").
   892           replace(/ +/g, " ");
   894     let onMessage = (type, message) => logs.push(clean(message));
   895     let { loader } = LoaderWithHookedConsole(module, onMessage);
   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 });
   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         ]);
   913         done();
   914       }
   915     });
   916   }
   917 );
   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     });
   934     worker.port.on("response", (data, origin) => {
   935       assert.equal(data, "from -> window", "Communication from content-script to window completed");
   936       done();
   937     });
   938 });
   940 require("test").run(exports);

mercurial