/* 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/. */

"use strict";

let Ci = Components.interfaces;

/**
 * Access key that allows privileged code to unwrap proxy wrappers through 
 * valueOf:
 *   let xpcWrapper = proxyWrapper.valueOf(UNWRAP_ACCESS_KEY);
 * This key should only be used by proxy unit test.
 */
 const UNWRAP_ACCESS_KEY = {};


 /**
 * Returns a closure that wraps arguments before calling the given function,
 * which can be given to native functions that accept a function, such that when
 * the closure is called, the given function is called with wrapped arguments.
 *
 * @param fun {Function}
 *        the function for which to create a closure wrapping its arguments
 * @param obj {Object}
 *        target object from which `fun` comes from
 *        (optional, for debugging purpose)
 * @param name {String}
 *        name of the attribute from which `fun` is binded on `obj`
 *        (optional, for debugging purpose)
 *
 * Example:
 *   function contentScriptListener(event) {}
 *   let wrapper = ContentScriptFunctionWrapper(contentScriptListener);
 *   xray.addEventListener("...", wrapper, false);
 * -> Allow to `event` to be wrapped
 */
function ContentScriptFunctionWrapper(fun, obj, name) {
  if ("___proxy" in fun && typeof fun.___proxy == "function")
    return fun.___proxy;
  
  let wrappedFun = function () {
    let args = [];
    for (let i = 0, l = arguments.length; i < l; i++)
      args.push(wrap(arguments[i]));
    
    //console.log("Called from native :"+obj+"."+name);
    //console.log(">args "+arguments.length);
    //console.log(fun);
    
    // Native code can execute this callback with `this` being the wrapped 
    // function. For example, window.mozRequestAnimationFrame.
    if (this == wrappedFun)
      return fun.apply(fun, args);
    
    return fun.apply(wrap(this), args);
  };
  
  Object.defineProperty(fun, "___proxy", {value : wrappedFun,
                                          writable : false,
                                          enumerable : false,
                                          configurable : false});
  
  return wrappedFun;
}

/**
 * Returns a closure that unwraps arguments before calling the `fun` function,
 * which can be used to build a wrapper for a native function that accepts
 * wrapped arguments, since native function only accept unwrapped arguments.
 *
 * @param fun {Function}
 *        the function to wrap
 * @param originalObject {Object}
 *        target object from which `fun` comes from
 *        (optional, for debugging purpose)
 * @param name {String}
 *        name of the attribute from which `fun` is binded on `originalObject`
 *        (optional, for debugging purpose)
 *
 * Example:
 *   wrapper.appendChild = NativeFunctionWrapper(xray.appendChild, xray);
 *   wrapper.appendChild(anotherWrapper);
 * -> Allow to call xray.appendChild with unwrapped version of anotherWrapper
 */
function NativeFunctionWrapper(fun, originalObject, name) {
  return function () {
    let args = [];
    let obj = this && typeof this.valueOf == "function" ?
              this.valueOf(UNWRAP_ACCESS_KEY) : this;

    for (let i = 0, l = arguments.length; i < l; i++)
      args.push( unwrap(arguments[i], obj, name) );
    
    //if (name != "toString")
    //console.log(">>calling native ["+(name?name:'#closure#')+"]: \n"+fun.apply+"\n"+obj+"\n("+args.join(', ')+")\nthis :"+obj+"from:"+originalObject+"\n");
    
    // Need to use Function.prototype.apply.apply because XMLHttpRequest 
    // is a function (typeof return 'function') and fun.apply is null :/
    let unwrapResult = Function.prototype.apply.apply(fun, [obj, args]);
    let result = wrap(unwrapResult, obj, name);
    
    //console.log("<< "+rr+" -> "+r);
    
    return result;
  };
}

/*
 * Unwrap a JS value that comes from the content script.
 * Mainly converts proxy wrapper to XPCNativeWrapper.
 */
