Bug 987007, part 2 - Handle assignment to named and indexed setters without using JSRESOLVE_ASSIGNING. r=bz, r=bholley.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 25 Apr 2014 15:07:18 -0500
changeset 198836 b588b9285415b66ecdf297b9bc7cbb557f94a5a4
parent 198835 024eb3e19b741769861ffac85edb3d624c0c9201
child 198837 cfe98fe62a8fb5138ed80d8d1024cb66687e6f8a
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, bholley
bugs987007
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 987007, part 2 - Handle assignment to named and indexed setters without using JSRESOLVE_ASSIGNING. r=bz, r=bholley.
dom/bindings/Codegen.py
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
js/src/jsproxy.h
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8131,16 +8131,17 @@ class ClassMethod(ClassItem):
                  virtual=False, const=False, bodyInHeader=False,
                  templateArgs=None, visibility='public', body=None,
                  breakAfterReturnDecl="\n",
                  breakAfterSelf="\n", override=False):
         """
         override indicates whether to flag the method as MOZ_OVERRIDE
         """
         assert not override or virtual
+        assert not (override and static)
         self.returnType = returnType
         self.args = args
         self.inline = inline or bodyInHeader
         self.static = static
         self.virtual = virtual
         self.const = const
         self.bodyInHeader = bodyInHeader
         self.templateArgs = templateArgs
@@ -8776,17 +8777,17 @@ class CGClassForwardDeclare(CGThing):
 class CGProxySpecialOperation(CGPerSignatureCall):
     """
     Base class for classes for calling an indexed or named special operation
     (don't use this directly, use the derived classes below).
 
     If checkFound is False, will just assert that the prop is found instead of
     checking that it is before wrapping the value.
     """
-    def __init__(self, descriptor, operation, checkFound=True):
+    def __init__(self, descriptor, operation, checkFound=True, argumentMutableValue=None):
         self.checkFound = checkFound
 
         nativeName = MakeNativeName(descriptor.binaryNames.get(operation, operation))
         operation = descriptor.operations[operation]
         assert len(operation.signatures()) == 1
         signature = operation.signatures()[0]
 
         returnType, arguments = signature
