Bug 852094 - Support Unforgeable on proxy-based DOM bindings. r=bz.
authorPeter Van der Beken <peterv@propagandism.org>
Thu, 20 Dec 2012 10:56:11 +0100
changeset 141201 4d3894186e6e554fa0a5bf26da1b8063a4bf552b
parent 141200 c9143c77058c283f6f86acf7269aede896e3a46d
child 141202 f9d45f30cdc78f51f6734c44f5237e35afb63eed
push id350
push userbbajaj@mozilla.com
push dateMon, 29 Jul 2013 23:00:49 +0000
treeherdermozilla-release@064965b37dbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs852094
milestone23.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 852094 - Support Unforgeable on proxy-based DOM bindings. r=bz.
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/DOMJSClass.h
dom/bindings/DOMJSProxyHandler.cpp
dom/webidl/HTMLDocument.webidl
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1740,12 +1740,21 @@ InterfaceHasInstance(JSContext* cx, JSHa
 JSBool
 InterfaceHasInstance(JSContext* cx, JSHandleObject obj, JSMutableHandleValue vp,
                      JSBool* bp);
 
 // Helper for lenient getters/setters to report to console
 void
 ReportLenientThisUnwrappingFailure(JSContext* cx, JS::Handle<JSObject*> obj);
 
+inline JSObject*
+GetUnforgeableHolder(JSObject* aGlobal, prototypes::ID aId)
+{
+  JSObject** protoAndIfaceArray = GetProtoAndIfaceArray(aGlobal);
+  JSObject* interfaceProto = protoAndIfaceArray[aId];
+  return &js::GetReservedSlot(interfaceProto,
+                              DOM_INTERFACE_PROTO_SLOTS_BASE).toObject();
+}
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -204,30 +204,72 @@ def PrototypeIDAndDepth(descriptor):
         if descriptor.workers:
             prototypeID += "_workers"
         depth = "PrototypeTraits<%s>::Depth" % prototypeID
     else:
         prototypeID += "_ID_Count"
         depth = "0"
     return (prototypeID, depth)
 
+def UseHolderForUnforgeable(descriptor):
+    return (descriptor.concrete and
+            descriptor.proxy and
+            any(m for m in descriptor.interface.members if m.isAttr() and m.isUnforgeable()))
+
+def CallOnUnforgeableHolder(descriptor, code, isXrayCheck=None):
+    """
+    Generate the code to execute the code in "code" on an unforgeable holder if
+    needed. code should be a string containing the code to execute. If it
+    contains a ${holder} string parameter it will be replaced with the
+    unforgeable holder object.
+
+    If isXrayCheck is not None it should be a string that contains a statement
+    returning whether proxy is an Xray. If isXrayCheck is None the generated
+    code won't try to unwrap Xrays.
+    """
+    code = string.Template(code).substitute({ "holder": "unforgeableHolder" })
+    if not isXrayCheck is None:
+        pre = """// Scope for 'global', 'ac' and 'unforgeableHolder'
+{
+  JSObject* global;
+  Maybe<JSAutoCompartment> ac;
+  if (""" + isXrayCheck + """) {
+    global = js::GetGlobalForObjectCrossCompartment(js::UncheckedUnwrap(proxy));
+    ac.construct(cx, global);
+  } else {
+    global = js::GetGlobalForObjectCrossCompartment(proxy);
+  }"""
+    else:
+        pre = """// Scope for 'global' and 'unforgeableHolder'
+{
+  JSObject* global = js::GetGlobalForObjectCrossCompartment(proxy);"""
+
+    return (pre + """
+  JSObject* unforgeableHolder = GetUnforgeableHolder(global, prototypes::id::%s);
+""" + CGIndenter(CGGeneric(code)).define() + """
+}
+""") % descriptor.name
+
 class CGPrototypeJSClass(CGThing):
     def __init__(self, descriptor, properties):
         CGThing.__init__(self)
         self.descriptor = descriptor
         self.properties = properties
     def declare(self):
         # We're purely for internal consumption
         return ""
     def define(self):
         (prototypeID, depth) = PrototypeIDAndDepth(self.descriptor)
+        slotCount = "DOM_INTERFACE_PROTO_SLOTS_BASE"
+        if UseHolderForUnforgeable(self.descriptor):
+            slotCount += " + 1 /* slot for the JSObject holding the unforgeable properties */"
         return """static DOMIfaceAndProtoJSClass PrototypeClass = {
   {
     "%sPrototype",
-    JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(2),
+    JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(%s),
     JS_PropertyStub,       /* addProperty */
     JS_DeletePropertyStub, /* delProperty */
     JS_PropertyStub,       /* getProperty */
     JS_StrictPropertyStub, /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,               /* finalize */
@@ -239,17 +281,17 @@ class CGPrototypeJSClass(CGThing):
     JSCLASS_NO_INTERNAL_MEMBERS
   },
   eInterfacePrototype,
   %s,
   "[object %sPrototype]",
   %s,
   %s
 };
-""" % (self.descriptor.interface.identifier.name,
+""" % (self.descriptor.interface.identifier.name, slotCount,
        NativePropertyHooks(self.descriptor),
        self.descriptor.interface.identifier.name,
        prototypeID, depth)
 
 def NeedsGeneratedHasInstance(descriptor):
     return descriptor.hasXPConnectImpls or descriptor.interface.isConsequential()
 
 class CGInterfaceObjectJSClass(CGThing):
@@ -267,21 +309,25 @@ class CGInterfaceObjectJSClass(CGThing):
             ctorname = "ThrowingConstructor"
         if NeedsGeneratedHasInstance(self.descriptor):
             hasinstance = HASINSTANCE_HOOK_NAME
         elif self.descriptor.interface.hasInterfacePrototypeObject():
             hasinstance = "InterfaceHasInstance"
         else:
             hasinstance = "nullptr"
         (prototypeID, depth) = PrototypeIDAndDepth(self.descriptor)
+        slotCount = "DOM_INTERFACE_SLOTS_BASE"
+        if len(self.descriptor.interface.namedConstructors) > 0:
+            slotCount += (" + %i /* slots for the named constructors */" %
+                          len(self.descriptor.interface.namedConstructors))
         return """
 static DOMIfaceAndProtoJSClass InterfaceObjectClass = {
   {
     "Function",
-    JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(DOM_INTERFACE_SLOTS_BASE + %i),
+    JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(%s),
     JS_PropertyStub,       /* addProperty */
     JS_DeletePropertyStub, /* delProperty */
     JS_PropertyStub,       /* getProperty */
     JS_StrictPropertyStub, /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,               /* finalize */
@@ -293,17 +339,17 @@ static DOMIfaceAndProtoJSClass Interface
     JSCLASS_NO_INTERNAL_MEMBERS
   },
   eInterface,
   %s,
   "function %s() {\\n    [native code]\\n}",
   %s,
   %s
 };
-""" % (len(self.descriptor.interface.namedConstructors), ctorname,
+""" % (slotCount, ctorname,
        hasinstance, ctorname, NativePropertyHooks(self.descriptor),
        self.descriptor.interface.identifier.name,
        prototypeID, depth)
 
 class CGList(CGThing):
     """
     Generate code for a list of GCThings.  Just concatenates them together, with
     an optional joiner string.  "\n" is a common joiner.
@@ -1431,23 +1477,16 @@ class AttrDefiner(PropertyDefiner):
             if not descriptor.interface.hasInterfaceObject():
                 # static attributes go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
         else:
             if not descriptor.interface.hasInterfacePrototypeObject():
                 # non-static attributes go on the interface prototype object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
-        if unforgeable and len(attributes) != 0 and descriptor.proxy:
-            raise TypeError("Unforgeable properties are not supported on "
-                            "proxy bindings without [NamedPropertiesObject].  "
-                            "And not even supported on the ones with "
-                            "[NamedPropertiesObject] yet, but we should fix "
-                            "that, since they're safe there.")
-
     def generateArray(self, array, name, doIdArrays):
         if len(array) == 0:
             return ""
 
         def flags(attr):
             unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
             return ("JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" +
                     unforgeable)
@@ -1641,16 +1680,30 @@ class CGCreateInterfaceObjectsMethod(CGA
             prefCache = CGWrapper(CGIndenter(CGList(prefCacheData, "\n")),
                                   pre=("static bool sPrefCachesInited = false;\n"
                                        "if (!sPrefCachesInited) {\n"
                                        "  sPrefCachesInited = true;\n"),
                                   post="\n}")
         else:
             prefCache = None
             
+        if UseHolderForUnforgeable(self.descriptor):
+            createUnforgeableHolder = CGGeneric("""JSObject* unforgeableHolder = JS_NewObjectWithGivenProto(aCx, nullptr, nullptr, nullptr);
+if (!unforgeableHolder) {
+  return;
+}""")
+            defineUnforgeables = InitUnforgeablePropertiesOnObject(self.descriptor,
+                                                                   "unforgeableHolder",
+                                                                   self.properties)
+            createUnforgeableHolder = CGList([createUnforgeableHolder,
+                                              defineUnforgeables],
+                                             "\n")
+        else:
+            createUnforgeableHolder = None
+
         getParentProto = ("JSObject* parentProto = %s;\n" +
                           "if (!parentProto) {\n" +
                           "  return;\n" +
                           "}\n") % getParentProto
 
         if (needInterfaceObject and
             self.descriptor.needsConstructHookHolder()):
             constructHookHolder = "&" + CONSTRUCT_HOOK_NAME + "_holder"
@@ -1708,18 +1761,29 @@ class CGCreateInterfaceObjectsMethod(CGA
             protoClass, protoCache,
             interfaceClass, constructHookHolder, constructArgs,
             namedConstructors,
             interfaceCache,
             domClass,
             properties,
             chromeProperties,
             '"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "NULL"))
+        if UseHolderForUnforgeable(self.descriptor):
+            assert needInterfacePrototypeObject
+            setUnforgeableHolder = CGGeneric(
+                "JSObject* proto = protoAndIfaceArray[prototypes::id::%s];\n"
+                "if (proto) {\n"
+                "  js::SetReservedSlot(proto, DOM_INTERFACE_PROTO_SLOTS_BASE,\n"
+                "                      JS::ObjectValue(*unforgeableHolder));\n"
+                "}" % self.descriptor.name)
+        else:
+            setUnforgeableHolder = None
         functionBody = CGList(
-            [CGGeneric(getParentProto), initIds, prefCache, CGGeneric(call)],
+            [CGGeneric(getParentProto), initIds, prefCache,
+             createUnforgeableHolder, CGGeneric(call), setUnforgeableHolder],
             "\n\n")
         return CGIndenter(functionBody).define()
 
 class CGGetPerInterfaceObject(CGAbstractMethod):
     """
     A method for getting a per-interface object (a prototype object or interface
     constructor object).
     """
@@ -1871,54 +1935,74 @@ def CreateBindingJSObject(descriptor, pa
         assert descriptor.nativeOwnership == 'owned'
         create += """  // Make sure the native objects inherit from NonRefcountedDOMObject so that we
   // log their ctor and dtor.
   MustInheritFromNonRefcountedDOMObject(aObject);
   *aTookOwnership = true;
 """
     return create % parent
 
-def GetAccessCheck(descriptor, globalName):
-    """
-    globalName is the name of the global JSObject*
+def GetAccessCheck(descriptor, object):
+    """
+    object is the name of a JSObject*
 
     returns a string
     """
     if descriptor.workers:
         accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()"
     else:
-        accessCheck = "xpc::AccessCheck::isChrome(%s)" % globalName
+        accessCheck = "xpc::AccessCheck::isChrome(%s)" % object
     return accessCheck
 
-def InitUnforgeableProperties(descriptor, properties):
+def InitUnforgeablePropertiesOnObject(descriptor, obj, properties, failureReturnValue=""):
     """
     properties is a PropertyArrays instance
     """
-    defineUnforgeables = ("if (!DefineUnforgeableAttributes(aCx, obj, %s)) {\n"
-                          "  return nullptr;\n"
+    failureReturn = "return"
+    if len(failureReturnValue) > 0:
+        failureReturn += " " + failureReturnValue
+    failureReturn += ";"
+        
+    defineUnforgeables = ("if (!DefineUnforgeableAttributes(aCx, " + obj + ", %s)) {\n"
+                          "  " + failureReturn + "\n"
                           "}")
+
     unforgeableAttrs = properties.unforgeableAttrs
     unforgeables = []
     if unforgeableAttrs.hasNonChromeOnly():
         unforgeables.append(CGGeneric(defineUnforgeables %
                                       unforgeableAttrs.variableName(False)))
     if unforgeableAttrs.hasChromeOnly():
         unforgeables.append(
             CGIfWrapper(CGGeneric(defineUnforgeables %
                                   unforgeableAttrs.variableName(True)),
-                        GetAccessCheck(descriptor, "global")))
-
-    return CGIndenter(CGWrapper(
-            CGList(unforgeables, "\n"),
-            pre=("\n"
+                        GetAccessCheck(descriptor, obj)))
+    return CGList(unforgeables, "\n")
+
+def InitUnforgeableProperties(descriptor, properties):
+    """
+    properties is a PropertyArrays instance
+    """
+    unforgeableAttrs = properties.unforgeableAttrs
+    if not unforgeableAttrs.hasNonChromeOnly() and not unforgeableAttrs.hasChromeOnly():
+        return ""
+
+    if descriptor.proxy:
+        unforgeableProperties = CGGeneric(
+            "// Unforgeable properties on proxy-based bindings are stored in an object held\n"
+            "// by the interface prototype object.\n")
+    else:
+        unforgeableProperties = CGWrapper(
+            InitUnforgeablePropertiesOnObject(descriptor, "obj", properties, "nullptr"),
+            pre=(
             "// Important: do unforgeable property setup after we have handed\n"
             "// over ownership of the C++ object to obj as needed, so that if\n"
             "// we fail and it ends up GCed it won't have problems in the\n"
-            "// finalizer trying to drop its ownership of the C++ object.\n"),
-            post="\n")).define() if len(unforgeables) > 0 else ""
+            "// finalizer trying to drop its ownership of the C++ object.\n"))
+    return CGIndenter(CGWrapper(unforgeableProperties, pre="\n", post="\n")).define()
 
 def AssertInheritanceChain(descriptor):
     asserts = ""
     iface = descriptor.interface
     while iface:
         desc = descriptor.getDescriptor(iface.identifier.name)
         asserts += (
             "  MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
@@ -6193,61 +6277,84 @@ class CGDOMJSProxyHandler_getOwnProperty
                 Argument('JS::Handle<jsid>', 'id'),
                 Argument('JSPropertyDescriptor*', 'desc'), Argument('unsigned', 'flags')]
         ClassMethod.__init__(self, "getOwnPropertyDescriptor", "bool", args)
         self.descriptor = descriptor
     def getBody(self):
         indexedGetter = self.descriptor.operations['IndexedGetter']
         indexedSetter = self.descriptor.operations['IndexedSetter']
 
-        setOrIndexedGet = ""
+        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;" % readonly
             templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value',
                               'obj': 'proxy', 'successCode': fillDescriptor}
             get = ("if (IsArrayIndex(index)) {\n" +
                    CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" +
                    "}\n") % (self.descriptor.nativeType)
 