function unwrap(value, obj, name) {
  //console.log("unwrap : "+value+" ("+name+")");
  if (!value)
    return value;
  let type = typeof value;  
  
  // In case of proxy, unwrap them recursively 
  // (it should not be recursive, just in case of)
  if (["object", "function"].indexOf(type) !== -1 && 
      "__isWrappedProxy" in value) {
    while("__isWrappedProxy" in value)
      value = value.valueOf(UNWRAP_ACCESS_KEY);
    return value;
  }
  
  // In case of functions we need to return a wrapper that converts native 
  // arguments applied to this function into proxies.
  if (type == "function")
    return ContentScriptFunctionWrapper(value, obj, name);
  
  // We must wrap objects coming from content script too, as they may have
  // a function that will be called by a native method.
  // For example:
  //   addEventListener(..., { handleEvent: function(event) {} }, ...);
  if (type == "object")
    return ContentScriptObjectWrapper(value);

  if (["string", "number", "boolean"].indexOf(type) !== -1)
    return value;
  //console.log("return non-wrapped to native : "+typeof value+" -- "+value);
  return value;
}

/**
 * Returns an XrayWrapper proxy object that allow to wrap any of its function
 * though `ContentScriptFunctionWrapper`. These proxies are given to
 * XrayWrappers in order to automatically wrap values when they call a method
 * of these proxies. So that they are only used internaly and content script,
 * nor web page have ever access to them. As a conclusion, we can consider
 * this code as being safe regarding web pages overload.
 *
 *
 * @param obj {Object}
 *        object coming from content script context to wrap
 *
 * Example:
 *   let myListener = { handleEvent: function (event) {} };
 *   node.addEventListener("click", myListener, false);
 *   `event` has to be wrapped, so handleEvent has to be wrapped using
 *   `ContentScriptFunctionWrapper` function.
 *   In order to do so, we build this new kind of proxies.
 */
function ContentScriptObjectWrapper(obj) {
  if ("___proxy" in obj && typeof obj.___proxy == "object")
    return obj.___proxy;

  function valueOf(key) {
    if (key === UNWRAP_ACCESS_KEY)
      return obj;
    return this;
  }

  let proxy = Proxy.create({
    // Fundamental traps
    getPropertyDescriptor:  function(name) {
      return Object.getOwnPropertyDescriptor(obj, name);
    },
    defineProperty: function(name, desc) {
      return Object.defineProperty(obj, name, desc);
    },
    getOwnPropertyNames: function () {
      return Object.getOwnPropertyNames(obj);
    },
    delete: function(name) {
      return delete obj[name];
    },
    // derived traps
    has: function(name) {
      return name === "__isXrayWrapperProxy" ||
             name in obj;
    },
    hasOwn: function(name) {
      return Object.prototype.hasOwnProperty.call(obj, name);
    },
    get: function(receiver, name) {
      if (name == "valueOf")
        return valueOf;
      let value = obj[name];
      if (!value)
        return value;

      return unwrap(value);
    },
    set: function(receiver, name, val) {
      obj[name] = val;
      return true;
    },
    enumerate: function() {
      var result = [];
      for each (let name in obj) {
        result.push(name);
      };
      return result;
    },
    keys: function() {
      return Object.keys(obj);
    }
  });

  Object.defineProperty(obj, "___proxy", {value : proxy,
                                          writable : false,
                                          enumerable : false,
                                          configurable : false});

  return proxy;
}

// List of all existing typed arrays.
//   Can be found here:
//   http://mxr.mozilla.org/mozilla-central/source/js/src/jsapi.cpp#1790
const typedArraysCtor = [
  ArrayBuffer,
  Int8Array,
  Uint8Array,
  Int16Array,
  Uint16Array,
  Int32Array,
  Uint32Array,
  Float32Array,
  Float64Array,
  Uint8ClampedArray
];

/*
 * Wrap a JS value coming from the document by building a proxy wrapper.
 */
