addon-sdk/source/test/test-page-worker.js

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4 "use strict";
     6 const { Loader } = require('sdk/test/loader');
     7 const { Page } = require("sdk/page-worker");
     8 const { URL } = require("sdk/url");
     9 const fixtures = require("./fixtures");
    10 const testURI = fixtures.url("test.html");
    12 const ERR_DESTROYED =
    13   "Couldn't find the worker to receive this message. " +
    14   "The script may not be initialized yet, or may already have been unloaded.";
    16 exports.testSimplePageCreation = function(assert, done) {
    17   let page = new Page({
    18     contentScript: "self.postMessage(window.location.href)",
    19     contentScriptWhen: "end",
    20     onMessage: function (message) {
    21       assert.equal(message, "about:blank",
    22                        "Page Worker should start with a blank page by default");
    23       assert.equal(this, page, "The 'this' object is the page itself.");
    24       done();
    25     }
    26   });
    27 }
    29 /*
    30  * Tests that we can't be tricked by document overloads as we have access
    31  * to wrapped nodes
    32  */
    33 exports.testWrappedDOM = function(assert, done) {
    34   let page = Page({
    35     allow: { script: true },
    36     contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
    37     contentScript: "window.addEventListener('load', function () " +
    38                    "self.postMessage([typeof(document.getElementById), " +
    39                    "typeof(window.scrollTo)]), true)",
    40     onMessage: function (message) {
    41       assert.equal(message[0],
    42                        "function",
    43                        "getElementById from content script is the native one");
    45       assert.equal(message[1],
    46                        "function",
    47                        "scrollTo from content script is the native one");
    49       done();
    50     }
    51   });
    52 }
    54 /*
    55 // We do not offer unwrapped access to DOM since bug 601295 landed
    56 // See 660780 to track progress of unwrap feature
    57 exports.testUnwrappedDOM = function(assert, done) {
    58   let page = Page({
    59     allow: { script: true },
    60     contentURL: "data:text/html;charset=utf-8,<script>document.getElementById=3;window.scrollTo=3;</script>",
    61     contentScript: "window.addEventListener('load', function () " +
    62                    "self.postMessage([typeof(unsafeWindow.document.getElementById), " +
    63                    "typeof(unsafeWindow.scrollTo)]), true)",
    64     onMessage: function (message) {
    65       assert.equal(message[0],
    66                        "number",
    67                        "document inside page is free to be changed");
    69       assert.equal(message[1],
    70                        "number",
    71                        "window inside page is free to be changed");
    73       done();
    74     }
    75   });
    76 }
    77 */
    79 exports.testPageProperties = function(assert) {
    80   let page = new Page();
    82   for each (let prop in ['contentURL', 'allow', 'contentScriptFile',
    83                          'contentScript', 'contentScriptWhen', 'on',
    84                          'postMessage', 'removeListener']) {
    85     assert.ok(prop in page, prop + " property is defined on page.");
    86   }
    88   assert.ok(function () page.postMessage("foo") || true,
    89               "postMessage doesn't throw exception on page.");
    90 }
    92 exports.testConstructorAndDestructor = function(assert, done) {
    93   let loader = Loader(module);
    94   let { Page } = loader.require("sdk/page-worker");
    95   let global = loader.sandbox("sdk/page-worker");
    97   let pagesReady = 0;
    99   let page1 = Page({
   100     contentScript:      "self.postMessage('')",
   101     contentScriptWhen:  "end",
   102     onMessage:          pageReady
   103   });
   104   let page2 = Page({
   105     contentScript:      "self.postMessage('')",
   106     contentScriptWhen:  "end",
   107     onMessage:          pageReady
   108   });
   110   assert.notEqual(page1, page2,
   111                       "Page 1 and page 2 should be different objects.");
   113   function pageReady() {
   114     if (++pagesReady == 2) {
   115       page1.destroy();
   116       page2.destroy();
   118       assert.ok(isDestroyed(page1), "page1 correctly unloaded.");
   119       assert.ok(isDestroyed(page2), "page2 correctly unloaded.");
   121       loader.unload();
   122       done();
   123     }
   124   }
   125 }
   127 exports.testAutoDestructor = function(assert, done) {
   128   let loader = Loader(module);
   129   let { Page } = loader.require("sdk/page-worker");
   131   let page = Page({
   132     contentScript: "self.postMessage('')",
   133     contentScriptWhen: "end",
   134     onMessage: function() {
   135       loader.unload();
   136       assert.ok(isDestroyed(page), "Page correctly unloaded.");
   137       done();
   138     }
   139   });
   140 }
   142 exports.testValidateOptions = function(assert) {
   143   assert.throws(
   144     function () Page({ contentURL: 'home' }),
   145     /The `contentURL` option must be a valid URL\./,
   146     "Validation correctly denied a non-URL contentURL"
   147   );
   149   assert.throws(
   150     function () Page({ onMessage: "This is not a function."}),
   151     /The option "onMessage" must be one of the following types: function/,
   152     "Validation correctly denied a non-function onMessage."
   153   );
   155   assert.pass("Options validation is working.");
   156 }
   158 exports.testContentAndAllowGettersAndSetters = function(assert, done) {
   159   let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>";
   161   // Load up the page with testURI initially for the resource:// principal,
   162   // then load the actual data:* content, as data:* URIs no longer
   163   // have localStorage
   164   let page = Page({
   165     contentURL: testURI,
   166     contentScript: "if (window.location.href==='"+testURI+"')" +
   167       "  self.postMessage('reload');" +
   168       "else " +
   169       "  self.postMessage(window.localStorage.allowScript)",
   170     contentScriptWhen: "end",
   171     onMessage: step0
   172   });
   174   function step0(message) {
   175     if (message === 'reload')
   176       return page.contentURL = content;
   177     assert.equal(message, "3",
   178                      "Correct value expected for allowScript - 3");
   179     assert.equal(page.contentURL, content,
   180                      "Correct content expected");
   181     page.removeListener('message', step0);
   182     page.on('message', step1);
   183     page.allow = { script: false };
   184     page.contentURL = content =
   185       "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='f'</script>";
   186   }
   188   function step1(message) {
   189     assert.equal(message, "3",
   190                      "Correct value expected for allowScript - 3");
   191     assert.equal(page.contentURL, content, "Correct content expected");
   192     page.removeListener('message', step1);
   193     page.on('message', step2);
   194     page.allow = { script: true };
   195     page.contentURL = content =
   196       "data:text/html;charset=utf-8,<script>window.localStorage.allowScript='g'</script>";
   197   }
   199   function step2(message) {
   200     assert.equal(message, "g",
   201                      "Correct value expected for allowScript - g");
   202     assert.equal(page.contentURL, content, "Correct content expected");
   203     page.removeListener('message', step2);
   204     page.on('message', step3);
   205     page.allow.script = false;
   206     page.contentURL = content =
   207       "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3</script>";
   208   }
   210   function step3(message) {
   211     assert.equal(message, "g",
   212                      "Correct value expected for allowScript - g");
   213     assert.equal(page.contentURL, content, "Correct content expected");
   214     page.removeListener('message', step3);
   215     page.on('message', step4);
   216     page.allow.script = true;
   217     page.contentURL = content =
   218       "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=4</script>";
   219   }
   221   function step4(message) {
   222     assert.equal(message, "4",
   223                      "Correct value expected for allowScript - 4");
   224     assert.equal(page.contentURL, content, "Correct content expected");
   225     done();
   226   }
   228 }
   230 exports.testOnMessageCallback = function(assert, done) {
   231   Page({
   232     contentScript: "self.postMessage('')",
   233     contentScriptWhen: "end",
   234     onMessage: function() {
   235       assert.pass("onMessage callback called");
   236       done();
   237     }
   238   });
   239 }
   241 exports.testMultipleOnMessageCallbacks = function(assert, done) {
   242   let count = 0;
   243   let page = Page({
   244     contentScript: "self.postMessage('')",
   245     contentScriptWhen: "end",
   246     onMessage: () => count += 1
   247   });
   248   page.on('message', () => count += 2);
   249   page.on('message', () => count *= 3);
   250   page.on('message', () =>
   251     assert.equal(count, 9, "All callbacks were called, in order."));
   252   page.on('message', done);
   253 };
   255 exports.testLoadContentPage = function(assert, done) {
   256   let page = Page({
   257     onMessage: function(message) {
   258       // The message is an array whose first item is the test method to call
   259       // and the rest of whose items are arguments to pass it.
   260       let msg = message.shift();
   261       if (msg == "done")
   262         return done();
   263       assert[msg].apply(assert, message);
   264     },
   265     contentURL: fixtures.url("test-page-worker.html"),
   266     contentScriptFile: fixtures.url("test-page-worker.js"),
   267     contentScriptWhen: "ready"
   268   });
   269 }
   271 exports.testAllowScriptDefault = function(assert, done) {
   272   let page = Page({
   273     onMessage: function(message) {
   274       assert.ok(message, "Script is allowed to run by default.");
   275       done();
   276     },
   277     contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
   278     contentScript: "self.postMessage(document.documentElement.getAttribute('foo'))",
   279     contentScriptWhen: "ready"
   280   });
   281 }
   283 exports.testAllowScript = function(assert, done) {
   284   let page = Page({
   285     onMessage: function(message) {
   286       assert.ok(message, "Script runs when allowed to do so.");
   287       done();
   288     },
   289     allow: { script: true },
   290     contentURL: "data:text/html;charset=utf-8,<script>document.documentElement.setAttribute('foo', 3);</script>",
   291     contentScript: "self.postMessage(document.documentElement.hasAttribute('foo') && " +
   292                    "                 document.documentElement.getAttribute('foo') == 3)",
   293     contentScriptWhen: "ready"
   294   });
   295 }
   297 exports.testPingPong = function(assert, done) {
   298   let page = Page({
   299     contentURL: 'data:text/html;charset=utf-8,ping-pong',
   300     contentScript: 'self.on("message", function(message) self.postMessage("pong"));'
   301       + 'self.postMessage("ready");',
   302     onMessage: function(message) {
   303       if ('ready' == message) {
   304         page.postMessage('ping');
   305       }
   306       else {
   307         assert.ok(message, 'pong', 'Callback from contentScript');
   308         done();
   309       }
   310     }
   311   });
   312 };
   314 exports.testRedirect = function (assert, done) {
   315   let page = Page({
   316     contentURL: 'data:text/html;charset=utf-8,first-page',
   317     contentScriptWhen: "end",
   318     contentScript: '' +
   319       'if (/first-page/.test(document.location.href)) ' +
   320       '  document.location.href = "data:text/html;charset=utf-8,redirect";' +
   321       'else ' +
   322       '  self.port.emit("redirect", document.location.href);'
   323   });
   325   page.port.on('redirect', function (url) {
   326     assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
   327     done();
   328   });
   329 };
   331 exports.testRedirectIncludeArrays = function (assert, done) {
   332   let firstURL = 'data:text/html;charset=utf-8,first-page';
   333   let page = Page({
   334     contentURL: firstURL,
   335     contentScript: '(function () {' +
   336       'self.port.emit("load", document.location.href);' +
   337       '  self.port.on("redirect", function (url) {' +
   338       '   document.location.href = url;' +
   339       '  })' +
   340       '})();',
   341     include: ['about:blank', 'data:*']
   342   });
   344   page.port.on('load', function (url) {
   345     if (url === firstURL) {
   346       page.port.emit('redirect', 'about:blank');
   347     } else if (url === 'about:blank') {
   348       page.port.emit('redirect', 'about:mozilla');
   349       assert.ok('`include` property handles arrays');
   350       assert.equal(url, 'about:blank', 'Redirects work with accepted domains');
   351       done();
   352     } else if (url === 'about:mozilla') {
   353       assert.fail('Should not redirect to restricted domain');
   354     }
   355   });
   356 };
   358 exports.testRedirectFromWorker = function (assert, done) {
   359   let firstURL = 'data:text/html;charset=utf-8,first-page';
   360   let secondURL = 'data:text/html;charset=utf-8,second-page';
   361   let thirdURL = 'data:text/html;charset=utf-8,third-page';
   362   let page = Page({
   363     contentURL: firstURL,
   364     contentScript: '(function () {' +
   365       'self.port.emit("load", document.location.href);' +
   366       '  self.port.on("redirect", function (url) {' +
   367       '   document.location.href = url;' +
   368       '  })' +
   369       '})();',
   370     include: 'data:*'
   371   });
   373   page.port.on('load', function (url) {
   374     if (url === firstURL) {
   375       page.port.emit('redirect', secondURL);
   376     } else if (url === secondURL) {
   377       page.port.emit('redirect', thirdURL);
   378     } else if (url === thirdURL) {
   379       page.port.emit('redirect', 'about:mozilla');
   380       assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
   381       done();
   382     } else {
   383       assert.fail('Should not redirect to unauthorized domains');
   384     }
   385   });
   386 };
   388 exports.testRedirectWithContentURL = function (assert, done) {
   389   let firstURL = 'data:text/html;charset=utf-8,first-page';
   390   let secondURL = 'data:text/html;charset=utf-8,second-page';
   391   let thirdURL = 'data:text/html;charset=utf-8,third-page';
   392   let page = Page({
   393     contentURL: firstURL,
   394     contentScript: '(function () {' +
   395       'self.port.emit("load", document.location.href);' +
   396       '})();',
   397     include: 'data:*'
   398   });
   400   page.port.on('load', function (url) {
   401     if (url === firstURL) {
   402       page.contentURL = secondURL;
   403     } else if (url === secondURL) {
   404       page.contentURL = thirdURL;
   405     } else if (url === thirdURL) {
   406       page.contentURL = 'about:mozilla';
   407       assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
   408       done();
   409     } else {
   410       assert.fail('Should not redirect to unauthorized domains');
   411     }
   412   });
   413 };
   416 exports.testMultipleDestroys = function(assert) {
   417   let page = Page();
   418   page.destroy();
   419   page.destroy();
   420   assert.pass("Multiple destroys should not cause an error");
   421 };
   423 exports.testContentScriptOptionsOption = function(assert, done) {
   424   let page = new Page({
   425     contentScript: "self.postMessage( [typeof self.options.d, self.options] );",
   426     contentScriptWhen: "end",
   427     contentScriptOptions: {a: true, b: [1,2,3], c: "string", d: function(){ return 'test'}},
   428     onMessage: function(msg) {
   429       assert.equal(msg[0], 'undefined', 'functions are stripped from contentScriptOptions');
   430       assert.equal(typeof msg[1], 'object', 'object as contentScriptOptions');
   431       assert.equal(msg[1].a, true, 'boolean in contentScriptOptions');
   432       assert.equal(msg[1].b.join(), '1,2,3', 'array and numbers in contentScriptOptions');
   433       assert.equal(msg[1].c, 'string', 'string in contentScriptOptions');
   434       done();
   435     }
   436   });
   437 };
   439 exports.testMessageQueue = function (assert, done) {
   440   let page = new Page({
   441     contentScript: 'self.on("message", function (m) {' +
   442       'self.postMessage(m);' +
   443       '});',
   444     contentURL: 'data:text/html;charset=utf-8,',
   445   });
   446   page.postMessage('ping');
   447   page.on('message', function (m) {
   448     assert.equal(m, 'ping', 'postMessage should queue messages');
   449     done();
   450   });
   451 };
   453 function isDestroyed(page) {
   454   try {
   455     page.postMessage("foo");
   456   }
   457   catch (err) {
   458     if (err.message == ERR_DESTROYED) {
   459       return true;
   460     }
   461     else {
   462       throw err;
   463     }
   464   }
   465   return false;
   466 }
   468 require("test").run(exports);

mercurial