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);