function wrap(value, obj, name, debug) {
  if (!value)
    return value;
  let type = typeof value;
  if (type == "object") {
    // Bug 671016: Typed arrays don't need to be proxified.
    // We avoid checking the whole constructor list on all objects
    // by doing this check only on non-extensible objects:
    if (!Object.isExtensible(value) &&
        typedArraysCtor.indexOf(value.constructor) !== -1)
      return value;

    // Bug 715755: do not proxify COW wrappers
    // These wrappers throw an exception when trying to access
    // any attribute that is not in a white list
    try {
      ("nonExistantAttribute" in value);
    }
    catch(e) {
      if (e.message.indexOf("Permission denied to access property") !== -1)
        return value;
    }

    // We may have a XrayWrapper proxy.
    // For example:
    //   let myListener = { handleEvent: function () {} };
    //   node.addEventListener("click", myListener, false);
    // When native code want to call handleEvent,
    // we go though ContentScriptFunctionWrapper that calls `wrap(this)`
    // `this` is the XrayWrapper proxy of myListener.
    // We return this object without building a CS proxy as it is already
    // a value coming from the CS.
    if ("__isXrayWrapperProxy" in value)
      return value.valueOf(UNWRAP_ACCESS_KEY);

    // Unwrap object before wrapping it.
    // It should not happen with CS proxy objects.
    while("__isWrappedProxy" in value) {
      value = value.valueOf(UNWRAP_ACCESS_KEY);
    }

    if (XPCNativeWrapper.unwrap(value) !== value)
      return getProxyForObject(value);
    // In case of Event, HTMLCollection or NodeList or ???
    // XPCNativeWrapper.unwrap(value) === value
    // but it's still a XrayWrapper so let's build a proxy
    return getProxyForObject(value);
  }
  if (type == "function") {
    if (XPCNativeWrapper.unwrap(value) !== value
        || (typeof value.toString === "function" && 
            value.toString().match(/\[native code\]/))) {
      return getProxyForFunction(value, NativeFunctionWrapper(value, obj, name));
    }
    return value;
  }
  if (type == "string")
    return value;
  if (type == "number")
    return value;
  if (type == "boolean")
    return value;
  //console.log("return non-wrapped to wrapped : "+value);
  return value;
}

/* 
 * Wrap an object from the document to a proxy wrapper
 */
function getProxyForObject(obj) {
  if (typeof obj != "object") {
    let msg = "tried to proxify something other than an object: " + typeof obj;
    console.warn(msg);
    throw msg;
  }
  if ("__isWrappedProxy" in obj) {
    return obj;
  }
  // Check if there is a proxy cached on this wrapper,
  // but take care of prototype ___proxy attribute inheritance!
  if (obj && obj.___proxy && obj.___proxy.valueOf(UNWRAP_ACCESS_KEY) === obj) {
    return obj.___proxy;
  }
  
  let proxy = Proxy.create(handlerMaker(obj));
  
  Object.defineProperty(obj, "___proxy", {value : proxy,
                                          writable : false,
                                          enumerable : false,
                                          configurable : false});
  return proxy;
}

/* 
 * Wrap a function from the document to a proxy wrapper
 */
function getProxyForFunction(fun, callTrap) {
  if (typeof fun != "function") {
    let msg = "tried to proxify something other than a function: " + typeof fun;
    console.warn(msg);
    throw msg;
  }
  if ("__isWrappedProxy" in fun)
    return obj;
  if ("___proxy" in fun)
    return fun.___proxy;
  
  let proxy = Proxy.createFunction(handlerMaker(fun), callTrap);
  
  Object.defineProperty(fun, "___proxy", {value : proxy,
                                          writable : false,
                                          enumerable : false,
                                          configurable : false});
  
  return proxy;
}

/* 
 * Check if a DOM attribute name is an event name.
 */