+        if UseHolderForUnforgeable(self.descriptor):
+            getUnforgeable = """if (!JS_GetPropertyDescriptorById(cx, ${holder}, id, flags, desc)) {
+  return false;
+}
+MOZ_ASSERT_IF(desc->obj, desc->obj == ${holder});"""
+            getUnforgeable = CallOnUnforgeableHolder(self.descriptor,
+                                                     getUnforgeable, "isXray")
+            getUnforgeable += """if (desc->obj) {
+  desc->obj = proxy;
+  return !isXray || JS_WrapPropertyDescriptor(cx, desc);
+}
+
+"""
+        else:
+            getUnforgeable = ""
+
         if indexedSetter or self.descriptor.operations['NamedSetter']:
             setOrIndexedGet += "if (flags & JSRESOLVE_ASSIGNING) {\n"
             if indexedSetter:
                 setOrIndexedGet += ("  if (IsArrayIndex(index)) {\n")
                 if not 'IndexedCreator' 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 += CGIndenter(CGGeneric(getUnforgeable)).define()
             if self.descriptor.operations['NamedSetter']:
                 if not 'NamedCreator' 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;")
                 # If we have an indexed setter we've already returned
                 if (self.descriptor.supportsIndexedProperties() and
                     not indexedSetter):
                     create = CGIfWrapper(create, "!IsArrayIndex(index)")
                 setOrIndexedGet += CGIndenter(create).define() + "\n"
             setOrIndexedGet += "}"
             if indexedGetter:
                 setOrIndexedGet += (" else {\n" +
-                                    CGIndenter(CGGeneric(get)).define() +
+                                    CGIndenter(CGGeneric(get + "\n" + getUnforgeable)).define() +
+                                    "}")
+            else:
+                setOrIndexedGet += (" else {\n" +
+                                    CGIndenter(CGGeneric(getUnforgeable)).define() +
                                     "}")
             setOrIndexedGet += "\n\n"
