/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { PageMod } = require("sdk/page-mod"); const testPageMod = require("./pagemod-test-helpers").testPageMod; const { Loader } = require('sdk/test/loader'); const tabs = require("sdk/tabs"); const timer = require("sdk/timers"); const { Cc, Ci, Cu } = require("chrome"); const { open, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils'); const windowUtils = require('sdk/deprecated/window-utils'); const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils'); const xulApp = require("sdk/system/xul-app"); const { isPrivateBrowsingSupported } = require('sdk/self'); const { isPrivate } = require('sdk/private-browsing'); const { openWebpage } = require('./private-browsing/helper'); const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils'); const promise = require("sdk/core/promise"); const { pb } = require('./private-browsing/helper'); const { URL } = require("sdk/url"); const { waitUntil } = require("sdk/test/utils"); const data = require("./fixtures"); const testPageURI = data.url("test.html"); // The following adds Debugger constructor to the global namespace. const { addDebuggerToGlobal } = Cu.import('resource://gre/modules/jsdebugger.jsm', {}); addDebuggerToGlobal(this); function Isolate(worker) { return "(" + worker + ")()"; } /* Tests for the PageMod APIs */ exports.testPageMod1 = function(assert, done) { let mods = testPageMod(assert, done, "about:", [{ include: /about:/, contentScriptWhen: 'end', contentScript: 'new ' + function WorkerScope() { window.document.body.setAttribute("JEP-107", "worked"); }, onAttach: function() { assert.equal(this, mods[0], "The 'this' object is the page mod."); } }], function(win, done) { assert.equal( win.document.body.getAttribute("JEP-107"), "worked", "PageMod.onReady test" ); done(); } ); }; exports.testPageMod2 = function(assert, done) { testPageMod(assert, done, "about:", [{ include: "about:*", contentScript: [ 'new ' + function contentScript() { window.AUQLUE = function() { return 42; } try { window.AUQLUE() } catch(e) { throw new Error("PageMod scripts executed in order"); } document.documentElement.setAttribute("first", "true"); }, 'new ' + function contentScript() { document.documentElement.setAttribute("second", "true"); } ] }], function(win, done) { assert.equal(win.document.documentElement.getAttribute("first"), "true", "PageMod test #2: first script has run"); assert.equal(win.document.documentElement.getAttribute("second"), "true", "PageMod test #2: second script has run"); assert.equal("AUQLUE" in win, false, "PageMod test #2: scripts get a wrapped window"); done(); }); }; exports.testPageModIncludes = function(assert, done) { var asserts = []; function createPageModTest(include, expectedMatch) { // Create an 'onload' test function... asserts.push(function(test, win) { var matches = include in win.localStorage; assert.ok(expectedMatch ? matches : !matches, "'" + include + "' match test, expected: " + expectedMatch); }); // ...and corresponding PageMod options return { include: include, contentScript: 'new ' + function() { self.on("message", function(msg) { window.localStorage[msg] = true; }); }, // The testPageMod callback with test assertions is called on 'end', // and we want this page mod to be attached before it gets called, // so we attach it on 'start'. contentScriptWhen: 'start', onAttach: function(worker) { worker.postMessage(this.include[0]); } }; } testPageMod(assert, done, testPageURI, [ createPageModTest("*", false), createPageModTest("*.google.com", false), createPageModTest("resource:*", true), createPageModTest("resource:", false), createPageModTest(testPageURI, true) ], function (win, done) { waitUntil(function () win.localStorage[testPageURI], testPageURI + " page-mod to be executed") .then(function () { asserts.forEach(function(fn) { fn(assert, win); }); done(); }); } ); }; exports.testPageModErrorHandling = function(assert) { assert.throws(function() { new PageMod(); }, /The `include` option must always contain atleast one rule/, "PageMod() throws when 'include' option is not specified."); }; /* Tests for internal functions. */ exports.testCommunication1 = function(assert, done) { let workerDone = false, callbackDone = null; testPageMod(assert, done, "about:", [{ include: "about:*", contentScriptWhen: 'end', contentScript: 'new ' + function WorkerScope() { self.on('message', function(msg) { document.body.setAttribute('JEP-107', 'worked'); self.postMessage(document.body.getAttribute('JEP-107')); }) }, onAttach: function(worker) { worker.on('error', function(e) { assert.fail('Errors where reported'); }); worker.on('message', function(value) { assert.equal( "worked", value, "test comunication" ); workerDone = true; if (callbackDone) callbackDone(); }); worker.postMessage('do it!') } }], function(win, done) { (callbackDone = function() { if (workerDone) { assert.equal( 'worked', win.document.body.getAttribute('JEP-107'), 'attribute should be modified' ); done(); } })(); } ); }; exports.testCommunication2 = function(assert, done) { let callbackDone = null, window; testPageMod(assert, done, "about:license", [{ include: "about:*", contentScriptWhen: 'start', contentScript: 'new ' + function WorkerScope() { document.documentElement.setAttribute('AUQLUE', 42); window.addEventListener('load', function listener() { self.postMessage('onload'); }, false); self.on("message", function() { self.postMessage(document.documentElement.getAttribute("test")) }); }, onAttach: function(worker) { worker.on('error', function(e) { assert.fail('Errors where reported'); }); worker.on('message', function(msg) { if ('onload' == msg) { assert.equal( '42', window.document.documentElement.getAttribute('AUQLUE'), 'PageMod scripts executed in order' ); window.document.documentElement.setAttribute('test', 'changes in window'); worker.postMessage('get window.test') } else { assert.equal( 'changes in window', msg, 'PageMod test #2: second script has run' ) callbackDone(); } }); } }], function(win, done) { window = win; callbackDone = done; } ); }; exports.testEventEmitter = function(assert, done) { let workerDone = false, callbackDone = null; testPageMod(assert, done, "about:", [{ include: "about:*", contentScript: 'new ' + function WorkerScope() { self.port.on('addon-to-content', function(data) { self.port.emit('content-to-addon', data); }); }, onAttach: function(worker) { worker.on('error', function(e) { assert.fail('Errors were reported : '+e); }); worker.port.on('content-to-addon', function(value) { assert.equal( "worked", value, "EventEmitter API works!" ); if (callbackDone) callbackDone(); else workerDone = true; }); worker.port.emit('addon-to-content', 'worked'); } }], function(win, done) { if (workerDone) done(); else callbackDone = done; } ); }; // Execute two concurrent page mods on same document to ensure that their // JS contexts are different exports.testMixedContext = function(assert, done) { let doneCallback = null; let messages = 0; let modObject = { include: "data:text/html;charset=utf-8,", contentScript: 'new ' + function WorkerScope() { // Both scripts will execute this, // context is shared if one script see the other one modification. let isContextShared = "sharedAttribute" in document; self.postMessage(isContextShared); document.sharedAttribute = true; }, onAttach: function(w) { w.on("message", function (isContextShared) { if (isContextShared) { assert.fail("Page mod contexts are mixed."); doneCallback(); } else if (++messages == 2) { assert.pass("Page mod contexts are different."); doneCallback(); } }); } }; testPageMod(assert, done, "data:text/html;charset=utf-8,", [modObject, modObject], function(win, done) { doneCallback = done; } ); }; exports.testHistory = function(assert, done) { // We need a valid url in order to have a working History API. // (i.e do not work on data: or about: pages) // Test bug 679054. let url = data.url("test-page-mod.html"); let callbackDone = null; testPageMod(assert, done, url, [{ include: url, contentScriptWhen: 'end', contentScript: 'new ' + function WorkerScope() { history.pushState({}, "", "#"); history.replaceState({foo: "bar"}, "", "#"); self.postMessage(history.state); }, onAttach: function(worker) { worker.on('message', function (data) { assert.equal(JSON.stringify(data), JSON.stringify({foo: "bar"}), "History API works!"); callbackDone(); }); } }], function(win, done) { callbackDone = done; } ); }; exports.testRelatedTab = function(assert, done) { let tab; let pageMod = new PageMod({ include: "about:*", onAttach: function(worker) { assert.ok(!!worker.tab, "Worker.tab exists"); assert.equal(tab, worker.tab, "Worker.tab is valid"); pageMod.destroy(); tab.close(done); } }); tabs.open({ url: "about:", onOpen: function onOpen(t) { tab = t; } }); }; exports.testRelatedTabNoRequireTab = function(assert, done) { let loader = Loader(module); let tab; let url = "data:text/html;charset=utf-8," + encodeURI("Test related worker tab 2"); let { PageMod } = loader.require("sdk/page-mod"); let pageMod = new PageMod({ include: url, onAttach: function(worker) { assert.equal(worker.tab.url, url, "Worker.tab.url is valid"); worker.tab.close(function() { pageMod.destroy(); loader.unload(); done(); }); } }); tabs.open(url); }; exports.testRelatedTabNoOtherReqs = function(assert, done) { let loader = Loader(module); let { PageMod } = loader.require("sdk/page-mod"); let pageMod = new PageMod({ include: "about:blank?testRelatedTabNoOtherReqs", onAttach: function(worker) { assert.ok(!!worker.tab, "Worker.tab exists"); pageMod.destroy(); worker.tab.close(function() { worker.destroy(); loader.unload(); done(); }); } }); tabs.open({ url: "about:blank?testRelatedTabNoOtherReqs" }); }; exports.testWorksWithExistingTabs = function(assert, done) { let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document"); let { PageMod } = require("sdk/page-mod"); tabs.open({ url: url, onReady: function onReady(tab) { let pageModOnExisting = new PageMod({ include: url, attachTo: ["existing", "top", "frame"], onAttach: function(worker) { assert.ok(!!worker.tab, "Worker.tab exists"); assert.equal(tab, worker.tab, "A worker has been created on this existing tab"); timer.setTimeout(function() { pageModOnExisting.destroy(); pageModOffExisting.destroy(); tab.close(done); }, 0); } }); let pageModOffExisting = new PageMod({ include: url, onAttach: function(worker) { assert.fail("pageModOffExisting page-mod should not have attached to anything"); } }); } }); }; exports.testExistingFrameDoesntMatchInclude = function(assert, done) { let iframeURL = 'data:text/html;charset=utf-8,UNIQUE-TEST-STRING-42'; let iframe = '