Bug 820846 - Modify the ListBase IC to work with [OverrideBuiltins] bindings, codegen changes and make HTMLDocument OverrideBuiltins. r=bz.
☠☠ backed out by 2b63238518a3 ☠ ☠
authorPeter Van der Beken <peterv@propagandism.org>
Sat, 20 Apr 2013 18:04:09 +0200
changeset 141805 2a999f8ee317323c22e7ebdd81a065f1de508387
parent 141804 6dba144500f3bbe0c957b3453fb2b0f89ab1a3b0
child 141806 a2245b038bcc305c8cdb048e8c3199e0ee6d047b
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs820846
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 820846 - Modify the ListBase IC to work with [OverrideBuiltins] bindings, codegen changes and make HTMLDocument OverrideBuiltins. r=bz.
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsGenericHTMLElement.h
content/html/document/src/nsHTMLDocument.cpp
dom/bindings/Codegen.py
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
dom/bindings/parser/WebIDL.py
dom/webidl/HTMLDocument.webidl
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -432,16 +432,24 @@ nsIdentifierMapEntry::AddNameElement(nsI
 void
 nsIdentifierMapEntry::RemoveNameElement(Element* aElement)
 {
   if (mNameContentList) {
     mNameContentList->RemoveElement(aElement);
   }
 }
 
+bool
+nsIdentifierMapEntry::HasIdElementExposedAsHTMLDocumentProperty()
+{
+  Element* idElement = GetIdElement();
+  return idElement &&
+         nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement);
+}
+
 // static
 size_t
 nsIdentifierMapEntry::SizeOfExcludingThis(nsIdentifierMapEntry* aEntry,
                                           nsMallocSizeOfFun aMallocSizeOf,
                                           void*)
 {
   return aEntry->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf);
 }
@@ -1779,16 +1787,19 @@ CustomPrototypeTrace(const nsAString& aN
   traceArgs->callback(aObject, "mCustomPrototypes entry", traceArgs->closure);
   return PL_DHASH_NEXT;
 }
 
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument)
   CustomPrototypeTraceArgs customPrototypeArgs = { aCallback, aClosure };
   tmp->mCustomPrototypes.EnumerateRead(CustomPrototypeTrace, &customPrototypeArgs);
+  if (tmp->PreservingWrapper()) {
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mExpandoAndGeneration.expando);
+  }
   nsINode::Trace(tmp, aCallback, aClosure);
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
   tmp->mInUnlinkOrDeletion = true;
 
   // Clear out our external resources
@@ -1843,16 +1854,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   tmp->mRadioGroups.Clear();
 
   // nsDocument has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
   // else, and not unlink an awful lot here.
 
   tmp->mIdentifierMap.Clear();
+  ++tmp->mExpandoAndGeneration.generation;
+  tmp->mExpandoAndGeneration.expando = JS::UndefinedValue();
 
   tmp->mCustomPrototypes.Clear();
 
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Unlink();
   }
 
   tmp->mPendingTitleChangeEvent.Revoke();