-        elif indexedGetter:
-            setOrIndexedGet += ("if (!(flags & JSRESOLVE_ASSIGNING)) {\n" +
-                                CGIndenter(CGGeneric(get)).define() +
-                                "}\n\n")
+        else:
+            if indexedGetter:
+                setOrIndexedGet += ("if (!(flags & JSRESOLVE_ASSIGNING)) {\n" +
+                                    CGIndenter(CGGeneric(get)).define() +
+                                    "}\n\n")
+            setOrIndexedGet += getUnforgeable
 
         if self.descriptor.supportsNamedProperties():
             readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None)
             fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;" % readonly
             templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value',
                               'obj': 'proxy', 'successCode': fillDescriptor}
             # Once we start supporting OverrideBuiltins we need to make
             # ResolveOwnProperty or EnumerateOwnProperties filter out named
@@ -6258,17 +6365,17 @@ class CGDOMJSProxyHandler_getOwnProperty
             namedGet = ("\n" +
                         CGIfWrapper(CGProxyNamedGetter(self.descriptor, templateValues),
                                     condition).define() +
                         "\n")
         else:
             namedGet = ""
 
         return setOrIndexedGet + """JSObject* expando;
-if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
+if (!isXray && (expando = GetExpandoObject(proxy))) {
   if (!JS_GetPropertyDescriptorById(cx, expando, id, flags, desc)) {
     return false;
   }
   if (desc->obj) {
     // Pretend the property lives on the wrapper.
     desc->obj = proxy;
     return true;
   }
@@ -6299,16 +6406,29 @@ class CGDOMJSProxyHandler_defineProperty
         elif self.descriptor.supportsIndexedProperties():
             # XXXbz Once this is fixed to only throw in strict mode, update the
             # code that decides whether to do a
             # CGDOMJSProxyHandler_defineProperty at all.
             set += ("if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {\n" +
                     "  return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" +
                     "}\n") % self.descriptor.name
 
+        if UseHolderForUnforgeable(self.descriptor):
+            defineOnUnforgeable = ("JSBool hasUnforgeable;\n"
+                                   "if (!JS_HasPropertyById(cx, ${holder}, id, &hasUnforgeable)) {\n"
+                                   "  return false;\n"
+                                   "}\n"
+                                   "if (hasUnforgeable) {\n"
+                                   "  JSBool defined;\n"
+                                   "  return js_DefineOwnProperty(cx, ${holder}, id, *desc, &defined);\n"
+                                   "}\n")
+            set += CallOnUnforgeableHolder(self.descriptor,
+                                           defineOnUnforgeable,
+                                           "xpc::WrapperFactory::IsXrayWrapper(proxy)")
+
         namedSetter = self.descriptor.operations['NamedSetter']
         if namedSetter:
             if not self.descriptor.operations['NamedCreator'] is namedSetter:
                 raise TypeError("Can't handle creator that's different from the setter")
             # If we support indexed properties, we won't get down here for
             # indices, so we can just do our setter unconditionally here.
             set += (CGProxyNamedSetter(self.descriptor).define() + "\n" +
                     "return true;\n")
@@ -6363,27 +6483,43 @@ class CGDOMJSProxyHandler_delete(ClassMe
                         "  *bp = false;\n"
                         "} else {\n"
                         "  *bp = true;\n"
                         "}")
             else:
                 body = None
             return body
 
-        delete = ""
+        delete = """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+          "Should not have a XrayWrapper here");
+
+"""
 
         indexedBody = getDeleterBody("Indexed")
         if indexedBody is not None:
             delete += ("int32_t index = GetArrayIndexFromId(cx, id);\n" +
                        "if (IsArrayIndex(index)) {\n" +
                        CGIndenter(CGGeneric(indexedBody)).define() + "\n"
                        "  // We always return here, even if the property was not found\n"
                        "  return true;\n" +
                        "}\n") % self.descriptor.nativeType
 
+        if UseHolderForUnforgeable(self.descriptor):
+            unforgeable = ("JSBool hasUnforgeable;\n"
+                           "if (!JS_HasPropertyById(cx, ${holder}, id, &hasUnforgeable)) {\n"
+                           "  return false;\n"
+                           "}\n"
+                           "if (hasUnforgeable) {\n"
+                           "  // We should throw if Throw is true!\n"
+                           "  *bp = false;\n"
+                           "  return true;\n"
+                           "}")
+            delete += CallOnUnforgeableHolder(self.descriptor, unforgeable)
+            delete += "\n"
+
         namedBody = getDeleterBody("Named")
         if namedBody is not None:
             # We always return above for an index id in the case when we support
             # indexed properties, so we can just treat the id as a name
             # unconditionally here.
             delete += ("if (!HasPropertyOnPrototype(cx, proxy, this, id)) {\n" +
                        CGIndenter(CGGeneric(namedBody)).define() + "\n"
                        "  if (found) {\n"
@@ -6400,41 +6536,53 @@ class CGDOMJSProxyHandler_getOwnProperty
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::AutoIdVector&', 'props')]
         ClassMethod.__init__(self, "getOwnPropertyNames", "bool", args)
         self.descriptor = descriptor
     def getBody(self):
         # Per spec, we do indices, then named props, then everything else
         if self.descriptor.supportsIndexedProperties():
-            addIndices = """uint32_t length = UnwrapProxy(proxy)->Length();
+            addIndices = """
+uint32_t length = UnwrapProxy(proxy)->Length();
 MOZ_ASSERT(int32_t(length) >= 0);
 for (int32_t i = 0; i < int32_t(length); ++i) {
   if (!props.append(INT_TO_JSID(i))) {
     return false;
   }
 }
-
 """
         else:
             addIndices = ""
 
         if self.descriptor.supportsNamedProperties():
