/* 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 xulApp = require("sdk/system/xul-app");

const USE_JS_PROXIES = !xulApp.versionInRange(xulApp.platformVersion,
                                              "17.0a2", "*");

const { Loader } = require('sdk/test/loader');

/*
 * 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,' + encodeURI(html);

    let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
      onReady: function () {

        function onDOMReady() {
          hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
                                                  false);

          let xrayWindow = hiddenFrame.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;
              hiddenFrames.remove(hiddenFrame);
              done();
            }
          }
          callback(helper, assert);
        }

        hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
        hiddenFrame.element.setAttribute("src", url);

      }
    }));
  };
}

function createWorker(assert, xrayWindow, contentScript, done) {
  // We have to use Sandboxed loader in order to get access to the private
  // unlock key `PRIVATE_KEY`. This key should not be used anywhere else.
  // See `PRIVATE_KEY` definition in worker.js
  let loader = Loader(module);
  let Worker = loader.require("sdk/content/worker").Worker;
  let key = loader.sandbox("sdk/content/worker").PRIVATE_KEY;
  let worker = Worker({
    exposeUnlockKey : USE_JS_PROXIES ? key : null,
    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();
  });

});

if (USE_JS_PROXIES) {
  // Verify that the attribute `exposeUnlockKey`, that allow this test
  // to identify proxies, works correctly.
  // See `PRIVATE_KEY` definition in worker.js
  exports["test Key Access"] = createProxyTest("", function(helper) {

    helper.createWorker(
      'new ' + function ContentScriptScope() {
        assert("UNWRAP_ACCESS_KEY" in window, "have access to `UNWRAP_ACCESS_KEY`");
        done();
      }
    );

  });
}

// Bug 714778: There was some issue around `toString` functions
//             that ended up being shared between content scripts
exports["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";
      if ('UNWRAP_ACCESS_KEY' in window)
        assert(!("foo" in document.location.toString), "document.location.toString can't be modified");
      else
        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 iframe
let 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
    if (USE_JS_PROXIES) {
      assert.equal(event.source, ifWindow,
                       "event.source is the iframe window");
    }
    else {
      // JS proxies had different behavior than xrays, xrays use current
      // compartments when calling postMessage method. Whereas js proxies
      // was using postMessage method compartment, not the caller one.
      assert.equal(event.source, helper.xrayWindow,
                       "event.source is the top window");
    }
    assert.equal(event.origin, "null", "origin is null");

    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;
          if ('UNWRAP_ACCESS_KEY' in window)
            assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
          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() {
      // 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;
          if ('UNWRAP_ACCESS_KEY' in window)
            assert(event.valueOf() !== event.valueOf(UNWRAP_ACCESS_KEY), "event is wrapped");
          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 == "null", "origin is null");
          assert(event.data == "ok", "message data is correct");
          done();
        }
      };

      window.addEventListener("message", myMessageListener, true);
      document.defaultView.postMessage("ok", '*');
    }
  );

});

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;
      if ('UNWRAP_ACCESS_KEY' in window)
        assert(body.customAttribute.valueOf() === body.customAttribute.valueOf(UNWRAP_ACCESS_KEY), "custom JS attributes are not wrapped");
      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++;
      }
      // JS proxies were broken, we can iterate over some other items:
      // length, item and iterator
      let expectedCount;
      if ('UNWRAP_ACCESS_KEY' in window)
        expectedCount = 3;
      else
        expectedCount = 6;
      assert(count == expectedCount, "body.childNodes is iterable");
      done();
    }
  );

});

exports["test valueOf"] = createProxyTest("", function (helper) {

  if (USE_JS_PROXIES) {
    helper.createWorker(
      'new ' + function ContentScriptScope() {
        // Check internal use of valueOf() for JS proxies API
        assert(/\[object Window.*\]/.test(window.valueOf().toString()),
               "proxy.valueOf() returns the wrapped version");
        assert(/\[object Window.*\]/.test(window.valueOf({}).toString()),
               "proxy.valueOf({}) returns the wrapped version");
        done();
      }
    );
  }
  else {
    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");
        if ('UNWRAP_ACCESS_KEY' in window)
          assert("__isWrappedProxy" in event.target, "event object is a proxy");

        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");
          if ('UNWRAP_ACCESS_KEY' in window)
            assert("__isWrappedProxy" in event.target, "event object is a proxy");

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

});

if (USE_JS_PROXIES) {
  // Bug 671016: Typed arrays should not be proxified
  exports["test Typed ArraysX"] = createProxyTest("", function (helper) {

    helper.createWorker(
      'new ' + function ContentScriptScope() {
        let canvas = document.createElement("canvas");
        let context = canvas.getContext("2d");
        let imageData = context.getImageData(0,0, 1, 1);
        let unwrappedData;
        if ('UNWRAP_ACCESS_KEY' in window)
          unwrappedData = imageData.valueOf(UNWRAP_ACCESS_KEY).data
        else
          unwrappedData = imageData.wrappedJSObject.data;
        let data = imageData.data;
        dump(unwrappedData+" === "+data+"\n");
        assert(unwrappedData === data, "Typed array isn't proxified")
        done();
      }
    );

  });
}

// Bug 715755: proxy code throw an exception on COW
// Create an http server in order to simulate real cross domain documents
exports["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 proxies
let 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");
    }
  );

});

if (require("sdk/system/xul-app").is("Fennec")) {
  module.exports = {
    "test Unsupported Test": function UnsupportedTest (assert) {
        assert.pass(
          "Skipping this test until Fennec support is implemented." +
          "See bug 806813");
    }
  }
}

require("test").run(exports);