function isEventName(id) {
  if (id.indexOf("on") != 0 || id.length == 2) 
    return false;
  // Taken from:
  // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#7616
  switch (id[2]) {
    case 'a' :
      return (id == "onabort" ||
              id == "onafterscriptexecute" ||
              id == "onafterprint");
    case 'b' :
      return (id == "onbeforeunload" ||
              id == "onbeforescriptexecute" ||
              id == "onblur" ||
              id == "onbeforeprint");
    case 'c' :
      return (id == "onchange"       ||
              id == "onclick"        ||
              id == "oncontextmenu"  ||
              id == "oncopy"         ||
              id == "oncut"          ||
              id == "oncanplay"      ||
              id == "oncanplaythrough");
    case 'd' :
      return (id == "ondblclick"     || 
              id == "ondrag"         ||
              id == "ondragend"      ||
              id == "ondragenter"    ||
              id == "ondragleave"    ||
              id == "ondragover"     ||
              id == "ondragstart"    ||
              id == "ondrop"         ||
              id == "ondurationchange");
    case 'e' :
      return (id == "onerror" ||
              id == "onemptied" ||
              id == "onended");
    case 'f' :
      return id == "onfocus";
    case 'h' :
      return id == "onhashchange";
    case 'i' :
      return (id == "oninput" ||
              id == "oninvalid");
    case 'k' :
      return (id == "onkeydown"      ||
              id == "onkeypress"     ||
              id == "onkeyup");
    case 'l' :
      return (id == "onload"           ||
              id == "onloadeddata"     ||
              id == "onloadedmetadata" ||
              id == "onloadstart");
    case 'm' :
      return (id == "onmousemove"    ||
              id == "onmouseout"     ||
              id == "onmouseover"    ||
              id == "onmouseup"      ||
              id == "onmousedown"    ||
              id == "onmessage");
    case 'p' :
      return (id == "onpaint"        ||
              id == "onpageshow"     ||
              id == "onpagehide"     ||
              id == "onpaste"        ||
              id == "onpopstate"     ||
              id == "onpause"        ||
              id == "onplay"         ||
              id == "onplaying"      ||
              id == "onprogress");
    case 'r' :
      return (id == "onreadystatechange" ||
              id == "onreset"            ||
              id == "onresize"           ||
              id == "onratechange");
    case 's' :
      return (id == "onscroll"       ||
              id == "onselect"       ||
              id == "onsubmit"       || 
              id == "onseeked"       ||
              id == "onseeking"      ||
              id == "onstalled"      ||
              id == "onsuspend");
    case 't':
      return id == "ontimeupdate" 
      /* 
        // TODO: Make it work for mobile version
        ||
        (nsDOMTouchEvent::PrefEnabled() &&
         (id == "ontouchstart" ||
          id == "ontouchend" ||
          id == "ontouchmove" ||
          id == "ontouchenter" ||
          id == "ontouchleave" ||
          id == "ontouchcancel"))*/;
      
    case 'u' :
      return id == "onunload";
    case 'v':
      return id == "onvolumechange";
    case 'w':
      return id == "onwaiting";
    }
  
  return false;
}

