michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: const { Loader } = require('sdk/test/loader'); michael@0: const { Page } = require("sdk/page-worker"); michael@0: const { URL } = require("sdk/url"); michael@0: const fixtures = require("./fixtures"); michael@0: const testURI = fixtures.url("test.html"); michael@0: michael@0: const ERR_DESTROYED = michael@0: "Couldn't find the worker to receive this message. " + michael@0: "The script may not be initialized yet, or may already have been unloaded."; michael@0: michael@0: exports.testSimplePageCreation = function(assert, done) { michael@0: let page = new Page({ michael@0: contentScript: "self.postMessage(window.location.href)", michael@0: contentScriptWhen: "end", michael@0: onMessage: function (message) { michael@0: assert.equal(message, "about:blank", michael@0: "Page Worker should start with a blank page by default"); michael@0: assert.equal(this, page, "The 'this' object is the page itself."); michael@0: done(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /* michael@0: * Tests that we can't be tricked by document overloads as we have access michael@0: * to wrapped nodes michael@0: */ michael@0: exports.testWrappedDOM = function(assert, done) { michael@0: let page = Page({ michael@0: allow: { script: true }, michael@0: contentURL: "data:text/html;charset=utf-8,", michael@0: contentScript: "window.addEventListener('load', function () " + michael@0: "self.postMessage([typeof(document.getElementById), " + michael@0: "typeof(window.scrollTo)]), true)", michael@0: onMessage: function (message) { michael@0: assert.equal(message[0], michael@0: "function", michael@0: "getElementById from content script is the native one"); michael@0: michael@0: assert.equal(message[1], michael@0: "function", michael@0: "scrollTo from content script is the native one"); michael@0: michael@0: done(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /* michael@0: // We do not offer unwrapped access to DOM since bug 601295 landed michael@0: // See 660780 to track progress of unwrap feature michael@0: exports.testUnwrappedDOM = function(assert, done) { michael@0: let page = Page({ michael@0: allow: { script: true }, michael@0: contentURL: "data:text/html;charset=utf-8,", michael@0: contentScript: "window.addEventListener('load', function () " + michael@0: "self.postMessage([typeof(unsafeWindow.document.getElementById), " + michael@0: "typeof(unsafeWindow.scrollTo)]), true)", michael@0: onMessage: function (message) { michael@0: assert.equal(message[0], michael@0: "number", michael@0: "document inside page is free to be changed"); michael@0: michael@0: assert.equal(message[1], michael@0: "number", michael@0: "window inside page is free to be changed"); michael@0: michael@0: done(); michael@0: } michael@0: }); michael@0: } michael@0: */ michael@0: michael@0: exports.testPageProperties = function(assert) { michael@0: let page = new Page(); michael@0: michael@0: for each (let prop in ['contentURL', 'allow', 'contentScriptFile', michael@0: 'contentScript', 'contentScriptWhen', 'on', michael@0: 'postMessage', 'removeListener']) { michael@0: assert.ok(prop in page, prop + " property is defined on page."); michael@0: } michael@0: michael@0: assert.ok(function () page.postMessage("foo") || true, michael@0: "postMessage doesn't throw exception on page."); michael@0: } michael@0: michael@0: exports.testConstructorAndDestructor = function(assert, done) { michael@0: let loader = Loader(module); michael@0: let { Page } = loader.require("sdk/page-worker"); michael@0: let global = loader.sandbox("sdk/page-worker"); michael@0: michael@0: let pagesReady = 0; michael@0: michael@0: let page1 = Page({ michael@0: contentScript: "self.postMessage('')", michael@0: contentScriptWhen: "end", michael@0: onMessage: pageReady michael@0: }); michael@0: let page2 = Page({ michael@0: contentScript: "self.postMessage('')", michael@0: contentScriptWhen: "end", michael@0: onMessage: pageReady michael@0: }); michael@0: michael@0: assert.notEqual(page1, page2, michael@0: "Page 1 and page 2 should be different objects."); michael@0: michael@0: function pageReady() { michael@0: if (++pagesReady == 2) { michael@0: page1.destroy(); michael@0: page2.destroy(); michael@0: michael@0: assert.ok(isDestroyed(page1), "page1 correctly unloaded."); michael@0: assert.ok(isDestroyed(page2), "page2 correctly unloaded."); michael@0: michael@0: loader.unload(); michael@0: done(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: exports.testAutoDestructor = function(assert, done) { michael@0: let loader = Loader(module); michael@0: let { Page } = loader.require("sdk/page-worker"); michael@0: michael@0: let page = Page({ michael@0: contentScript: "self.postMessage('')", michael@0: contentScriptWhen: "end", michael@0: onMessage: function() { michael@0: loader.unload(); michael@0: assert.ok(isDestroyed(page), "Page correctly unloaded."); michael@0: done(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: exports.testValidateOptions = function(assert) { michael@0: assert.throws( michael@0: function () Page({ contentURL: 'home' }), michael@0: /The `contentURL` option must be a valid URL\./, michael@0: "Validation correctly denied a non-URL contentURL" michael@0: ); michael@0: michael@0: assert.throws( michael@0: function () Page({ onMessage: "This is not a function."}), michael@0: /The option "onMessage" must be one of the following types: function/, michael@0: "Validation correctly denied a non-function onMessage." michael@0: ); michael@0: michael@0: assert.pass("Options validation is working."); michael@0: } michael@0: michael@0: exports.testContentAndAllowGettersAndSetters = function(assert, done) { michael@0: let content = "data:text/html;charset=utf-8,"; michael@0: michael@0: // Load up the page with testURI initially for the resource:// principal, michael@0: // then load the actual data:* content, as data:* URIs no longer michael@0: // have localStorage michael@0: let page = Page({ michael@0: contentURL: testURI, michael@0: contentScript: "if (window.location.href==='"+testURI+"')" + michael@0: " self.postMessage('reload');" + michael@0: "else " + michael@0: " self.postMessage(window.localStorage.allowScript)", michael@0: contentScriptWhen: "end", michael@0: onMessage: step0 michael@0: }); michael@0: michael@0: function step0(message) { michael@0: if (message === 'reload') michael@0: return page.contentURL = content; michael@0: assert.equal(message, "3", michael@0: "Correct value expected for allowScript - 3"); michael@0: assert.equal(page.contentURL, content, michael@0: "Correct content expected"); michael@0: page.removeListener('message', step0); michael@0: page.on('message', step1); michael@0: page.allow = { script: false }; michael@0: page.contentURL = content = michael@0: "data:text/html;charset=utf-8,"; michael@0: } michael@0: michael@0: function step1(message) { michael@0: assert.equal(message, "3", michael@0: "Correct value expected for allowScript - 3"); michael@0: assert.equal(page.contentURL, content, "Correct content expected"); michael@0: page.removeListener('message', step1); michael@0: page.on('message', step2); michael@0: page.allow = { script: true }; michael@0: page.contentURL = content = michael@0: "data:text/html;charset=utf-8,"; michael@0: } michael@0: michael@0: function step2(message) { michael@0: assert.equal(message, "g", michael@0: "Correct value expected for allowScript - g"); michael@0: assert.equal(page.contentURL, content, "Correct content expected"); michael@0: page.removeListener('message', step2); michael@0: page.on('message', step3); michael@0: page.allow.script = false; michael@0: page.contentURL = content = michael@0: "data:text/html;charset=utf-8,"; michael@0: } michael@0: michael@0: function step3(message) { michael@0: assert.equal(message, "g", michael@0: "Correct value expected for allowScript - g"); michael@0: assert.equal(page.contentURL, content, "Correct content expected"); michael@0: page.removeListener('message', step3); michael@0: page.on('message', step4); michael@0: page.allow.script = true; michael@0: page.contentURL = content = michael@0: "data:text/html;charset=utf-8,"; michael@0: } michael@0: michael@0: function step4(message) { michael@0: assert.equal(message, "4", michael@0: "Correct value expected for allowScript - 4"); michael@0: assert.equal(page.contentURL, content, "Correct content expected"); michael@0: done(); michael@0: } michael@0: michael@0: } michael@0: michael@0: exports.testOnMessageCallback = function(assert, done) { michael@0: Page({ michael@0: contentScript: "self.postMessage('')", michael@0: contentScriptWhen: "end", michael@0: onMessage: function() { michael@0: assert.pass("onMessage callback called"); michael@0: done(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: exports.testMultipleOnMessageCallbacks = function(assert, done) { michael@0: let count = 0; michael@0: let page = Page({ michael@0: contentScript: "self.postMessage('')", michael@0: contentScriptWhen: "end", michael@0: onMessage: () => count += 1 michael@0: }); michael@0: page.on('message', () => count += 2); michael@0: page.on('message', () => count *= 3); michael@0: page.on('message', () => michael@0: assert.equal(count, 9, "All callbacks were called, in order.")); michael@0: page.on('message', done); michael@0: }; michael@0: michael@0: exports.testLoadContentPage = function(assert, done) { michael@0: let page = Page({ michael@0: onMessage: function(message) { michael@0: // The message is an array whose first item is the test method to call michael@0: // and the rest of whose items are arguments to pass it. michael@0: let msg = message.shift(); michael@0: if (msg == "done") michael@0: return done(); michael@0: assert[msg].apply(assert, message); michael@0: }, michael@0: contentURL: fixtures.url("test-page-worker.html"), michael@0: contentScriptFile: fixtures.url("test-page-worker.js"), michael@0: contentScriptWhen: "ready" michael@0: }); michael@0: } michael@0: michael@0: exports.testAllowScriptDefault = function(assert, done) { michael@0: let page = Page({ michael@0: onMessage: function(message) { michael@0: assert.ok(message, "Script is allowed to run by default."); michael@0: done(); michael@0: }, michael@0: contentURL: "data:text/html;charset=utf-8,", michael@0: contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))", michael@0: contentScriptWhen: "ready" michael@0: }); michael@0: } michael@0: michael@0: exports.testAllowScript = function(assert, done) { michael@0: let page = Page({ michael@0: onMessage: function(message) { michael@0: assert.ok(message, "Script runs when allowed to do so."); michael@0: done(); michael@0: }, michael@0: allow: { script: true }, michael@0: contentURL: "data:text/html;charset=utf-8,", michael@0: contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " + michael@0: " document.documentElement.getAttribute('foo') == 3)", michael@0: contentScriptWhen: "ready" michael@0: }); michael@0: } michael@0: michael@0: exports.testPingPong = function(assert, done) { michael@0: let page = Page({ michael@0: contentURL: 'data:text/html;charset=utf-8,ping-pong', michael@0: contentScript: 'self.on("message", function(message) self.postMessage("pong"));' michael@0: + 'self.postMessage("ready");', michael@0: onMessage: function(message) { michael@0: if ('ready' == message) { michael@0: page.postMessage('ping'); michael@0: } michael@0: else { michael@0: assert.ok(message, 'pong', 'Callback from contentScript'); michael@0: done(); michael@0: } michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testRedirect = function (assert, done) { michael@0: let page = Page({ michael@0: contentURL: 'data:text/html;charset=utf-8,first-page', michael@0: contentScriptWhen: "end", michael@0: contentScript: '' + michael@0: 'if (/first-page/.test(document.location.href)) ' + michael@0: ' document.location.href = "data:text/html;charset=utf-8,redirect";' + michael@0: 'else ' + michael@0: ' self.port.emit("redirect", document.location.href);' michael@0: }); michael@0: michael@0: page.port.on('redirect', function (url) { michael@0: assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload'); michael@0: done(); michael@0: }); michael@0: }; michael@0: michael@0: exports.testRedirectIncludeArrays = function (assert, done) { michael@0: let firstURL = 'data:text/html;charset=utf-8,first-page'; michael@0: let page = Page({ michael@0: contentURL: firstURL, michael@0: contentScript: '(function () {' + michael@0: 'self.port.emit("load", document.location.href);' + michael@0: ' self.port.on("redirect", function (url) {' + michael@0: ' document.location.href = url;' + michael@0: ' })' + michael@0: '})();', michael@0: include: ['about:blank', 'data:*'] michael@0: }); michael@0: michael@0: page.port.on('load', function (url) { michael@0: if (url === firstURL) { michael@0: page.port.emit('redirect', 'about:blank'); michael@0: } else if (url === 'about:blank') { michael@0: page.port.emit('redirect', 'about:mozilla'); michael@0: assert.ok('`include` property handles arrays'); michael@0: assert.equal(url, 'about:blank', 'Redirects work with accepted domains'); michael@0: done(); michael@0: } else if (url === 'about:mozilla') { michael@0: assert.fail('Should not redirect to restricted domain'); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testRedirectFromWorker = function (assert, done) { michael@0: let firstURL = 'data:text/html;charset=utf-8,first-page'; michael@0: let secondURL = 'data:text/html;charset=utf-8,second-page'; michael@0: let thirdURL = 'data:text/html;charset=utf-8,third-page'; michael@0: let page = Page({ michael@0: contentURL: firstURL, michael@0: contentScript: '(function () {' + michael@0: 'self.port.emit("load", document.location.href);' + michael@0: ' self.port.on("redirect", function (url) {' + michael@0: ' document.location.href = url;' + michael@0: ' })' + michael@0: '})();', michael@0: include: 'data:*' michael@0: }); michael@0: michael@0: page.port.on('load', function (url) { michael@0: if (url === firstURL) { michael@0: page.port.emit('redirect', secondURL); michael@0: } else if (url === secondURL) { michael@0: page.port.emit('redirect', thirdURL); michael@0: } else if (url === thirdURL) { michael@0: page.port.emit('redirect', 'about:mozilla'); michael@0: assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings'); michael@0: done(); michael@0: } else { michael@0: assert.fail('Should not redirect to unauthorized domains'); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testRedirectWithContentURL = function (assert, done) { michael@0: let firstURL = 'data:text/html;charset=utf-8,first-page'; michael@0: let secondURL = 'data:text/html;charset=utf-8,second-page'; michael@0: let thirdURL = 'data:text/html;charset=utf-8,third-page'; michael@0: let page = Page({ michael@0: contentURL: firstURL, michael@0: contentScript: '(function () {' + michael@0: 'self.port.emit("load", document.location.href);' + michael@0: '})();', michael@0: include: 'data:*' michael@0: }); michael@0: michael@0: page.port.on('load', function (url) { michael@0: if (url === firstURL) { michael@0: page.contentURL = secondURL; michael@0: } else if (url === secondURL) { michael@0: page.contentURL = thirdURL; michael@0: } else if (url === thirdURL) { michael@0: page.contentURL = 'about:mozilla'; michael@0: assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings'); michael@0: done(); michael@0: } else { michael@0: assert.fail('Should not redirect to unauthorized domains'); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: michael@0: exports.testMultipleDestroys = function(assert) { michael@0: let page = Page(); michael@0: page.destroy(); michael@0: page.destroy(); michael@0: assert.pass("Multiple destroys should not cause an error"); michael@0: }; michael@0: michael@0: exports.testContentScriptOptionsOption = function(assert, done) { michael@0: let page = new Page({ michael@0: contentScript: "self.postMessage( [typeof self.options.d, self.options] );", michael@0: contentScriptWhen: "end", michael@0: contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}}, michael@0: onMessage: function(msg) { michael@0: assert.equal(msg[0], 'undefined', 'functions are stripped from contentScriptOptions'); michael@0: assert.equal(typeof msg[1], 'object', 'object as contentScriptOptions'); michael@0: assert.equal(msg[1].a, true, 'boolean in contentScriptOptions'); michael@0: assert.equal(msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions'); michael@0: assert.equal(msg[1].c, 'string', 'string in contentScriptOptions'); michael@0: done(); michael@0: } michael@0: }); michael@0: }; michael@0: michael@0: exports.testMessageQueue = function (assert, done) { michael@0: let page = new Page({ michael@0: contentScript: 'self.on("message", function (m) {' + michael@0: 'self.postMessage(m);' + michael@0: '});', michael@0: contentURL: 'data:text/html;charset=utf-8,', michael@0: }); michael@0: page.postMessage('ping'); michael@0: page.on('message', function (m) { michael@0: assert.equal(m, 'ping', 'postMessage should queue messages'); michael@0: done(); michael@0: }); michael@0: }; michael@0: michael@0: function isDestroyed(page) { michael@0: try { michael@0: page.postMessage("foo"); michael@0: } michael@0: catch (err) { michael@0: if (err.message == ERR_DESTROYED) { michael@0: return true; michael@0: } michael@0: else { michael@0: throw err; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: require("test").run(exports);