-            addNames = """nsTArray<nsString> names;
+            addNames = """
+nsTArray<nsString> names;
 UnwrapProxy(proxy)->GetSupportedNames(names);
 if (!AppendNamedPropertyIds(cx, proxy, names, props)) {
   return false;
 }
-
 """
         else:
             addNames = ""
 
-        return addIndices + addNames + """JSObject* expando;
-if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
+        if UseHolderForUnforgeable(self.descriptor):
+            addUnforgeable = (
+                "if (!js::GetPropertyNames(cx, ${holder}, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {\n"
+                "  return false;\n"
+                "}")
+            addUnforgeable = CallOnUnforgeableHolder(self.descriptor,
+                                                     addUnforgeable,
+                                                     "isXray")
+        else:
+            addUnforgeable = ""
+        return """bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);
+""" + addIndices + addUnforgeable + addNames + """
+JSObject* expando;
+if (!isXray && (expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
     !js::GetPropertyNames(cx, expando, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {
   return false;
 }
 
 return true;"""
 
 class CGDOMJSProxyHandler_hasOwn(ClassMethod):
     def __init__(self, descriptor):
@@ -6450,29 +6598,44 @@ class CGDOMJSProxyHandler_hasOwn(ClassMe
                        "if (IsArrayIndex(index)) {\n" +
                        CGIndenter(CGProxyIndexedPresenceChecker(self.descriptor)).define() + "\n" +
                        "  *bp = found;\n" +
                        "  return true;\n" +
                        "}\n\n") % (self.descriptor.nativeType)
         else:
             indexed = ""
 