@@ -8800,21 +8801,23 @@ class CGProxySpecialOperation(CGPerSigna
         if operation.isSetter() or operation.isCreator():
             # arguments[0] is the index or name of the item that we're setting.
             argument = arguments[1]
             info = getJSToNativeConversionInfo(
                 argument.type, descriptor,
                 treatNullAs=argument.treatNullAs,
                 sourceDescription=("value being assigned to %s setter" %
                                    descriptor.interface.identifier.name))
+            if argumentMutableValue is None:
+                argumentMutableValue = "desc.value()"
             templateValues = {
                 "declName": argument.identifier.name,
                 "holderName": argument.identifier.name + "_holder",
-                "val": "desc.value()",
-                "mutableVal": "desc.value()",
+                "val": argumentMutableValue,
+                "mutableVal": argumentMutableValue,
                 "obj": "obj"
             }
             self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
         elif operation.isGetter() or operation.isDeleter():
             self.cgRoot.prepend(CGGeneric("bool found;\n"))
 
     def getArguments(self):
         args = [(a, a.identifier.name) for a in self.arguments]
@@ -8842,19 +8845,21 @@ class CGProxyIndexedOperation(CGProxySpe
 
     If doUnwrap is False, the caller is responsible for making sure a variable
     named 'self' holds the C++ object somewhere where the code we generate
     will see it.
 
     If checkFound is False, will just assert that the prop is found instead of
     checking that it is before wrapping the value.
     """
-    def __init__(self, descriptor, name, doUnwrap=True, checkFound=True):
+    def __init__(self, descriptor, name, doUnwrap=True, checkFound=True,
+                argumentMutableValue=None):
         self.doUnwrap = doUnwrap
-        CGProxySpecialOperation.__init__(self, descriptor, name, checkFound)
+        CGProxySpecialOperation.__init__(self, descriptor, name, checkFound,
+                                         argumentMutableValue=argumentMutableValue)
 
     def define(self):
         # Our first argument is the id we're getting.
         argName = self.arguments[0].identifier.name
         if argName == "index":
             # We already have our index in a variable with that name
             setIndex = ""
         else:
@@ -8896,18 +8901,19 @@ class CGProxyIndexedPresenceChecker(CGPr
         CGProxyIndexedGetter.__init__(self, descriptor)
         self.cgRoot.append(CGGeneric("(void)result;\n"))
 
 
 class CGProxyIndexedSetter(CGProxyIndexedOperation):
     """
     Class to generate a call to an indexed setter.
     """
-    def __init__(self, descriptor):
-        CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedSetter')
+    def __init__(self, descriptor, argumentMutableValue=None):
+        CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedSetter',
+                                         argumentMutableValue=argumentMutableValue)
 
 
 class CGProxyIndexedDeleter(CGProxyIndexedOperation):
     """
     Class to generate a call to an indexed deleter.
     """
     def __init__(self, descriptor):
         CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedDeleter')
@@ -8915,18 +8921,19 @@ class CGProxyIndexedDeleter(CGProxyIndex
 
 class CGProxyNamedOperation(CGProxySpecialOperation):
     """
     Class to generate a call to a named operation.
 
     'value' is the jsval to use for the name; None indicates that it should be
     gotten from the property id.
     """
-    def __init__(self, descriptor, name, value=None):
-        CGProxySpecialOperation.__init__(self, descriptor, name)
+    def __init__(self, descriptor, name, value=None, argumentMutableValue=None):
+        CGProxySpecialOperation.__init__(self, descriptor, name,
+                                         argumentMutableValue=argumentMutableValue)
         self.value = value
 
     def define(self):
         # Our first argument is the id we're getting.
         argName = self.arguments[0].identifier.name
         if argName == "id":
             # deal with the name collision
             idDecl = "JS::Rooted<jsid> id_(cx, id);\n"
@@ -9003,18 +9010,19 @@ class CGProxyNamedPresenceChecker(CGProx
         CGProxyNamedGetter.__init__(self, descriptor)
         self.cgRoot.append(CGGeneric("(void)result;\n"))
 
 
 class CGProxyNamedSetter(CGProxyNamedOperation):
     """
     Class to generate a call to a named setter.
     """
-    def __init__(self, descriptor):
-        CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter')
+    def __init__(self, descriptor, argumentMutableValue=None):
+        CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter',
+                                       argumentMutableValue=argumentMutableValue)
 
 
 class CGProxyNamedDeleter(CGProxyNamedOperation):
     """
     Class to generate a call to a named deleter.
     """
     def __init__(self, descriptor):
         CGProxyNamedOperation.__init__(self, descriptor, 'NamedDeleter')
@@ -9064,42 +9072,45 @@ class CGDOMJSProxyHandler_getOwnProperty
         ClassMethod.__init__(self, "getOwnPropertyDescriptor", "bool", args,
                              virtual=True, override=True)
         self.descriptor = descriptor
 
     def getBody(self):
         indexedGetter = self.descriptor.operations['IndexedGetter']
         indexedSetter = self.descriptor.operations['IndexedSetter']
 
-        setOrIndexedGet = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n"
         if self.descriptor.supportsIndexedProperties():
-            setOrIndexedGet += "int32_t index = GetArrayIndexFromId(cx, id);\n"
             readonly = toStringBool(indexedSetter is None)
             fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;\n" % readonly
             templateValues = {
                 'jsvalRef': 'desc.value()',
                 'jsvalHandle': 'desc.value()',
                 'obj': 'proxy',
                 'successCode': fillDescriptor
             }
-            get = fill(
+            getIndexed = fill(
                 """
+                int32_t index = GetArrayIndexFromId(cx, id);
                 if (IsArrayIndex(index)) {
                   $*{callGetter}
                 }
+
                 """,
                 callGetter=CGProxyIndexedGetter(self.descriptor, templateValues).define())
+        else:
+            getIndexed = ""
 
         if UseHolderForUnforgeable(self.descriptor):
             tryHolder = dedent("""
                 if (!JS_GetPropertyDescriptorById(cx, ${holder}, id, flags, desc)) {
                   return false;
                 }
                 MOZ_ASSERT_IF(desc.object(), desc.object() == ${holder});
                 """)
+
             # We don't want to look at the unforgeable holder at all
             # in the xray case; that part got handled already.
             getUnforgeable = fill(
                 """
                 if (!isXray) {
                   $*{callOnUnforgeable}
                   if (desc.object()) {
                     desc.object().set(proxy);
@@ -9107,54 +9118,16 @@ class CGDOMJSProxyHandler_getOwnProperty
                   }
                 }
 
                 """,
                 callOnUnforgeable=CallOnUnforgeableHolder(self.descriptor, tryHolder))
         else:
             getUnforgeable = ""
 
-        if indexedSetter or self.descriptor.operations['NamedSetter']:
-            setOrIndexedGet += "if (flags & JSRESOLVE_ASSIGNING) {\n"
-            if indexedSetter:
-                setOrIndexedGet += ("  if (IsArrayIndex(index)) {\n")
-                if 'IndexedCreator' not in self.descriptor.operations:
-                    # FIXME need to check that this is a 'supported property
-                    # index'.  But if that happens, watch out for the assumption
-                    # below that the name setter always returns for
-                    # IsArrayIndex(index).
-                    assert False
-                setOrIndexedGet += ("    FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n" +
-                                    "    return true;\n" +
-                                    "  }\n")
-            setOrIndexedGet += indent(getUnforgeable)
-            if self.descriptor.operations['NamedSetter']:
-                if 'NamedCreator' not in self.descriptor.operations:
-                    # FIXME need to check that this is a 'supported property name'
-                    assert False
-                create = CGGeneric("FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n"
-                                   "return true;\n")
-                # If we have an indexed setter we've already returned
-                if (self.descriptor.supportsIndexedProperties() and
-                        not indexedSetter):
-                    create = CGIfWrapper(create, "!IsArrayIndex(index)")
-                setOrIndexedGet += indent(create.define())
-            setOrIndexedGet += "} else {\n"
-            if indexedGetter:
-                setOrIndexedGet += indent(get + "\n" + getUnforgeable)
-            else:
-                setOrIndexedGet += indent(getUnforgeable)
-            setOrIndexedGet += "}\n\n"
-        else:
-            if indexedGetter:
-                setOrIndexedGet += ("if (!(flags & JSRESOLVE_ASSIGNING)) {\n" +
-                                    indent(get) +
-                                    "}\n\n")
-            setOrIndexedGet += getUnforgeable
-
         if self.descriptor.supportsNamedProperties():
             operations = self.descriptor.operations
             readonly = toStringBool(operations['NamedSetter'] is None)
             enumerable = (
                 "self->NameIsEnumerable(Constify(%s))" %
                 # First [0] means first (and only) signature, [1] means
                 # "arguments" as opposed to return type, [0] means first (and
                 # only) argument.
@@ -9162,45 +9135,47 @@ class CGDOMJSProxyHandler_getOwnProperty
             fillDescriptor = (
                 "FillPropertyDescriptor(desc, proxy, %s, %s);\n"
                 "return true;\n" % (readonly, enumerable))
             templateValues = {'jsvalRef': 'desc.value()', 'jsvalHandle': 'desc.value()',
                               'obj': 'proxy', 'successCode': fillDescriptor}
             condition = "!HasPropertyOnPrototype(cx, proxy, id)"
             if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
                 condition = "(!isXray || %s)" % condition
-            condition = "!(flags & JSRESOLVE_ASSIGNING) && " + condition
             if self.descriptor.supportsIndexedProperties():
                 condition = "!IsArrayIndex(index) && " + condition
             namedGet = (CGIfWrapper(CGProxyNamedGetter(self.descriptor, templateValues),
                                     condition).define() +
                         "\n")
         else:
             namedGet = ""
 
         return fill(
             """
-            $*{setOrIndexedGet}
+            bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);
+            $*{getIndexed}
+            $*{getUnforgeable}
             JS::Rooted<JSObject*> expando(cx);
             if (!isXray && (expando = GetExpandoObject(proxy))) {
               if (!JS_GetPropertyDescriptorById(cx, expando, id, flags, desc)) {
                 return false;
               }
               if (desc.object()) {
                 // Pretend the property lives on the wrapper.
                 desc.object().set(proxy);
                 return true;
               }
             }
 
             $*{namedGet}
             desc.object().set(nullptr);
             return true;
             """,
-            setOrIndexedGet=setOrIndexedGet,
+            getIndexed=getIndexed,
+            getUnforgeable=getUnforgeable,
             namedGet=namedGet)
 
 
 class CGDOMJSProxyHandler_defineProperty(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::Handle<jsid>', 'id'),
@@ -9639,16 +9614,105 @@ class CGDOMJSProxyHandler_get(ClassMetho
             $*{named}
             vp.setUndefined();
             return true;
             """,
             indexedOrExpando=getIndexedOrExpando,
             named=getNamed)
 
 