@@ -2720,21 +2733,29 @@ nsIDocument::GetLastModified(nsAString& 
     // (or even the current time), fall back to what NS4.x returned.
     aLastModified.Assign(NS_LITERAL_STRING("01/01/1970 00:00:00"));
   }
 }
 
 void
 nsDocument::AddToNameTable(Element *aElement, nsIAtom* aName)
 {
+  MOZ_ASSERT(nsGenericHTMLElement::ShouldExposeNameAsHTMLDocumentProperty(aElement),
+             "Only put elements that need to be exposed as document['name'] in "
+             "the named table.");
+
   nsIdentifierMapEntry *entry =
     mIdentifierMap.PutEntry(nsDependentAtomString(aName));
 
   // Null for out-of-memory
   if (entry) {
+    if (!entry->HasNameElement() &&
+        !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+      ++mExpandoAndGeneration.generation;
+    }
     entry->AddNameElement(this, aElement);
   }
 }
 
 void
 nsDocument::RemoveFromNameTable(Element *aElement, nsIAtom* aName)
 {
   // Speed up document teardown
@@ -2742,25 +2763,34 @@ nsDocument::RemoveFromNameTable(Element 
     return;
 
   nsIdentifierMapEntry *entry =
     mIdentifierMap.GetEntry(nsDependentAtomString(aName));
   if (!entry) // Could be false if the element was anonymous, hence never added
     return;
 
   entry->RemoveNameElement(aElement);
+  if (!entry->HasNameElement() &&
+      !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+    ++mExpandoAndGeneration.generation;
+  }
 }
 
 void
 nsDocument::AddToIdTable(Element *aElement, nsIAtom* aId)
 {
   nsIdentifierMapEntry *entry =
     mIdentifierMap.PutEntry(nsDependentAtomString(aId));
 
   if (entry) { /* True except on OOM */
+    if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+        !entry->HasNameElement() &&
+        !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+      ++mExpandoAndGeneration.generation;
+    }
     entry->AddIdElement(aElement);
   }
 }
 
 void
 nsDocument::RemoveFromIdTable(Element *aElement, nsIAtom* aId)
 {
   NS_ASSERTION(aId, "huhwhatnow?");
@@ -2771,16 +2801,21 @@ nsDocument::RemoveFromIdTable(Element *a
   }
 
   nsIdentifierMapEntry *entry =
     mIdentifierMap.GetEntry(nsDependentAtomString(aId));
   if (!entry) // Can be null for XML elements with changing ids.
     return;
 
   entry->RemoveIdElement(aElement);
+  if (nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(aElement) &&
+      !entry->HasNameElement() &&
+      !entry->HasIdElementExposedAsHTMLDocumentProperty()) {
+    ++mExpandoAndGeneration.generation;
+  }
   if (entry->IsEmpty()) {
     mIdentifierMap.RawRemoveEntry(entry);
   }
 }
 
 nsIPrincipal*
 nsDocument::GetPrincipal()
 {
@@ -8117,16 +8152,17 @@ nsDocument::ForgetLink(Link* aLink)
 void
 nsDocument::DestroyElementMaps()
 {
 #ifdef DEBUG
   mStyledLinksCleared = true;
 #endif
   mStyledLinks.Clear();
   mIdentifierMap.Clear();
+  ++mExpandoAndGeneration.generation;
 }
 
 static
 PLDHashOperator
 EnumerateStyledLinks(nsPtrHashKey<Link>* aEntry, void* aArray)
 {
   LinkArray* array = static_cast<LinkArray*>(aArray);
   (void)array->AppendElement(aEntry->GetKey());
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -173,16 +173,17 @@ public:
    * This can fire ID change callbacks.
    */
   void RemoveIdElement(Element* aElement);
   /**
    * Set the image element override for this ID. This will be returned by
    * GetIdElement(true) if non-null.
    */
   void SetImageElement(Element* aElement);
+  bool HasIdElementExposedAsHTMLDocumentProperty();
 
   bool HasContentChangeCallback() { return mChangeCallbacks != nullptr; }
   void AddContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
                                 void* aData, bool aForImage);
   void RemoveContentChangeCallback(nsIDocument::IDTargetObserver aCallback,
                                 void* aData, bool aForImage);
 
   void Traverse(nsCycleCollectionTraversalCallback* aCallback);
@@ -1134,16 +1135,19 @@ protected:
   void GetTitleFromElement(uint32_t aNodeType, nsAString& aTitle);
 public:
   // Get our title
   virtual void GetTitle(nsString& aTitle);
   // Set our title
   virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv);
 
   static void XPCOMShutdown();
