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.

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

mercurial