+class CGDOMJSProxyHandler_set(ClassMethod):
+    def __init__(self, descriptor):
+        args = [Argument('JSContext*', 'cx'),
+                Argument('JS::Handle<JSObject*>', 'proxy'),
+                Argument('JS::Handle<JSObject*>', 'receiver'),
+                Argument('JS::Handle<jsid>', 'id'),
+                Argument('bool', 'strict'),
+                Argument('JS::MutableHandle<JS::Value>', 'vp')]
+        ClassMethod.__init__(self, "set", "bool", args, virtual=True, override=True)
+        self.descriptor = descriptor
+
+    def getBody(self):
+        return dedent("""
+            MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+                       "Should not have a XrayWrapper here");
+            bool done;
+            if (!setCustom(cx, proxy, id, vp, &done))
+                return false;
+            if (done)
+                return true;
+            return mozilla::dom::DOMProxyHandler::set(cx, proxy, receiver, id, strict, vp);
+            """)
+
+
+class CGDOMJSProxyHandler_setCustom(ClassMethod):
+    def __init__(self, descriptor):
+        args = [Argument('JSContext*', 'cx'),
+                Argument('JS::Handle<JSObject*>', 'proxy'),
+                Argument('JS::Handle<jsid>', 'id'),
+                Argument('JS::MutableHandle<JS::Value>', 'vp'),
+                Argument('bool*', 'done')]
+        ClassMethod.__init__(self, "setCustom", "bool", args, virtual=True, override=True)
+        self.descriptor = descriptor
+
+    def getBody(self):
+        assertion = ("MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n"
+                     '           "Should not have a XrayWrapper here");\n')
+
+        # Correctness first. If we have a NamedSetter and [OverrideBuiltins],
+        # always call the NamedSetter and never do anything else.
+        namedSetter = self.descriptor.operations['NamedSetter']
+        if (namedSetter is not None and
+            self.descriptor.interface.getExtendedAttribute('OverrideBuiltins')):
+            # Check assumptions.
+            if self.descriptor.supportsIndexedProperties():
+                raise ValueError("In interface " + self.descriptor.name + ": " +
+                                 "Can't cope with [OverrideBuiltins] and an indexed getter")
+            if self.descriptor.operations['NamedCreator'] is not namedSetter:
+                raise ValueError("In interface " + self.descriptor.name + ": " +
+                                 "Can't cope with named setter that is not also a named creator")
+            if UseHolderForUnforgeable(self.descriptor):
+                raise ValueError("In interface " + self.descriptor.name + ": " +
+                                 "Can't cope with [OverrideBuiltins] and unforgeable members")
+
+            callSetter = CGProxyNamedSetter(self.descriptor, argumentMutableValue="vp")
+            return (assertion +
+                    callSetter.define() +
+                    "*done = true;\n"
+                    "return true;\n")
+
+        # As an optimization, if we are going to call an IndexedSetter, go
+        # ahead and call it and have done.
+        indexedSetter = self.descriptor.operations['IndexedSetter']
+        if indexedSetter is not None:
+            if self.descriptor.operations['IndexedCreator'] is not indexedSetter:
+                raise ValueError("In interface " + self.descriptor.name + ": " +
+                                 "Can't cope with indexed setter that is not " +
+                                 "also an indexed creator")
+            setIndexed = fill(
+                """
+                int32_t index = GetArrayIndexFromId(cx, id);
+                if (IsArrayIndex(index)) {
+                  $*{callSetter}
+                  *done = true;
+                  return true;
+                }
+
+                """,
+                callSetter=CGProxyIndexedSetter(self.descriptor,
+                                                argumentMutableValue="vp").define())
+        else:
+            setIndexed = ""
+
+        return (assertion +
+                setIndexed +
+                "*done = false;\n"
+                "return true;\n")
+
+
 class CGDOMJSProxyHandler_className(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy')]
         ClassMethod.__init__(self, "className", "const char*", args,
                              virtual=True, override=True)
         self.descriptor = descriptor
 
@@ -9683,17 +9747,17 @@ class CGDOMJSProxyHandler_slice(ClassMet
     def __init__(self, descriptor):
         assert descriptor.supportsIndexedProperties()
 
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('uint32_t', 'begin'),
                 Argument('uint32_t', 'end'),
                 Argument('JS::Handle<JSObject*>', 'array')]
-        ClassMethod.__init__(self, "slice", "bool", args)
+        ClassMethod.__init__(self, "slice", "bool", args, virtual=True, override=True)
         self.descriptor = descriptor
 
     def getBody(self):
         # Just like getOwnPropertyNames we'll assume that we have no holes, so
         # we have all properties from 0 to length.  If that ever changes
         # (unlikely), we'll need to do something a bit more clever with how we
         # forward on to our ancestor.
 
@@ -9759,16 +9823,21 @@ class CGDOMJSProxyHandler(CGClass):
                    CGDOMJSProxyHandler_get(descriptor),
                    CGDOMJSProxyHandler_className(descriptor),
                    CGDOMJSProxyHandler_finalizeInBackground(descriptor),
                    CGDOMJSProxyHandler_finalize(descriptor),
                    CGDOMJSProxyHandler_getInstance(),
                    CGDOMJSProxyHandler_delete(descriptor)]
         if descriptor.supportsIndexedProperties():
             methods.append(CGDOMJSProxyHandler_slice(descriptor))
