test-content-worker.js 29 KB

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