diff -r 000000000000 -r 6474c204b198 addon-sdk/source/test/test-page-mod.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/addon-sdk/source/test/test-page-mod.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1598 @@ +/* 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, handleReadyState } = require("./pagemod-test-helpers"); +const { Loader } = require('sdk/test/loader'); +const tabs = require("sdk/tabs"); +const { setTimeout } = require("sdk/timers"); +const { Cc, Ci, Cu } = require("chrome"); +const { + open, + getFrames, + getMostRecentBrowserWindow, + getInnerId +} = require('sdk/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 { LoaderWithHookedConsole } = require('sdk/test/loader'); + +const { waitUntil } = require("sdk/test/utils"); +const data = require("./fixtures"); + +const { gDevToolsExtensions } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {}); + +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.testPageModValidationAttachTo = function(assert) { + [{ val: 'top', type: 'string "top"' }, + { val: 'frame', type: 'string "frame"' }, + { val: ['top', 'existing'], type: 'array with "top" and "existing"' }, + { val: ['frame', 'existing'], type: 'array with "frame" and "existing"' }, + { val: ['top'], type: 'array with "top"' }, + { val: ['frame'], type: 'array with "frame"' }, + { val: undefined, type: 'undefined' }].forEach((attachTo) => { + new PageMod({ attachTo: attachTo.val, include: '*.validation111' }); + assert.pass("PageMod() does not throw when attachTo is " + attachTo.type); + }); + + [{ val: 'existing', type: 'string "existing"' }, + { val: ['existing'], type: 'array with "existing"' }, + { val: 'not-legit', type: 'string with "not-legit"' }, + { val: ['not-legit'], type: 'array with "not-legit"' }, + { val: {}, type: 'object' }].forEach((attachTo) => { + assert.throws(() => + new PageMod({ attachTo: attachTo.val, include: '*.validation111' }), + /The `attachTo` option/, + "PageMod() throws when 'attachTo' option is " + attachTo.type + "."); + }); +}; + +exports.testPageModValidationInclude = function(assert) { + [{ val: undefined, type: 'undefined' }, + { val: {}, type: 'object' }, + { val: [], type: 'empty array'}, + { val: [/regexp/, 1], type: 'array with non string/regexp' }, + { val: 1, type: 'number' }].forEach((include) => { + assert.throws(() => new PageMod({ include: include.val }), + /The `include` option must always contain atleast one rule/, + "PageMod() throws when 'include' option is " + include.type + "."); + }); + + [{ val: '*.validation111', type: 'string' }, + { val: /validation111/, type: 'regexp' }, + { val: ['*.validation111'], type: 'array with length > 0'}].forEach((include) => { + new PageMod({ include: include.val }); + assert.pass("PageMod() does not throw when include option is " + include.type); + }); +}; + +/* 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"); + + 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 = '