+        if (descriptor.operations['IndexedSetter'] is not None or
+            (descriptor.operations['NamedSetter'] is not None and
+             descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
+            methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
+            methods.append(CGDOMJSProxyHandler_set(descriptor))
 
         CGClass.__init__(self, 'DOMProxyHandler',
                          bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
                          methods=methods)
 
 
 class CGDOMJSProxyHandlerDeclarer(CGThing):
     """
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -306,10 +306,18 @@ IdToInt32(JSContext* cx, JS::Handle<jsid
       !JS::ToNumber(cx, idval, &array_index) ||
       !::JS_DoubleIsInt32(array_index, &i)) {
     return -1;
   }
 
   return i;
 }
 
+bool
+DOMProxyHandler::setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+                           JS::MutableHandle<JS::Value> vp, bool *done)
+{
+  *done = false;
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -92,16 +92,24 @@ public:
   }
   virtual bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                               JS::MutableHandle<JSPropertyDescriptor> desc, bool* defined);
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) MOZ_OVERRIDE;
 
+  /*
+   * If assigning to proxy[id] hits a named setter with OverrideBuiltins or
+   * an indexed setter, call it and set *done to true on success. Otherwise, set
+   * *done to false.
+   */
+  virtual bool setCustom(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+                         JS::MutableHandle<JS::Value> vp, bool *done);
+
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
     if (v.isObject()) {
       return &v.toObject();
     }
 
@@ -115,16 +123,23 @@ public:
     return v.isUndefined() ? nullptr : &v.toObject();
   }
   /* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
 };
 
+inline DOMProxyHandler*
+GetDOMProxyHandler(JSObject* obj)
+{
+  MOZ_ASSERT(IsDOMProxy(obj));
+  return static_cast<DOMProxyHandler*>(js::GetProxyHandler(obj));
+}
+
 extern jsid s_length_id;
 
 int32_t IdToInt32(JSContext* cx, JS::Handle<jsid> id);
 
 // XXXbz this should really return uint32_t, with the maximum value
 // meaning "not an index"...
 inline int32_t
 GetArrayIndexFromId(JSContext* cx, JS::Handle<jsid> id)
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -83,16 +83,34 @@ class JS_FRIEND_API(Wrapper);
  *
  * Important: If you add a trap here, you should probably also add a Proxy::foo
  * entry point with an AutoEnterPolicy. If you don't, you need an explicit
  * override for the trap in SecurityWrapper. See bug 945826 comment 0.
  */
 class JS_FRIEND_API(BaseProxyHandler)
 {
     const void *mFamily;
+
+    /*
+     * Proxy handlers can use mHasPrototype to request the following special
+     * treatment from the JS engine:
+     *
+     *   - When mHasPrototype is true, the engine never calls these methods:
+     *     getPropertyDescriptor, has, set, enumerate, iterate.  Instead, for
+     *     these operations, it calls the "own" traps like
+     *     getOwnPropertyDescriptor, hasOwn, defineProperty, keys, etc., and
+     *     consults the prototype chain if needed.
+     *
+     *   - When mHasPrototype is true, the engine calls handler->get() only if
+     *     handler->hasOwn() says an own property exists on the proxy. If not,
+     *     it consults the prototype chain.
+     *
+     * This is useful because it frees the ProxyHandler from having to implement
+     * any behavior having to do with the prototype chain.
+     */
     bool mHasPrototype;
 
     /*
      * All proxies indicate whether they have any sort of interesting security
      * policy that might prevent the caller from doing something it wants to
      * the object. In the case of wrappers, this distinction is used to
      * determine whether the caller may strip off the wrapper if it so desires.
      */
@@ -343,27 +361,16 @@ class Proxy
 extern JS_FRIEND_DATA(const js::Class* const) CallableProxyClassPtr;
 extern JS_FRIEND_DATA(const js::Class* const) UncallableProxyClassPtr;
 
 inline bool IsProxy(JSObject *obj)
 {
     return GetObjectClass(obj)->isProxy();
 }
 
-BaseProxyHandler *
-GetProxyHandler(JSObject *obj);
-
-inline bool IsScriptedProxy(JSObject *obj)
-{
-    if (!IsProxy(obj))
-        return false;
-
-    return GetProxyHandler(obj)->isScripted();
-}
-
 /*
  * These are part of the API.
  *
  * NOTE: PROXY_PRIVATE_SLOT is 0 because that way slot 0 is usable by API
  * clients for both proxy and non-proxy objects.  So an API client that only
  * needs to store one slot's worth of data doesn't need to branch on what sort
  * of object it has.
  */
@@ -410,16 +417,22 @@ SetProxyHandler(JSObject *obj, BaseProxy
 inline void
 SetProxyExtra(JSObject *obj, size_t n, const Value &extra)
 {
     JS_ASSERT(IsProxy(obj));
     JS_ASSERT(n <= 1);
     SetReservedSlot(obj, PROXY_EXTRA_SLOT + n, extra);
 }
 
+inline bool
+IsScriptedProxy(JSObject *obj)
+{
+    return IsProxy(obj) && GetProxyHandler(obj)->isScripted();
+}
+
 class MOZ_STACK_CLASS ProxyOptions {
   protected:
     /* protected constructor for subclass */
     ProxyOptions(bool singletonArg, const Class *claspArg)
       : singleton_(singletonArg),
         clasp_(claspArg)
     {}
 
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -158,16 +158,19 @@ public:
     // properties.
     virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper,
                                     HandleObject wrapper, HandleObject holder,
                                     HandleId id, MutableHandle<JSPropertyDescriptor> desc,
                                     unsigned flags);
 
     virtual void preserveWrapper(JSObject *target) = 0;
 
+    static bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id,
+                    bool strict, MutableHandleValue vp);
+
     JSObject* getExpandoObject(JSContext *cx, HandleObject target,
                                HandleObject consumer);
     JSObject* ensureExpandoObject(JSContext *cx, HandleObject wrapper,
                                   HandleObject target);
 
     JSObject* getHolder(JSObject *wrapper);
     JSObject* ensureHolder(JSContext *cx, HandleObject wrapper);
     virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) = 0;
@@ -257,16 +260,18 @@ public:
                                        HandleObject holder, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc, unsigned flags);
     virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper,
                                     HandleObject holder, HandleId id,
                                     MutableHandle<JSPropertyDescriptor> desc, unsigned flags);
     static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                MutableHandle<JSPropertyDescriptor> desc,
                                Handle<JSPropertyDescriptor> existingDesc, bool *defined);
+    static bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id,
+                    bool strict, MutableHandleValue vp);
     virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                                 AutoIdVector &props);
     static bool call(JSContext *cx, HandleObject wrapper,
                      const JS::CallArgs &args, js::Wrapper& baseInstance);
     static bool construct(JSContext *cx, HandleObject wrapper,
                           const JS::CallArgs &args, js::Wrapper& baseInstance);
 
     static bool isResolving(JSContext *cx, JSObject *holder, jsid id)
@@ -1184,16 +1189,25 @@ XrayTraits::resolveOwnProperty(JSContext
         desc.object().set(wrapper);
         return true;
     }
 
     return true;
 }
 
 bool
+XrayTraits::set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id,
+                bool strict, MutableHandleValue vp)
+{
+    // Skip our Base if it isn't already BaseProxyHandler.
+    js::BaseProxyHandler *handler = js::GetProxyHandler(wrapper);
+    return handler->js::BaseProxyHandler::set(cx, wrapper, receiver, id, strict, vp);
+}
+
+bool
 XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper,
                                                HandleObject wrapper, HandleObject holder,
                                                HandleId id, MutableHandle<JSPropertyDescriptor> desc,
                                                unsigned flags)
 {
     // Call the common code.
     bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder,
                                              id, desc, flags);
@@ -1467,16 +1481,34 @@ DOMXrayTraits::defineProperty(JSContext 
     if (!existingDesc.object())
         return true;
 
     JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper));
     return XrayDefineProperty(cx, wrapper, obj, id, desc, defined);
 }
 
 bool
+DOMXrayTraits::set(JSContext *cx, HandleObject wrapper, HandleObject receiver, HandleId id,
+                   bool strict, MutableHandleValue vp)
+{
+    MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(wrapper));
+    RootedObject obj(cx, getTargetObject(wrapper));
+    if (IsDOMProxy(obj)) {
+        DOMProxyHandler* handler = GetDOMProxyHandler(obj);
+
+        bool done;
+        if (!handler->setCustom(cx, obj, id, vp, &done))
+            return false;
+        if (done)
+            return true;
+    }
+    return XrayTraits::set(cx, wrapper, receiver, id, strict, vp);
+}
+
+bool
 DOMXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                               AutoIdVector &props)
 {
     JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper));
     return XrayEnumerateProperties(cx, wrapper, obj, flags, props);
 }
 
 bool
@@ -2077,20 +2109,20 @@ XrayWrapper<Base, Traits>::get(JSContext
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::set(JSContext *cx, HandleObject wrapper,
                                HandleObject receiver, HandleId id,
                                bool strict, MutableHandleValue vp)
 {
-    // Skip our Base if it isn't already BaseProxyHandler.
+    // Delegate to Traits.
     // NB: None of the functions we call are prepared for the receiver not
     // being the wrapper, so ignore the receiver here.
-    return js::BaseProxyHandler::set(cx, wrapper, Traits::HasPrototype ? receiver : wrapper, id, strict, vp);
+    return Traits::set(cx, wrapper, Traits::HasPrototype ? receiver : wrapper, id, strict, vp);
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::has(JSContext *cx, HandleObject wrapper,
                                HandleId id, bool *bp)
 {
     // Skip our Base if it isn't already ProxyHandler.