test-page-mod.js 36 KB

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