+        if UseHolderForUnforgeable(self.descriptor):
+            unforgeable = ("JSBool b = true;\n"
+                           "JSBool ok = JS_AlreadyHasOwnPropertyById(cx, ${holder}, id, &b);\n"
+                           "*bp = !!b;\n"
+                           "if (!ok || *bp) {\n"
+                           "  return ok;\n"
+                           "}")
+            unforgeable = CallOnUnforgeableHolder(self.descriptor, unforgeable)
+        else:
+            unforgeable = ""
+
         if self.descriptor.supportsNamedProperties():
             # If we support indexed properties we always return above for index
             # property names, so no need to check for those here.
             named = ("if (!HasPropertyOnPrototype(cx, proxy, this, id)) {\n" +
                      CGIndenter(CGProxyNamedPresenceChecker(self.descriptor)).define() + "\n" +
                      "  *bp = found;\n"
                      "  return true;\n"
                      "}\n" +
                      "\n")
         else:
             named = ""
 
-        return indexed + """JSObject* expando = GetExpandoObject(proxy);
+        return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+          "Should not have a XrayWrapper here");
+
+""" + indexed + unforgeable + """
+JSObject* expando = GetExpandoObject(proxy);
 if (expando) {
   JSBool b = true;
   JSBool ok = JS_HasPropertyById(cx, expando, id, &b);
   *bp = !!b;
   if (!ok || *bp) {
     return ok;
   }
 }
@@ -6485,17 +6648,30 @@ class CGDOMJSProxyHandler_get(ClassMetho
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::Handle<JSObject*>', 'receiver'),
                 Argument('JS::Handle<jsid>', 'id'),
                 Argument('JS::MutableHandle<JS::Value>', 'vp')]
         ClassMethod.__init__(self, "get", "bool", args)
         self.descriptor = descriptor
     def getBody(self):
-        getFromExpando = """JSObject* expando = DOMProxyHandler::GetExpandoObject(proxy);
+        if UseHolderForUnforgeable(self.descriptor):
+            hasUnforgeable = (
+                "JSBool hasUnforgeable;\n"
+                 "if (!JS_AlreadyHasOwnPropertyById(cx, ${holder}, id, &hasUnforgeable)) {\n"
+                 "  return false;\n"
+                 "}\n"
+                 "if (hasUnforgeable) {\n"
+                 "  return JS_ForwardGetPropertyTo(cx, ${holder}, id, proxy, vp.address());\n"
+                 "}")
+            getUnforgeableOrExpando = CallOnUnforgeableHolder(self.descriptor,
+                                                              hasUnforgeable)
+        else:
+            getUnforgeableOrExpando = ""
+        getUnforgeableOrExpando += """JSObject* expando = DOMProxyHandler::GetExpandoObject(proxy);
 if (expando) {
   JSBool hasProp;
   if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
     return false;
   }
 
   if (hasProp) {
     return JS_GetPropertyById(cx, expando, id, vp.address());
@@ -6509,19 +6685,19 @@ if (expando) {
                                    "if (IsArrayIndex(index)) {\n" +
                                    CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) % (self.descriptor.nativeType)
             getIndexedOrExpando += """
   // Even if we don't have this index, we don't forward the
   // get on to our expando object.
 } else {
   %s
 }