// XrayWrappers miss some attributes.
// Here is a list of functions that return a value when it detects a miss:
const NODES_INDEXED_BY_NAME = ["IMG", "FORM", "APPLET", "EMBED", "OBJECT"];
const xRayWrappersMissFixes = [
  
  // Fix bug with XPCNativeWrapper on HTMLCollection
  // We can only access array item once, then it's undefined :o
  function (obj, name) {
    let i = parseInt(name);
    if (obj.toString().match(/HTMLCollection|NodeList/) && 
        i >= 0 && i < obj.length) {
      return wrap(XPCNativeWrapper(obj.wrappedJSObject[name]), obj, name);
    }
    return null;
  },
  
  // Trap access to document["form name"] 
  // that may refer to an existing form node
  // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9285
  function (obj, name) {
    if ("nodeType" in obj && obj.nodeType == 9) {
      let node = obj.wrappedJSObject[name];
      // List of supported tag:
      // http://mxr.mozilla.org/mozilla-central/source/content/html/content/src/nsGenericHTMLElement.cpp#1267
      if (node && NODES_INDEXED_BY_NAME.indexOf(node.tagName) != -1)
        return wrap(XPCNativeWrapper(node));
    }
    return null;
  },
  
  // Trap access to window["frame name"] and window.frames[i]
  // that refer to an (i)frame internal window object
  // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#6824
  function (obj, name) {
    if (typeof obj == "object" && "document" in obj) {
      // Ensure that we are on a window object
      try {
        obj.QueryInterface(Ci.nsIDOMWindow);
      }
      catch(e) {
        return null;
      }

      // Integer case:
      let i = parseInt(name);
      if (i >= 0 && i in obj) {
        return wrap(XPCNativeWrapper(obj[i]));
      }

      // String name case:
      if (name in obj.wrappedJSObject) {
        let win = obj.wrappedJSObject[name];
        let nodes = obj.document.getElementsByName(name);
        for (let i = 0, l = nodes.length; i < l; i++) {
          let node = nodes[i];
          if ("contentWindow" in node && node.contentWindow == win)
            return wrap(node.contentWindow);
        }
      }
    }
    return null;
  },
  
  // Trap access to form["node name"]
  // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsDOMClassInfo.cpp#9477
  function (obj, name) {
    if (typeof obj == "object" && obj.tagName == "FORM") {
      let match = obj.wrappedJSObject[name];
      let nodes = obj.ownerDocument.getElementsByName(name);
      for (let i = 0, l = nodes.length; i < l; i++) {
        let node = nodes[i];
        if (node == match)
          return wrap(node);
      }
    }
    return null;
  },

  // Fix XPathResult's constants being undefined on XrayWrappers
  // these constants are defined here:
  // http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/xpath/nsIDOMXPathResult.idl
  // and are only numbers.
  // The platform bug 665279 was fixed in Gecko 10.0a1.
  // FIXME: remove this workaround once the SDK no longer supports Firefox 9.
  function (obj, name) {
    if (typeof obj == "object" && name in Ci.nsIDOMXPathResult) {
      let value = Ci.nsIDOMXPathResult[name];
      if (typeof value == "number" && value === obj.wrappedJSObject[name])
        return value;
    }
    return null;
  }

];

