test-page-worker.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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 { Loader } = require('sdk/test/loader');
  6. const Pages = require("sdk/page-worker");
  7. const Page = Pages.Page;
  8. const { URL } = require("sdk/url");
  9. const fixtures = require("./fixtures");
  10. const testURI = fixtures.url("test.html");
  11. const ERR_DESTROYED =
  12. "Couldn't find the worker to receive this message. " +
  13. "The script may not be initialized yet, or may already have been unloaded.";
  14. exports.testSimplePageCreation = function(assert, done) {
  15. let page = new Page({
  16. contentScript: "self.postMessage(window.location.href)",
  17. contentScriptWhen: "end",
  18. onMessage: function (message) {
  19. assert.equal(message, "about:blank",
  20. "Page Worker should start with a blank page by default");
  21. assert.equal(this, page, "The 'this' object is the page itself.");
  22. done();
  23. }
  24. });
  25. }
  26. /*
  27. * Tests that we can't be tricked by document overloads as we have access
  28. * to wrapped nodes
  29. */
  30. exports.testWrappedDOM = function(assert, done) {
  31. let page = Page({
  32. allow: { script: true },
  33. contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
  34. contentScript: "window.addEventListener('load', function () " +
  35. "self.postMessage([typeof(document.getElementById), " +
  36. "typeof(window.scrollTo)]), true)",
  37. onMessage: function (message) {
  38. assert.equal(message[0],
  39. "function",
  40. "getElementById from content script is the native one");
  41. assert.equal(message[1],
  42. "function",
  43. "scrollTo from content script is the native one");
  44. done();
  45. }
  46. });
  47. }
  48. /*
  49. // We do not offer unwrapped access to DOM since bug 601295 landed
  50. // See 660780 to track progress of unwrap feature
  51. exports.testUnwrappedDOM = function(assert, done) {
  52. let page = Page({
  53. allow: { script: true },
  54. contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
  55. contentScript: "window.addEventListener('load', function () " +
  56. "self.postMessage([typeof(unsafeWindow.document.getElementById), " +
  57. "typeof(unsafeWindow.scrollTo)]), true)",
  58. onMessage: function (message) {
  59. assert.equal(message[0],
  60. "number",
  61. "document inside page is free to be changed");
  62. assert.equal(message[1],
  63. "number",
  64. "window inside page is free to be changed");
  65. done();
  66. }
  67. });
  68. }
  69. */
  70. exports.testPageProperties = function(assert) {
  71. let page = new Page();
  72. for each (let prop in ['contentURL', 'allow', 'contentScriptFile',
  73. 'contentScript', 'contentScriptWhen', 'on',
  74. 'postMessage', 'removeListener']) {
  75. assert.ok(prop in page, prop + " property is defined on page.");
  76. }
  77. assert.ok(function () page.postMessage("foo") || true,
  78. "postMessage doesn't throw exception on page.");
  79. }
  80. exports.testConstructorAndDestructor = function(assert, done) {
  81. let loader = Loader(module);
  82. let Pages = loader.require("sdk/page-worker");
  83. let global = loader.sandbox("sdk/page-worker");
  84. let pagesReady = 0;
  85. let page1 = Pages.Page({
  86. contentScript: "self.postMessage('')",
  87. contentScriptWhen: "end",
  88. onMessage: pageReady
  89. });
  90. let page2 = Pages.Page({
  91. contentScript: "self.postMessage('')",
  92. contentScriptWhen: "end",
  93. onMessage: pageReady
  94. });
  95. assert.notEqual(page1, page2,
  96. "Page 1 and page 2 should be different objects.");
  97. function pageReady() {
  98. if (++pagesReady == 2) {
  99. page1.destroy();
  100. page2.destroy();
  101. assert.ok(isDestroyed(page1), "page1 correctly unloaded.");
  102. assert.ok(isDestroyed(page2), "page2 correctly unloaded.");
  103. loader.unload();
  104. done();
  105. }
  106. }
  107. }
  108. exports.testAutoDestructor = function(assert, done) {
  109. let loader = Loader(module);
  110. let Pages = loader.require("sdk/page-worker");
  111. let page = Pages.Page({
  112. contentScript: "self.postMessage('')",
  113. contentScriptWhen: "end",
  114. onMessage: function() {
  115. loader.unload();
  116. assert.ok(isDestroyed(page), "Page correctly unloaded.");
  117. done();
  118. }
  119. });
  120. }
  121. exports.testValidateOptions = function(assert) {
  122. assert.throws(
  123. function () Page({ contentURL: 'home' }),
  124. /The `contentURL` option must be a valid URL\./,
  125. "Validation correctly denied a non-URL contentURL"
  126. );
  127. assert.throws(
  128. function () Page({ onMessage: "This is not a function."}),
  129. /The option "onMessage" must be one of the following types: function/,
  130. "Validation correctly denied a non-function onMessage."
  131. );
  132. assert.pass("Options validation is working.");
  133. }
  134. exports.testContentAndAllowGettersAndSetters = function(assert, done) {
  135. let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>";
  136. // Load up the page with testURI initially for the resource:// principal,
  137. // then load the actual data:* content, as data:* URIs no longer
  138. // have localStorage
  139. let page = Page({
  140. contentURL: testURI,
  141. contentScript: "if (window.location.href==='"+testURI+"')" +
  142. " self.postMessage('reload');" +
  143. "else " +
  144. " self.postMessage(window.localStorage.allowScript)",
  145. contentScriptWhen: "end",
  146. onMessage: step0
  147. });
  148. function step0(message) {
  149. if (message === 'reload')
  150. return page.contentURL = content;
  151. assert.equal(message, "3",
  152. "Correct value expected for allowScript - 3");
  153. assert.equal(page.contentURL, content,
  154. "Correct content expected");
  155. page.removeListener('message', step0);
  156. page.on('message', step1);
  157. page.allow = { script: false };
  158. page.contentURL = content =
  159. "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='f'</script>";
  160. }
  161. function step1(message) {
  162. assert.equal(message, "3",
  163. "Correct value expected for allowScript - 3");
  164. assert.equal(page.contentURL, content, "Correct content expected");
  165. page.removeListener('message', step1);
  166. page.on('message', step2);
  167. page.allow = { script: true };
  168. page.contentURL = content =
  169. "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='g'</script>";
  170. }
  171. function step2(message) {
  172. assert.equal(message, "g",
  173. "Correct value expected for allowScript - g");
  174. assert.equal(page.contentURL, content, "Correct content expected");
  175. page.removeListener('message', step2);
  176. page.on('message', step3);
  177. page.allow.script = false;
  178. page.contentURL = content =
  179. "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3</script>";
  180. }
  181. function step3(message) {
  182. assert.equal(message, "g",
  183. "Correct value expected for allowScript - g");
  184. assert.equal(page.contentURL, content, "Correct content expected");
  185. page.removeListener('message', step3);
  186. page.on('message', step4);
  187. page.allow.script = true;
  188. page.contentURL = content =
  189. "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=4</script>";
  190. }
  191. function step4(message) {
  192. assert.equal(message, "4",
  193. "Correct value expected for allowScript - 4");
  194. assert.equal(page.contentURL, content, "Correct content expected");
  195. done();
  196. }
  197. }
  198. exports.testOnMessageCallback = function(assert, done) {
  199. Page({
  200. contentScript: "self.postMessage('')",
  201. contentScriptWhen: "end",
  202. onMessage: function() {
  203. assert.pass("onMessage callback called");
  204. done();
  205. }
  206. });
  207. }
  208. exports.testMultipleOnMessageCallbacks = function(assert, done) {
  209. let count = 0;
  210. let page = Page({
  211. contentScript: "self.postMessage('')",
  212. contentScriptWhen: "end",
  213. onMessage: () => count += 1
  214. });
  215. page.on('message', () => count += 2);
  216. page.on('message', () => count *= 3);
  217. page.on('message', () =>
  218. assert.equal(count, 9, "All callbacks were called, in order."));
  219. page.on('message', done);
  220. };
  221. exports.testLoadContentPage = function(assert, done) {
  222. let page = Page({
  223. onMessage: function(message) {
  224. // The message is an array whose first item is the test method to call
  225. // and the rest of whose items are arguments to pass it.
  226. let msg = message.shift();
  227. if (msg == "done")
  228. return done();
  229. assert[msg].apply(assert, message);
  230. },
  231. contentURL: fixtures.url("test-page-worker.html"),
  232. contentScriptFile: fixtures.url("test-page-worker.js"),
  233. contentScriptWhen: "ready"
  234. });
  235. }
  236. exports.testAllowScriptDefault = function(assert, done) {
  237. let page = Page({
  238. onMessage: function(message) {
  239. assert.ok(message, "Script is allowed to run by default.");
  240. done();
  241. },
  242. contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
  243. contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))",
  244. contentScriptWhen: "ready"
  245. });
  246. }
  247. exports.testAllowScript = function(assert, done) {
  248. let page = Page({
  249. onMessage: function(message) {
  250. assert.ok(message, "Script runs when allowed to do so.");
  251. done();
  252. },
  253. allow: { script: true },
  254. contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
  255. contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " +
  256. " document.documentElement.getAttribute('foo') == 3)",
  257. contentScriptWhen: "ready"
  258. });
  259. }
  260. exports.testPingPong = function(assert, done) {
  261. let page = Page({
  262. contentURL: 'data:text/html;charset=utf-8,ping-pong',
  263. contentScript: 'self.on("message", function(message) self.postMessage("pong"));'
  264. + 'self.postMessage("ready");',
  265. onMessage: function(message) {
  266. if ('ready' == message) {
  267. page.postMessage('ping');
  268. }
  269. else {
  270. assert.ok(message, 'pong', 'Callback from contentScript');
  271. done();
  272. }
  273. }
  274. });
  275. };
  276. exports.testRedirect = function (assert, done) {
  277. let page = Page({
  278. contentURL: 'data:text/html;charset=utf-8,first-page',
  279. contentScript: '(function () {' +
  280. 'if (/first-page/.test(document.location.href)) ' +
  281. ' document.location.href = "data:text/html;charset=utf-8,redirect";' +
  282. 'else ' +
  283. ' self.port.emit("redirect", document.location.href);' +
  284. '})();'
  285. });
  286. page.port.on('redirect', function (url) {
  287. assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
  288. done();
  289. });
  290. };
  291. exports.testRedirectIncludeArrays = function (assert, done) {
  292. let firstURL = 'data:text/html;charset=utf-8,first-page';
  293. let page = Page({
  294. contentURL: firstURL,
  295. contentScript: '(function () {' +
  296. 'self.port.emit("load", document.location.href);' +
  297. ' self.port.on("redirect", function (url) {' +
  298. ' document.location.href = url;' +
  299. ' })' +
  300. '})();',
  301. include: ['about:blank', 'data:*']
  302. });
  303. page.port.on('load', function (url) {
  304. if (url === firstURL) {
  305. page.port.emit('redirect', 'about:blank');
  306. } else if (url === 'about:blank') {
  307. page.port.emit('redirect', 'about:home');
  308. assert.ok('`include` property handles arrays');
  309. assert.equal(url, 'about:blank', 'Redirects work with accepted domains');
  310. done();
  311. } else if (url === 'about:home') {
  312. assert.fail('Should not redirect to restricted domain');
  313. }
  314. });
  315. };
  316. exports.testRedirectFromWorker = function (assert, done) {
  317. let firstURL = 'data:text/html;charset=utf-8,first-page';
  318. let secondURL = 'data:text/html;charset=utf-8,second-page';
  319. let thirdURL = 'data:text/html;charset=utf-8,third-page';
  320. let page = Page({
  321. contentURL: firstURL,
  322. contentScript: '(function () {' +
  323. 'self.port.emit("load", document.location.href);' +
  324. ' self.port.on("redirect", function (url) {' +
  325. ' document.location.href = url;' +
  326. ' })' +
  327. '})();',
  328. include: 'data:*'
  329. });
  330. page.port.on('load', function (url) {
  331. if (url === firstURL) {
  332. page.port.emit('redirect', secondURL);
  333. } else if (url === secondURL) {
  334. page.port.emit('redirect', thirdURL);
  335. } else if (url === thirdURL) {
  336. page.port.emit('redirect', 'about:home');
  337. assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
  338. done();
  339. } else {
  340. assert.fail('Should not redirect to unauthorized domains');
  341. }
  342. });
  343. };
  344. exports.testRedirectWithContentURL = function (assert, done) {
  345. let firstURL = 'data:text/html;charset=utf-8,first-page';
  346. let secondURL = 'data:text/html;charset=utf-8,second-page';
  347. let thirdURL = 'data:text/html;charset=utf-8,third-page';
  348. let page = Page({
  349. contentURL: firstURL,
  350. contentScript: '(function () {' +
  351. 'self.port.emit("load", document.location.href);' +
  352. '})();',
  353. include: 'data:*'
  354. });
  355. page.port.on('load', function (url) {
  356. if (url === firstURL) {
  357. page.contentURL = secondURL;
  358. } else if (url === secondURL) {
  359. page.contentURL = thirdURL;
  360. } else if (url === thirdURL) {
  361. page.contentURL = 'about:home';
  362. assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
  363. done();
  364. } else {
  365. assert.fail('Should not redirect to unauthorized domains');
  366. }
  367. });
  368. };
  369. exports.testMultipleDestroys = function(assert) {
  370. let page = Page();
  371. page.destroy();
  372. page.destroy();
  373. assert.pass("Multiple destroys should not cause an error");
  374. };
  375. exports.testContentScriptOptionsOption = function(assert, done) {
  376. let page = new Page({
  377. contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
  378. contentScriptWhen: "end",
  379. contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
  380. onMessage: function(msg) {
  381. assert.equal(msg[0], 'undefined', 'functions are stripped from contentScriptOptions');
  382. assert.equal(typeof msg[1], 'object', 'object as contentScriptOptions');
  383. assert.equal(msg[1].a, true, 'boolean in contentScriptOptions');
  384. assert.equal(msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions');
  385. assert.equal(msg[1].c, 'string', 'string in contentScriptOptions');
  386. done();
  387. }
  388. });
  389. };
  390. exports.testMessageQueue = function (assert, done) {
  391. let page = new Page({
  392. contentScript: 'self.on("message", function (m) {' +
  393. 'self.postMessage(m);' +
  394. '});',
  395. contentURL: 'data:text/html;charset=utf-8,',
  396. });
  397. page.postMessage('ping');
  398. page.on('message', function (m) {
  399. assert.equal(m, 'ping', 'postMessage should queue messages');
  400. done();
  401. });
  402. };
  403. function isDestroyed(page) {
  404. try {
  405. page.postMessage("foo");
  406. }
  407. catch (err if err.message == ERR_DESTROYED) {
  408. return true;
  409. }
  410. return false;
  411. }
  412. require("test").run(exports);