-""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n  ')))
-        else:
-            getIndexedOrExpando = getFromExpando + "\n"
+""" % (stripTrailingWhitespace(getUnforgeableOrExpando.replace('\n', '\n  ')))
+        else:
+            getIndexedOrExpando = getUnforgeableOrExpando + "\n"
 
         if self.descriptor.supportsNamedProperties():
             getNamed = CGProxyNamedGetter(self.descriptor, templateValues)
             if self.descriptor.supportsIndexedProperties():
                 getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)")
             getNamed = getNamed.define() + "\n"
         else:
             getNamed = ""
@@ -6638,17 +6814,19 @@ return &instance;"""
 
 class CGDOMJSProxyHandler(CGClass):
     def __init__(self, descriptor):
         constructors = [CGDOMJSProxyHandler_CGDOMJSProxyHandler()]
         methods = [CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)]
         # XXXbz This should really just test supportsIndexedProperties() and
         # supportsNamedProperties(), but that would make us throw in all cases
         # because we don't know whether we're in strict mode.
-        if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']:
+        if (descriptor.operations['IndexedSetter'] or
+            descriptor.operations['NamedSetter'] or
+            UseHolderForUnforgeable(descriptor)):
             methods.append(CGDOMJSProxyHandler_defineProperty(descriptor))
         methods.extend([CGDOMJSProxyHandler_getOwnPropertyNames(descriptor),
                         CGDOMJSProxyHandler_hasOwn(descriptor),
                         CGDOMJSProxyHandler_get(descriptor),
                         CGDOMJSProxyHandler_obj_toString(descriptor),
                         CGDOMJSProxyHandler_finalizeInBackground(descriptor),
                         CGDOMJSProxyHandler_finalize(descriptor),
                         CGDOMJSProxyHandler_getElementIfPresent(descriptor),
--- a/dom/bindings/DOMJSClass.h
+++ b/dom/bindings/DOMJSClass.h
@@ -37,19 +37,24 @@ class nsCycleCollectionParticipant;
 #define JSCLASS_IS_DOMIFACEANDPROTOJSCLASS JSCLASS_USERBIT2
 
 // NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
 // LSetDOMProperty. Those constants need to be changed accordingly if this value
 // changes.
 #define DOM_PROTO_INSTANCE_CLASS_SLOT 0
 
 // Interface objects store a number of reserved slots equal to
-// DOM_INTERFACE_BASE_SLOTS + number of named constructors.
+// DOM_INTERFACE_SLOTS_BASE + number of named constructors.
 #define DOM_INTERFACE_SLOTS_BASE (DOM_XRAY_EXPANDO_SLOT + 1)
 
+// Interface prototype objects store a number of reserved slots equal to
+// DOM_INTERFACE_PROTO_SLOTS_BASE or DOM_INTERFACE_PROTO_SLOTS_BASE + 1 if a
+// slot for the unforgeable holder is needed.
+#define DOM_INTERFACE_PROTO_SLOTS_BASE (DOM_XRAY_EXPANDO_SLOT + 1)
+
 MOZ_STATIC_ASSERT(DOM_PROTO_INSTANCE_CLASS_SLOT != DOM_XRAY_EXPANDO_SLOT,
                   "Interface prototype object use both of these, so they must "
                   "not be the same slot.");
 
 namespace mozilla {
 namespace dom {
 
 typedef bool
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -138,18 +138,18 @@ DOMProxyHandler::defineProperty(JSContex
     return true;
   }
 
   JSObject* expando = EnsureExpandoObject(cx, proxy);
   if (!expando) {
     return false;
   }
 
-  return JS_DefinePropertyById(cx, expando, id, desc->value, desc->getter, desc->setter,
-                               desc->attrs);
+  JSBool dummy;
+  return js_DefineOwnProperty(cx, expando, id, *desc, &dummy);
 }
 
 bool
 DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, bool* bp)
 {
   JSBool b = true;
 
--- a/dom/webidl/HTMLDocument.webidl
+++ b/dom/webidl/HTMLDocument.webidl
@@ -7,17 +7,18 @@
 interface Selection;
 
 interface HTMLDocument : Document {
            [Throws]
            attribute DOMString? domain;
            [Throws]
            attribute DOMString cookie;
   // DOM tree accessors
-  //(Not proxy yet)getter object (DOMString name);
+  [Throws]
+  getter object (DOMString name);
            [SetterThrows]
            attribute HTMLElement? body;
   readonly attribute HTMLHeadElement? head;
   readonly attribute HTMLCollection images;
   readonly attribute HTMLCollection embeds;
   readonly attribute HTMLCollection plugins;
   readonly attribute HTMLCollection links;
   readonly attribute HTMLCollection forms;