1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/test/test-page-mod.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1598 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +"use strict"; 1.8 + 1.9 +const { PageMod } = require("sdk/page-mod"); 1.10 +const { testPageMod, handleReadyState } = require("./pagemod-test-helpers"); 1.11 +const { Loader } = require('sdk/test/loader'); 1.12 +const tabs = require("sdk/tabs"); 1.13 +const { setTimeout } = require("sdk/timers"); 1.14 +const { Cc, Ci, Cu } = require("chrome"); 1.15 +const { 1.16 + open, 1.17 + getFrames, 1.18 + getMostRecentBrowserWindow, 1.19 + getInnerId 1.20 +} = require('sdk/window/utils'); 1.21 +const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils'); 1.22 +const xulApp = require("sdk/system/xul-app"); 1.23 +const { isPrivateBrowsingSupported } = require('sdk/self'); 1.24 +const { isPrivate } = require('sdk/private-browsing'); 1.25 +const { openWebpage } = require('./private-browsing/helper'); 1.26 +const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils'); 1.27 +const promise = require("sdk/core/promise"); 1.28 +const { pb } = require('./private-browsing/helper'); 1.29 +const { URL } = require("sdk/url"); 1.30 +const { LoaderWithHookedConsole } = require('sdk/test/loader'); 1.31 + 1.32 +const { waitUntil } = require("sdk/test/utils"); 1.33 +const data = require("./fixtures"); 1.34 + 1.35 +const { gDevToolsExtensions } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {}); 1.36 + 1.37 +const testPageURI = data.url("test.html"); 1.38 + 1.39 +// The following adds Debugger constructor to the global namespace. 1.40 +const { addDebuggerToGlobal } = 1.41 + Cu.import('resource://gre/modules/jsdebugger.jsm', {}); 1.42 +addDebuggerToGlobal(this); 1.43 + 1.44 +function Isolate(worker) { 1.45 + return "(" + worker + ")()"; 1.46 +} 1.47 + 1.48 +/* Tests for the PageMod APIs */ 1.49 + 1.50 +exports.testPageMod1 = function(assert, done) { 1.51 + let mods = testPageMod(assert, done, "about:", [{ 1.52 + include: /about:/, 1.53 + contentScriptWhen: 'end', 1.54 + contentScript: 'new ' + function WorkerScope() { 1.55 + window.document.body.setAttribute("JEP-107", "worked"); 1.56 + }, 1.57 + onAttach: function() { 1.58 + assert.equal(this, mods[0], "The 'this' object is the page mod."); 1.59 + } 1.60 + }], 1.61 + function(win, done) { 1.62 + assert.equal( 1.63 + win.document.body.getAttribute("JEP-107"), 1.64 + "worked", 1.65 + "PageMod.onReady test" 1.66 + ); 1.67 + done(); 1.68 + } 1.69 + ); 1.70 +}; 1.71 + 1.72 +exports.testPageMod2 = function(assert, done) { 1.73 + testPageMod(assert, done, "about:", [{ 1.74 + include: "about:*", 1.75 + contentScript: [ 1.76 + 'new ' + function contentScript() { 1.77 + window.AUQLUE = function() { return 42; } 1.78 + try { 1.79 + window.AUQLUE() 1.80 + } 1.81 + catch(e) { 1.82 + throw new Error("PageMod scripts executed in order"); 1.83 + } 1.84 + document.documentElement.setAttribute("first", "true"); 1.85 + }, 1.86 + 'new ' + function contentScript() { 1.87 + document.documentElement.setAttribute("second", "true"); 1.88 + } 1.89 + ] 1.90 + }], function(win, done) { 1.91 + assert.equal(win.document.documentElement.getAttribute("first"), 1.92 + "true", 1.93 + "PageMod test #2: first script has run"); 1.94 + assert.equal(win.document.documentElement.getAttribute("second"), 1.95 + "true", 1.96 + "PageMod test #2: second script has run"); 1.97 + assert.equal("AUQLUE" in win, false, 1.98 + "PageMod test #2: scripts get a wrapped window"); 1.99 + done(); 1.100 + }); 1.101 +}; 1.102 + 1.103 +exports.testPageModIncludes = function(assert, done) { 1.104 + var asserts = []; 1.105 + function createPageModTest(include, expectedMatch) { 1.106 + // Create an 'onload' test function... 1.107 + asserts.push(function(test, win) { 1.108 + var matches = include in win.localStorage; 1.109 + assert.ok(expectedMatch ? matches : !matches, 1.110 + "'" + include + "' match test, expected: " + expectedMatch); 1.111 + }); 1.112 + // ...and corresponding PageMod options 1.113 + return { 1.114 + include: include, 1.115 + contentScript: 'new ' + function() { 1.116 + self.on("message", function(msg) { 1.117 + window.localStorage[msg] = true; 1.118 + }); 1.119 + }, 1.120 + // The testPageMod callback with test assertions is called on 'end', 1.121 + // and we want this page mod to be attached before it gets called, 1.122 + // so we attach it on 'start'. 1.123 + contentScriptWhen: 'start', 1.124 + onAttach: function(worker) { 1.125 + worker.postMessage(this.include[0]); 1.126 + } 1.127 + }; 1.128 + } 1.129 + 1.130 + testPageMod(assert, done, testPageURI, [ 1.131 + createPageModTest("*", false), 1.132 + createPageModTest("*.google.com", false), 1.133 + createPageModTest("resource:*", true), 1.134 + createPageModTest("resource:", false), 1.135 + createPageModTest(testPageURI, true) 1.136 + ], 1.137 + function (win, done) { 1.138 + waitUntil(function () win.localStorage[testPageURI], 1.139 + testPageURI + " page-mod to be executed") 1.140 + .then(function () { 1.141 + asserts.forEach(function(fn) { 1.142 + fn(assert, win); 1.143 + }); 1.144 + done(); 1.145 + }); 1.146 + } 1.147 + ); 1.148 +}; 1.149 + 1.150 +exports.testPageModValidationAttachTo = function(assert) { 1.151 + [{ val: 'top', type: 'string "top"' }, 1.152 + { val: 'frame', type: 'string "frame"' }, 1.153 + { val: ['top', 'existing'], type: 'array with "top" and "existing"' }, 1.154 + { val: ['frame', 'existing'], type: 'array with "frame" and "existing"' }, 1.155 + { val: ['top'], type: 'array with "top"' }, 1.156 + { val: ['frame'], type: 'array with "frame"' }, 1.157 + { val: undefined, type: 'undefined' }].forEach((attachTo) => { 1.158 + new PageMod({ attachTo: attachTo.val, include: '*.validation111' }); 1.159 + assert.pass("PageMod() does not throw when attachTo is " + attachTo.type); 1.160 + }); 1.161 + 1.162 + [{ val: 'existing', type: 'string "existing"' }, 1.163 + { val: ['existing'], type: 'array with "existing"' }, 1.164 + { val: 'not-legit', type: 'string with "not-legit"' }, 1.165 + { val: ['not-legit'], type: 'array with "not-legit"' }, 1.166 + { val: {}, type: 'object' }].forEach((attachTo) => { 1.167 + assert.throws(() => 1.168 + new PageMod({ attachTo: attachTo.val, include: '*.validation111' }), 1.169 + /The `attachTo` option/, 1.170 + "PageMod() throws when 'attachTo' option is " + attachTo.type + "."); 1.171 + }); 1.172 +}; 1.173 + 1.174 +exports.testPageModValidationInclude = function(assert) { 1.175 + [{ val: undefined, type: 'undefined' }, 1.176 + { val: {}, type: 'object' }, 1.177 + { val: [], type: 'empty array'}, 1.178 + { val: [/regexp/, 1], type: 'array with non string/regexp' }, 1.179 + { val: 1, type: 'number' }].forEach((include) => { 1.180 + assert.throws(() => new PageMod({ include: include.val }), 1.181 + /The `include` option must always contain atleast one rule/, 1.182 + "PageMod() throws when 'include' option is " + include.type + "."); 1.183 + }); 1.184 + 1.185 + [{ val: '*.validation111', type: 'string' }, 1.186 + { val: /validation111/, type: 'regexp' }, 1.187 + { val: ['*.validation111'], type: 'array with length > 0'}].forEach((include) => { 1.188 + new PageMod({ include: include.val }); 1.189 + assert.pass("PageMod() does not throw when include option is " + include.type); 1.190 + }); 1.191 +}; 1.192 + 1.193 +/* Tests for internal functions. */ 1.194 +exports.testCommunication1 = function(assert, done) { 1.195 + let workerDone = false, 1.196 + callbackDone = null; 1.197 + 1.198 + testPageMod(assert, done, "about:", [{ 1.199 + include: "about:*", 1.200 + contentScriptWhen: 'end', 1.201 + contentScript: 'new ' + function WorkerScope() { 1.202 + self.on('message', function(msg) { 1.203 + document.body.setAttribute('JEP-107', 'worked'); 1.204 + self.postMessage(document.body.getAttribute('JEP-107')); 1.205 + }) 1.206 + }, 1.207 + onAttach: function(worker) { 1.208 + worker.on('error', function(e) { 1.209 + assert.fail('Errors where reported'); 1.210 + }); 1.211 + worker.on('message', function(value) { 1.212 + assert.equal( 1.213 + "worked", 1.214 + value, 1.215 + "test comunication" 1.216 + ); 1.217 + workerDone = true; 1.218 + if (callbackDone) 1.219 + callbackDone(); 1.220 + }); 1.221 + worker.postMessage('do it!') 1.222 + } 1.223 + }], 1.224 + function(win, done) { 1.225 + (callbackDone = function() { 1.226 + if (workerDone) { 1.227 + assert.equal( 1.228 + 'worked', 1.229 + win.document.body.getAttribute('JEP-107'), 1.230 + 'attribute should be modified' 1.231 + ); 1.232 + done(); 1.233 + } 1.234 + })(); 1.235 + } 1.236 + ); 1.237 +}; 1.238 + 1.239 +exports.testCommunication2 = function(assert, done) { 1.240 + let callbackDone = null, 1.241 + window; 1.242 + 1.243 + testPageMod(assert, done, "about:license", [{ 1.244 + include: "about:*", 1.245 + contentScriptWhen: 'start', 1.246 + contentScript: 'new ' + function WorkerScope() { 1.247 + document.documentElement.setAttribute('AUQLUE', 42); 1.248 + window.addEventListener('load', function listener() { 1.249 + self.postMessage('onload'); 1.250 + }, false); 1.251 + self.on("message", function() { 1.252 + self.postMessage(document.documentElement.getAttribute("test")) 1.253 + }); 1.254 + }, 1.255 + onAttach: function(worker) { 1.256 + worker.on('error', function(e) { 1.257 + assert.fail('Errors where reported'); 1.258 + }); 1.259 + worker.on('message', function(msg) { 1.260 + if ('onload' == msg) { 1.261 + assert.equal( 1.262 + '42', 1.263 + window.document.documentElement.getAttribute('AUQLUE'), 1.264 + 'PageMod scripts executed in order' 1.265 + ); 1.266 + window.document.documentElement.setAttribute('test', 'changes in window'); 1.267 + worker.postMessage('get window.test') 1.268 + } else { 1.269 + assert.equal( 1.270 + 'changes in window', 1.271 + msg, 1.272 + 'PageMod test #2: second script has run' 1.273 + ) 1.274 + callbackDone(); 1.275 + } 1.276 + }); 1.277 + } 1.278 + }], 1.279 + function(win, done) { 1.280 + window = win; 1.281 + callbackDone = done; 1.282 + } 1.283 + ); 1.284 +}; 1.285 + 1.286 +exports.testEventEmitter = function(assert, done) { 1.287 + let workerDone = false, 1.288 + callbackDone = null; 1.289 + 1.290 + testPageMod(assert, done, "about:", [{ 1.291 + include: "about:*", 1.292 + contentScript: 'new ' + function WorkerScope() { 1.293 + self.port.on('addon-to-content', function(data) { 1.294 + self.port.emit('content-to-addon', data); 1.295 + }); 1.296 + }, 1.297 + onAttach: function(worker) { 1.298 + worker.on('error', function(e) { 1.299 + assert.fail('Errors were reported : '+e); 1.300 + }); 1.301 + worker.port.on('content-to-addon', function(value) { 1.302 + assert.equal( 1.303 + "worked", 1.304 + value, 1.305 + "EventEmitter API works!" 1.306 + ); 1.307 + if (callbackDone) 1.308 + callbackDone(); 1.309 + else 1.310 + workerDone = true; 1.311 + }); 1.312 + worker.port.emit('addon-to-content', 'worked'); 1.313 + } 1.314 + }], 1.315 + function(win, done) { 1.316 + if (workerDone) 1.317 + done(); 1.318 + else 1.319 + callbackDone = done; 1.320 + } 1.321 + ); 1.322 +}; 1.323 + 1.324 +// Execute two concurrent page mods on same document to ensure that their 1.325 +// JS contexts are different 1.326 +exports.testMixedContext = function(assert, done) { 1.327 + let doneCallback = null; 1.328 + let messages = 0; 1.329 + let modObject = { 1.330 + include: "data:text/html;charset=utf-8,", 1.331 + contentScript: 'new ' + function WorkerScope() { 1.332 + // Both scripts will execute this, 1.333 + // context is shared if one script see the other one modification. 1.334 + let isContextShared = "sharedAttribute" in document; 1.335 + self.postMessage(isContextShared); 1.336 + document.sharedAttribute = true; 1.337 + }, 1.338 + onAttach: function(w) { 1.339 + w.on("message", function (isContextShared) { 1.340 + if (isContextShared) { 1.341 + assert.fail("Page mod contexts are mixed."); 1.342 + doneCallback(); 1.343 + } 1.344 + else if (++messages == 2) { 1.345 + assert.pass("Page mod contexts are different."); 1.346 + doneCallback(); 1.347 + } 1.348 + }); 1.349 + } 1.350 + }; 1.351 + testPageMod(assert, done, "data:text/html;charset=utf-8,", [modObject, modObject], 1.352 + function(win, done) { 1.353 + doneCallback = done; 1.354 + } 1.355 + ); 1.356 +}; 1.357 + 1.358 +exports.testHistory = function(assert, done) { 1.359 + // We need a valid url in order to have a working History API. 1.360 + // (i.e do not work on data: or about: pages) 1.361 + // Test bug 679054. 1.362 + let url = data.url("test-page-mod.html"); 1.363 + let callbackDone = null; 1.364 + testPageMod(assert, done, url, [{ 1.365 + include: url, 1.366 + contentScriptWhen: 'end', 1.367 + contentScript: 'new ' + function WorkerScope() { 1.368 + history.pushState({}, "", "#"); 1.369 + history.replaceState({foo: "bar"}, "", "#"); 1.370 + self.postMessage(history.state); 1.371 + }, 1.372 + onAttach: function(worker) { 1.373 + worker.on('message', function (data) { 1.374 + assert.equal(JSON.stringify(data), JSON.stringify({foo: "bar"}), 1.375 + "History API works!"); 1.376 + callbackDone(); 1.377 + }); 1.378 + } 1.379 + }], 1.380 + function(win, done) { 1.381 + callbackDone = done; 1.382 + } 1.383 + ); 1.384 +}; 1.385 + 1.386 +exports.testRelatedTab = function(assert, done) { 1.387 + let tab; 1.388 + let pageMod = new PageMod({ 1.389 + include: "about:*", 1.390 + onAttach: function(worker) { 1.391 + assert.ok(!!worker.tab, "Worker.tab exists"); 1.392 + assert.equal(tab, worker.tab, "Worker.tab is valid"); 1.393 + pageMod.destroy(); 1.394 + tab.close(done); 1.395 + } 1.396 + }); 1.397 + 1.398 + tabs.open({ 1.399 + url: "about:", 1.400 + onOpen: function onOpen(t) { 1.401 + tab = t; 1.402 + } 1.403 + }); 1.404 +}; 1.405 + 1.406 +exports.testRelatedTabNoRequireTab = function(assert, done) { 1.407 + let loader = Loader(module); 1.408 + let tab; 1.409 + let url = "data:text/html;charset=utf-8," + encodeURI("Test related worker tab 2"); 1.410 + let { PageMod } = loader.require("sdk/page-mod"); 1.411 + let pageMod = new PageMod({ 1.412 + include: url, 1.413 + onAttach: function(worker) { 1.414 + assert.equal(worker.tab.url, url, "Worker.tab.url is valid"); 1.415 + worker.tab.close(function() { 1.416 + pageMod.destroy(); 1.417 + loader.unload(); 1.418 + done(); 1.419 + }); 1.420 + } 1.421 + }); 1.422 + 1.423 + tabs.open(url); 1.424 +}; 1.425 + 1.426 +exports.testRelatedTabNoOtherReqs = function(assert, done) { 1.427 + let loader = Loader(module); 1.428 + let { PageMod } = loader.require("sdk/page-mod"); 1.429 + let pageMod = new PageMod({ 1.430 + include: "about:blank?testRelatedTabNoOtherReqs", 1.431 + onAttach: function(worker) { 1.432 + assert.ok(!!worker.tab, "Worker.tab exists"); 1.433 + pageMod.destroy(); 1.434 + worker.tab.close(function() { 1.435 + worker.destroy(); 1.436 + loader.unload(); 1.437 + done(); 1.438 + }); 1.439 + } 1.440 + }); 1.441 + 1.442 + tabs.open({ 1.443 + url: "about:blank?testRelatedTabNoOtherReqs" 1.444 + }); 1.445 +}; 1.446 + 1.447 +exports.testWorksWithExistingTabs = function(assert, done) { 1.448 + let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document"); 1.449 + let { PageMod } = require("sdk/page-mod"); 1.450 + tabs.open({ 1.451 + url: url, 1.452 + onReady: function onReady(tab) { 1.453 + let pageModOnExisting = new PageMod({ 1.454 + include: url, 1.455 + attachTo: ["existing", "top", "frame"], 1.456 + onAttach: function(worker) { 1.457 + assert.ok(!!worker.tab, "Worker.tab exists"); 1.458 + assert.equal(tab, worker.tab, "A worker has been created on this existing tab"); 1.459 + 1.460 + setTimeout(function() { 1.461 + pageModOnExisting.destroy(); 1.462 + pageModOffExisting.destroy(); 1.463 + tab.close(done); 1.464 + }, 0); 1.465 + } 1.466 + }); 1.467 + 1.468 + let pageModOffExisting = new PageMod({ 1.469 + include: url, 1.470 + onAttach: function(worker) { 1.471 + assert.fail("pageModOffExisting page-mod should not have attached to anything"); 1.472 + } 1.473 + }); 1.474 + } 1.475 + }); 1.476 +}; 1.477 + 1.478 +exports.testExistingFrameDoesntMatchInclude = function(assert, done) { 1.479 + let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42'; 1.480 + let iframe = '<iframe src="' + iframeURL + '" />'; 1.481 + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe); 1.482 + tabs.open({ 1.483 + url: url, 1.484 + onReady: function onReady(tab) { 1.485 + let pagemod = new PageMod({ 1.486 + include: url, 1.487 + attachTo: ['existing', 'frame'], 1.488 + onAttach: function() { 1.489 + assert.fail("Existing iframe URL doesn't match include, must not attach to anything"); 1.490 + } 1.491 + }); 1.492 + setTimeout(function() { 1.493 + assert.pass("PageMod didn't attach to anything") 1.494 + pagemod.destroy(); 1.495 + tab.close(done); 1.496 + }, 250); 1.497 + } 1.498 + }); 1.499 +}; 1.500 + 1.501 +exports.testExistingOnlyFrameMatchesInclude = function(assert, done) { 1.502 + let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-43'; 1.503 + let iframe = '<iframe src="' + iframeURL + '" />'; 1.504 + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iframe); 1.505 + tabs.open({ 1.506 + url: url, 1.507 + onReady: function onReady(tab) { 1.508 + let pagemod = new PageMod({ 1.509 + include: iframeURL, 1.510 + attachTo: ['existing', 'frame'], 1.511 + onAttach: function(worker) { 1.512 + assert.equal(iframeURL, worker.url, 1.513 + "PageMod attached to existing iframe when only it matches include rules"); 1.514 + pagemod.destroy(); 1.515 + tab.close(done); 1.516 + } 1.517 + }); 1.518 + } 1.519 + }); 1.520 +}; 1.521 + 1.522 +exports.testContentScriptWhenDefault = function(assert) { 1.523 + let pagemod = PageMod({include: '*'}); 1.524 + 1.525 + assert.equal(pagemod.contentScriptWhen, 'end', "Default contentScriptWhen is 'end'"); 1.526 + pagemod.destroy(); 1.527 +} 1.528 + 1.529 +// test timing for all 3 contentScriptWhen options (start, ready, end) 1.530 +// for new pages, or tabs opened after PageMod is created 1.531 +exports.testContentScriptWhenForNewTabs = function(assert, done) { 1.532 + const url = "data:text/html;charset=utf-8,testContentScriptWhenForNewTabs"; 1.533 + 1.534 + let count = 0; 1.535 + 1.536 + handleReadyState(url, 'start', { 1.537 + onLoading: (tab) => { 1.538 + assert.pass("PageMod is attached while document is loading"); 1.539 + if (++count === 3) 1.540 + tab.close(done); 1.541 + }, 1.542 + onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), 1.543 + onComplete: () => assert.fail("onComplete should not be called with 'start'."), 1.544 + }); 1.545 + 1.546 + handleReadyState(url, 'ready', { 1.547 + onInteractive: (tab) => { 1.548 + assert.pass("PageMod is attached while document is interactive"); 1.549 + if (++count === 3) 1.550 + tab.close(done); 1.551 + }, 1.552 + onLoading: () => assert.fail("onLoading should not be called with 'ready'."), 1.553 + onComplete: () => assert.fail("onComplete should not be called with 'ready'."), 1.554 + }); 1.555 + 1.556 + handleReadyState(url, 'end', { 1.557 + onComplete: (tab) => { 1.558 + assert.pass("PageMod is attached when document is complete"); 1.559 + if (++count === 3) 1.560 + tab.close(done); 1.561 + }, 1.562 + onLoading: () => assert.fail("onLoading should not be called with 'end'."), 1.563 + onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), 1.564 + }); 1.565 + 1.566 + tabs.open(url); 1.567 +} 1.568 + 1.569 +// test timing for all 3 contentScriptWhen options (start, ready, end) 1.570 +// for PageMods created right as the tab is created (in tab.onOpen) 1.571 +exports.testContentScriptWhenOnTabOpen = function(assert, done) { 1.572 + const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabOpen"; 1.573 + 1.574 + tabs.open({ 1.575 + url: url, 1.576 + onOpen: function(tab) { 1.577 + let count = 0; 1.578 + 1.579 + handleReadyState(url, 'start', { 1.580 + onLoading: () => { 1.581 + assert.pass("PageMod is attached while document is loading"); 1.582 + if (++count === 3) 1.583 + tab.close(done); 1.584 + }, 1.585 + onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), 1.586 + onComplete: () => assert.fail("onComplete should not be called with 'start'."), 1.587 + }); 1.588 + 1.589 + handleReadyState(url, 'ready', { 1.590 + onInteractive: () => { 1.591 + assert.pass("PageMod is attached while document is interactive"); 1.592 + if (++count === 3) 1.593 + tab.close(done); 1.594 + }, 1.595 + onLoading: () => assert.fail("onLoading should not be called with 'ready'."), 1.596 + onComplete: () => assert.fail("onComplete should not be called with 'ready'."), 1.597 + }); 1.598 + 1.599 + handleReadyState(url, 'end', { 1.600 + onComplete: () => { 1.601 + assert.pass("PageMod is attached when document is complete"); 1.602 + if (++count === 3) 1.603 + tab.close(done); 1.604 + }, 1.605 + onLoading: () => assert.fail("onLoading should not be called with 'end'."), 1.606 + onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), 1.607 + }); 1.608 + 1.609 + } 1.610 + }); 1.611 +} 1.612 + 1.613 +// test timing for all 3 contentScriptWhen options (start, ready, end) 1.614 +// for PageMods created while the tab is interactive (in tab.onReady) 1.615 +exports.testContentScriptWhenOnTabReady = function(assert, done) { 1.616 + const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabReady"; 1.617 + 1.618 + tabs.open({ 1.619 + url: url, 1.620 + onReady: function(tab) { 1.621 + let count = 0; 1.622 + 1.623 + handleReadyState(url, 'start', { 1.624 + onInteractive: () => { 1.625 + assert.pass("PageMod is attached while document is interactive"); 1.626 + if (++count === 3) 1.627 + tab.close(done); 1.628 + }, 1.629 + onLoading: () => assert.fail("onLoading should not be called with 'start'."), 1.630 + onComplete: () => assert.fail("onComplete should not be called with 'start'."), 1.631 + }); 1.632 + 1.633 + handleReadyState(url, 'ready', { 1.634 + onInteractive: () => { 1.635 + assert.pass("PageMod is attached while document is interactive"); 1.636 + if (++count === 3) 1.637 + tab.close(done); 1.638 + }, 1.639 + onLoading: () => assert.fail("onLoading should not be called with 'ready'."), 1.640 + onComplete: () => assert.fail("onComplete should not be called with 'ready'."), 1.641 + }); 1.642 + 1.643 + handleReadyState(url, 'end', { 1.644 + onComplete: () => { 1.645 + assert.pass("PageMod is attached when document is complete"); 1.646 + if (++count === 3) 1.647 + tab.close(done); 1.648 + }, 1.649 + onLoading: () => assert.fail("onLoading should not be called with 'end'."), 1.650 + onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), 1.651 + }); 1.652 + 1.653 + } 1.654 + }); 1.655 +} 1.656 + 1.657 +// test timing for all 3 contentScriptWhen options (start, ready, end) 1.658 +// for PageMods created after a tab has completed loading (in tab.onLoad) 1.659 +exports.testContentScriptWhenOnTabLoad = function(assert, done) { 1.660 + const url = "data:text/html;charset=utf-8,testContentScriptWhenOnTabLoad"; 1.661 + 1.662 + tabs.open({ 1.663 + url: url, 1.664 + onLoad: function(tab) { 1.665 + let count = 0; 1.666 + 1.667 + handleReadyState(url, 'start', { 1.668 + onComplete: () => { 1.669 + assert.pass("PageMod is attached when document is complete"); 1.670 + if (++count === 3) 1.671 + tab.close(done); 1.672 + }, 1.673 + onLoading: () => assert.fail("onLoading should not be called with 'start'."), 1.674 + onInteractive: () => assert.fail("onInteractive should not be called with 'start'."), 1.675 + }); 1.676 + 1.677 + handleReadyState(url, 'ready', { 1.678 + onComplete: () => { 1.679 + assert.pass("PageMod is attached when document is complete"); 1.680 + if (++count === 3) 1.681 + tab.close(done); 1.682 + }, 1.683 + onLoading: () => assert.fail("onLoading should not be called with 'ready'."), 1.684 + onInteractive: () => assert.fail("onInteractive should not be called with 'ready'."), 1.685 + }); 1.686 + 1.687 + handleReadyState(url, 'end', { 1.688 + onComplete: () => { 1.689 + assert.pass("PageMod is attached when document is complete"); 1.690 + if (++count === 3) 1.691 + tab.close(done); 1.692 + }, 1.693 + onLoading: () => assert.fail("onLoading should not be called with 'end'."), 1.694 + onInteractive: () => assert.fail("onInteractive should not be called with 'end'."), 1.695 + }); 1.696 + 1.697 + } 1.698 + }); 1.699 +} 1.700 + 1.701 +exports.testTabWorkerOnMessage = function(assert, done) { 1.702 + let { browserWindows } = require("sdk/windows"); 1.703 + let tabs = require("sdk/tabs"); 1.704 + let { PageMod } = require("sdk/page-mod"); 1.705 + 1.706 + let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>"; 1.707 + let url2 = "data:text/html;charset=utf-8,<title>tab2</title><h1>worker2.tab</h1>"; 1.708 + let worker1 = null; 1.709 + 1.710 + let mod = PageMod({ 1.711 + include: "data:text/html*", 1.712 + contentScriptWhen: "ready", 1.713 + contentScript: "self.postMessage('#1');", 1.714 + onAttach: function onAttach(worker) { 1.715 + worker.on("message", function onMessage() { 1.716 + this.tab.attach({ 1.717 + contentScriptWhen: "ready", 1.718 + contentScript: "self.postMessage({ url: window.location.href, title: document.title });", 1.719 + onMessage: function onMessage(data) { 1.720 + assert.equal(this.tab.url, data.url, "location is correct"); 1.721 + assert.equal(this.tab.title, data.title, "title is correct"); 1.722 + if (this.tab.url === url1) { 1.723 + worker1 = this; 1.724 + tabs.open({ url: url2, inBackground: true }); 1.725 + } 1.726 + else if (this.tab.url === url2) { 1.727 + mod.destroy(); 1.728 + worker1.tab.close(function() { 1.729 + worker1.destroy(); 1.730 + worker.tab.close(function() { 1.731 + worker.destroy(); 1.732 + done(); 1.733 + }); 1.734 + }); 1.735 + } 1.736 + } 1.737 + }); 1.738 + }); 1.739 + } 1.740 + }); 1.741 + 1.742 + tabs.open(url1); 1.743 +}; 1.744 + 1.745 +exports.testAutomaticDestroy = function(assert, done) { 1.746 + let loader = Loader(module); 1.747 + 1.748 + let pageMod = loader.require("sdk/page-mod").PageMod({ 1.749 + include: "about:*", 1.750 + contentScriptWhen: "start", 1.751 + onAttach: function(w) { 1.752 + assert.fail("Page-mod should have been detroyed during module unload"); 1.753 + } 1.754 + }); 1.755 + 1.756 + // Unload the page-mod module so that our page mod is destroyed 1.757 + loader.unload(); 1.758 + 1.759 + // Then create a second tab to ensure that it is correctly destroyed 1.760 + let tabs = require("sdk/tabs"); 1.761 + tabs.open({ 1.762 + url: "about:", 1.763 + onReady: function onReady(tab) { 1.764 + assert.pass("check automatic destroy"); 1.765 + tab.close(done); 1.766 + } 1.767 + }); 1.768 +}; 1.769 + 1.770 +exports.testAttachToTabsOnly = function(assert, done) { 1.771 + let { PageMod } = require('sdk/page-mod'); 1.772 + let openedTab = null; // Tab opened in openTabWithIframe() 1.773 + let workerCount = 0; 1.774 + 1.775 + let mod = PageMod({ 1.776 + include: 'data:text/html*', 1.777 + contentScriptWhen: 'start', 1.778 + contentScript: '', 1.779 + onAttach: function onAttach(worker) { 1.780 + if (worker.tab === openedTab) { 1.781 + if (++workerCount == 3) { 1.782 + assert.pass('Succesfully applied to tab documents and its iframe'); 1.783 + worker.destroy(); 1.784 + mod.destroy(); 1.785 + openedTab.close(done); 1.786 + } 1.787 + } 1.788 + else { 1.789 + assert.fail('page-mod attached to a non-tab document'); 1.790 + } 1.791 + } 1.792 + }); 1.793 + 1.794 + function openHiddenFrame() { 1.795 + assert.pass('Open iframe in hidden window'); 1.796 + let hiddenFrames = require('sdk/frame/hidden-frame'); 1.797 + let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({ 1.798 + onReady: function () { 1.799 + let element = this.element; 1.800 + element.addEventListener('DOMContentLoaded', function onload() { 1.801 + element.removeEventListener('DOMContentLoaded', onload, false); 1.802 + hiddenFrames.remove(hiddenFrame); 1.803 + 1.804 + if (!xulApp.is("Fennec")) { 1.805 + openToplevelWindow(); 1.806 + } 1.807 + else { 1.808 + openBrowserIframe(); 1.809 + } 1.810 + }, false); 1.811 + element.setAttribute('src', 'data:text/html;charset=utf-8,foo'); 1.812 + } 1.813 + })); 1.814 + } 1.815 + 1.816 + function openToplevelWindow() { 1.817 + assert.pass('Open toplevel window'); 1.818 + let win = open('data:text/html;charset=utf-8,bar'); 1.819 + win.addEventListener('DOMContentLoaded', function onload() { 1.820 + win.removeEventListener('DOMContentLoaded', onload, false); 1.821 + win.close(); 1.822 + openBrowserIframe(); 1.823 + }, false); 1.824 + } 1.825 + 1.826 + function openBrowserIframe() { 1.827 + assert.pass('Open iframe in browser window'); 1.828 + let window = require('sdk/deprecated/window-utils').activeBrowserWindow; 1.829 + let document = window.document; 1.830 + let iframe = document.createElement('iframe'); 1.831 + iframe.setAttribute('type', 'content'); 1.832 + iframe.setAttribute('src', 'data:text/html;charset=utf-8,foobar'); 1.833 + iframe.addEventListener('DOMContentLoaded', function onload() { 1.834 + iframe.removeEventListener('DOMContentLoaded', onload, false); 1.835 + iframe.parentNode.removeChild(iframe); 1.836 + openTabWithIframes(); 1.837 + }, false); 1.838 + document.documentElement.appendChild(iframe); 1.839 + } 1.840 + 1.841 + // Only these three documents will be accepted by the page-mod 1.842 + function openTabWithIframes() { 1.843 + assert.pass('Open iframes in a tab'); 1.844 + let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' 1.845 + let content = '<iframe src="data:text/html;charset=utf-8,' + 1.846 + encodeURIComponent(subContent) + '" />'; 1.847 + require('sdk/tabs').open({ 1.848 + url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content), 1.849 + onOpen: function onOpen(tab) { 1.850 + openedTab = tab; 1.851 + } 1.852 + }); 1.853 + } 1.854 + 1.855 + openHiddenFrame(); 1.856 +}; 1.857 + 1.858 +exports['test111 attachTo [top]'] = function(assert, done) { 1.859 + let { PageMod } = require('sdk/page-mod'); 1.860 + 1.861 + let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />' 1.862 + let content = '<iframe src="data:text/html;charset=utf-8,' + 1.863 + encodeURIComponent(subContent) + '" />'; 1.864 + let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) 1.865 + 1.866 + let workerCount = 0; 1.867 + 1.868 + let mod = PageMod({ 1.869 + include: 'data:text/html*', 1.870 + contentScriptWhen: 'start', 1.871 + contentScript: 'self.postMessage(document.location.href);', 1.872 + attachTo: ['top'], 1.873 + onAttach: function onAttach(worker) { 1.874 + if (++workerCount == 1) { 1.875 + worker.on('message', function (href) { 1.876 + assert.equal(href, topDocumentURL, 1.877 + "worker on top level document only"); 1.878 + let tab = worker.tab; 1.879 + worker.destroy(); 1.880 + mod.destroy(); 1.881 + tab.close(done); 1.882 + }); 1.883 + } 1.884 + else { 1.885 + assert.fail('page-mod attached to a non-top document'); 1.886 + } 1.887 + } 1.888 + }); 1.889 + 1.890 + require('sdk/tabs').open(topDocumentURL); 1.891 +}; 1.892 + 1.893 +exports['test111 attachTo [frame]'] = function(assert, done) { 1.894 + let { PageMod } = require('sdk/page-mod'); 1.895 + 1.896 + let subFrameURL = 'data:text/html;charset=utf-8,subframe'; 1.897 + let subContent = '<iframe src="' + subFrameURL + '" />'; 1.898 + let frameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subContent); 1.899 + let content = '<iframe src="' + frameURL + '" />'; 1.900 + let topDocumentURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(content) 1.901 + 1.902 + let workerCount = 0, messageCount = 0; 1.903 + 1.904 + function onMessage(href) { 1.905 + if (href == frameURL) 1.906 + assert.pass("worker on first frame"); 1.907 + else if (href == subFrameURL) 1.908 + assert.pass("worker on second frame"); 1.909 + else 1.910 + assert.fail("worker on unexpected document: " + href); 1.911 + this.destroy(); 1.912 + if (++messageCount == 2) { 1.913 + mod.destroy(); 1.914 + require('sdk/tabs').activeTab.close(done); 1.915 + } 1.916 + } 1.917 + let mod = PageMod({ 1.918 + include: 'data:text/html*', 1.919 + contentScriptWhen: 'start', 1.920 + contentScript: 'self.postMessage(document.location.href);', 1.921 + attachTo: ['frame'], 1.922 + onAttach: function onAttach(worker) { 1.923 + if (++workerCount <= 2) { 1.924 + worker.on('message', onMessage); 1.925 + } 1.926 + else { 1.927 + assert.fail('page-mod attached to a non-frame document'); 1.928 + } 1.929 + } 1.930 + }); 1.931 + 1.932 + require('sdk/tabs').open(topDocumentURL); 1.933 +}; 1.934 + 1.935 +exports.testContentScriptOptionsOption = function(assert, done) { 1.936 + let callbackDone = null; 1.937 + testPageMod(assert, done, "about:", [{ 1.938 + include: "about:*", 1.939 + contentScript: "self.postMessage( [typeof self.options.d, self.options] );", 1.940 + contentScriptWhen: "end", 1.941 + contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, 1.942 + onAttach: function(worker) { 1.943 + worker.on('message', function(msg) { 1.944 + assert.equal( msg[0], 'undefined', 'functions are stripped from contentScriptOptions' ); 1.945 + assert.equal( typeof msg[1], 'object', 'object as contentScriptOptions' ); 1.946 + assert.equal( msg[1].a, true, 'boolean in contentScriptOptions' ); 1.947 + assert.equal( msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions' ); 1.948 + assert.equal( msg[1].c, 'string', 'string in contentScriptOptions' ); 1.949 + callbackDone(); 1.950 + }); 1.951 + } 1.952 + }], 1.953 + function(win, done) { 1.954 + callbackDone = done; 1.955 + } 1.956 + ); 1.957 +}; 1.958 + 1.959 +exports.testPageModCss = function(assert, done) { 1.960 + let [pageMod] = testPageMod(assert, done, 1.961 + 'data:text/html;charset=utf-8,<div style="background: silver">css test</div>', [{ 1.962 + include: ["*", "data:*"], 1.963 + contentStyle: "div { height: 100px; }", 1.964 + contentStyleFile: data.url("css-include-file.css") 1.965 + }], 1.966 + function(win, done) { 1.967 + let div = win.document.querySelector("div"); 1.968 + assert.equal( 1.969 + div.clientHeight, 1.970 + 100, 1.971 + "PageMod contentStyle worked" 1.972 + ); 1.973 + assert.equal( 1.974 + div.offsetHeight, 1.975 + 120, 1.976 + "PageMod contentStyleFile worked" 1.977 + ); 1.978 + done(); 1.979 + } 1.980 + ); 1.981 +}; 1.982 + 1.983 +exports.testPageModCssList = function(assert, done) { 1.984 + let [pageMod] = testPageMod(assert, done, 1.985 + 'data:text/html;charset=utf-8,<div style="width:320px; max-width: 480px!important">css test</div>', [{ 1.986 + include: "data:*", 1.987 + contentStyleFile: [ 1.988 + // Highlight evaluation order in this list 1.989 + "data:text/css;charset=utf-8,div { border: 1px solid black; }", 1.990 + "data:text/css;charset=utf-8,div { border: 10px solid black; }", 1.991 + // Highlight evaluation order between contentStylesheet & contentStylesheetFile 1.992 + "data:text/css;charset=utf-8s,div { height: 1000px; }", 1.993 + // Highlight precedence between the author and user style sheet 1.994 + "data:text/css;charset=utf-8,div { width: 200px; max-width: 640px!important}", 1.995 + ], 1.996 + contentStyle: [ 1.997 + "div { height: 10px; }", 1.998 + "div { height: 100px; }" 1.999 + ] 1.1000 + }], 1.1001 + function(win, done) { 1.1002 + let div = win.document.querySelector("div"), 1.1003 + style = win.getComputedStyle(div); 1.1004 + 1.1005 + assert.equal( 1.1006 + div.clientHeight, 1.1007 + 100, 1.1008 + "PageMod contentStyle list works and is evaluated after contentStyleFile" 1.1009 + ); 1.1010 + 1.1011 + assert.equal( 1.1012 + div.offsetHeight, 1.1013 + 120, 1.1014 + "PageMod contentStyleFile list works" 1.1015 + ); 1.1016 + 1.1017 + assert.equal( 1.1018 + style.width, 1.1019 + "320px", 1.1020 + "PageMod add-on author/page author style sheet precedence works" 1.1021 + ); 1.1022 + 1.1023 + assert.equal( 1.1024 + style.maxWidth, 1.1025 + "480px", 1.1026 + "PageMod add-on author/page author style sheet precedence with !important works" 1.1027 + ); 1.1028 + 1.1029 + done(); 1.1030 + } 1.1031 + ); 1.1032 +}; 1.1033 + 1.1034 +exports.testPageModCssDestroy = function(assert, done) { 1.1035 + let [pageMod] = testPageMod(assert, done, 1.1036 + 'data:text/html;charset=utf-8,<div style="width:200px">css test</div>', [{ 1.1037 + include: "data:*", 1.1038 + contentStyle: "div { width: 100px!important; }" 1.1039 + }], 1.1040 + 1.1041 + function(win, done) { 1.1042 + let div = win.document.querySelector("div"), 1.1043 + style = win.getComputedStyle(div); 1.1044 + 1.1045 + assert.equal( 1.1046 + style.width, 1.1047 + "100px", 1.1048 + "PageMod contentStyle worked" 1.1049 + ); 1.1050 + 1.1051 + pageMod.destroy(); 1.1052 + assert.equal( 1.1053 + style.width, 1.1054 + "200px", 1.1055 + "PageMod contentStyle is removed after destroy" 1.1056 + ); 1.1057 + 1.1058 + done(); 1.1059 + 1.1060 + } 1.1061 + ); 1.1062 +}; 1.1063 + 1.1064 +exports.testPageModCssAutomaticDestroy = function(assert, done) { 1.1065 + let loader = Loader(module); 1.1066 + 1.1067 + let pageMod = loader.require("sdk/page-mod").PageMod({ 1.1068 + include: "data:*", 1.1069 + contentStyle: "div { width: 100px!important; }" 1.1070 + }); 1.1071 + 1.1072 + tabs.open({ 1.1073 + url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>", 1.1074 + 1.1075 + onReady: function onReady(tab) { 1.1076 + let browserWindow = getMostRecentBrowserWindow(); 1.1077 + let win = getTabContentWindow(getActiveTab(browserWindow)); 1.1078 + 1.1079 + let div = win.document.querySelector("div"); 1.1080 + let style = win.getComputedStyle(div); 1.1081 + 1.1082 + assert.equal( 1.1083 + style.width, 1.1084 + "100px", 1.1085 + "PageMod contentStyle worked" 1.1086 + ); 1.1087 + 1.1088 + loader.unload(); 1.1089 + 1.1090 + assert.equal( 1.1091 + style.width, 1.1092 + "200px", 1.1093 + "PageMod contentStyle is removed after loader's unload" 1.1094 + ); 1.1095 + 1.1096 + tab.close(done); 1.1097 + } 1.1098 + }); 1.1099 +}; 1.1100 + 1.1101 + 1.1102 +exports.testPageModTimeout = function(assert, done) { 1.1103 + let tab = null 1.1104 + let loader = Loader(module); 1.1105 + let { PageMod } = loader.require("sdk/page-mod"); 1.1106 + 1.1107 + let mod = PageMod({ 1.1108 + include: "data:*", 1.1109 + contentScript: Isolate(function() { 1.1110 + var id = setTimeout(function() { 1.1111 + self.port.emit("fired", id) 1.1112 + }, 10) 1.1113 + self.port.emit("scheduled", id); 1.1114 + }), 1.1115 + onAttach: function(worker) { 1.1116 + worker.port.on("scheduled", function(id) { 1.1117 + assert.pass("timer was scheduled") 1.1118 + worker.port.on("fired", function(data) { 1.1119 + assert.equal(id, data, "timer was fired") 1.1120 + tab.close(function() { 1.1121 + worker.destroy() 1.1122 + loader.unload() 1.1123 + done() 1.1124 + }); 1.1125 + }) 1.1126 + }) 1.1127 + } 1.1128 + }); 1.1129 + 1.1130 + tabs.open({ 1.1131 + url: "data:text/html;charset=utf-8,timeout", 1.1132 + onReady: function($) { tab = $ } 1.1133 + }) 1.1134 +} 1.1135 + 1.1136 + 1.1137 +exports.testPageModcancelTimeout = function(assert, done) { 1.1138 + let tab = null 1.1139 + let loader = Loader(module); 1.1140 + let { PageMod } = loader.require("sdk/page-mod"); 1.1141 + 1.1142 + let mod = PageMod({ 1.1143 + include: "data:*", 1.1144 + contentScript: Isolate(function() { 1.1145 + var id1 = setTimeout(function() { 1.1146 + self.port.emit("failed") 1.1147 + }, 10) 1.1148 + var id2 = setTimeout(function() { 1.1149 + self.port.emit("timeout") 1.1150 + }, 100) 1.1151 + clearTimeout(id1) 1.1152 + }), 1.1153 + onAttach: function(worker) { 1.1154 + worker.port.on("failed", function() { 1.1155 + assert.fail("cancelled timeout fired") 1.1156 + }) 1.1157 + worker.port.on("timeout", function(id) { 1.1158 + assert.pass("timer was scheduled") 1.1159 + tab.close(function() { 1.1160 + worker.destroy(); 1.1161 + mod.destroy(); 1.1162 + loader.unload(); 1.1163 + done(); 1.1164 + }); 1.1165 + }) 1.1166 + } 1.1167 + }); 1.1168 + 1.1169 + tabs.open({ 1.1170 + url: "data:text/html;charset=utf-8,cancell timeout", 1.1171 + onReady: function($) { tab = $ } 1.1172 + }) 1.1173 +} 1.1174 + 1.1175 +exports.testExistingOnFrames = function(assert, done) { 1.1176 + let subFrameURL = 'data:text/html;charset=utf-8,testExistingOnFrames-sub-frame'; 1.1177 + let subIFrame = '<iframe src="' + subFrameURL + '" />' 1.1178 + let iFrameURL = 'data:text/html;charset=utf-8,' + encodeURIComponent(subIFrame) 1.1179 + let iFrame = '<iframe src="' + iFrameURL + '" />'; 1.1180 + let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(iFrame); 1.1181 + 1.1182 + // we want all urls related to the test here, and not just the iframe urls 1.1183 + // because we need to fail if the test is applied to the top window url. 1.1184 + let urls = [url, iFrameURL, subFrameURL]; 1.1185 + 1.1186 + let counter = 0; 1.1187 + let tab = openTab(getMostRecentBrowserWindow(), url); 1.1188 + let window = getTabContentWindow(tab); 1.1189 + 1.1190 + function wait4Iframes() { 1.1191 + if (window.document.readyState != "complete" || 1.1192 + getFrames(window).length != 2) { 1.1193 + return; 1.1194 + } 1.1195 + 1.1196 + let pagemodOnExisting = PageMod({ 1.1197 + include: ["*", "data:*"], 1.1198 + attachTo: ["existing", "frame"], 1.1199 + contentScriptWhen: 'ready', 1.1200 + onAttach: function(worker) { 1.1201 + // need to ignore urls that are not part of the test, because other 1.1202 + // tests are not closing their tabs when they complete.. 1.1203 + if (urls.indexOf(worker.url) == -1) 1.1204 + return; 1.1205 + 1.1206 + assert.notEqual(url, 1.1207 + worker.url, 1.1208 + 'worker should not be attached to the top window'); 1.1209 + 1.1210 + if (++counter < 2) { 1.1211 + // we can rely on this order in this case because we are sure that 1.1212 + // the frames being tested have completely loaded 1.1213 + assert.equal(iFrameURL, worker.url, '1st attach is for top frame'); 1.1214 + } 1.1215 + else if (counter > 2) { 1.1216 + assert.fail('applied page mod too many times'); 1.1217 + } 1.1218 + else { 1.1219 + assert.equal(subFrameURL, worker.url, '2nd attach is for sub frame'); 1.1220 + // need timeout because onAttach is called before the constructor returns 1.1221 + setTimeout(function() { 1.1222 + pagemodOnExisting.destroy(); 1.1223 + pagemodOffExisting.destroy(); 1.1224 + closeTab(tab); 1.1225 + done(); 1.1226 + }, 0); 1.1227 + } 1.1228 + } 1.1229 + }); 1.1230 + 1.1231 + let pagemodOffExisting = PageMod({ 1.1232 + include: ["*", "data:*"], 1.1233 + attachTo: ["frame"], 1.1234 + contentScriptWhen: 'ready', 1.1235 + onAttach: function(mod) { 1.1236 + assert.fail('pagemodOffExisting page-mod should not have been attached'); 1.1237 + } 1.1238 + }); 1.1239 + } 1.1240 + 1.1241 + window.addEventListener("load", wait4Iframes, false); 1.1242 +}; 1.1243 + 1.1244 +exports.testIFramePostMessage = function(assert, done) { 1.1245 + let count = 0; 1.1246 + 1.1247 + tabs.open({ 1.1248 + url: data.url("test-iframe.html"), 1.1249 + onReady: function(tab) { 1.1250 + var worker = tab.attach({ 1.1251 + contentScriptFile: data.url('test-iframe.js'), 1.1252 + contentScript: 'var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'', 1.1253 + onMessage: function(msg) { 1.1254 + assert.equal(++count, 1); 1.1255 + assert.equal(msg.first, 'a string'); 1.1256 + assert.ok(msg.second[1], "array"); 1.1257 + assert.equal(typeof msg.third, 'object'); 1.1258 + 1.1259 + worker.destroy(); 1.1260 + tab.close(done); 1.1261 + } 1.1262 + }); 1.1263 + } 1.1264 + }); 1.1265 +}; 1.1266 + 1.1267 +exports.testEvents = function(assert, done) { 1.1268 + let content = "<script>\n new " + function DocumentScope() { 1.1269 + window.addEventListener("ContentScriptEvent", function () { 1.1270 + window.receivedEvent = true; 1.1271 + }, false); 1.1272 + } + "\n</script>"; 1.1273 + let url = "data:text/html;charset=utf-8," + encodeURIComponent(content); 1.1274 + testPageMod(assert, done, url, [{ 1.1275 + include: "data:*", 1.1276 + contentScript: 'new ' + function WorkerScope() { 1.1277 + let evt = document.createEvent("Event"); 1.1278 + evt.initEvent("ContentScriptEvent", true, true); 1.1279 + document.body.dispatchEvent(evt); 1.1280 + } 1.1281 + }], 1.1282 + function(win, done) { 1.1283 + assert.ok( 1.1284 + win.receivedEvent, 1.1285 + "Content script sent an event and document received it" 1.1286 + ); 1.1287 + done(); 1.1288 + } 1.1289 + ); 1.1290 +}; 1.1291 + 1.1292 +exports["test page-mod on private tab"] = function (assert, done) { 1.1293 + let fail = assert.fail.bind(assert); 1.1294 + 1.1295 + let privateUri = "data:text/html;charset=utf-8," + 1.1296 + "<iframe src=\"data:text/html;charset=utf-8,frame\" />"; 1.1297 + let nonPrivateUri = "data:text/html;charset=utf-8,non-private"; 1.1298 + 1.1299 + let pageMod = new PageMod({ 1.1300 + include: "data:*", 1.1301 + onAttach: function(worker) { 1.1302 + if (isTabPBSupported || isWindowPBSupported) { 1.1303 + // When PB isn't supported, the page-mod will apply to all document 1.1304 + // as all of them will be non-private 1.1305 + assert.equal(worker.tab.url, 1.1306 + nonPrivateUri, 1.1307 + "page-mod should only attach to the non-private tab"); 1.1308 + } 1.1309 + 1.1310 + assert.ok(!isPrivate(worker), 1.1311 + "The worker is really non-private"); 1.1312 + assert.ok(!isPrivate(worker.tab), 1.1313 + "The document is really non-private"); 1.1314 + pageMod.destroy(); 1.1315 + 1.1316 + page1.close(). 1.1317 + then(page2.close). 1.1318 + then(done, fail); 1.1319 + } 1.1320 + }); 1.1321 + 1.1322 + let page1, page2; 1.1323 + page1 = openWebpage(privateUri, true); 1.1324 + page1.ready.then(function() { 1.1325 + page2 = openWebpage(nonPrivateUri, false); 1.1326 + }, fail); 1.1327 +} 1.1328 + 1.1329 +exports["test page-mod on private tab in global pb"] = function (assert, done) { 1.1330 + if (!isGlobalPBSupported) { 1.1331 + assert.pass(); 1.1332 + return done(); 1.1333 + } 1.1334 + 1.1335 + let privateUri = "data:text/html;charset=utf-8," + 1.1336 + "<iframe%20src=\"data:text/html;charset=utf-8,frame\"/>"; 1.1337 + 1.1338 + let pageMod = new PageMod({ 1.1339 + include: privateUri, 1.1340 + onAttach: function(worker) { 1.1341 + assert.equal(worker.tab.url, 1.1342 + privateUri, 1.1343 + "page-mod should attach"); 1.1344 + assert.equal(isPrivateBrowsingSupported, 1.1345 + false, 1.1346 + "private browsing is not supported"); 1.1347 + assert.ok(isPrivate(worker), 1.1348 + "The worker is really non-private"); 1.1349 + assert.ok(isPrivate(worker.tab), 1.1350 + "The document is really non-private"); 1.1351 + pageMod.destroy(); 1.1352 + 1.1353 + worker.tab.close(function() { 1.1354 + pb.once('stop', function() { 1.1355 + assert.pass('global pb stop'); 1.1356 + done(); 1.1357 + }); 1.1358 + pb.deactivate(); 1.1359 + }); 1.1360 + } 1.1361 + }); 1.1362 + 1.1363 + let page1; 1.1364 + pb.once('start', function() { 1.1365 + assert.pass('global pb start'); 1.1366 + tabs.open({ url: privateUri }); 1.1367 + }); 1.1368 + pb.activate(); 1.1369 +} 1.1370 + 1.1371 +// Bug 699450: Calling worker.tab.close() should not lead to exception 1.1372 +exports.testWorkerTabClose = function(assert, done) { 1.1373 + let callbackDone; 1.1374 + testPageMod(assert, done, "about:", [{ 1.1375 + include: "about:", 1.1376 + contentScript: '', 1.1377 + onAttach: function(worker) { 1.1378 + assert.pass("The page-mod was attached"); 1.1379 + 1.1380 + worker.tab.close(function () { 1.1381 + // On Fennec, tab is completely destroyed right after close event is 1.1382 + // dispatch, so we need to wait for the next event loop cycle to 1.1383 + // check for tab nulliness. 1.1384 + setTimeout(function () { 1.1385 + assert.ok(!worker.tab, 1.1386 + "worker.tab should be null right after tab.close()"); 1.1387 + callbackDone(); 1.1388 + }, 0); 1.1389 + }); 1.1390 + } 1.1391 + }], 1.1392 + function(win, done) { 1.1393 + callbackDone = done; 1.1394 + } 1.1395 + ); 1.1396 +}; 1.1397 + 1.1398 +exports.testDebugMetadata = function(assert, done) { 1.1399 + let dbg = new Debugger; 1.1400 + let globalDebuggees = []; 1.1401 + dbg.onNewGlobalObject = function(global) { 1.1402 + globalDebuggees.push(global); 1.1403 + } 1.1404 + 1.1405 + let mods = testPageMod(assert, done, "about:", [{ 1.1406 + include: "about:", 1.1407 + contentScriptWhen: "start", 1.1408 + contentScript: "null;", 1.1409 + }], function(win, done) { 1.1410 + assert.ok(globalDebuggees.some(function(global) { 1.1411 + try { 1.1412 + let metadata = Cu.getSandboxMetadata(global.unsafeDereference()); 1.1413 + return metadata && metadata.addonID && metadata.SDKContentScript && 1.1414 + metadata['inner-window-id'] == getInnerId(win); 1.1415 + } catch(e) { 1.1416 + // Some of the globals might not be Sandbox instances and thus 1.1417 + // will cause getSandboxMetadata to fail. 1.1418 + return false; 1.1419 + } 1.1420 + }), "one of the globals is a content script"); 1.1421 + done(); 1.1422 + } 1.1423 + ); 1.1424 +}; 1.1425 + 1.1426 +exports.testDevToolsExtensionsGetContentGlobals = function(assert, done) { 1.1427 + let mods = testPageMod(assert, done, "about:", [{ 1.1428 + include: "about:", 1.1429 + contentScriptWhen: "start", 1.1430 + contentScript: "null;", 1.1431 + }], function(win, done) { 1.1432 + assert.equal(gDevToolsExtensions.getContentGlobals({ 'inner-window-id': getInnerId(win) }).length, 1); 1.1433 + done(); 1.1434 + } 1.1435 + ); 1.1436 +}; 1.1437 + 1.1438 +exports.testDetachOnDestroy = function(assert, done) { 1.1439 + let tab; 1.1440 + const TEST_URL = 'data:text/html;charset=utf-8,detach'; 1.1441 + const loader = Loader(module); 1.1442 + const { PageMod } = loader.require('sdk/page-mod'); 1.1443 + 1.1444 + let mod1 = PageMod({ 1.1445 + include: TEST_URL, 1.1446 + contentScript: Isolate(function() { 1.1447 + self.port.on('detach', function(reason) { 1.1448 + window.document.body.innerHTML += '!' + reason; 1.1449 + }); 1.1450 + }), 1.1451 + onAttach: worker => { 1.1452 + assert.pass('attach[1] happened'); 1.1453 + 1.1454 + worker.on('detach', _ => setTimeout(_ => { 1.1455 + assert.pass('detach happened'); 1.1456 + 1.1457 + let mod2 = PageMod({ 1.1458 + attachTo: [ 'existing', 'top' ], 1.1459 + include: TEST_URL, 1.1460 + contentScript: Isolate(function() { 1.1461 + self.port.on('test', _ => { 1.1462 + self.port.emit('result', { result: window.document.body.innerHTML}); 1.1463 + }); 1.1464 + }), 1.1465 + onAttach: worker => { 1.1466 + assert.pass('attach[2] happened'); 1.1467 + worker.port.once('result', ({ result }) => { 1.1468 + assert.equal(result, 'detach!', 'the body.innerHTML is as expected'); 1.1469 + mod1.destroy(); 1.1470 + mod2.destroy(); 1.1471 + loader.unload(); 1.1472 + tab.close(done); 1.1473 + }); 1.1474 + worker.port.emit('test'); 1.1475 + } 1.1476 + }); 1.1477 + })); 1.1478 + 1.1479 + worker.destroy(); 1.1480 + } 1.1481 + }); 1.1482 + 1.1483 + tabs.open({ 1.1484 + url: TEST_URL, 1.1485 + onOpen: t => tab = t 1.1486 + }) 1.1487 +} 1.1488 + 1.1489 +exports.testDetachOnUnload = function(assert, done) { 1.1490 + let tab; 1.1491 + const TEST_URL = 'data:text/html;charset=utf-8,detach'; 1.1492 + const loader = Loader(module); 1.1493 + const { PageMod } = loader.require('sdk/page-mod'); 1.1494 + 1.1495 + let mod1 = PageMod({ 1.1496 + include: TEST_URL, 1.1497 + contentScript: Isolate(function() { 1.1498 + self.port.on('detach', function(reason) { 1.1499 + window.document.body.innerHTML += '!' + reason; 1.1500 + }); 1.1501 + }), 1.1502 + onAttach: worker => { 1.1503 + assert.pass('attach[1] happened'); 1.1504 + 1.1505 + worker.on('detach', _ => setTimeout(_ => { 1.1506 + assert.pass('detach happened'); 1.1507 + 1.1508 + let mod2 = require('sdk/page-mod').PageMod({ 1.1509 + attachTo: [ 'existing', 'top' ], 1.1510 + include: TEST_URL, 1.1511 + contentScript: Isolate(function() { 1.1512 + self.port.on('test', _ => { 1.1513 + self.port.emit('result', { result: window.document.body.innerHTML}); 1.1514 + }); 1.1515 + }), 1.1516 + onAttach: worker => { 1.1517 + assert.pass('attach[2] happened'); 1.1518 + worker.port.once('result', ({ result }) => { 1.1519 + assert.equal(result, 'detach!shutdown', 'the body.innerHTML is as expected'); 1.1520 + mod2.destroy(); 1.1521 + tab.close(done); 1.1522 + }); 1.1523 + worker.port.emit('test'); 1.1524 + } 1.1525 + }); 1.1526 + })); 1.1527 + 1.1528 + loader.unload('shutdown'); 1.1529 + } 1.1530 + }); 1.1531 + 1.1532 + tabs.open({ 1.1533 + url: TEST_URL, 1.1534 + onOpen: t => tab = t 1.1535 + }) 1.1536 +} 1.1537 + 1.1538 +exports.testConsole = function(assert, done) { 1.1539 + let innerID; 1.1540 + const TEST_URL = 'data:text/html;charset=utf-8,console'; 1.1541 + const { loader } = LoaderWithHookedConsole(module, onMessage); 1.1542 + const { PageMod } = loader.require('sdk/page-mod'); 1.1543 + const system = require("sdk/system/events"); 1.1544 + 1.1545 + let seenMessage = false; 1.1546 + function onMessage(type, msg, msgID) { 1.1547 + seenMessage = true; 1.1548 + innerID = msgID; 1.1549 + } 1.1550 + 1.1551 + let mod = PageMod({ 1.1552 + include: TEST_URL, 1.1553 + contentScriptWhen: "ready", 1.1554 + contentScript: Isolate(function() { 1.1555 + console.log("Hello from the page mod"); 1.1556 + self.port.emit("done"); 1.1557 + }), 1.1558 + onAttach: function(worker) { 1.1559 + worker.port.on("done", function() { 1.1560 + let window = getTabContentWindow(tab); 1.1561 + let id = getInnerId(window); 1.1562 + assert.ok(seenMessage, "Should have seen the console message"); 1.1563 + assert.equal(innerID, id, "Should have seen the right inner ID"); 1.1564 + closeTab(tab); 1.1565 + done(); 1.1566 + }); 1.1567 + }, 1.1568 + }); 1.1569 + 1.1570 + let tab = openTab(getMostRecentBrowserWindow(), TEST_URL); 1.1571 +} 1.1572 + 1.1573 +exports.testSyntaxErrorInContentScript = function(assert, done) { 1.1574 + const url = "data:text/html;charset=utf-8,testSyntaxErrorInContentScript"; 1.1575 + let hitError = null; 1.1576 + let attached = false; 1.1577 + 1.1578 + testPageMod(assert, done, url, [{ 1.1579 + include: url, 1.1580 + contentScript: 'console.log(23', 1.1581 + 1.1582 + onAttach: function() { 1.1583 + attached = true; 1.1584 + }, 1.1585 + 1.1586 + onError: function(e) { 1.1587 + hitError = e; 1.1588 + } 1.1589 + }], 1.1590 + 1.1591 + function(win, done) { 1.1592 + assert.ok(attached, "The worker was attached."); 1.1593 + assert.notStrictEqual(hitError, null, "The syntax error was reported."); 1.1594 + if (hitError) 1.1595 + assert.equal(hitError.name, "SyntaxError", "The error thrown should be a SyntaxError"); 1.1596 + done(); 1.1597 + } 1.1598 + ); 1.1599 +}; 1.1600 + 1.1601 +require('sdk/test').run(exports);