addon-sdk/source/test/test-page-mod.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial