addon-sdk/source/test/test-content-script.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/addon-sdk/source/test/test-content-script.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,868 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const hiddenFrames = require("sdk/frame/hidden-frame");
     1.9 +const { create: makeFrame } = require("sdk/frame/utils");
    1.10 +const { window } = require("sdk/addon/window");
    1.11 +const { Loader } = require('sdk/test/loader');
    1.12 +const { URL } = require("sdk/url");
    1.13 +const testURI = require("./fixtures").url("test.html");
    1.14 +const testHost = URL(testURI).scheme + '://' + URL(testURI).host;
    1.15 +
    1.16 +/*
    1.17 + * Utility function that allow to easily run a proxy test with a clean
    1.18 + * new HTML document. See first unit test for usage.
    1.19 + */
    1.20 +function createProxyTest(html, callback) {
    1.21 +  return function (assert, done) {
    1.22 +    let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
    1.23 +    let principalLoaded = false;
    1.24 +
    1.25 +    let element = makeFrame(window.document, {
    1.26 +      nodeName: "iframe",
    1.27 +      type: "content",
    1.28 +      allowJavascript: true,
    1.29 +      allowPlugins: true,
    1.30 +      allowAuth: true,
    1.31 +      uri: testURI
    1.32 +    });
    1.33 +
    1.34 +    element.addEventListener("DOMContentLoaded", onDOMReady, false);
    1.35 +
    1.36 +    function onDOMReady() {
    1.37 +      // Reload frame after getting principal from `testURI`
    1.38 +      if (!principalLoaded) {
    1.39 +        element.setAttribute("src", url);
    1.40 +        principalLoaded = true;
    1.41 +        return;
    1.42 +      }
    1.43 +
    1.44 +      assert.equal(element.getAttribute("src"), url, "correct URL loaded");
    1.45 +      element.removeEventListener("DOMContentLoaded", onDOMReady,
    1.46 +                                                  false);
    1.47 +      let xrayWindow = element.contentWindow;
    1.48 +      let rawWindow = xrayWindow.wrappedJSObject;
    1.49 +
    1.50 +      let isDone = false;
    1.51 +      let helper = {
    1.52 +        xrayWindow: xrayWindow,
    1.53 +        rawWindow: rawWindow,
    1.54 +        createWorker: function (contentScript) {
    1.55 +          return createWorker(assert, xrayWindow, contentScript, helper.done);
    1.56 +        },
    1.57 +        done: function () {
    1.58 +          if (isDone)
    1.59 +            return;
    1.60 +          isDone = true;
    1.61 +          element.parentNode.removeChild(element);
    1.62 +          done();
    1.63 +        }
    1.64 +      };
    1.65 +      callback(helper, assert);
    1.66 +    }
    1.67 +  };
    1.68 +}
    1.69 +
    1.70 +function createWorker(assert, xrayWindow, contentScript, done) {
    1.71 +  let loader = Loader(module);
    1.72 +  let Worker = loader.require("sdk/content/worker").Worker;
    1.73 +  let worker = Worker({
    1.74 +    window: xrayWindow,
    1.75 +    contentScript: [
    1.76 +      'new ' + function () {
    1.77 +        assert = function assert(v, msg) {
    1.78 +          self.port.emit("assert", {assertion:v, msg:msg});
    1.79 +        }
    1.80 +        done = function done() {
    1.81 +          self.port.emit("done");
    1.82 +        }
    1.83 +      },
    1.84 +      contentScript
    1.85 +    ]
    1.86 +  });
    1.87 +
    1.88 +  worker.port.on("done", done);
    1.89 +  worker.port.on("assert", function (data) {
    1.90 +    assert.ok(data.assertion, data.msg);
    1.91 +  });
    1.92 +
    1.93 +  return worker;
    1.94 +}
    1.95 +
    1.96 +/* Examples for the `createProxyTest` uses */
    1.97 +
    1.98 +let html = "<script>var documentGlobal = true</script>";
    1.99 +
   1.100 +exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) {
   1.101 +  // You can get access to regular `test` object in second argument of
   1.102 +  // `createProxyTest` method:
   1.103 +  assert.ok(helper.rawWindow.documentGlobal,
   1.104 +              "You have access to a raw window reference via `helper.rawWindow`");
   1.105 +  assert.ok(!("documentGlobal" in helper.xrayWindow),
   1.106 +              "You have access to an XrayWrapper reference via `helper.xrayWindow`");
   1.107 +
   1.108 +  // If you do not create a Worker, you have to call helper.done(),
   1.109 +  // in order to say when your test is finished
   1.110 +  helper.done();
   1.111 +});
   1.112 +
   1.113 +exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) {
   1.114 +
   1.115 +  helper.createWorker(
   1.116 +    "new " + function WorkerScope() {
   1.117 +      assert(true, "You can do assertions in your content script");
   1.118 +      // And if you create a worker, you either have to call `done`
   1.119 +      // from content script or helper.done()
   1.120 +      done();
   1.121 +    }
   1.122 +  );
   1.123 +
   1.124 +});
   1.125 +
   1.126 +exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) {
   1.127 +
   1.128 +  let worker = helper.createWorker(
   1.129 +    "new " + function WorkerScope() {
   1.130 +      self.port.emit("foo");
   1.131 +    }
   1.132 +  );
   1.133 +
   1.134 +  worker.port.on("foo", function () {
   1.135 +    assert.pass("You can use events");
   1.136 +    // And terminate your test with helper.done:
   1.137 +    helper.done();
   1.138 +  });
   1.139 +
   1.140 +});
   1.141 +
   1.142 +// Bug 714778: There was some issue around `toString` functions
   1.143 +//             that ended up being shared between content scripts
   1.144 +exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
   1.145 +
   1.146 +  let worker = helper.createWorker(
   1.147 +    'new ' + function ContentScriptScope() {
   1.148 +      // We ensure that `toString` can't be modified so that nothing could
   1.149 +      // leak to/from the document and between content scripts
   1.150 +      // It only applies to JS proxies, there isn't any such issue with xrays.
   1.151 +      //document.location.toString = function foo() {};
   1.152 +      document.location.toString.foo = "bar";
   1.153 +      assert("foo" in document.location.toString, "document.location.toString can be modified");
   1.154 +      assert(document.location.toString() == "data:text/html;charset=utf-8,",
   1.155 +             "First document.location.toString()");
   1.156 +      self.postMessage("next");
   1.157 +    }
   1.158 +  );
   1.159 +  worker.on("message", function () {
   1.160 +    helper.createWorker(
   1.161 +      'new ' + function ContentScriptScope2() {
   1.162 +        assert(!("foo" in document.location.toString),
   1.163 +               "document.location.toString is different for each content script");
   1.164 +        assert(document.location.toString() == "data:text/html;charset=utf-8,",
   1.165 +               "Second document.location.toString()");
   1.166 +        done();
   1.167 +      }
   1.168 +    );
   1.169 +  });
   1.170 +});
   1.171 +
   1.172 +
   1.173 +// Ensure that postMessage is working correctly across documents with an iframe
   1.174 +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
   1.175 +exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
   1.176 +  let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
   1.177 +  // Listen without proxies, to check that it will work in regular case
   1.178 +  // simulate listening from a web document.
   1.179 +  ifWindow.addEventListener("message", function listener(event) {
   1.180 +    ifWindow.removeEventListener("message", listener, false);
   1.181 +    // As we are in system principal, event is an XrayWrapper
   1.182 +    // xrays use current compartments when calling postMessage method.
   1.183 +    // Whereas js proxies was using postMessage method compartment,
   1.184 +    // not the caller one.
   1.185 +    assert.strictEqual(event.source, helper.xrayWindow,
   1.186 +                      "event.source is the top window");
   1.187 +    assert.equal(event.origin, testHost, "origin matches testHost");
   1.188 +
   1.189 +    assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
   1.190 +                     "message data is correct");
   1.191 +
   1.192 +    helper.done();
   1.193 +  }, false);
   1.194 +
   1.195 +  helper.createWorker(
   1.196 +    'new ' + function ContentScriptScope() {
   1.197 +      var json = JSON.stringify({foo : "bar\n \"escaped\"."});
   1.198 +
   1.199 +      document.getElementById("iframe").contentWindow.postMessage(json, "*");
   1.200 +    }
   1.201 +  );
   1.202 +});
   1.203 +
   1.204 +let html = '<input id="input2" type="checkbox" />';
   1.205 +exports["test Object Listener"] = createProxyTest(html, function (helper) {
   1.206 +
   1.207 +  helper.createWorker(
   1.208 +    'new ' + function ContentScriptScope() {
   1.209 +      // Test objects being given as event listener
   1.210 +      let input = document.getElementById("input2");
   1.211 +      let myClickListener = {
   1.212 +        called: false,
   1.213 +        handleEvent: function(event) {
   1.214 +          assert(this === myClickListener, "`this` is the original object");
   1.215 +          assert(!this.called, "called only once");
   1.216 +          this.called = true;
   1.217 +          assert(event.target, input, "event.target is the wrapped window");
   1.218 +          done();
   1.219 +        }
   1.220 +      };
   1.221 +
   1.222 +      window.addEventListener("click", myClickListener, true);
   1.223 +      input.click();
   1.224 +      window.removeEventListener("click", myClickListener, true);
   1.225 +    }
   1.226 +  );
   1.227 +
   1.228 +});
   1.229 +
   1.230 +exports["test Object Listener 2"] = createProxyTest("", function (helper) {
   1.231 +
   1.232 +  helper.createWorker(
   1.233 +    ('new ' + function ContentScriptScope() {
   1.234 +      // variable replaced with `testHost`
   1.235 +      let testHost = "TOKEN";
   1.236 +      // Verify object as DOM event listener
   1.237 +      let myMessageListener = {
   1.238 +        called: false,
   1.239 +        handleEvent: function(event) {
   1.240 +          window.removeEventListener("message", myMessageListener, true);
   1.241 +
   1.242 +          assert(this == myMessageListener, "`this` is the original object");
   1.243 +          assert(!this.called, "called only once");
   1.244 +          this.called = true;
   1.245 +          assert(event.target == document.defaultView, "event.target is the wrapped window");
   1.246 +          assert(event.source == document.defaultView, "event.source is the wrapped window");
   1.247 +          assert(event.origin == testHost, "origin matches testHost");
   1.248 +          assert(event.data == "ok", "message data is correct");
   1.249 +          done();
   1.250 +        }
   1.251 +      };
   1.252 +
   1.253 +      window.addEventListener("message", myMessageListener, true);
   1.254 +      document.defaultView.postMessage("ok", '*');
   1.255 +    }
   1.256 +  ).replace("TOKEN", testHost));
   1.257 +
   1.258 +});
   1.259 +
   1.260 +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
   1.261 +             '<input id="input2" type="checkbox" />';
   1.262 +
   1.263 +exports.testStringOverload = createProxyTest(html, function (helper, assert) {
   1.264 +  // Proxy - toString error
   1.265 +  let originalString = "string";
   1.266 +  let p = Proxy.create({
   1.267 +    get: function(receiver, name) {
   1.268 +      if (name == "binded")
   1.269 +        return originalString.toString.bind(originalString);
   1.270 +      return originalString[name];
   1.271 +    }
   1.272 +  });
   1.273 +  assert.throws(function () {
   1.274 +    p.toString();
   1.275 +  },
   1.276 +  /toString method called on incompatible Proxy/,
   1.277 +  "toString can't be called with this being the proxy");
   1.278 +  assert.equal(p.binded(), "string", "but it works if we bind this to the original string");
   1.279 +
   1.280 +  helper.createWorker(
   1.281 +    'new ' + function ContentScriptScope() {
   1.282 +      // RightJS is hacking around String.prototype, and do similar thing:
   1.283 +      // Pass `this` from a String prototype method.
   1.284 +      // It is funny because typeof this == "object"!
   1.285 +      // So that when we pass `this` to a native method,
   1.286 +      // our proxy code can fail on another even more crazy thing.
   1.287 +      // See following test to see what fails around proxies.
   1.288 +      String.prototype.update = function () {
   1.289 +        assert(typeof this == "object", "in update, `this` is an object");
   1.290 +        assert(this.toString() == "input", "in update, `this.toString works");
   1.291 +        return document.querySelectorAll(this);
   1.292 +      };
   1.293 +      assert("input".update().length == 3, "String.prototype overload works");
   1.294 +      done();
   1.295 +    }
   1.296 +  );
   1.297 +});
   1.298 +
   1.299 +exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {
   1.300 +  helper.createWorker(
   1.301 +    'new ' + function ContentScriptScope() {
   1.302 +      // Check mozMatchesSelector XrayWrappers bug:
   1.303 +      // mozMatchesSelector returns bad results when we are not calling it from the node itself
   1.304 +      // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
   1.305 +      assert(document.createElement( "div" ).mozMatchesSelector("div"),
   1.306 +             "mozMatchesSelector works while being called from the node");
   1.307 +      assert(document.documentElement.mozMatchesSelector.call(
   1.308 +               document.createElement( "div" ),
   1.309 +               "div"
   1.310 +             ),
   1.311 +             "mozMatchesSelector works while being called from a " +
   1.312 +             "function reference to " +
   1.313 +             "document.documentElement.mozMatchesSelector.call");
   1.314 +      done();
   1.315 +    }
   1.316 +  );
   1.317 +});
   1.318 +
   1.319 +exports["test Events Overload"] = createProxyTest("", function (helper) {
   1.320 +
   1.321 +  helper.createWorker(
   1.322 +    'new ' + function ContentScriptScope() {
   1.323 +      // If we add a "____proxy" attribute on XrayWrappers in order to store
   1.324 +      // the related proxy to create an unique proxy for each wrapper;
   1.325 +      // we end up setting this attribute to prototype objects :x
   1.326 +      // And so, instances created with such prototype will be considered
   1.327 +      // as equal to the prototype ...
   1.328 +      //   // Internal method that return the proxy for a given XrayWrapper
   1.329 +      //   function proxify(obj) {
   1.330 +      //     if (obj._proxy) return obj._proxy;
   1.331 +      //     return obj._proxy = Proxy.create(...);
   1.332 +      //   }
   1.333 +      //
   1.334 +      //   // Get a proxy of an XrayWrapper prototype object
   1.335 +      //   let proto = proxify(xpcProto);
   1.336 +      //
   1.337 +      //   // Use this proxy as a prototype
   1.338 +      //   function Constr() {}
   1.339 +      //   Constr.proto = proto;
   1.340 +      //
   1.341 +      //   // Try to create an instance using this prototype
   1.342 +      //   let xpcInstance = new Constr();
   1.343 +      //   let wrapper = proxify(xpcInstance)
   1.344 +      //
   1.345 +      //   xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,
   1.346 +      //   xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(
   1.347 +      //
   1.348 +      let proto = window.document.createEvent('HTMLEvents').__proto__;
   1.349 +      window.Event.prototype = proto;
   1.350 +      let event = document.createEvent('HTMLEvents');
   1.351 +      assert(event !== proto, "Event should not be equal to its prototype");
   1.352 +      event.initEvent('dataavailable', true, true);
   1.353 +      assert(event.type === 'dataavailable', "Events are working fine");
   1.354 +      done();
   1.355 +    }
   1.356 +  );
   1.357 +
   1.358 +});
   1.359 +
   1.360 +exports["test Nested Attributes"] = createProxyTest("", function (helper) {
   1.361 +
   1.362 +  helper.createWorker(
   1.363 +    'new ' + function ContentScriptScope() {
   1.364 +      // XrayWrappers has a bug when you set an attribute on it,
   1.365 +      // in some cases, it creates an unnecessary wrapper that introduces
   1.366 +      // a different object that refers to the same original object
   1.367 +      // Check that our wrappers don't reproduce this bug
   1.368 +      // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
   1.369 +      let o = {sandboxObject:true};
   1.370 +      window.nested = o;
   1.371 +      o.foo = true;
   1.372 +      assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");
   1.373 +      window.nested = document;
   1.374 +      assert(window.nested === document, "Nested attribute to proxy should not be double proxified");
   1.375 +      done();
   1.376 +    }
   1.377 +  );
   1.378 +
   1.379 +});
   1.380 +
   1.381 +exports["test Form nodeName"] = createProxyTest("", function (helper) {
   1.382 +
   1.383 +  helper.createWorker(
   1.384 +    'new ' + function ContentScriptScope() {
   1.385 +      let body = document.body;
   1.386 +      // Check form[nodeName]
   1.387 +      let form = document.createElement("form");
   1.388 +      let input = document.createElement("input");
   1.389 +      input.setAttribute("name", "test");
   1.390 +      form.appendChild(input);
   1.391 +      body.appendChild(form);
   1.392 +      assert(form.test == input, "form[nodeName] is valid");
   1.393 +      body.removeChild(form);
   1.394 +      done();
   1.395 +    }
   1.396 +  );
   1.397 +
   1.398 +});
   1.399 +
   1.400 +exports["test localStorage"] = createProxyTest("", function (helper, assert) {
   1.401 +
   1.402 +  let worker = helper.createWorker(
   1.403 +    'new ' + function ContentScriptScope() {
   1.404 +      // Check localStorage:
   1.405 +      assert(window.localStorage, "has access to localStorage");
   1.406 +      window.localStorage.name = 1;
   1.407 +      assert(window.localStorage.name == 1, "localStorage appears to work");
   1.408 +
   1.409 +      self.port.on("step2", function () {
   1.410 +        window.localStorage.clear();
   1.411 +        assert(window.localStorage.name == undefined, "localStorage really, really works");
   1.412 +        done();
   1.413 +      });
   1.414 +      self.port.emit("step1");
   1.415 +    }
   1.416 +  );
   1.417 +
   1.418 +  worker.port.on("step1", function () {
   1.419 +    assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works");
   1.420 +    worker.port.emit("step2");
   1.421 +  });
   1.422 +
   1.423 +});
   1.424 +
   1.425 +exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) {
   1.426 +
   1.427 +  helper.createWorker(
   1.428 +    'new ' + function ContentScriptScope() {
   1.429 +      let body = document.body;
   1.430 +      // Setting a custom object to a proxy attribute is not wrapped when we get it afterward
   1.431 +      let object = {custom: true, enumerable: false};
   1.432 +      body.customAttribute = object;
   1.433 +      assert(object === body.customAttribute, "custom JS attributes are not wrapped");
   1.434 +      done();
   1.435 +    }
   1.436 +  );
   1.437 +
   1.438 +});
   1.439 +
   1.440 +exports["test Object Tag"] = createProxyTest("", function (helper) {
   1.441 +
   1.442 +  helper.createWorker(
   1.443 +    'new ' + function ContentScriptScope() {
   1.444 +      // <object>, <embed> and other tags return typeof 'function'
   1.445 +      let flash = document.createElement("object");
   1.446 +      assert(typeof flash == "function", "<object> is typeof 'function'");
   1.447 +      assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");
   1.448 +      assert("setAttribute" in flash, "<object> has a setAttribute method");
   1.449 +      done();
   1.450 +    }
   1.451 +  );
   1.452 +
   1.453 +});
   1.454 +
   1.455 +exports["test Highlight toString Behavior"] = createProxyTest("", function (helper, assert) {
   1.456 +  // We do not have any workaround this particular use of toString
   1.457 +  // applied on <object> elements. So disable this test until we found one!
   1.458 +  //assert.equal(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");
   1.459 +  function f() {};
   1.460 +  let funToString = Object.prototype.toString.call(f);
   1.461 +  assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");
   1.462 +
   1.463 +  // This is how jquery call toString:
   1.464 +  let strToString = helper.rawWindow.Object.prototype.toString.call("");
   1.465 +  assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
   1.466 +
   1.467 +  let o = {__exposedProps__:{}};
   1.468 +  let objToString = helper.rawWindow.Object.prototype.toString.call(o);
   1.469 +  assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
   1.470 +
   1.471 +  // Make sure to pass a function from the same compartments
   1.472 +  // or toString will return [object Object] on FF8+
   1.473 +  let f2 = helper.rawWindow.eval("(function () {})");
   1.474 +  let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);
   1.475 +  assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");
   1.476 +
   1.477 +  helper.done();
   1.478 +});
   1.479 +
   1.480 +exports["test Document TagName"] = createProxyTest("", function (helper) {
   1.481 +
   1.482 +  helper.createWorker(
   1.483 +    'new ' + function ContentScriptScope() {
   1.484 +      let body = document.body;
   1.485 +      // Check document[tagName]
   1.486 +      let div = document.createElement("div");
   1.487 +      div.setAttribute("name", "test");
   1.488 +      body.appendChild(div);
   1.489 +      assert(!document.test, "document[divName] is undefined");
   1.490 +      body.removeChild(div);
   1.491 +
   1.492 +      let form = document.createElement("form");
   1.493 +      form.setAttribute("name", "test");
   1.494 +      body.appendChild(form);
   1.495 +      assert(document.test == form, "document[formName] is valid");
   1.496 +      body.removeChild(form);
   1.497 +
   1.498 +      let img = document.createElement("img");
   1.499 +      img.setAttribute("name", "test");
   1.500 +      body.appendChild(img);
   1.501 +      assert(document.test == img, "document[imgName] is valid");
   1.502 +      body.removeChild(img);
   1.503 +      done();
   1.504 +    }
   1.505 +  );
   1.506 +
   1.507 +});
   1.508 +
   1.509 +let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
   1.510 +exports["test Window Frames"] = createProxyTest(html, function (helper) {
   1.511 +
   1.512 +  helper.createWorker(
   1.513 +    'let glob = this; new ' + function ContentScriptScope() {
   1.514 +      // Check window[frameName] and window.frames[i]
   1.515 +      let iframe = document.getElementById("iframe");
   1.516 +      //assert(window.frames.length == 1, "The iframe is reported in window.frames check1");
   1.517 +      //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");
   1.518 +      assert(window.test == iframe.contentWindow, "window[frameName] is valid");
   1.519 +      done();
   1.520 +    }
   1.521 +  );
   1.522 +
   1.523 +});
   1.524 +
   1.525 +exports["test Collections"] = createProxyTest("", function (helper) {
   1.526 +
   1.527 +  helper.createWorker(
   1.528 +    'new ' + function ContentScriptScope() {
   1.529 +      // Highlight XPCNativeWrapper bug with HTMLCollection
   1.530 +      // tds[0] is only defined on first access :o
   1.531 +      let body = document.body;
   1.532 +      let div = document.createElement("div");
   1.533 +      body.appendChild(div);
   1.534 +      div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
   1.535 +      let tds = div.getElementsByTagName("td");
   1.536 +      assert(tds[0] == tds[0], "We can get array element multiple times");
   1.537 +      body.removeChild(div);
   1.538 +      done();
   1.539 +    }
   1.540 +  );
   1.541 +
   1.542 +});
   1.543 +
   1.544 +let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
   1.545 +             '<input id="input2" type="checkbox" />';
   1.546 +exports["test Collections 2"] = createProxyTest(html, function (helper) {
   1.547 +
   1.548 +  helper.createWorker(
   1.549 +    'new ' + function ContentScriptScope() {
   1.550 +      // Verify that NodeList/HTMLCollection are working fine
   1.551 +      let body = document.body;
   1.552 +      let inputs = body.getElementsByTagName("input");
   1.553 +      assert(body.childNodes.length == 3, "body.childNodes length is correct");
   1.554 +      assert(inputs.length == 3, "inputs.length is correct");
   1.555 +      assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");
   1.556 +      assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");
   1.557 +      assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");
   1.558 +      let count = 0;
   1.559 +      for(let i in body.childNodes) {
   1.560 +        count++;
   1.561 +      }
   1.562 +      assert(count == 6, "body.childNodes is iterable");
   1.563 +      done();
   1.564 +    }
   1.565 +  );
   1.566 +
   1.567 +});
   1.568 +
   1.569 +exports["test valueOf"] = createProxyTest("", function (helper) {
   1.570 +
   1.571 +    helper.createWorker(
   1.572 +      'new ' + function ContentScriptScope() {
   1.573 +        // Bug 787013: Until this bug is fixed, we are missing some methods
   1.574 +        // on JS objects that comes from global `Object` object
   1.575 +        assert(!('valueOf' in window), "valueOf is missing");
   1.576 +        assert(!('toLocateString' in window), "toLocaleString is missing");
   1.577 +        done();
   1.578 +      }
   1.579 +    );
   1.580 +
   1.581 +});
   1.582 +
   1.583 +exports["test XMLHttpRequest"] = createProxyTest("", function (helper) {
   1.584 +
   1.585 +  helper.createWorker(
   1.586 +    'new ' + function ContentScriptScope() {
   1.587 +      // XMLHttpRequest doesn't support XMLHttpRequest.apply,
   1.588 +      // that may break our proxy code
   1.589 +      assert(new window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");
   1.590 +      done();
   1.591 +    }
   1.592 +  );
   1.593 +
   1.594 +});
   1.595 +
   1.596 +exports["test XPathResult"] = createProxyTest("", function (helper, assert) {
   1.597 +  const XPathResultTypes = ["ANY_TYPE",
   1.598 +                            "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE",
   1.599 +                            "UNORDERED_NODE_ITERATOR_TYPE",
   1.600 +                            "ORDERED_NODE_ITERATOR_TYPE",
   1.601 +                            "UNORDERED_NODE_SNAPSHOT_TYPE",
   1.602 +                            "ORDERED_NODE_SNAPSHOT_TYPE",
   1.603 +                            "ANY_UNORDERED_NODE_TYPE",
   1.604 +                            "FIRST_ORDERED_NODE_TYPE"];
   1.605 +
   1.606 +  // Check XPathResult bug with constants being undefined on XPCNativeWrapper
   1.607 +  let xpcXPathResult = helper.xrayWindow.XPathResult;
   1.608 +
   1.609 +  XPathResultTypes.forEach(function(type, i) {
   1.610 +    assert.equal(xpcXPathResult.wrappedJSObject[type],
   1.611 +                     helper.rawWindow.XPathResult[type],
   1.612 +                     "XPathResult's constants are valid on unwrapped node");
   1.613 +
   1.614 +    assert.equal(xpcXPathResult[type], i,
   1.615 +                     "XPathResult's constants are defined on " +
   1.616 +                     "XPCNativeWrapper (platform bug #)");
   1.617 +  });
   1.618 +
   1.619 +  let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
   1.620 +  let worker = helper.createWorker(
   1.621 +    'new ' + function ContentScriptScope() {
   1.622 +      self.port.on("value", function (value) {
   1.623 +        // Check that our work around is working:
   1.624 +        assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,
   1.625 +               "XPathResult works correctly on Proxies");
   1.626 +        done();
   1.627 +      });
   1.628 +    }
   1.629 +  );
   1.630 +  worker.port.emit("value", value);
   1.631 +});
   1.632 +
   1.633 +exports["test Prototype Inheritance"] = createProxyTest("", function (helper) {
   1.634 +
   1.635 +  helper.createWorker(
   1.636 +    'new ' + function ContentScriptScope() {
   1.637 +      // Verify that inherited prototype function like initEvent
   1.638 +      // are handled correctly. (e2.type will return an error if it's not the case)
   1.639 +      let event1 = document.createEvent( 'MouseEvents' );
   1.640 +      event1.initEvent( "click", true, true );
   1.641 +      let event2 = document.createEvent( 'MouseEvents' );
   1.642 +      event2.initEvent( "click", true, true );
   1.643 +      assert(event2.type == "click", "We are able to create an event");
   1.644 +      done();
   1.645 +    }
   1.646 +  );
   1.647 +
   1.648 +});
   1.649 +
   1.650 +exports["test Functions"] = createProxyTest("", function (helper) {
   1.651 +
   1.652 +  helper.rawWindow.callFunction = function callFunction(f) f();
   1.653 +  helper.rawWindow.isEqual = function isEqual(a, b) a == b;
   1.654 +  // bug 784116: workaround in order to allow proxy code to cache proxies on
   1.655 +  // these functions:
   1.656 +  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
   1.657 +  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
   1.658 +
   1.659 +  helper.createWorker(
   1.660 +    'new ' + function ContentScriptScope() {
   1.661 +      // Check basic usage of functions
   1.662 +      let closure2 = function () {return "ok";};
   1.663 +      assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");
   1.664 +
   1.665 +      // Ensure that functions are cached when being wrapped to native code
   1.666 +      let closure = function () {};
   1.667 +      assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");
   1.668 +      done();
   1.669 +    }
   1.670 +  );
   1.671 +
   1.672 +});
   1.673 +
   1.674 +let html = '<input id="input2" type="checkbox" />';
   1.675 +exports["test Listeners"] = createProxyTest(html, function (helper) {
   1.676 +
   1.677 +  helper.createWorker(
   1.678 +    'new ' + function ContentScriptScope() {
   1.679 +      // Verify listeners:
   1.680 +      let input = document.getElementById("input2");
   1.681 +      assert(input, "proxy.getElementById works");
   1.682 +
   1.683 +      function onclick() {};
   1.684 +      input.onclick = onclick;
   1.685 +      assert(input.onclick === onclick, "on* attributes are equal to original function set");
   1.686 +
   1.687 +      let addEventListenerCalled = false;
   1.688 +      let expandoCalled = false;
   1.689 +      input.addEventListener("click", function onclick(event) {
   1.690 +        input.removeEventListener("click", onclick, true);
   1.691 +
   1.692 +        assert(!addEventListenerCalled, "closure given to addEventListener is called once");
   1.693 +        if (addEventListenerCalled)
   1.694 +          return;
   1.695 +        addEventListenerCalled = true;
   1.696 +
   1.697 +        assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
   1.698 +
   1.699 +        let input2 = document.getElementById("input2");
   1.700 +
   1.701 +        input.onclick = function (event) {
   1.702 +          input.onclick = null;
   1.703 +          assert(!expandoCalled, "closure set to expando is called once");
   1.704 +          if (expandoCalled) return;
   1.705 +          expandoCalled = true;
   1.706 +
   1.707 +          assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");
   1.708 +
   1.709 +          setTimeout(function () {
   1.710 +            input.click();
   1.711 +            done();
   1.712 +          }, 0);
   1.713 +
   1.714 +        }
   1.715 +
   1.716 +        setTimeout(function () {
   1.717 +          input.click();
   1.718 +        }, 0);
   1.719 +
   1.720 +      }, true);
   1.721 +
   1.722 +      input.click();
   1.723 +    }
   1.724 +  );
   1.725 +
   1.726 +});
   1.727 +
   1.728 +exports["test MozRequestAnimationFrame"] = createProxyTest("", function (helper) {
   1.729 +
   1.730 +  helper.createWorker(
   1.731 +    'new ' + function ContentScriptScope() {
   1.732 +      window.mozRequestAnimationFrame(function callback() {
   1.733 +        assert(callback == this, "callback is equal to `this`");
   1.734 +        done();
   1.735 +      });
   1.736 +    }
   1.737 +  );
   1.738 +
   1.739 +});
   1.740 +
   1.741 +exports["testGlobalScope"] = createProxyTest("", function (helper) {
   1.742 +
   1.743 +  helper.createWorker(
   1.744 +    'let toplevelScope = true;' +
   1.745 +    'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +
   1.746 +    'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +
   1.747 +    'done();'
   1.748 +  );
   1.749 +
   1.750 +});
   1.751 +
   1.752 +// Bug 715755: proxy code throw an exception on COW
   1.753 +// Create an http server in order to simulate real cross domain documents
   1.754 +exports["test Cross Domain Iframe"] = createProxyTest("", function (helper) {
   1.755 +  let serverPort = 8099;
   1.756 +  let server = require("sdk/test/httpd").startServerAsync(serverPort);
   1.757 +  server.registerPathHandler("/", function handle(request, response) {
   1.758 +    // Returns the webpage that receive a message and forward it back to its
   1.759 +    // parent document by appending ' world'.
   1.760 +    let content = "<html><head><meta charset='utf-8'></head>\n";
   1.761 +    content += "<script>\n";
   1.762 +    content += "  window.addEventListener('message', function (event) {\n";
   1.763 +    content += "    parent.postMessage(event.data + ' world', '*');\n";
   1.764 +    content += "  }, true);\n";
   1.765 +    content += "</script>\n";
   1.766 +    content += "<body></body>\n";
   1.767 +    content += "</html>\n";
   1.768 +    response.write(content);
   1.769 +  });
   1.770 +
   1.771 +  let worker = helper.createWorker(
   1.772 +    'new ' + function ContentScriptScope() {
   1.773 +      // Waits for the server page url
   1.774 +      self.on("message", function (url) {
   1.775 +        // Creates an iframe with this page
   1.776 +        let iframe = document.createElement("iframe");
   1.777 +        iframe.addEventListener("load", function onload() {
   1.778 +          iframe.removeEventListener("load", onload, true);
   1.779 +          try {
   1.780 +            // Try to communicate with iframe's content
   1.781 +            window.addEventListener("message", function onmessage(event) {
   1.782 +              window.removeEventListener("message", onmessage, true);
   1.783 +
   1.784 +              assert(event.data == "hello world", "COW works properly");
   1.785 +              self.port.emit("end");
   1.786 +            }, true);
   1.787 +            iframe.contentWindow.postMessage("hello", "*");
   1.788 +          } catch(e) {
   1.789 +            assert(false, "COW fails : "+e.message);
   1.790 +          }
   1.791 +        }, true);
   1.792 +        iframe.setAttribute("src", url);
   1.793 +        document.body.appendChild(iframe);
   1.794 +      });
   1.795 +    }
   1.796 +  );
   1.797 +
   1.798 +  worker.port.on("end", function () {
   1.799 +    server.stop(helper.done);
   1.800 +  });
   1.801 +
   1.802 +  worker.postMessage("http://localhost:" + serverPort + "/");
   1.803 +
   1.804 +});
   1.805 +
   1.806 +// Bug 769006: Ensure that MutationObserver works fine with proxies
   1.807 +let html = '<a href="foo">link</a>';
   1.808 +exports["test MutationObvserver"] = createProxyTest(html, function (helper) {
   1.809 +
   1.810 +  helper.createWorker(
   1.811 +    'new ' + function ContentScriptScope() {
   1.812 +      if (typeof MutationObserver == "undefined") {
   1.813 +        assert(true, "No MutationObserver for this FF version");
   1.814 +        done();
   1.815 +        return;
   1.816 +      }
   1.817 +      let link = document.getElementsByTagName("a")[0];
   1.818 +
   1.819 +      // Register a Mutation observer
   1.820 +      let obs = new MutationObserver(function(mutations){
   1.821 +        // Ensure that mutation data are valid
   1.822 +        assert(mutations.length == 1, "only one attribute mutation");
   1.823 +        let mutation = mutations[0];
   1.824 +        assert(mutation.type == "attributes", "check `type`");
   1.825 +        assert(mutation.target == link, "check `target`");
   1.826 +        assert(mutation.attributeName == "href", "check `attributeName`");
   1.827 +        assert(mutation.oldValue == "foo", "check `oldValue`");
   1.828 +        obs.disconnect();
   1.829 +        done();
   1.830 +      });
   1.831 +      obs.observe(document, {
   1.832 +        subtree: true,
   1.833 +        attributes: true,
   1.834 +        attributeOldValue: true,
   1.835 +        attributeFilter: ["href"]
   1.836 +      });
   1.837 +
   1.838 +      // Modify the DOM
   1.839 +      link.setAttribute("href", "bar");
   1.840 +    }
   1.841 +  );
   1.842 +
   1.843 +});
   1.844 +
   1.845 +let html = '<script>' +
   1.846 +  'var accessCheck = function() {' +
   1.847 +  '  assert(true, "exporting function works");' +
   1.848 +  '  try{' +
   1.849 +  '    exportedObj.prop;' +
   1.850 +  '    assert(false, "content should not have access to content-script");' +
   1.851 +  '  } catch(e) {' +
   1.852 +  '    assert(e.toString().indexOf("Permission denied") != -1,' +
   1.853 +  '           "content should not have access to content-script");' +
   1.854 +  '  }' +
   1.855 +  '}</script>';
   1.856 +exports["test nsEp for content-script"] = createProxyTest(html, function (helper) {
   1.857 +
   1.858 +  helper.createWorker(
   1.859 +    'let glob = this; new ' + function ContentScriptScope() {
   1.860 +
   1.861 +      exportFunction(assert, unsafeWindow, { defineAs: "assert" });
   1.862 +      window.wrappedJSObject.assert(true, "assert exported");
   1.863 +      window.wrappedJSObject.exportedObj = { prop: 42 };
   1.864 +      window.wrappedJSObject.accessCheck();
   1.865 +      done();
   1.866 +    }
   1.867 +  );
   1.868 +
   1.869 +});
   1.870 +
   1.871 +require("test").run(exports);

mercurial