// XrayWrappers have some buggy methods.
// Here is the list of them with functions returning some replacement 
// for a given object `obj`:
const xRayWrappersMethodsFixes = {
  // postMessage method is checking the Javascript global
  // and it expects it to be a window object.
  // But in our case, the global object is our sandbox global object.
  // See nsGlobalWindow::CallerInnerWindow():
  // http://mxr.mozilla.org/mozilla-central/source/dom/base/nsGlobalWindow.cpp#5893
  // nsCOMPtr<nsPIDOMWindow> win = do_QueryWrappedNative(wrapper);
  //   win is null
  postMessage: function (obj) {
    // Ensure that we are on a window object
    try {
      obj.QueryInterface(Ci.nsIDOMWindow);
    }
    catch(e) {
      return null;
    }
    // Create a wrapper that is going to call `postMessage` through `eval`
    let f = function postMessage(message, targetOrigin) {
      message = message.toString().replace(/['\\]/g,"\\$&");
      targetOrigin = targetOrigin.toString().replace(/['\\]/g,"\\$&");

      let jscode = "this.postMessage('" + message + "', '" +
                                targetOrigin + "')";
      return this.wrappedJSObject.eval(jscode);
    };
    return getProxyForFunction(f, NativeFunctionWrapper(f));
  },
  
  // Fix mozMatchesSelector uses that is broken on XrayWrappers
  // when we use document.documentElement.mozMatchesSelector.call(node, expr)
  // It's only working if we call mozMatchesSelector on the node itself.
  // SEE BUG 658909: mozMatchesSelector returns incorrect results with XrayWrappers
  mozMatchesSelector: function (obj) {
    // Ensure that we are on an object to expose this buggy method
    try {
      // Bug 707576 removed nsIDOMNSElement.
      // Can be simplified as soon as Firefox 11 become the minversion
      obj.QueryInterface("nsIDOMElement" in Ci ? Ci.nsIDOMElement :
                                                 Ci.nsIDOMNSElement);
    }
    catch(e) {
      return null;
    }
    // We can't use `wrap` function as `f` is not a native function,
    // so wrap it manually:
    let f = function mozMatchesSelector(selectors) {
      return this.mozMatchesSelector(selectors);
    };

    return getProxyForFunction(f, NativeFunctionWrapper(f));
  },

  // Bug 679054: History API doesn't work with Proxy objects. We have to pass
  // regular JS objects on `pushState` and `replaceState` methods.
  // In addition, the first argument has to come from the same compartment.
  pushState: function (obj) {
    // Ensure that we are on an object that expose History API
    try {
      obj.QueryInterface(Ci.nsIDOMHistory);
    }
    catch(e) {
      return null;
    }
    let f = function fix() {
      // Call native method with JSON objects
      // (need to convert `arguments` to an array via `slice`)
      return this.pushState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
    };

    return getProxyForFunction(f, NativeFunctionWrapper(f));
  },
  replaceState: function (obj) {
    // Ensure that we are on an object that expose History API
    try {
      obj.QueryInterface(Ci.nsIDOMHistory);
    }
    catch(e) {
      return null;
    }
    let f = function fix() {
      // Call native method with JSON objects
      // (need to convert `arguments` to an array via `slice`)
      return this.replaceState.apply(this, JSON.parse(JSON.stringify(Array.slice(arguments))));
    };

    return getProxyForFunction(f, NativeFunctionWrapper(f));
  }
};

/* 
 * Generate handler for proxy wrapper
 */
function handlerMaker(obj) {
  // Overloaded attributes dictionary
  let overload = {};
  // Expando attributes dictionary (i.e. onclick, onfocus, on* ...)
  let expando = {};
  // Cache of methods overloaded to fix XrayWrapper bug
  let methodFixes = {};
  return {
    // Fundamental traps
    getPropertyDescriptor:  function(name) {
      return Object.getOwnPropertyDescriptor(obj, name);
    },
    defineProperty: function(name, desc) {
      return Object.defineProperty(obj, name, desc);
    },
    getOwnPropertyNames: function () {
      return Object.getOwnPropertyNames(obj);
    },
    delete: function(name) {
      delete expando[name];
      delete overload[name];
      return delete obj[name];
    },
    
    // derived traps
    has: function(name) {
      if (name == "___proxy") return false;
      if (isEventName(name)) {
        // XrayWrappers throw exception when we try to access expando attributes
        // even on "name in wrapper". So avoid doing it!
        return name in expando;
      }
      return name in obj || name in overload || name == "__isWrappedProxy" ||
             undefined !== this.get(null, name);
    },
    hasOwn: function(name) {
      return Object.prototype.hasOwnProperty.call(obj, name);
    },
    get: function(receiver, name) {
      if (name == "___proxy")
        return undefined;
      
      // Overload toString in order to avoid returning "[XrayWrapper [object HTMLElement]]"
      // or "[object Function]" for function's Proxy
      if (name == "toString") {
        if ("wrappedJSObject" in obj) {
          // Bug 714778: we should not pass obj.wrappedJSObject.toString
          // in order to avoid sharing its proxy over contents scripts:
          return wrap(function () {
            return obj.wrappedJSObject.toString.call(
                     this.valueOf(UNWRAP_ACCESS_KEY), arguments);
          }, obj, name);
        }
        else {
          return wrap(obj.toString, obj, name);
        }
      }

      // Offer a way to retrieve XrayWrapper from a proxified node through `valueOf`
      if (name == "valueOf")
        return function (key) {
          if (key === UNWRAP_ACCESS_KEY)
            return obj;
          return this;
        };
      
      // Return overloaded value if there is one.
      // It allows to overload native methods like addEventListener that
      // are not saved, even on the wrapper itself.
      // (And avoid some methods like toSource from being returned here! [__proto__ test])
      if (name in overload &&
          overload[name] != Object.getPrototypeOf(overload)[name] &&
          name != "__proto__") {
        return overload[name];
      }
      
      // Catch exceptions thrown by XrayWrappers when we try to access on* 
      // attributes like onclick, onfocus, ...
      if (isEventName(name)) {
        //console.log("expando:"+obj+" - "+obj.nodeType);
        return name in expando ? expando[name].original : undefined;
      }
      
      // Overload some XrayWrappers method in order to fix its bugs
      if (name in methodFixes && 
          methodFixes[name] != Object.getPrototypeOf(methodFixes)[name] &&
          name != "__proto__")
        return methodFixes[name];
      if (Object.keys(xRayWrappersMethodsFixes).indexOf(name) !== -1) {
        let fix = xRayWrappersMethodsFixes[name](obj);
        if (fix)
          return methodFixes[name] = fix;
      }
      
      let o = obj[name];
      
      // XrayWrapper miss some attributes, try to catch these and return a value
      if (!o) {
        for each(let atttributeFixer in xRayWrappersMissFixes) {
          let fix = atttributeFixer(obj, name);
          if (fix)
            return fix;
        }
      }

      // Generic case
      return wrap(o, obj, name);
      
    },
    
    set: function(receiver, name, val) {

      if (isEventName(name)) {
        //console.log("SET on* attribute : " + name + " / " + val + "/" + obj);
        let shortName = name.replace(/^on/,"");
        
        // Unregister previously set listener
        if (expando[name]) {
          obj.removeEventListener(shortName, expando[name], true);
          delete expando[name];
        }
        
        // Only accept functions
        if (typeof val != "function")
          return false;
        
        // Register a new listener
        let original = val;
        val = ContentScriptFunctionWrapper(val);
        expando[name] = val;
        val.original = original;
        obj.addEventListener(name.replace(/^on/, ""), val, true);
        return true;
      }
      
      obj[name] = val;
      
      // Handle native method not overloaded on XrayWrappers:
      //   obj.addEventListener = val; -> obj.addEventlistener = native method
      // And, XPCNativeWrapper bug where nested values appear to be wrapped:
      // obj.customNestedAttribute = val -> obj.customNestedAttribute !== val
      //                                    obj.customNestedAttribute = "waive wrapper something"
      // SEE BUG 658560: Fix identity problem with CrossOriginWrappers
      // TODO: check that DOM can't be updated by the document itself and so overloaded value becomes wrong
      //       but I think such behavior is limited to primitive type
      if ((typeof val == "function" || typeof val == "object") && name) {
        overload[name] = val;
      }
      
      return true;
    },
    
    enumerate: function() {
      var result = [];
      for each (let name in Object.keys(obj)) {
        result.push(name);
      };
      return result;
    },
    
    keys: function() {
      return Object.keys(obj);
    }
  };
};


/* 
 * Wrap an object from the document to a proxy wrapper.
 */
function create(object) {
  if ("wrappedJSObject" in object)
    object = object.wrappedJSObject;
  let xpcWrapper = XPCNativeWrapper(object);
  // If we can't build an XPCNativeWrapper, it doesn't make sense to build
  // a proxy. All proxy code is based on having such wrapper that store
  // different JS attributes set.
  // (we can't build XPCNativeWrapper when object is from the same
  // principal/domain)
  if (object === xpcWrapper) {
    return object;
  }
  return getProxyForObject(xpcWrapper);
}