+
+  js::ExpandoAndGeneration mExpandoAndGeneration;
+
 protected:
   already_AddRefed<nsIPresShell> doCreateShell(nsPresContext* aContext,
                                                nsViewManager* aViewManager,
                                                nsStyleSet* aStyleSet,
                                                nsCompatibility aCompatMode);
 
   void RemoveDocStyleSheetsFromStyleSets();
   void RemoveStyleSheetsFromStyleSets(nsCOMArray<nsIStyleSheet>& aSheets, 
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -1042,26 +1042,16 @@ nsGenericHTMLElement::UnsetAttr(int32_t 
 void
 nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const
 {
   OwnerDoc()->GetBaseTarget(aBaseTarget);
 }
 
 //----------------------------------------------------------------------
 
-static bool
-CanHaveName(nsIAtom* aTag)
-{
-  return aTag == nsGkAtoms::img ||
-         aTag == nsGkAtoms::form ||
-         aTag == nsGkAtoms::applet ||
-         aTag == nsGkAtoms::embed ||
-         aTag == nsGkAtoms::object;
-}
-
 bool
 nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID,
                                      nsIAtom* aAttribute,
                                      const nsAString& aValue,
                                      nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::dir) {
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -713,16 +713,30 @@ public:
     return HasAttr(kNameSpaceID_None, nsGkAtoms::hidden);
   }
 
   virtual bool IsLabelable() const;
 
   static bool TouchEventsEnabled(JSContext* /* unused */, JSObject* /* unused */);
 
   static inline bool
+  CanHaveName(nsIAtom* aTag)
+  {
+    return aTag == nsGkAtoms::img ||
+           aTag == nsGkAtoms::form ||
+           aTag == nsGkAtoms::applet ||
+           aTag == nsGkAtoms::embed ||
+           aTag == nsGkAtoms::object;
+  }
+  static inline bool
+  ShouldExposeNameAsHTMLDocumentProperty(Element* aElement)
+  {
+    return aElement->IsHTML() && CanHaveName(aElement->Tag());
+  }
+  static inline bool
   ShouldExposeIdAsHTMLDocumentProperty(Element* aElement)
   {
     if (!aElement->IsHTML()) {
       return false;
     }
     nsIAtom* tag = aElement->Tag();
     return tag == nsGkAtoms::img ||
            tag == nsGkAtoms::applet ||
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -2380,20 +2380,18 @@ nsHTMLDocument::NamedGetter(JSContext* c
   aFound = true;
   return &val.toObject();
 }
 
 static PLDHashOperator
 IdentifierMapEntryAddNames(nsIdentifierMapEntry* aEntry, void* aArg)
 {
   nsTArray<nsString>* aNames = static_cast<nsTArray<nsString>*>(aArg);
-  Element* idElement;
   if (aEntry->HasNameElement() ||
-      ((idElement = aEntry->GetIdElement()) &&
-       nsGenericHTMLElement::ShouldExposeIdAsHTMLDocumentProperty(idElement))) {
+      aEntry->HasIdElementExposedAsHTMLDocumentProperty()) {
     aNames->AppendElement(aEntry->GetKey());
   }
   return PL_DHASH_NEXT;
 }
 
 void
 nsHTMLDocument::GetSupportedNames(nsTArray<nsString>& aNames)
 {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -990,17 +990,20 @@ class CGDeferredFinalize(CGAbstractStati
   }
   return false;""" % { 'smartPtr': smartPtr }
 
 def finalizeHook(descriptor, hookName, context):
     if descriptor.customFinalize:
         finalize = "self->%s(%s);" % (hookName, context)
     else:
         finalize = "JSBindingFinalized<%s>::Finalized(self);\n" % descriptor.nativeType
-        finalize += "ClearWrapper(self, self);\n" if descriptor.wrapperCache else ""
+        if descriptor.wrapperCache:
+            finalize += "ClearWrapper(self, self);\n"
+        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            finalize += "self->mExpandoAndGeneration.expando = JS::UndefinedValue();\n"
         if descriptor.workers:
             finalize += "self->Release();"
         elif descriptor.nativeOwnership == 'nsisupports':
             finalize += """XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
 if (rt) {
   rt->DeferredRelease(reinterpret_cast<nsISupports*>(self));
 } else {
   NS_RELEASE(self);
@@ -2002,16 +2005,21 @@ def CreateBindingJSObject(descriptor, pr
     if descriptor.proxy:
         create = """  obj = NewProxyObject(aCx, DOMProxyHandler::getInstance(),
                        JS::PrivateValue(aObject), proto, %s);
   if (!obj) {
     return NULL;
   }
 
 """
+        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            create += """  js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO,
+                    JS::PrivateValue(&aObject->mExpandoAndGeneration));
+
+"""
     else:
         create = """  obj = JS_NewObject(aCx, &Class.mBase, proto, %s);
   if (!obj) {
     return NULL;
   }
 
   js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
 """
@@ -6229,32 +6237,28 @@ class CGResolveOwnProperty(CGAbstractMet
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'wrapper'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('JS::Handle<jsid>', 'id'),
                 Argument('JSPropertyDescriptor*', 'desc'), Argument('unsigned', 'flags'),
                 ]
         CGAbstractMethod.__init__(self, descriptor, "ResolveOwnProperty", "bool", args)
     def definition_body(self):
-        return """  // We rely on getOwnPropertyDescriptor not shadowing prototype properties by named
-  // properties. If that changes we'll need to filter here.
-  return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc, flags);
+        return """  return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc, flags);
 """
 
 class CGEnumerateOwnProperties(CGAbstractMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'wrapper'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('JS::AutoIdVector&', 'props')]
         CGAbstractMethod.__init__(self, descriptor, "EnumerateOwnProperties", "bool", args)
     def definition_body(self):
-        return """  // We rely on getOwnPropertyNames not shadowing prototype properties by named
-  // properties. If that changes we'll need to filter here.
-  return js::GetProxyHandler(obj)->getOwnPropertyNames(cx, wrapper, props);
+        return """  return js::GetProxyHandler(obj)->getOwnPropertyNames(cx, wrapper, props);
 """
 
 class CGPrototypeTraitsClass(CGClass):
     def __init__(self, descriptor, indent=''):
         templateArgs = [Argument('prototypes::ID', 'PrototypeID')]
         templateSpecialization = ['prototypes::id::' + descriptor.name]
         enums = [ClassEnum('', ['Depth'],
                            [descriptor.interface.inheritanceDepth()])]
@@ -6579,20 +6583,20 @@ MOZ_ASSERT_IF(desc->obj, desc->obj == ${
                                     "}\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
-            # properties that shadow prototype properties.
-            condition = "!(flags & JSRESOLVE_ASSIGNING) && !HasPropertyOnPrototype(cx, proxy, this, id)"
+            condition = "!HasPropertyOnPrototype(cx, proxy, this, 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 = ("\n" +
                         CGIfWrapper(CGProxyNamedGetter(self.descriptor, templateValues),
                                     condition).define() +
                         "\n")
         else:
             namedGet = ""
@@ -6740,24 +6744,26 @@ class CGDOMJSProxyHandler_delete(ClassMe
             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"
-                       "    return true;\n"
-                       "  }\n"
+            delete += (CGGeneric(namedBody).define() + "\n"
+                       "if (found) {\n"
+                       "  return true;\n"
                        "}\n")
-
-        delete += "return dom::DOMProxyHandler::delete_(cx, proxy, id, bp);";
+            if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+                delete = CGIfWrapper(CGGeneric(delete),
+                                     "!HasPropertyOnPrototype(cx, proxy, this, id)").define()
+        delete += """
+
+return dom::DOMProxyHandler::delete_(cx, proxy, id, bp);"""
 
         return delete
 
 class CGDOMJSProxyHandler_getOwnPropertyNames(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::AutoIdVector&', 'props')]
@@ -6774,23 +6780,27 @@ for (int32_t i = 0; i < int32_t(length);
     return false;
   }
 }
 """
         else:
             addIndices = ""
 
         if self.descriptor.supportsNamedProperties():
+            if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+                shadow = "!isXray"
+            else:
+                shadow = "false"
             addNames = """
 nsTArray<nsString> names;
 UnwrapProxy(proxy)->GetSupportedNames(names);
-if (!AppendNamedPropertyIds(cx, proxy, names, props)) {
+if (!AppendNamedPropertyIds(cx, proxy, names, %s, props)) {
   return false;
 }
-"""
+""" % shadow
         else:
             addNames = ""
 
         if UseHolderForUnforgeable(self.descriptor):
             addUnforgeable = (
                 "if (!js::GetPropertyNames(cx, ${holder}, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {\n"
                 "  return false;\n"
                 "}")
@@ -6837,40 +6847,41 @@ class CGDOMJSProxyHandler_hasOwn(ClassMe
                            "}")
             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 = ""
+            named = (CGProxyNamedPresenceChecker(self.descriptor).define() + "\n" +
+                     "*bp = found;\n")
+            if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+                named = CGIfWrapper(CGGeneric(named + "return true;\n"),
+                                    "!HasPropertyOnPrototype(cx, proxy, this, id)").define()
+                named += ("\n"
+                          "*bp = false;")
+        else:
+            named = "*bp = false;"
 
         return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
           "Should not have a XrayWrapper here");
 
 """ + indexed + unforgeable + """
 JS::Rooted<JSObject*> expando(cx, GetExpandoObject(proxy));
 if (expando) {
   JSBool b = true;
   JSBool ok = JS_HasPropertyById(cx, expando, id, &b);
   *bp = !!b;
   if (!ok || *bp) {
     return ok;
   }
 }
 
-""" + named + """*bp = false;
+""" + named + """
 return true;"""
 
 class CGDOMJSProxyHandler_get(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('JS::Handle<JSObject*>', 'receiver'),
                 Argument('JS::Handle<jsid>', 'id'),
@@ -6910,45 +6921,49 @@ 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(getUnforgeableOrExpando.replace('\n', '\n  ')))
         else:
-            getIndexedOrExpando = getUnforgeableOrExpando + "\n"
+            getIndexedOrExpando = getUnforgeableOrExpando + "\n\n"
 
         if self.descriptor.supportsNamedProperties():
             getNamed = CGProxyNamedGetter(self.descriptor, templateValues)
             if self.descriptor.supportsIndexedProperties():
                 getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)")
-            getNamed = getNamed.define() + "\n"
+            getNamed = getNamed.define() + "\n\n"
         else:
             getNamed = ""
 
+        getOnPrototype = """bool foundOnPrototype;
+if (!GetPropertyOnPrototype(cx, proxy, id, &foundOnPrototype, vp.address())) {
+  return false;
+}
+
+if (foundOnPrototype) {
+  return true;
+}
+
+"""
+        if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            getNamed = getNamed + getOnPrototype
+        else:
+            getNamed = getOnPrototype + getNamed
+
         return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
             "Should not have a XrayWrapper here");
 
-%s
-{  // Scope for this "found" so it doesn't leak to things below
-  bool found;
-  if (!GetPropertyOnPrototype(cx, proxy, id, &found, vp.address())) {
-    return false;
-  }
-
-  if (found) {
-    return true;
-  }
-}
-%s
-vp.setUndefined();
-return true;""" % (getIndexedOrExpando, getNamed)
+""" + getIndexedOrExpando + getNamed + """vp.setUndefined();
+return true;"""
 
 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
     def getBody(self):
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -70,47 +70,78 @@ struct SetListBaseInformation
 };
 
 SetListBaseInformation gSetListBaseInformation;
 
 // static
 JSObject*
 DOMProxyHandler::GetAndClearExpandoObject(JSObject* obj)
 {
-  JSObject* expando = GetExpandoObject(obj);
-  XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj);
-  scope->RemoveDOMExpandoObject(obj);
-  js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue());
-  return expando;
+  MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
+  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  if (v.isUndefined()) {
+    return nullptr;
+  }
+
+  if (v.isObject()) {
+    js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, UndefinedValue());
+    return &v.toObject();
+  }
+
+  js::ExpandoAndGeneration* expandoAndGeneration =
+    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+  v = expandoAndGeneration->expando;
+  if (v.isUndefined()) {
+    return nullptr;
+  }
+  expandoAndGeneration->expando = UndefinedValue();
+  return &v.toObject();
 }
 
 // static
 JSObject*
 DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JS::Handle<JSObject*> obj)
 {
   NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object");
-  JS::Rooted<JSObject*> expando(cx, GetExpandoObject(obj));
-  if (!expando) {
-    expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr,
-                                         js::GetObjectParent(obj));
-    if (!expando) {
-      return NULL;
+  JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+  if (v.isObject()) {
+    return &v.toObject();
+  }
+
+  js::ExpandoAndGeneration* expandoAndGeneration;
+  if (!v.isUndefined()) {
+    expandoAndGeneration = static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+    if (expandoAndGeneration->expando.isObject()) {
+      return &expandoAndGeneration->expando.toObject();
     }
+  } else {
+    expandoAndGeneration = nullptr;
+  }
 
-    XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj);
-    if (!scope->RegisterDOMExpandoObject(obj)) {
-      return NULL;
-    }
+  JSObject* expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr,
+                                                 js::GetObjectParent(obj));
+  if (!expando) {
+    return nullptr;
+  }
 
-    nsWrapperCache* cache;
-    CallQueryInterface(UnwrapDOMObject<nsISupports>(obj), &cache);
-    cache->SetPreservingWrapper(true);
+  XPCWrappedNativeScope* scope = xpc::GetObjectScope(obj);
+  if (!scope->RegisterDOMExpandoObject(obj)) {
+    return nullptr;
+  }
 
+  nsWrapperCache* cache;
+  CallQueryInterface(UnwrapDOMObject<nsISupports>(obj), &cache);
+  cache->SetPreservingWrapper(true);
+
+  if (expandoAndGeneration) {
+    expandoAndGeneration->expando.setObject(*expando);
+  } else {
     js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
   }
+
   return expando;
 }
 
 bool
 DOMProxyHandler::isExtensible(JSObject *proxy)
 {
   return true; // always extensible per WebIDL
 }
@@ -229,30 +260,32 @@ DOMProxyHandler::has(JSContext* cx, JS::
   }
   return ok;
 }
 
 bool
 DOMProxyHandler::AppendNamedPropertyIds(JSContext* cx,
                                         JS::Handle<JSObject*> proxy,
                                         nsTArray<nsString>& names,
+                                        bool shadowPrototypeProperties,
                                         JS::AutoIdVector& props)
 {
   for (uint32_t i = 0; i < names.Length(); ++i) {
     JS::Rooted<JS::Value> v(cx);
     if (!xpc::NonVoidStringToJsval(cx, names[i], v.address())) {
       return false;
     }
 
     JS::Rooted<jsid> id(cx);
     if (!JS_ValueToId(cx, v, id.address())) {
       return false;
     }
 
-    if (!HasPropertyOnPrototype(cx, proxy, this, id)) {
+    if (shadowPrototypeProperties ||
+        !HasPropertyOnPrototype(cx, proxy, this, id)) {
       if (!props.append(id)) {
         return false;
       }
     }
   }
 
   return true;
 }
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -46,29 +46,42 @@ public:
   bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
 
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
-    return v.isUndefined() ? NULL : v.toObjectOrNull();
+    if (v.isObject()) {
+      return &v.toObject();
+    }
+
+    if (v.isUndefined()) {
+      return nullptr;
+    }
+
+    js::ExpandoAndGeneration* expandoAndGeneration =
+      static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+    v = expandoAndGeneration->expando;
+    return v.isUndefined() ? nullptr : &v.toObject();
   }
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
 
   const DOMClass& mClass;
 
 protected:
-  // Append the property names in "names" that don't live on our proto
-  // chain to "props"
+  // Append the property names in "names" to "props". If
+  // shadowPrototypeProperties is false then skip properties that are also
+  // present on our proto chain.
   bool AppendNamedPropertyIds(JSContext* cx, JS::Handle<JSObject*> proxy,
                               nsTArray<nsString>& names,
+                              bool shadowPrototypeProperties,
                               JS::AutoIdVector& props);
 };
 
 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
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -898,17 +898,18 @@ class IDLInterface(IDLObjectWithScope):
                     elif not newMethod in self.namedConstructors:
                         raise WebIDLError("NamedConstructor conflicts with a NamedConstructor of a different interface",
                                           [method.location, newMethod.location])
             elif (identifier == "PrefControlled" or
                   identifier == "Pref" or
                   identifier == "NeedNewResolve" or
                   identifier == "JSImplementation" or
                   identifier == "HeaderFile" or
-                  identifier == "NavigatorProperty"):
+                  identifier == "NavigatorProperty" or
+                  identifier == "OverrideBuiltins"):
                 # Known attributes that we don't need to do anything with here
                 pass
             else:
                 raise WebIDLError("Unknown extended attribute %s on interface" % identifier,
                                   [attr.location])
 
             attrlist = attr.listValue()
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
--- a/dom/webidl/HTMLDocument.webidl
+++ b/dom/webidl/HTMLDocument.webidl
@@ -1,16 +1,17 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  */
 
 interface Selection;
 
+[OverrideBuiltins]
 interface HTMLDocument : Document {
            [Throws]
            attribute DOMString? domain;
            [Throws]
            attribute DOMString cookie;
   // DOM tree accessors
   [Throws]
   getter object (DOMString name);