PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/test/test-page-mod.js

https://github.com/apm/addon-sdk
JavaScript | 1786 lines | 1536 code | 197 blank | 53 comment | 50 complexity | 3c675c2493abda4e0e9e48075f9eff2f MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception

Large files files are truncated, but you can click here to view the full file

  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. const { PageMod } = require("sdk/page-mod");
  6. const { testPageMod, handleReadyState, openNewTab,
  7. contentScriptWhenServer, createLoader } = require("./pagemod-test-helpers");
  8. const { Loader } = require('sdk/test/loader');
  9. const tabs = require("sdk/tabs");
  10. const { setTimeout } = require("sdk/timers");
  11. const { Cc, Ci, Cu } = require("chrome");
  12. const system = require("sdk/system/events");
  13. const { open, getFrames, getMostRecentBrowserWindow, getInnerId } = require('sdk/window/utils');
  14. const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
  15. const xulApp = require("sdk/system/xul-app");
  16. const { isPrivateBrowsingSupported } = require('sdk/self');
  17. const { isPrivate } = require('sdk/private-browsing');
  18. const { openWebpage } = require('./private-browsing/helper');
  19. const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
  20. const promise = require("sdk/core/promise");
  21. const { pb } = require('./private-browsing/helper');
  22. const { URL } = require("sdk/url");
  23. const { defer, all } = require('sdk/core/promise');
  24. const { waitUntil } = require("sdk/test/utils");
  25. const data = require("./fixtures");
  26. const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
  27. const { require: devtoolsRequire } = devtools;
  28. const contentGlobals = devtoolsRequire("devtools/server/content-globals");
  29. const { cleanUI } = require("sdk/test/utils");
  30. const testPageURI = data.url("test.html");
  31. // The following adds Debugger constructor to the global namespace.
  32. const { addDebuggerToGlobal } = require('resource://gre/modules/jsdebugger.jsm');
  33. addDebuggerToGlobal(this);
  34. function Isolate(worker) {
  35. return "(" + worker + ")()";
  36. }
  37. /* Tests for the PageMod APIs */
  38. exports.testPageMod1 = function(assert, done) {
  39. let mods = testPageMod(assert, done, "about:", [{
  40. include: /about:/,
  41. contentScriptWhen: 'end',
  42. contentScript: 'new ' + function WorkerScope() {
  43. window.document.body.setAttribute("JEP-107", "worked");
  44. },
  45. onAttach: function() {
  46. assert.equal(this, mods[0], "The 'this' object is the page mod.");
  47. }
  48. }],
  49. function(win, done) {
  50. assert.equal(
  51. win.document.body.getAttribute("JEP-107"),
  52. "worked",
  53. "PageMod.onReady test"
  54. );
  55. done();
  56. },
  57. 100
  58. );
  59. };
  60. exports.testPageMod2 = function(assert, done) {
  61. testPageMod(assert, done, "about:", [{
  62. include: "about:*",
  63. contentScript: [
  64. 'new ' + function contentScript() {
  65. window.AUQLUE = function() { return 42; }
  66. try {
  67. window.AUQLUE()
  68. }
  69. catch(e) {
  70. throw new Error("PageMod scripts executed in order");
  71. }
  72. document.documentElement.setAttribute("first", "true");
  73. },
  74. 'new ' + function contentScript() {
  75. document.documentElement.setAttribute("second", "true");
  76. }
  77. ]
  78. }], function(win, done) {
  79. assert.equal(win.document.documentElement.getAttribute("first"),
  80. "true",
  81. "PageMod test #2: first script has run");
  82. assert.equal(win.document.documentElement.getAttribute("second"),
  83. "true",
  84. "PageMod test #2: second script has run");
  85. assert.equal("AUQLUE" in win, false,
  86. "PageMod test #2: scripts get a wrapped window");
  87. done();
  88. },
  89. 100
  90. );
  91. };
  92. exports.testPageModIncludes = function(assert, done) {
  93. var asserts = [];
  94. function createPageModTest(include, expectedMatch) {
  95. // Create an 'onload' test function...
  96. asserts.push(function(test, win) {
  97. var matches = include in win.localStorage;
  98. assert.ok(expectedMatch ? matches : !matches,
  99. "'" + include + "' match test, expected: " + expectedMatch);
  100. });
  101. // ...and corresponding PageMod options
  102. return {
  103. include: include,
  104. contentScript: 'new ' + function() {
  105. self.on("message", function(msg) {
  106. window.localStorage[msg] = true;
  107. });
  108. },
  109. // The testPageMod callback with test assertions is called on 'end',
  110. // and we want this page mod to be attached before it gets called,
  111. // so we attach it on 'start'.
  112. contentScriptWhen: 'start',
  113. onAttach: function(worker) {
  114. worker.postMessage(this.include[0]);
  115. }
  116. };
  117. }
  118. testPageMod(assert, done, testPageURI, [
  119. createPageModTest("*", false),
  120. createPageModTest("*.google.com", false),
  121. createPageModTest("resource:*", true),
  122. createPageModTest("resource:", false),
  123. createPageModTest(testPageURI, true)
  124. ],
  125. function (win, done) {
  126. waitUntil(() => win.localStorage[testPageURI],
  127. testPageURI + " page-mod to be executed")
  128. .then(() => {
  129. asserts.forEach(fn => fn(assert, win));
  130. win.localStorage.clear();
  131. done();
  132. });
  133. });
  134. };
  135. exports.testPageModExcludes = function(assert, done) {
  136. var asserts = [];
  137. function createPageModTest(include, exclude, expectedMatch) {
  138. // Create an 'onload' test function...
  139. asserts.push(function(test, win) {
  140. var matches = JSON.stringify([include, exclude]) in win.localStorage;
  141. assert.ok(expectedMatch ? matches : !matches,
  142. "[include, exclude] = [" + include + ", " + exclude +
  143. "] match test, expected: " + expectedMatch);
  144. });
  145. // ...and corresponding PageMod options
  146. return {
  147. include: include,
  148. exclude: exclude,
  149. contentScript: 'new ' + function() {
  150. self.on("message", function(msg) {
  151. // The key in localStorage is "[<include>, <exclude>]".
  152. window.localStorage[JSON.stringify(msg)] = true;
  153. });
  154. },
  155. // The testPageMod callback with test assertions is called on 'end',
  156. // and we want this page mod to be attached before it gets called,
  157. // so we attach it on 'start'.
  158. contentScriptWhen: 'start',
  159. onAttach: function(worker) {
  160. worker.postMessage([this.include[0], this.exclude[0]]);
  161. }
  162. };
  163. }
  164. testPageMod(assert, done, testPageURI, [
  165. createPageModTest("*", testPageURI, false),
  166. createPageModTest(testPageURI, testPageURI, false),
  167. createPageModTest(testPageURI, "resource://*", false),
  168. createPageModTest(testPageURI, "*.google.com", true)
  169. ],
  170. function (win, done) {
  171. waitUntil(() => win.localStorage[JSON.stringify([testPageURI, "*.google.com"])],
  172. testPageURI + " page-mod to be executed")
  173. .then(() => {
  174. asserts.forEach(fn => fn(assert, win));
  175. win.localStorage.clear();
  176. done();
  177. });
  178. });
  179. };
  180. exports.testPageModValidationAttachTo = function(assert) {
  181. [{ val: 'top', type: 'string "top"' },
  182. { val: 'frame', type: 'string "frame"' },
  183. { val: ['top', 'existing'], type: 'array with "top" and "existing"' },
  184. { val: ['frame', 'existing'], type: 'array with "frame" and "existing"' },
  185. { val: ['top'], type: 'array with "top"' },
  186. { val: ['frame'], type: 'array with "frame"' },
  187. { val: undefined, type: 'undefined' }].forEach((attachTo) => {
  188. new PageMod({ attachTo: attachTo.val, include: '*.validation111' });
  189. assert.pass("PageMod() does not throw when attachTo is " + attachTo.type);
  190. });
  191. [{ val: 'existing', type: 'string "existing"' },
  192. { val: ['existing'], type: 'array with "existing"' },
  193. { val: 'not-legit', type: 'string with "not-legit"' },
  194. { val: ['not-legit'], type: 'array with "not-legit"' },
  195. { val: {}, type: 'object' }].forEach((attachTo) => {
  196. assert.throws(() =>
  197. new PageMod({ attachTo: attachTo.val, include: '*.validation111' }),
  198. /The `attachTo` option/,
  199. "PageMod() throws when 'attachTo' option is " + attachTo.type + ".");
  200. });
  201. };
  202. exports.testPageModValidationInclude = function(assert) {
  203. [{ val: undefined, type: 'undefined' },
  204. { val: {}, type: 'object' },
  205. { val: [], type: 'empty array'},
  206. { val: [/regexp/, 1], type: 'array with non string/regexp' },
  207. { val: 1, type: 'number' }].forEach((include) => {
  208. assert.throws(() => new PageMod({ include: include.val }),
  209. /The `include` option must always contain atleast one rule/,
  210. "PageMod() throws when 'include' option is " + include.type + ".");
  211. });
  212. [{ val: '*.validation111', type: 'string' },
  213. { val: /validation111/, type: 'regexp' },
  214. { val: ['*.validation111'], type: 'array with length > 0'}].forEach((include) => {
  215. new PageMod({ include: include.val });
  216. assert.pass("PageMod() does not throw when include option is " + include.type);
  217. });
  218. };
  219. exports.testPageModValidationExclude = function(assert) {
  220. let includeVal = '*.validation111';
  221. [{ val: {}, type: 'object' },
  222. { val: [], type: 'empty array'},
  223. { val: [/regexp/, 1], type: 'array with non string/regexp' },
  224. { val: 1, type: 'number' }].forEach((exclude) => {
  225. assert.throws(() => new PageMod({ include: includeVal, exclude: exclude.val }),
  226. /If set, the `exclude` option must always contain at least one rule as a string, regular expression, or an array of strings and regular expressions./,
  227. "PageMod() throws when 'exclude' option is " + exclude.type + ".");
  228. });
  229. [{ val: undefined, type: 'undefined' },
  230. { val: '*.validation111', type: 'string' },
  231. { val: /validation111/, type: 'regexp' },
  232. { val: ['*.validation111'], type: 'array with length > 0'}].forEach((exclude) => {
  233. new PageMod({ include: includeVal, exclude: exclude.val });
  234. assert.pass("PageMod() does not throw when exclude option is " + exclude.type);
  235. });
  236. };
  237. /* Tests for internal functions. */
  238. exports.testCommunication1 = function(assert, done) {
  239. let workerDone = false,
  240. callbackDone = null;
  241. testPageMod(assert, done, "about:", [{
  242. include: "about:*",
  243. contentScriptWhen: 'end',
  244. contentScript: 'new ' + function WorkerScope() {
  245. self.on('message', function(msg) {
  246. document.body.setAttribute('JEP-107', 'worked');
  247. self.postMessage(document.body.getAttribute('JEP-107'));
  248. })
  249. },
  250. onAttach: function(worker) {
  251. worker.on('error', function(e) {
  252. assert.fail('Errors where reported');
  253. });
  254. worker.on('message', function(value) {
  255. assert.equal(
  256. "worked",
  257. value,
  258. "test comunication"
  259. );
  260. workerDone = true;
  261. if (callbackDone)
  262. callbackDone();
  263. });
  264. worker.postMessage('do it!')
  265. }
  266. }],
  267. function(win, done) {
  268. (callbackDone = function() {
  269. if (workerDone) {
  270. assert.equal(
  271. 'worked',
  272. win.document.body.getAttribute('JEP-107'),
  273. 'attribute should be modified'
  274. );
  275. done();
  276. }
  277. })();
  278. }
  279. );
  280. };
  281. exports.testCommunication2 = function(assert, done) {
  282. let callbackDone = null,
  283. window;
  284. testPageMod(assert, done, "about:license", [{
  285. include: "about:*",
  286. contentScriptWhen: 'start',
  287. contentScript: 'new ' + function WorkerScope() {
  288. document.documentElement.setAttribute('AUQLUE', 42);
  289. window.addEventListener('load', function listener() {
  290. self.postMessage('onload');
  291. }, false);
  292. self.on("message", function() {
  293. self.postMessage(document.documentElement.getAttribute("test"))
  294. });
  295. },
  296. onAttach: function(worker) {
  297. worker.on('error', function(e) {
  298. assert.fail('Errors where reported');
  299. });
  300. worker.on('message', function(msg) {
  301. if ('onload' == msg) {
  302. assert.equal(
  303. '42',
  304. window.document.documentElement.getAttribute('AUQLUE'),
  305. 'PageMod scripts executed in order'
  306. );
  307. window.document.documentElement.setAttribute('test', 'changes in window');
  308. worker.postMessage('get window.test')
  309. } else {
  310. assert.equal(
  311. 'changes in window',
  312. msg,
  313. 'PageMod test #2: second script has run'
  314. )
  315. callbackDone();
  316. }
  317. });
  318. }
  319. }],
  320. function(win, done) {
  321. window = win;
  322. callbackDone = done;
  323. }
  324. );
  325. };
  326. exports.testEventEmitter = function(assert, done) {
  327. let workerDone = false,
  328. callbackDone = null;
  329. testPageMod(assert, done, "about:", [{
  330. include: "about:*",
  331. contentScript: 'new ' + function WorkerScope() {
  332. self.port.on('addon-to-content', function(data) {
  333. self.port.emit('content-to-addon', data);
  334. });
  335. },
  336. onAttach: function(worker) {
  337. worker.on('error', function(e) {
  338. assert.fail('Errors were reported : '+e);
  339. });
  340. worker.port.on('content-to-addon', function(value) {
  341. assert.equal(
  342. "worked",
  343. value,
  344. "EventEmitter API works!"
  345. );
  346. if (callbackDone)
  347. callbackDone();
  348. else
  349. workerDone = true;
  350. });
  351. worker.port.emit('addon-to-content', 'worked');
  352. }
  353. }],
  354. function(win, done) {
  355. if (workerDone)
  356. done();
  357. else
  358. callbackDone = done;
  359. }
  360. );
  361. };
  362. // Execute two concurrent page mods on same document to ensure that their
  363. // JS contexts are different
  364. exports.testMixedContext = function(assert, done) {
  365. let doneCallback = null;
  366. let messages = 0;
  367. let modObject = {
  368. include: "data:text/html;charset=utf-8,",
  369. contentScript: 'new ' + function WorkerScope() {
  370. // Both scripts will execute this,
  371. // context is shared if one script see the other one modification.
  372. let isContextShared = "sharedAttribute" in document;
  373. self.postMessage(isContextShared);
  374. document.sharedAttribute = true;
  375. },
  376. onAttach: function(w) {
  377. w.on("message", function (isContextShared) {
  378. if (isContextShared) {
  379. assert.fail("Page mod contexts are mixed.");
  380. doneCallback();
  381. }
  382. else if (++messages == 2) {
  383. assert.pass("Page mod contexts are different.");
  384. doneCallback();
  385. }
  386. });
  387. }
  388. };
  389. testPageMod(assert, done, "data:text/html;charset=utf-8,", [modObject, modObject],
  390. function(win, done) {
  391. doneCallback = done;
  392. }
  393. );
  394. };
  395. exports.testHistory = function(assert, done) {
  396. // We need a valid url in order to have a working History API.
  397. // (i.e do not work on data: or about: pages)
  398. // Test bug 679054.
  399. let url = data.url("test-page-mod.html");
  400. let callbackDone = null;
  401. testPageMod(assert, done, url, [{
  402. include: url,
  403. contentScriptWhen: 'end',
  404. contentScript: 'new ' + function WorkerScope() {
  405. history.pushState({}, "", "#");
  406. history.replaceState({foo: "bar"}, "", "#");
  407. self.postMessage(history.state);
  408. },
  409. onAttach: function(worker) {
  410. worker.on('message', function (data) {
  411. assert.equal(JSON.stringify(data), JSON.stringify({foo: "bar"}),
  412. "History API works!");
  413. callbackDone();
  414. });
  415. }
  416. }],
  417. function(win, done) {
  418. callbackDone = done;
  419. }
  420. );
  421. };
  422. exports.testRelatedTab = function(assert, done) {
  423. let tab;
  424. let pageMod = new PageMod({
  425. include: "about:*",
  426. onAttach: function(worker) {
  427. assert.ok(!!worker.tab, "Worker.tab exists");
  428. assert.equal(tab, worker.tab, "Worker.tab is valid");
  429. pageMod.destroy();
  430. tab.close(done);
  431. }
  432. });
  433. tabs.open({
  434. url: "about:",
  435. onOpen: function onOpen(t) {
  436. tab = t;
  437. }
  438. });
  439. };
  440. exports.testRelatedTabNoRequireTab = function(assert, done) {
  441. let loader = Loader(module);
  442. let tab;
  443. let url = "data:text/html;charset=utf-8," + encodeURI("Test related worker tab 2");
  444. let { PageMod } = loader.require("sdk/page-mod");
  445. let pageMod = new PageMod({
  446. include: url,
  447. onAttach: function(worker) {
  448. assert.equal(worker.tab.url, url, "Worker.tab.url is valid");
  449. worker.tab.close(function() {
  450. pageMod.destroy();
  451. loader.unload();
  452. done();
  453. });
  454. }
  455. });
  456. tabs.open(url);
  457. };
  458. exports.testRelatedTabNoOtherReqs = function(assert, done) {
  459. let loader = Loader(module);
  460. let { PageMod } = loader.require("sdk/page-mod");
  461. let pageMod = new PageMod({
  462. include: "about:blank?testRelatedTabNoOtherReqs",
  463. onAttach: function(worker) {
  464. assert.ok(!!worker.tab, "Worker.tab exists");
  465. pageMod.destroy();
  466. worker.tab.close(function() {
  467. worker.destroy();
  468. loader.unload();
  469. done();
  470. });
  471. }
  472. });
  473. tabs.open({
  474. url: "about:blank?testRelatedTabNoOtherReqs"
  475. });
  476. };
  477. exports.testWorksWithExistingTabs = function(assert, done) {
  478. let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document");
  479. let { PageMod } = require("sdk/page-mod");
  480. tabs.open({
  481. url: url,
  482. onReady: function onReady(tab) {
  483. let pageModOnExisting = new PageMod({
  484. include: url,
  485. attachTo: ["existing", "top", "frame"],
  486. onAttach: function(worker) {
  487. assert.ok(!!worker.tab, "Worker.tab exists");
  488. assert.equal(tab, worker.tab, "A worker has been created on this existing tab");
  489. worker.on('pageshow', () => {
  490. assert.fail("Should not have seen pageshow for an already loaded page");
  491. });
  492. setTimeout(function() {
  493. pageModOnExisting.destroy();
  494. pageModOffExisting.destroy();
  495. tab.close(done);
  496. }, 0);
  497. }
  498. });
  499. let pageModOffExisting = new PageMod({
  500. include: url,
  501. onAttach: function(worker) {
  502. assert.fail("pageModOffExisting page-mod should not have attached to anything");
  503. }
  504. });
  505. }
  506. });
  507. };
  508. exports.testExistingFrameDoesntMatchInclude = function(assert, done) {
  509. let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42';
  510. let iframe = '<iframe src="' + iframeURL + '" />';
  511. let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
  512. tabs.open({
  513. url: url,
  514. onReady: function onReady(tab) {
  515. let pagemod = new PageMod({
  516. include: url,
  517. attachTo: ['existing', 'frame'],
  518. onAttach: function() {
  519. assert.fail("Existing iframe URL doesn't match include, must not attach to anything");
  520. }
  521. });
  522. setTimeout(function() {
  523. assert.pass("PageMod didn't attach to anything")
  524. pagemod.destroy();
  525. tab.close(done);
  526. }, 250);
  527. }
  528. });
  529. };
  530. exports.testExistingOnlyFrameMatchesInclude = function(assert, done) {
  531. let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-43';
  532. let iframe = '<iframe src="' + iframeURL + '" />';
  533. let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
  534. tabs.open({
  535. url: url,
  536. onReady: function onReady(tab) {
  537. let pagemod = new PageMod({
  538. include: iframeURL,
  539. attachTo: ['existing', 'frame'],
  540. onAttach: function(worker) {
  541. assert.equal(iframeURL, worker.url,
  542. "PageMod attached to existing iframe when only it matches include rules");
  543. pagemod.destroy();
  544. tab.close(done);
  545. }
  546. });
  547. }
  548. });
  549. };
  550. exports.testAttachOnlyOncePerDocument = function(assert, done) {
  551. let iframeURL = 'data:text/html;charset=utf-8,testAttachOnlyOncePerDocument';
  552. let iframe = '<iframe src="' + iframeURL + '" />';
  553. let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe);
  554. let count = 0;
  555. tabs.open({
  556. url: url,
  557. onReady: function onReady(tab) {
  558. let pagemod = new PageMod({
  559. include: iframeURL,
  560. attachTo: ['existing', 'frame'],
  561. onAttach: (worker) => {
  562. count++;
  563. assert.equal(iframeURL, worker.url,
  564. "PageMod attached to existing iframe");
  565. assert.equal(count, 1, "PageMod attached only once");
  566. setTimeout(_ => {
  567. assert.equal(count, 1, "PageMod attached only once");
  568. pagemod.destroy();
  569. tab.close(done);
  570. }, 1);
  571. }
  572. });
  573. }
  574. });
  575. }
  576. exports.testContentScriptWhenDefault = function(assert) {
  577. let pagemod = PageMod({include: '*'});
  578. assert.equal(pagemod.contentScriptWhen, 'end', "Default contentScriptWhen is 'end'");
  579. pagemod.destroy();
  580. }
  581. // test timing for all 3 contentScriptWhen options (start, ready, end)
  582. // for new pages, or tabs opened after PageMod is created
  583. exports.testContentScriptWhenForNewTabs = function(assert, done) {
  584. let srv = contentScriptWhenServer();
  585. let url = srv.URL + '?ForNewTabs';
  586. let count = 0;
  587. handleReadyState(url, 'start', {
  588. onLoading: (tab) => {
  589. assert.pass("PageMod is attached while document is loading");
  590. checkDone(++count, tab, srv, done);
  591. },
  592. onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
  593. onComplete: () => assert.fail("onComplete should not be called with 'start'."),
  594. });
  595. handleReadyState(url, 'ready', {
  596. onInteractive: (tab) => {
  597. assert.pass("PageMod is attached while document is interactive");
  598. checkDone(++count, tab, srv, done);
  599. },
  600. onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
  601. onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
  602. });
  603. handleReadyState(url, 'end', {
  604. onComplete: (tab) => {
  605. assert.pass("PageMod is attached when document is complete");
  606. checkDone(++count, tab, srv, done);
  607. },
  608. onLoading: () => assert.fail("onLoading should not be called with 'end'."),
  609. onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
  610. });
  611. tabs.open(url);
  612. }
  613. // test timing for all 3 contentScriptWhen options (start, ready, end)
  614. // for PageMods created right as the tab is created (in tab.onOpen)
  615. exports.testContentScriptWhenOnTabOpen = function(assert, done) {
  616. let srv = contentScriptWhenServer();
  617. let url = srv.URL + '?OnTabOpen';
  618. let count = 0;
  619. tabs.open({
  620. url: url,
  621. onOpen: function(tab) {
  622. handleReadyState(url, 'start', {
  623. onLoading: () => {
  624. assert.pass("PageMod is attached while document is loading");
  625. checkDone(++count, tab, srv, done);
  626. },
  627. onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
  628. onComplete: () => assert.fail("onComplete should not be called with 'start'."),
  629. });
  630. handleReadyState(url, 'ready', {
  631. onInteractive: () => {
  632. assert.pass("PageMod is attached while document is interactive");
  633. checkDone(++count, tab, srv, done);
  634. },
  635. onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
  636. onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
  637. });
  638. handleReadyState(url, 'end', {
  639. onComplete: () => {
  640. assert.pass("PageMod is attached when document is complete");
  641. checkDone(++count, tab, srv, done);
  642. },
  643. onLoading: () => assert.fail("onLoading should not be called with 'end'."),
  644. onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
  645. });
  646. }
  647. });
  648. }
  649. // test timing for all 3 contentScriptWhen options (start, ready, end)
  650. // for PageMods created while the tab is interactive (in tab.onReady)
  651. exports.testContentScriptWhenOnTabReady = function(assert, done) {
  652. let srv = contentScriptWhenServer();
  653. let url = srv.URL + '?OnTabReady';
  654. let count = 0;
  655. tabs.open({
  656. url: url,
  657. onReady: function(tab) {
  658. handleReadyState(url, 'start', {
  659. onInteractive: () => {
  660. assert.pass("PageMod is attached while document is interactive");
  661. checkDone(++count, tab, srv, done);
  662. },
  663. onLoading: () => assert.fail("onLoading should not be called with 'start'."),
  664. onComplete: () => assert.fail("onComplete should not be called with 'start'."),
  665. });
  666. handleReadyState(url, 'ready', {
  667. onInteractive: () => {
  668. assert.pass("PageMod is attached while document is interactive");
  669. checkDone(++count, tab, srv, done);
  670. },
  671. onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
  672. onComplete: () => assert.fail("onComplete should not be called with 'ready'."),
  673. });
  674. handleReadyState(url, 'end', {
  675. onComplete: () => {
  676. assert.pass("PageMod is attached when document is complete");
  677. checkDone(++count, tab, srv, done);
  678. },
  679. onLoading: () => assert.fail("onLoading should not be called with 'end'."),
  680. onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
  681. });
  682. }
  683. });
  684. }
  685. // test timing for all 3 contentScriptWhen options (start, ready, end)
  686. // for PageMods created after a tab has completed loading (in tab.onLoad)
  687. exports.testContentScriptWhenOnTabLoad = function(assert, done) {
  688. let srv = contentScriptWhenServer();
  689. let url = srv.URL + '?OnTabLoad';
  690. let count = 0;
  691. tabs.open({
  692. url: url,
  693. onLoad: function(tab) {
  694. handleReadyState(url, 'start', {
  695. onComplete: () => {
  696. assert.pass("PageMod is attached when document is complete");
  697. checkDone(++count, tab, srv, done);
  698. },
  699. onLoading: () => assert.fail("onLoading should not be called with 'start'."),
  700. onInteractive: () => assert.fail("onInteractive should not be called with 'start'."),
  701. });
  702. handleReadyState(url, 'ready', {
  703. onComplete: () => {
  704. assert.pass("PageMod is attached when document is complete");
  705. checkDone(++count, tab, srv, done);
  706. },
  707. onLoading: () => assert.fail("onLoading should not be called with 'ready'."),
  708. onInteractive: () => assert.fail("onInteractive should not be called with 'ready'."),
  709. });
  710. handleReadyState(url, 'end', {
  711. onComplete: () => {
  712. assert.pass("PageMod is attached when document is complete");
  713. checkDone(++count, tab, srv, done);
  714. },
  715. onLoading: () => assert.fail("onLoading should not be called with 'end'."),
  716. onInteractive: () => assert.fail("onInteractive should not be called with 'end'."),
  717. });
  718. }
  719. });
  720. }
  721. function checkDone(count, tab, srv, done) {
  722. if (count === 3)
  723. tab.close(_ => srv.stop(done));
  724. }
  725. exports.testTabWorkerOnMessage = function(assert, done) {
  726. let { browserWindows } = require("sdk/windows");
  727. let tabs = require("sdk/tabs");
  728. let { PageMod } = require("sdk/page-mod");
  729. let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>";
  730. let url2 = "data:text/html;charset=utf-8,<title>tab2</title><h1>worker2.tab</h1>";
  731. let worker1 = null;
  732. let mod = PageMod({
  733. include: "data:text/html*",
  734. contentScriptWhen: "ready",
  735. contentScript: "self.postMessage('#1');",
  736. onAttach: function onAttach(worker) {
  737. worker.on("message", function onMessage() {
  738. this.tab.attach({
  739. contentScriptWhen: "ready",
  740. contentScript: "self.postMessage({ url: window.location.href, title: document.title });",
  741. onMessage: function onMessage(data) {
  742. assert.equal(this.tab.url, data.url, "location is correct");
  743. assert.equal(this.tab.title, data.title, "title is correct");
  744. if (this.tab.url === url1) {
  745. worker1 = this;
  746. tabs.open({ url: url2, inBackground: true });
  747. }
  748. else if (this.tab.url === url2) {
  749. mod.destroy();
  750. worker1.tab.close(function() {
  751. worker1.destroy();
  752. worker.tab.close(function() {
  753. worker.destroy();
  754. done();
  755. });
  756. });
  757. }
  758. }
  759. });
  760. });
  761. }
  762. });
  763. tabs.open(url1);
  764. };
  765. exports.testAutomaticDestroy = function(assert, done) {
  766. let loader = Loader(module);
  767. let pageMod = loader.require("sdk/page-mod").PageMod({
  768. include: "about:*",
  769. contentScriptWhen: "start",
  770. onAttach: function(w) {
  771. assert.fail("Page-mod should have been detroyed during module unload");
  772. }
  773. });
  774. // Unload the page-mod module so that our page mod is destroyed
  775. loader.unload();
  776. // Then create a second tab to ensure that it is correctly destroyed
  777. let tabs = require("sdk/tabs");
  778. tabs.open({
  779. url: "about:",
  780. onReady: function onReady(tab) {
  781. assert.pass("check automatic destroy");
  782. tab.close(done);
  783. }
  784. });
  785. };
  786. exports.testAttachToTabsOnly = function(assert, done) {
  787. let { PageMod } = require('sdk/page-mod');
  788. let openedTab = null; // Tab opened in openTabWithIframe()
  789. let workerCount = 0;
  790. let mod = PageMod({
  791. include: 'data:text/html*',
  792. contentScriptWhen: 'start',
  793. contentScript: '',
  794. onAttach: function onAttach(worker) {
  795. if (worker.tab === openedTab) {
  796. if (++workerCount == 3) {
  797. assert.pass('Succesfully applied to tab documents and its iframe');
  798. worker.destroy();
  799. mod.destroy();
  800. openedTab.close(done);
  801. }
  802. }
  803. else {
  804. assert.fail('page-mod attached to a non-tab document');
  805. }
  806. }
  807. });
  808. function openHiddenFrame() {
  809. assert.pass('Open iframe in hidden window');
  810. let hiddenFrames = require('sdk/frame/hidden-frame');
  811. let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
  812. onReady: function () {
  813. let element = this.element;
  814. element.addEventListener('DOMContentLoaded', function onload() {
  815. element.removeEventListener('DOMContentLoaded', onload, false);
  816. hiddenFrames.remove(hiddenFrame);
  817. if (!xulApp.is("Fennec")) {
  818. openToplevelWindow();
  819. }
  820. else {
  821. openBrowserIframe();
  822. }
  823. }, false);
  824. element.setAttribute('src', 'data:text/html;charset=utf-8,foo');
  825. }
  826. }));
  827. }
  828. function openToplevelWindow() {
  829. assert.pass('Open toplevel window');
  830. let win = open('data:text/html;charset=utf-8,bar');
  831. win.addEventListener('DOMContentLoaded', function onload() {
  832. win.removeEventListener('DOMContentLoaded', onload, false);
  833. win.close();
  834. openBrowserIframe();
  835. }, false);
  836. }
  837. function openBrowserIframe() {
  838. assert.pass('Open iframe in browser window');
  839. let window = require('sdk/deprecated/window-utils').activeBrowserWindow;
  840. let document = window.document;
  841. let iframe = document.createElement('iframe');
  842. iframe.setAttribute('type', 'content');
  843. iframe.setAttribute('src', 'data:text/html;charset=utf-8,foobar');
  844. iframe.addEventListener('DOMContentLoaded', function onload() {
  845. iframe.removeEventListener('DOMContentLoaded', onload, false);
  846. iframe.parentNode.removeChild(iframe);
  847. openTabWithIframes();
  848. }, false);
  849. document.documentElement.appendChild(iframe);
  850. }
  851. // Only these three documents will be accepted by the page-mod
  852. function openTabWithIframes() {
  853. assert.pass('Open iframes in a tab');
  854. let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
  855. let content = '<iframe src="data:text/html;charset=utf-8,' +
  856. encodeURIComponent(subContent) + '" />';
  857. require('sdk/tabs').open({
  858. url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content),
  859. onOpen: function onOpen(tab) {
  860. openedTab = tab;
  861. }
  862. });
  863. }
  864. openHiddenFrame();
  865. };
  866. exports['test111 attachTo [top]'] = function(assert, done) {
  867. let { PageMod } = require('sdk/page-mod');
  868. let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
  869. let content = '<iframe src="data:text/html;charset=utf-8,' +
  870. encodeURIComponent(subContent) + '" />';
  871. let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content)
  872. let workerCount = 0;
  873. let mod = PageMod({
  874. include: 'data:text/html*',
  875. contentScriptWhen: 'start',
  876. contentScript: 'self.postMessage(document.location.href);',
  877. attachTo: ['top'],
  878. onAttach: function onAttach(worker) {
  879. if (++workerCount == 1) {
  880. worker.on('message', function (href) {
  881. assert.equal(href, topDocumentURL,
  882. "worker on top level document only");
  883. let tab = worker.tab;
  884. worker.destroy();
  885. mod.destroy();
  886. tab.close(done);
  887. });
  888. }
  889. else {
  890. assert.fail('page-mod attached to a non-top document');
  891. }
  892. }
  893. });
  894. require('sdk/tabs').open(topDocumentURL);
  895. };
  896. exports['test111 attachTo [frame]'] = function(assert, done) {
  897. let { PageMod } = require('sdk/page-mod');
  898. let subFrameURL = 'data:text/html;charset=utf-8,subframe';
  899. let subContent = '<iframe src="' + subFrameURL + '" />';
  900. let frameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subContent);
  901. let content = '<iframe src="' + frameURL + '" />';
  902. let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content)
  903. let workerCount = 0, messageCount = 0;
  904. function onMessage(href) {
  905. if (href == frameURL)
  906. assert.pass("worker on first frame");
  907. else if (href == subFrameURL)
  908. assert.pass("worker on second frame");
  909. else
  910. assert.fail("worker on unexpected document: " + href);
  911. this.destroy();
  912. if (++messageCount == 2) {
  913. mod.destroy();
  914. require('sdk/tabs').activeTab.close(done);
  915. }
  916. }
  917. let mod = PageMod({
  918. include: 'data:text/html*',
  919. contentScriptWhen: 'start',
  920. contentScript: 'self.postMessage(document.location.href);',
  921. attachTo: ['frame'],
  922. onAttach: function onAttach(worker) {
  923. if (++workerCount <= 2) {
  924. worker.on('message', onMessage);
  925. }
  926. else {
  927. assert.fail('page-mod attached to a non-frame document');
  928. }
  929. }
  930. });
  931. require('sdk/tabs').open(topDocumentURL);
  932. };
  933. exports.testContentScriptOptionsOption = function(assert, done) {
  934. let callbackDone = null;
  935. testPageMod(assert, done, "about:", [{
  936. include: "about:*",
  937. contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
  938. contentScriptWhen: "end",
  939. contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
  940. onAttach: function(worker) {
  941. worker.on('message', function(msg) {
  942. assert.equal( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' );
  943. assert.equal( typeof msg[1], 'object', 'object as contentScriptOptions' );
  944. assert.equal( msg[1].a, true, 'boolean in contentScriptOptions' );
  945. assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' );
  946. assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' );
  947. callbackDone();
  948. });
  949. }
  950. }],
  951. function(win, done) {
  952. callbackDone = done;
  953. }
  954. );
  955. };
  956. exports.testPageModCss = function(assert, done) {
  957. let [pageMod] = testPageMod(assert, done,
  958. 'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{
  959. include: ["*", "data:*"],
  960. contentStyle: "div { height: 100px; }",
  961. contentStyleFile: [data.url("include-file.css"), "./border-style.css"]
  962. }],
  963. function(win, done) {
  964. let div = win.document.querySelector("div");
  965. assert.equal(div.clientHeight, 100,
  966. "PageMod contentStyle worked");
  967. assert.equal(div.offsetHeight, 120,
  968. "PageMod contentStyleFile worked");
  969. assert.equal(win.getComputedStyle(div).borderTopStyle, "dashed",
  970. "PageMod contentStyleFile with relative path worked");
  971. done();
  972. }
  973. );
  974. };
  975. exports.testPageModCssList = function(assert, done) {
  976. let [pageMod] = testPageMod(assert, done,
  977. 'data:text/html;charset=utf-8,<div style="width:320px; max-width: 480px!important">css test</div>', [{
  978. include: "data:*",
  979. contentStyleFile: [
  980. // Highlight evaluation order in this list
  981. "data:text/css;charset=utf-8,div { border: 1px solid black; }",
  982. "data:text/css;charset=utf-8,div { border: 10px solid black; }",
  983. // Highlight evaluation order between contentStylesheet & contentStylesheetFile
  984. "data:text/css;charset=utf-8s,div { height: 1000px; }",
  985. // Highlight precedence between the author and user style sheet
  986. "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}",
  987. ],
  988. contentStyle: [
  989. "div { height: 10px; }",
  990. "div { height: 100px; }"
  991. ]
  992. }],
  993. function(win, done) {
  994. let div = win.document.querySelector("div"),
  995. style = win.getComputedStyle(div);
  996. assert.equal(
  997. div.clientHeight,
  998. 100,
  999. "PageMod contentStyle list works and is evaluated after contentStyleFile"
  1000. );
  1001. assert.equal(
  1002. div.offsetHeight,
  1003. 120,
  1004. "PageMod contentStyleFile list works"
  1005. );
  1006. assert.equal(
  1007. style.width,
  1008. "320px",
  1009. "PageMod add-on author/page author style sheet precedence works"
  1010. );
  1011. assert.equal(
  1012. style.maxWidth,
  1013. "480px",
  1014. "PageMod add-on author/page author style sheet precedence with !important works"
  1015. );
  1016. done();
  1017. }
  1018. );
  1019. };
  1020. exports.testPageModCssDestroy = function(assert, done) {
  1021. let [pageMod] = testPageMod(assert, done,
  1022. 'data:text/html;charset=utf-8,<div style="width:200px">css test</div>', [{
  1023. include: "data:*",
  1024. contentStyle: "div { width: 100px!important; }"
  1025. }],
  1026. function(win, done) {
  1027. let div = win.document.querySelector("div"),
  1028. style = win.getComputedStyle(div);
  1029. assert.equal(
  1030. style.width,
  1031. "100px",
  1032. "PageMod contentStyle worked"
  1033. );
  1034. pageMod.destroy();
  1035. assert.equal(
  1036. style.width,
  1037. "200px",
  1038. "PageMod contentStyle is removed after destroy"
  1039. );
  1040. done();
  1041. }
  1042. );
  1043. };
  1044. exports.testPageModCssAutomaticDestroy = function(assert, done) {
  1045. let loader = Loader(module);
  1046. let pageMod = loader.require("sdk/page-mod").PageMod({
  1047. include: "data:*",
  1048. contentStyle: "div { width: 100px!important; }"
  1049. });
  1050. tabs.open({
  1051. url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>",
  1052. onReady: function onReady(tab) {
  1053. let browserWindow = getMostRecentBrowserWindow();
  1054. let win = getTabContentWindow(getActiveTab(browserWindow));
  1055. let div = win.document.querySelector("div");
  1056. let style = win.getComputedStyle(div);
  1057. assert.equal(
  1058. style.width,
  1059. "100px",
  1060. "PageMod contentStyle worked"
  1061. );
  1062. loader.unload();
  1063. assert.equal(
  1064. style.width,
  1065. "200px",
  1066. "PageMod contentStyle is removed after loader's unload"
  1067. );
  1068. tab.close(done);
  1069. }
  1070. });
  1071. };
  1072. exports.testPageModContentScriptFile = function(assert, done) {
  1073. testPageMod(assert, done, "about:license", [{
  1074. include: "about:*",
  1075. contentScriptWhen: "start",
  1076. contentScriptFile: "./test-contentScriptFile.js",
  1077. onMessage: message => {
  1078. assert.equal(message, "msg from contentScriptFile",
  1079. "PageMod contentScriptFile with relative path worked");
  1080. }
  1081. }],
  1082. (win, done) => done()
  1083. );
  1084. };
  1085. exports.testPageModTimeout = function(assert, done) {
  1086. let tab = null
  1087. let loader = Loader(module);
  1088. let { PageMod } = loader.require("sdk/page-mod");
  1089. let mod = PageMod({
  1090. include: "data:*",
  1091. contentScript: Isolate(function() {
  1092. var id = setTimeout(function() {
  1093. self.port.emit("fired", id)
  1094. }, 10)
  1095. self.port.emit("scheduled", id);
  1096. }),
  1097. onAttach: function(worker) {
  1098. worker.port.on("scheduled", function(id) {
  1099. assert.pass("timer was scheduled")
  1100. worker.port.on("fired", function(data) {
  1101. assert.equal(id, data, "timer was fired")
  1102. tab.close(function() {
  1103. worker.destroy()
  1104. loader.unload()
  1105. done()
  1106. });
  1107. })
  1108. })
  1109. }
  1110. });
  1111. tabs.open({
  1112. url: "data:text/html;charset=utf-8,timeout",
  1113. onReady: function($) { tab = $ }
  1114. })
  1115. }
  1116. exports.testPageModcancelTimeout = function(assert, done) {
  1117. let tab = null
  1118. let loader = Loader(module);
  1119. let { PageMod } = loader.require("sdk/page-mod");
  1120. let mod = PageMod({
  1121. include: "data:*",
  1122. contentScript: Isolate(function() {
  1123. var id1 = setTimeout(function() {
  1124. self.port.emit("failed")
  1125. }, 10)
  1126. var id2 = setTimeout(function() {
  1127. self.port.emit("timeout")
  1128. }, 100)
  1129. clearTimeout(id1)
  1130. }),
  1131. onAttach: function(worker) {
  1132. worker.port.on("failed", function() {
  1133. assert.fail("cancelled timeout fired")
  1134. })
  1135. worker.port.on("timeout", function(id) {
  1136. assert.pass("timer was scheduled")
  1137. tab.close(function() {
  1138. worker.destroy();
  1139. mod.destroy();
  1140. loader.unload();
  1141. done();
  1142. });
  1143. })
  1144. }
  1145. });
  1146. tabs.open({
  1147. url: "data:text/html;charset=utf-8,cancell timeout",
  1148. onReady: function($) { tab = $ }
  1149. })
  1150. }
  1151. exports.testExistingOnFrames = function(assert, done) {
  1152. let subFrameURL = 'data:text/html;charset=utf-8,testExistingOnFrames-sub-frame';
  1153. let subIFrame = '<iframe src="' + subFrameURL + '" />'
  1154. let iFrameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subIFrame)
  1155. let iFrame = '<iframe src="' + iFrameURL + '" />';
  1156. let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iFrame);
  1157. // we want all urls related to the test here, and not just the iframe urls
  1158. // because we need to fail if the test is applied to the top window url.
  1159. let urls = [url, iFrameURL, subFrameURL];
  1160. let counter = 0;
  1161. let tab = openTab(getMostRecentBrowserWindow(), url);
  1162. let window = getTabContentWindow(tab);
  1163. function wait4Iframes() {
  1164. if (window.document.readyState != "complete" ||
  1165. getFrames(window).length != 2) {
  1166. return;
  1167. }
  1168. let pagemodOnExisting = PageMod({
  1169. include: ["*", "data:*"],
  1170. attachTo: ["existing", "frame"],
  1171. contentScriptWhen: 'ready',
  1172. onAttach: function(worker) {
  1173. // need to ignore urls that are not part of the test, because other
  1174. // tests are not closing their tabs when they complete..
  1175. if (urls.indexOf(worker.url) == -1)
  1176. return;
  1177. assert.notEqual(url,
  1178. worker.url,
  1179. 'worker should not be attached to the top window');
  1180. if (++counter < 2) {
  1181. // we can rely on this order in this case because we are sure that
  1182. // the frames being tested have completely loaded
  1183. assert.equal(iFrameURL, worker.url, '1st attach is for top frame');
  1184. }
  1185. else if (counter > 2) {
  1186. assert.fail('applied page mod too many times');
  1187. }
  1188. else {
  1189. assert.equal(subFrameURL, worker.url, '2nd attach is for sub frame');
  1190. // need timeout because onAttach is called before the constructor returns
  1191. setTimeout(function() {
  1192. pagemodOnExisting.destroy();
  1193. pagemodOffExisting.destroy();
  1194. closeTab(tab);
  1195. done();
  1196. }, 0);
  1197. }
  1198. }
  1199. });
  1200. let pagemodOffExisting = PageMod({
  1201. include: ["*", "data:*"],
  1202. attachTo: ["frame"],
  1203. contentScriptWhen: 'ready',
  1204. onAttach: function(mod) {
  1205. assert.fail('pagemodOffExisting page-mod should not have been attached');
  1206. }
  1207. });
  1208. }
  1209. window.addEventListener("load", wait4Iframes, false);
  1210. };
  1211. exports.testIFramePostMessage = function(assert, done) {
  1212. let count = 0;
  1213. tabs.open({
  1214. url: data.url("test-iframe.html"),
  1215. onReady: function(tab) {
  1216. var worker = tab.attach({
  1217. contentScriptFile: data.url('test-iframe.js'),
  1218. contentScript: 'var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'',
  1219. onMessage: function(msg) {
  1220. assert.equal(++count, 1);
  1221. assert.equal(msg.first, 'a string');
  1222. assert.ok(msg.second[1], "array");
  1223. assert.equal(typeof msg.third, 'object');
  1224. worker.destroy();
  1225. tab.close(done);
  1226. }
  1227. });
  1228. }
  1229. });
  1230. };
  1231. exports.testEvents = function(assert, done) {
  1232. let content = "<script>\n new " + function DocumentScope() {
  1233. window.addEventListener("ContentScriptEvent", function () {
  1234. window.document.body.setAttribute("receivedEvent", true);
  1235. }, false);
  1236. } + "\n</script>";
  1237. let url = "data:text/html;charset=utf-8," + encodeURIComponent(content);
  1238. testPageMod(assert, done, url, [{
  1239. include: "data:*",
  1240. contentScript: 'new ' + function WorkerScope() {
  1241. let evt = document.createEvent("Event");
  1242. evt.initEvent("ContentScriptEvent", true, true);
  1243. document.body.dispatchEvent(evt);
  1244. }
  1245. }],
  1246. function(win, done) {
  1247. assert.ok(
  1248. win.document.body.getAttribute("receivedEvent"),
  1249. "Content script sent an event and document received it"
  1250. );
  1251. done();
  1252. },
  1253. 100
  1254. );
  1255. };
  1256. exports["test page-mod on private tab"] = function (assert, done) {
  1257. let fail = assert.fail.bind(assert);
  1258. let privateUri = "data:text/html;charset=utf-8," +
  1259. "<iframe src=\"data:text/html;charset=utf-8,frame\" />";
  1260. let nonPrivateUri = "data:text/html;charset=utf-8,non-private";
  1261. let pageMod = new PageMod({
  1262. include: "data:*",
  1263. onAttach: function(worker) {
  1264. if (isTabPBSupported || isWindowPBSupported) {
  1265. // When PB isn't supported, the page-mod will apply to all document
  1266. // as all of them will be non-private
  1267. assert.equal(worker.tab.url,
  1268. nonPrivateUri,
  1269. "page-mod should only attach to the non-private tab");
  1270. }
  1271. assert.ok(!isPrivate(worker),
  1272. "The worker is really non-private");
  1273. assert.ok(!isPrivate(worker.tab),
  1274. "The document is really non-private");
  1275. pageMod.destroy();
  1276. page1.close().
  1277. then(page2.close).
  1278. then(done, fail);
  1279. }
  1280. });
  1281. let page1, page2;
  1282. page1 = openWebpage(privateUri, true);
  1283. page1.ready.then(function() {
  1284. page2 = openWebpage(nonPrivateUri, false);
  1285. }, fail);
  1286. }
  1287. // Bug 699450: Calling worker.tab.close() should not lead to exception
  1288. exports.testWorkerTabClose = function(assert, done) {
  1289. let callbackDone;
  1290. testPageMod(assert, done, "about:", [{
  1291. include: "about:",
  1292. contentScript: '',
  1293. onAttach: function(worker) {
  1294. assert.pass("The page-mod was attached");
  1295. worker.tab.close(function () {
  1296. // On Fennec, tab is completely destroyed right after close event is
  1297. // dispatch, so we need to wait for the next event loop cycle to
  1298. // check for tab nulliness.
  1299. setTimeout(function () {
  1300. assert.ok(!worker.tab,
  1301. "worker.tab should be null right after tab.close()");
  1302. callbackDone();
  1303. }, 0);
  1304. });
  1305. }
  1306. }],
  1307. function(win, done) {
  1308. callbackDone = done;
  1309. }
  1310. );
  1311. };
  1312. exports.testDebugMetadata = function(assert, done) {
  1313. let dbg = new Debugger;
  1314. let globalDebuggees = [];
  1315. dbg.onNewGlobalObject = function(global) {
  1316. globalDebuggees.push(global);
  1317. }
  1318. let mods = testPageMod(assert, done, "about:", [{
  1319. include: "about:",
  1320. contentScriptWhen: "start",
  1321. contentScript: "null;",
  1322. }], function(win, done) {
  1323. assert.ok(globalDebuggees.some(function(global) {
  1324. try {
  1325. let metadata = Cu.getSandboxMetadata(global.unsafeDereference());
  1326. return metadata && metadata.addonID && metadata.SDKContentScript &&
  1327. metadata['inner-window-id'] == getInnerId(win);
  1328. } catch(e) {
  1329. // Some of the globals might not be Sandbox instances and thus
  1330. // will cause getSandboxMetadata to fail.
  1331. return false;
  1332. }
  1333. }), "one of the globals is a content script");
  1334. done();
  1335. }
  1336. );
  1337. };
  1338. exports.testDevToolsExtensionsGetContentGlobals = function(assert, done) {
  1339. let mods = testPageMod(assert, done, "about:", [{
  1340. include: "about:",
  1341. contentScriptWhen: "start",
  1342. contentScript: "null;",
  1343. }], function(win, done) {
  1344. assert.equal(contentGlobals.getContentGlobals({ 'inner-window-id': getInnerId(win) }).length, 1);
  1345. done();
  1346. }
  1347. );
  1348. };
  1349. exports.testDetachOnDestroy = function(assert, done) {
  1350. let tab;
  1351. const TEST_URL = 'data:text/html;charset=utf-8,detach';
  1352. const loader = Loader(module);
  1353. const { PageMod } = loader.require('sdk/page-mod');
  1354. let mod1 = PageMod({
  1355. include: TEST_URL,
  1356. contentScript: Isolate(function() {
  1357. self.port.on('detach', function(reason) {
  1358. window.document.body.innerHTML += '!' + reason;
  1359. });
  1360. }),
  1361. onAttach: worker => {
  1362. assert.pass('attach[1] happened');
  1363. worker.on('detach', _ => setTimeout(_ => {
  1364. assert.pass('detach happened');
  1365. let mod2 = PageMod({
  1366. attachTo: [ 'existing', 'top' ],
  1367. include: TEST_URL,
  1368. contentScript: Isolate(function() {
  1369. self.port.on('test', _ => {
  1370. self.port.emit('result', { result: window.document.body.innerHTML});
  1371. });
  1372. }),
  1373. onAttach: worker => {
  1374. assert.pass('attach[2] happened');
  1375. worker.port.once('result', ({ result }) => {
  1376. assert.equal(result, 'detach!', 'the body.innerHTML is as expected');
  1377. mod1.destroy();
  1378. mod2.destroy();
  1379. loader.unload();
  1380. tab.close(done);
  1381. });
  1382. worker.port.emit('test');
  1383. }
  1384. });
  1385. }));
  1386. worker.destroy();
  1387. }
  1388. });
  1389. tabs.open({
  1390. url: TEST_URL,
  1391. on

Large files files are truncated, but you can click here to view the full file