| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849 | /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */const hiddenFrames = require("sdk/frame/hidden-frame");const { create: makeFrame } = require("sdk/frame/utils");const { window } = require("sdk/addon/window");const { Loader } = require('sdk/test/loader');const { URL } = require("sdk/url");const testURI = require("./fixtures").url("test.html");const testHost = URL(testURI).scheme + '://' + URL(testURI).host;/* * Utility function that allow to easily run a proxy test with a clean * new HTML document. See first unit test for usage. */function createProxyTest(html, callback) {  return function (assert, done) {    let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);    let principalLoaded = false;    let element = makeFrame(window.document, {      nodeName: "iframe",      type: "content",      allowJavascript: true,      allowPlugins: true,      allowAuth: true,      uri: testURI    });    element.addEventListener("DOMContentLoaded", onDOMReady, false);    function onDOMReady() {      // Reload frame after getting principal from `testURI`      if (!principalLoaded) {        element.setAttribute("src", url);        principalLoaded = true;        return;      }      assert.equal(element.getAttribute("src"), url, "correct URL loaded");      element.removeEventListener("DOMContentLoaded", onDOMReady,                                                  false);      let xrayWindow = element.contentWindow;      let rawWindow = xrayWindow.wrappedJSObject;      let isDone = false;      let helper = {        xrayWindow: xrayWindow,        rawWindow: rawWindow,        createWorker: function (contentScript) {          return createWorker(assert, xrayWindow, contentScript, helper.done);        },        done: function () {          if (isDone)            return;          isDone = true;          element.parentNode.removeChild(element);          done();        }      };      callback(helper, assert);    }  };}function createWorker(assert, xrayWindow, contentScript, done) {  let loader = Loader(module);  let Worker = loader.require("sdk/content/worker").Worker;  let worker = Worker({    window: xrayWindow,    contentScript: [      'new ' + function () {        assert = function assert(v, msg) {          self.port.emit("assert", {assertion:v, msg:msg});        }        done = function done() {          self.port.emit("done");        }      },      contentScript    ]  });  worker.port.on("done", done);  worker.port.on("assert", function (data) {    assert.ok(data.assertion, data.msg);  });  return worker;}/* Examples for the `createProxyTest` uses */let html = "<script>var documentGlobal = true</script>";exports["test Create Proxy Test"] = createProxyTest(html, function (helper, assert) {  // You can get access to regular `test` object in second argument of  // `createProxyTest` method:  assert.ok(helper.rawWindow.documentGlobal,              "You have access to a raw window reference via `helper.rawWindow`");  assert.ok(!("documentGlobal" in helper.xrayWindow),              "You have access to an XrayWrapper reference via `helper.xrayWindow`");  // If you do not create a Worker, you have to call helper.done(),  // in order to say when your test is finished  helper.done();});exports["test Create Proxy Test With Worker"] = createProxyTest("", function (helper) {  helper.createWorker(    "new " + function WorkerScope() {      assert(true, "You can do assertions in your content script");      // And if you create a worker, you either have to call `done`      // from content script or helper.done()      done();    }  );});exports["test Create Proxy Test With Events"] = createProxyTest("", function (helper, assert) {  let worker = helper.createWorker(    "new " + function WorkerScope() {      self.port.emit("foo");    }  );  worker.port.on("foo", function () {    assert.pass("You can use events");    // And terminate your test with helper.done:    helper.done();  });});// Bug 714778: There was some issue around `toString` functions//             that ended up being shared between content scriptsexports["test Shared To String Proxies"] = createProxyTest("", function(helper) {  let worker = helper.createWorker(    'new ' + function ContentScriptScope() {      // We ensure that `toString` can't be modified so that nothing could      // leak to/from the document and between content scripts      // It only applies to JS proxies, there isn't any such issue with xrays.      //document.location.toString = function foo() {};      document.location.toString.foo = "bar";      assert("foo" in document.location.toString, "document.location.toString can be modified");      assert(document.location.toString() == "data:text/html;charset=utf-8,",             "First document.location.toString()");      self.postMessage("next");    }  );  worker.on("message", function () {    helper.createWorker(      'new ' + function ContentScriptScope2() {        assert(!("foo" in document.location.toString),               "document.location.toString is different for each content script");        assert(document.location.toString() == "data:text/html;charset=utf-8,",               "Second document.location.toString()");        done();      }    );  });});// Ensure that postMessage is working correctly across documents with an iframelet html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';exports["test postMessage"] = createProxyTest(html, function (helper, assert) {  let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;  // Listen without proxies, to check that it will work in regular case  // simulate listening from a web document.  ifWindow.addEventListener("message", function listener(event) {    ifWindow.removeEventListener("message", listener, false);    // As we are in system principal, event is an XrayWrapper    // xrays use current compartments when calling postMessage method.    // Whereas js proxies was using postMessage method compartment,    // not the caller one.    assert.strictEqual(event.source, helper.xrayWindow,                      "event.source is the top window");    assert.equal(event.origin, testHost, "origin matches testHost");    assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",                     "message data is correct");    helper.done();  }, false);  helper.createWorker(    'new ' + function ContentScriptScope() {      assert(postMessage === postMessage,          "verify that we doesn't generate multiple functions for the same method");      var json = JSON.stringify({foo : "bar\n \"escaped\"."});      document.getElementById("iframe").contentWindow.postMessage(json, "*");    }  );});let html = '<input id="input2" type="checkbox" />';exports["test Object Listener"] = createProxyTest(html, function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Test objects being given as event listener      let input = document.getElementById("input2");      let myClickListener = {        called: false,        handleEvent: function(event) {          assert(this === myClickListener, "`this` is the original object");          assert(!this.called, "called only once");          this.called = true;          assert(event.target, input, "event.target is the wrapped window");          done();        }      };      window.addEventListener("click", myClickListener, true);      input.click();      window.removeEventListener("click", myClickListener, true);    }  );});exports["test Object Listener 2"] = createProxyTest("", function (helper) {  helper.createWorker(    ('new ' + function ContentScriptScope() {      // variable replaced with `testHost`      let testHost = "TOKEN";      // Verify object as DOM event listener      let myMessageListener = {        called: false,        handleEvent: function(event) {          window.removeEventListener("message", myMessageListener, true);          assert(this == myMessageListener, "`this` is the original object");          assert(!this.called, "called only once");          this.called = true;          assert(event.target == document.defaultView, "event.target is the wrapped window");          assert(event.source == document.defaultView, "event.source is the wrapped window");          assert(event.origin == testHost, "origin matches testHost");          assert(event.data == "ok", "message data is correct");          done();        }      };      window.addEventListener("message", myMessageListener, true);      document.defaultView.postMessage("ok", '*');    }  ).replace("TOKEN", testHost));});let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +             '<input id="input2" type="checkbox" />';/* Disable test to keep tree green until Bug 756214 is fixed.exports.testStringOverload = createProxyTest(html, function (helper, test) {  // Proxy - toString error  let originalString = "string";  let p = Proxy.create({    get: function(receiver, name) {      if (name == "binded")        return originalString.toString.bind(originalString);      return originalString[name];    }  });  assert.okRaises(function () {    p.toString();  },  /String.prototype.toString called on incompatible Proxy/,  "toString can't be called with this being the proxy");  assert.equal(p.binded(), "string", "but it works if we bind this to the original string");  helper.createWorker(    'new ' + function ContentScriptScope() {      // RightJS is hacking around String.prototype, and do similar thing:      // Pass `this` from a String prototype method.      // It is funny because typeof this == "object"!      // So that when we pass `this` to a native method,      // our proxy code can fail on another even more crazy thing.      // See following test to see what fails around proxies.      String.prototype.update = function () {        assert(typeof this == "object", "in update, `this` is an object");        assert(this.toString() == "input", "in update, `this.toString works");        return document.querySelectorAll(this);      };      assert("input".update().length == 3, "String.prototype overload works");      done();    }  );});*/exports["test MozMatchedSelector"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Check mozMatchesSelector XrayWrappers bug:      // mozMatchesSelector returns bad results when we are not calling it from the node itself      // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers      assert(document.createElement( "div" ).mozMatchesSelector("div"),             "mozMatchesSelector works while being called from the node");      assert(document.documentElement.mozMatchesSelector.call(               document.createElement( "div" ),               "div"             ),             "mozMatchesSelector works while being called from a " +             "function reference to " +             "document.documentElement.mozMatchesSelector.call");      done();    }  );});exports["test Events Overload"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // If we add a "____proxy" attribute on XrayWrappers in order to store      // the related proxy to create an unique proxy for each wrapper;      // we end up setting this attribute to prototype objects :x      // And so, instances created with such prototype will be considered      // as equal to the prototype ...      //   // Internal method that return the proxy for a given XrayWrapper      //   function proxify(obj) {      //     if (obj._proxy) return obj._proxy;      //     return obj._proxy = Proxy.create(...);      //   }      //      //   // Get a proxy of an XrayWrapper prototype object      //   let proto = proxify(xpcProto);      //      //   // Use this proxy as a prototype      //   function Constr() {}      //   Constr.proto = proto;      //      //   // Try to create an instance using this prototype      //   let xpcInstance = new Constr();      //   let wrapper = proxify(xpcInstance)      //      //   xpcProto._proxy = proto and as xpcInstance.__proto__ = xpcProto,      //   xpcInstance._proxy = proto ... and profixy(xpcInstance) = proto :(      //      let proto = window.document.createEvent('HTMLEvents').__proto__;      window.Event.prototype = proto;      let event = document.createEvent('HTMLEvents');      assert(event !== proto, "Event should not be equal to its prototype");      event.initEvent('dataavailable', true, true);      assert(event.type === 'dataavailable', "Events are working fine");      done();    }  );});exports["test Nested Attributes"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // XrayWrappers has a bug when you set an attribute on it,      // in some cases, it creates an unnecessary wrapper that introduces      // a different object that refers to the same original object      // Check that our wrappers don't reproduce this bug      // SEE BUG 658560: Fix identity problem with CrossOriginWrappers      let o = {sandboxObject:true};      window.nested = o;      o.foo = true;      assert(o === window.nested, "Nested attribute to sandbox object should not be proxified");      window.nested = document;      assert(window.nested === document, "Nested attribute to proxy should not be double proxified");      done();    }  );});exports["test Form nodeName"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      let body = document.body;      // Check form[nodeName]      let form = document.createElement("form");      let input = document.createElement("input");      input.setAttribute("name", "test");      form.appendChild(input);      body.appendChild(form);      assert(form.test == input, "form[nodeName] is valid");      body.removeChild(form);      done();    }  );});exports["test localStorage"] = createProxyTest("", function (helper, assert) {  let worker = helper.createWorker(    'new ' + function ContentScriptScope() {      // Check localStorage:      assert(window.localStorage, "has access to localStorage");      window.localStorage.name = 1;      assert(window.localStorage.name == 1, "localStorage appears to work");      self.port.on("step2", function () {        window.localStorage.clear();        assert(window.localStorage.name == undefined, "localStorage really, really works");        done();      });      self.port.emit("step1");    }  );  worker.port.on("step1", function () {    assert.equal(helper.rawWindow.localStorage.name, 1, "localStorage really works");    worker.port.emit("step2");  });});exports["test Auto Unwrap Custom Attributes"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      let body = document.body;      // Setting a custom object to a proxy attribute is not wrapped when we get it afterward      let object = {custom: true, enumerable: false};      body.customAttribute = object;      assert(object === body.customAttribute, "custom JS attributes are not wrapped");      done();    }  );});exports["test Object Tag"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // <object>, <embed> and other tags return typeof 'function'      let flash = document.createElement("object");      assert(typeof flash == "function", "<object> is typeof 'function'");      assert(flash.toString().match(/\[object HTMLObjectElement.*\]/), "<object> is HTMLObjectElement");      assert("setAttribute" in flash, "<object> has a setAttribute method");      done();    }  );});exports["test Highlight toString Behavior"] = createProxyTest("", function (helper, assert) {  // We do not have any workaround this particular use of toString  // applied on <object> elements. So disable this test until we found one!  //assert.equal(helper.rawWindow.Object.prototype.toString.call(flash), "[object HTMLObjectElement]", "<object> is HTMLObjectElement");  function f() {};  let funToString = Object.prototype.toString.call(f);  assert.ok(/\[object Function.*\]/.test(funToString), "functions are functions 1");  // This is how jquery call toString:  let strToString = helper.rawWindow.Object.prototype.toString.call("");  assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");  let o = {__exposedProps__:{}};  let objToString = helper.rawWindow.Object.prototype.toString.call(o);  assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");  // Make sure to pass a function from the same compartments  // or toString will return [object Object] on FF8+  let f2 = helper.rawWindow.eval("(function () {})");  let funToString2 = helper.rawWindow.Object.prototype.toString.call(f2);  assert.ok(/\[object Function.*\]/.test(funToString2), "functions are functions 2");  helper.done();});exports["test Document TagName"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      let body = document.body;      // Check document[tagName]      let div = document.createElement("div");      div.setAttribute("name", "test");      body.appendChild(div);      assert(!document.test, "document[divName] is undefined");      body.removeChild(div);      let form = document.createElement("form");      form.setAttribute("name", "test");      body.appendChild(form);      assert(document.test == form, "document[formName] is valid");      body.removeChild(form);      let img = document.createElement("img");      img.setAttribute("name", "test");      body.appendChild(img);      assert(document.test == img, "document[imgName] is valid");      body.removeChild(img);      done();    }  );});let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';exports["test Window Frames"] = createProxyTest(html, function (helper) {  helper.createWorker(    'let glob = this; new ' + function ContentScriptScope() {      // Check window[frameName] and window.frames[i]      let iframe = document.getElementById("iframe");      //assert(window.frames.length == 1, "The iframe is reported in window.frames check1");      //assert(window.frames[0] == iframe.contentWindow, "The iframe is reported in window.frames check2");      //console.log(window.test+ "-"+iframe.contentWindow);      //console.log(window);      assert(window.test == iframe.contentWindow, "window[frameName] is valid");      done();    }  );});exports["test Collections"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Highlight XPCNativeWrapper bug with HTMLCollection      // tds[0] is only defined on first access :o      let body = document.body;      let div = document.createElement("div");      body.appendChild(div);      div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";      let tds = div.getElementsByTagName("td");      assert(tds[0] == tds[0], "We can get array element multiple times");      body.removeChild(div);      done();    }  );});let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +             '<input id="input2" type="checkbox" />';exports["test Collections 2"] = createProxyTest(html, function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Verify that NodeList/HTMLCollection are working fine      let body = document.body;      let inputs = body.getElementsByTagName("input");      assert(body.childNodes.length == 3, "body.childNodes length is correct");      assert(inputs.length == 3, "inputs.length is correct");      assert(body.childNodes[0] == inputs[0], "body.childNodes[0] is correct");      assert(body.childNodes[1] == inputs[1], "body.childNodes[1] is correct");      assert(body.childNodes[2] == inputs[2], "body.childNodes[2] is correct");      let count = 0;      for(let i in body.childNodes) {        count++;      }      assert(count == 6, "body.childNodes is iterable");      done();    }  );});exports["test valueOf"] = createProxyTest("", function (helper) {    helper.createWorker(      'new ' + function ContentScriptScope() {        // Bug 787013: Until this bug is fixed, we are missing some methods        // on JS objects that comes from global `Object` object        assert(!('valueOf' in window), "valueOf is missing");        assert(!('toLocateString' in window), "toLocaleString is missing");        done();      }    );});exports["test XMLHttpRequest"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // XMLHttpRequest doesn't support XMLHttpRequest.apply,      // that may break our proxy code      assert(window.XMLHttpRequest(), "we are able to instantiate XMLHttpRequest object");      done();    }  );});exports["test XPathResult"] = createProxyTest("", function (helper, assert) {  const XPathResultTypes = ["ANY_TYPE",                            "NUMBER_TYPE", "STRING_TYPE", "BOOLEAN_TYPE",                            "UNORDERED_NODE_ITERATOR_TYPE",                            "ORDERED_NODE_ITERATOR_TYPE",                            "UNORDERED_NODE_SNAPSHOT_TYPE",                            "ORDERED_NODE_SNAPSHOT_TYPE",                            "ANY_UNORDERED_NODE_TYPE",                            "FIRST_ORDERED_NODE_TYPE"];  // Check XPathResult bug with constants being undefined on XPCNativeWrapper  let xpcXPathResult = helper.xrayWindow.XPathResult;  XPathResultTypes.forEach(function(type, i) {    assert.equal(xpcXPathResult.wrappedJSObject[type],                     helper.rawWindow.XPathResult[type],                     "XPathResult's constants are valid on unwrapped node");    assert.equal(xpcXPathResult[type], i,                     "XPathResult's constants are defined on " +                     "XPCNativeWrapper (platform bug #)");  });  let value = helper.rawWindow.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;  let worker = helper.createWorker(    'new ' + function ContentScriptScope() {      self.port.on("value", function (value) {        // Check that our work around is working:        assert(window.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === value,               "XPathResult works correctly on Proxies");        done();      });    }  );  worker.port.emit("value", value);});exports["test Prototype Inheritance"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Verify that inherited prototype function like initEvent      // are handled correctly. (e2.type will return an error if it's not the case)      let event1 = document.createEvent( 'MouseEvents' );      event1.initEvent( "click", true, true );      let event2 = document.createEvent( 'MouseEvents' );      event2.initEvent( "click", true, true );      assert(event2.type == "click", "We are able to create an event");      done();    }  );});exports["test Functions"] = createProxyTest("", function (helper) {  helper.rawWindow.callFunction = function callFunction(f) f();  helper.rawWindow.isEqual = function isEqual(a, b) a == b;  // bug 784116: workaround in order to allow proxy code to cache proxies on  // these functions:  helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};  helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};  helper.createWorker(    'new ' + function ContentScriptScope() {      // Check basic usage of functions      let closure2 = function () {return "ok";};      assert(window.wrappedJSObject.callFunction(closure2) == "ok", "Function references work");      // Ensure that functions are cached when being wrapped to native code      let closure = function () {};      assert(window.wrappedJSObject.isEqual(closure, closure), "Function references are cached before being wrapped to native");      done();    }  );});let html = '<input id="input2" type="checkbox" />';exports["test Listeners"] = createProxyTest(html, function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      // Verify listeners:      let input = document.getElementById("input2");      assert(input, "proxy.getElementById works");      function onclick() {};      input.onclick = onclick;      assert(input.onclick === onclick, "on* attributes are equal to original function set");      let addEventListenerCalled = false;      let expandoCalled = false;      input.addEventListener("click", function onclick(event) {        input.removeEventListener("click", onclick, true);        assert(!addEventListenerCalled, "closure given to addEventListener is called once");        if (addEventListenerCalled)          return;        addEventListenerCalled = true;        assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");        let input2 = document.getElementById("input2");        input.onclick = function (event) {          input.onclick = null;          assert(!expandoCalled, "closure set to expando is called once");          if (expandoCalled) return;          expandoCalled = true;          assert(!event.target.ownerDocument.defaultView.documentGlobal, "event object is still wrapped and doesn't expose document globals");          setTimeout(function () {            input.click();            done();          }, 0);        }        setTimeout(function () {          input.click();        }, 0);      }, true);      input.click();    }  );});exports["test MozRequestAnimationFrame"] = createProxyTest("", function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      window.mozRequestAnimationFrame(function callback() {        assert(callback == this, "callback is equal to `this`");        done();      });    }  );});exports["testGlobalScope"] = createProxyTest("", function (helper) {  helper.createWorker(    'let toplevelScope = true;' +    'assert(window.toplevelScope, "variables in toplevel scope are set to `window` object");' +    'assert(this.toplevelScope, "variables in toplevel scope are set to `this` object");' +    'done();'  );});// Bug 715755: proxy code throw an exception on COW// Create an http server in order to simulate real cross domain documentsexports["test Cross Domain Iframe"] = createProxyTest("", function (helper) {  let serverPort = 8099;  let server = require("sdk/test/httpd").startServerAsync(serverPort);  server.registerPathHandler("/", function handle(request, response) {    // Returns the webpage that receive a message and forward it back to its    // parent document by appending ' world'.    let content = "<html><head><meta charset='utf-8'></head>\n";    content += "<script>\n";    content += "  window.addEventListener('message', function (event) {\n";    content += "    parent.postMessage(event.data + ' world', '*');\n";    content += "  }, true);\n";    content += "</script>\n";    content += "<body></body>\n";    content += "</html>\n";    response.write(content);  });  let worker = helper.createWorker(    'new ' + function ContentScriptScope() {      // Waits for the server page url      self.on("message", function (url) {        // Creates an iframe with this page        let iframe = document.createElement("iframe");        iframe.addEventListener("load", function onload() {          iframe.removeEventListener("load", onload, true);          try {            // Try to communicate with iframe's content            window.addEventListener("message", function onmessage(event) {              window.removeEventListener("message", onmessage, true);              assert(event.data == "hello world", "COW works properly");              self.port.emit("end");            }, true);            iframe.contentWindow.postMessage("hello", "*");          } catch(e) {            assert(false, "COW fails : "+e.message);          }        }, true);        iframe.setAttribute("src", url);        document.body.appendChild(iframe);      });    }  );  worker.port.on("end", function () {    server.stop(helper.done);  });  worker.postMessage("http://localhost:" + serverPort + "/");});// Bug 769006: Ensure that MutationObserver works fine with proxieslet html = '<a href="foo">link</a>';exports["test MutationObvserver"] = createProxyTest(html, function (helper) {  helper.createWorker(    'new ' + function ContentScriptScope() {      if (typeof MutationObserver == "undefined") {        assert(true, "No MutationObserver for this FF version");        done();        return;      }      let link = document.getElementsByTagName("a")[0];      // Register a Mutation observer      let obs = new MutationObserver(function(mutations){        // Ensure that mutation data are valid        assert(mutations.length == 1, "only one attribute mutation");        let mutation = mutations[0];        assert(mutation.type == "attributes", "check `type`");        assert(mutation.target == link, "check `target`");        assert(mutation.attributeName == "href", "check `attributeName`");        assert(mutation.oldValue == "foo", "check `oldValue`");        obs.disconnect();        done();      });      obs.observe(document, {        subtree: true,        attributes: true,        attributeOldValue: true,        attributeFilter: ["href"]      });      // Modify the DOM      link.setAttribute("href", "bar");    }  );});require("test").run(exports);
 |