Bug 648801 (new DOM list bindings) - Support expandos on the new proxy-based DOM bindings. r=bz/jst/mrbkap.
authorPeter Van der Beken <peterv@propagandism.org>
Thu, 26 May 2011 21:58:35 +0200
changeset 79070 d64ee71954761887cceb784b141b529c49a315b4
parent 79069 93037e2151f3f3c7cfbf4f6eba441dda26fdeb21
child 79071 1169117ea7f1b124aae3b2b66607aeed0af087e8
push idunknown
push userunknown
push dateunknown
reviewersbz, jst, mrbkap
bugs648801
milestone10.0a1
Bug 648801 (new DOM list bindings) - Support expandos on the new proxy-based DOM bindings. r=bz/jst/mrbkap.
content/base/public/nsContentUtils.h
content/base/public/nsDOMEventTargetWrapperCache.h
content/base/src/nsContentList.cpp
content/base/src/nsContentList.h
content/base/src/nsContentUtils.cpp
content/base/src/nsDOMAttribute.cpp
content/base/src/nsDOMDocumentType.cpp
content/base/src/nsDOMEventTargetWrapperCache.cpp
content/base/src/nsDocument.cpp
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsGenericElement.cpp
content/base/src/nsGenericElement.h
content/base/src/nsNodeUtils.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsWrapperCache.h
dom/base/nsWrapperCacheInlines.h
js/src/xpconnect/src/dombindings.cpp
js/src/xpconnect/src/dombindings.h
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/qsgen.py
js/src/xpconnect/src/xpcconvert.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
js/src/xpconnect/src/xpcprivate.h
js/src/xpconnect/src/xpcpublic.h
js/src/xpconnect/src/xpcwrappednative.cpp
js/src/xpconnect/src/xpcwrappednativejsops.cpp
js/src/xpconnect/wrappers/WrapperFactory.cpp
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1268,32 +1268,19 @@ public:
       aCache->SetPreservingWrapper(PR_TRUE);
 #ifdef DEBUG
       // Make sure the cycle collector will be able to traverse to the wrapper.
       CheckCCWrapperTraversal(aScriptObjectHolder, aCache);
 #endif
     }
   }
   static void ReleaseWrapper(nsISupports* aScriptObjectHolder,
-                             nsWrapperCache* aCache)
-  {
-    if (aCache->PreservingWrapper()) {
-      DropJSObjects(aScriptObjectHolder);
-      aCache->SetPreservingWrapper(PR_FALSE);
-    }
-  }
+                             nsWrapperCache* aCache);
   static void TraceWrapper(nsWrapperCache* aCache, TraceCallback aCallback,
-                           void *aClosure)
-  {
-    if (aCache->PreservingWrapper()) {
-      aCallback(nsIProgrammingLanguage::JAVASCRIPT,
-                aCache->GetWrapperPreserveColor(),
-                "Preserved wrapper", aClosure);
-    }
-  }
+                           void *aClosure);
 
   /**
    * Convert nsIContent::IME_STATUS_* to nsIWidget::IME_STATUS_*
    */
   static PRUint32 GetWidgetStatusFromIMEStatus(PRUint32 aState);
 
   /*
    * Notify when the first XUL menu is opened and when the all XUL menus are
--- a/content/base/public/nsDOMEventTargetWrapperCache.h
+++ b/content/base/public/nsDOMEventTargetWrapperCache.h
@@ -85,17 +85,17 @@ public:
 
     return static_cast<nsDOMEventTargetWrapperCache*>(target);
   }
 
   void Init(JSContext* aCx = nsnull);
 
 protected:
   nsDOMEventTargetWrapperCache() : nsDOMEventTargetHelper(), nsWrapperCache() {}
-  virtual ~nsDOMEventTargetWrapperCache();
+  virtual ~nsDOMEventTargetWrapperCache() {}
 };
 
 #define NS_DECL_EVENT_HANDLER(_event)                                         \
   protected:                                                                  \
     nsRefPtr<nsDOMEventListenerWrapper> mOn##_event##Listener;                \
   public:
 
 #define NS_DECL_AND_IMPL_EVENT_HANDLER(_event)                                \
--- a/content/base/src/nsContentList.cpp
+++ b/content/base/src/nsContentList.cpp
@@ -73,20 +73,25 @@ using namespace mozilla::dom;
 
 nsBaseContentList::~nsBaseContentList()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsBaseContentList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsBaseContentList)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mElements)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsBaseContentList)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mElements)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsBaseContentList)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 #define NS_CONTENT_LIST_INTERFACES(_class)                                    \
     NS_INTERFACE_TABLE_ENTRY(_class, nsINodeList)                             \
     NS_INTERFACE_TABLE_ENTRY(_class, nsIDOMNodeList)
 
 DOMCI_DATA(NodeList, nsBaseContentList)
 
 // QueryInterface implementation for nsBaseContentList
--- a/content/base/src/nsContentList.h
+++ b/content/base/src/nsContentList.h
@@ -94,17 +94,17 @@ public:
   // nsINodeList
   virtual nsIContent* GetNodeAt(PRUint32 aIndex);
   virtual PRInt32 IndexOf(nsIContent* aContent);
   
   PRUint32 Length() const { 
     return mElements.Count();
   }
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsBaseContentList, nsINodeList)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsBaseContentList)
 
   void AppendElement(nsIContent *aContent);
   void MaybeAppendElement(nsIContent* aContent)
   {
     if (aContent)
       AppendElement(aContent);
   }
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -198,16 +198,17 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_
 #ifdef MOZ_MEDIA
 #include "nsHTMLMediaElement.h"
 #endif
 #include "nsDOMTouchEvent.h"
 #include "nsIScriptElement.h"
 #include "prdtoa.h"
 
 #include "mozilla/Preferences.h"
+#include "nsWrapperCacheInlines.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 using namespace mozilla;
 
 const char kLoadAsData[] = "loadAsData";
 
 static const char kJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
@@ -5798,8 +5799,40 @@ bool nsContentUtils::IsRequestFullScreen
   return !sTrustedFullScreenOnly || nsEventStateManager::IsHandlingUserInput();
 }
 
 bool
 nsContentUtils::IsFullScreenKeyInputRestricted()
 {
   return sFullScreenKeyInputRestricted;
 }
+
+// static
+void
+nsContentUtils::ReleaseWrapper(nsISupports* aScriptObjectHolder,
+                               nsWrapperCache* aCache)
+{
+  if (aCache->PreservingWrapper()) {
+    DropJSObjects(aScriptObjectHolder);
+    aCache->SetPreservingWrapper(false);
+  }
+
+  aCache->ClearWrapperIfProxy();
+}
+
+// static
+void
+nsContentUtils::TraceWrapper(nsWrapperCache* aCache, TraceCallback aCallback,
+                             void *aClosure)
+{
+  if (aCache->PreservingWrapper()) {
+    aCallback(nsIProgrammingLanguage::JAVASCRIPT,
+              aCache->GetWrapperPreserveColor(),
+              "Preserved wrapper", aClosure);
+  }
+  else {
+    JSObject *expando = aCache->GetExpandoObjectPreserveColor();
+    if (expando) {
+      aCallback(nsIProgrammingLanguage::JAVASCRIPT, expando, "Expando object",
+                aClosure);
+    }
+  }
+}
--- a/content/base/src/nsDOMAttribute.cpp
+++ b/content/base/src/nsDOMAttribute.cpp
@@ -55,16 +55,17 @@
 #include "nsGkAtoms.h"
 #include "nsCOMArray.h"
 #include "nsNodeUtils.h"
 #include "nsEventListenerManager.h"
 #include "nsTextNode.h"
 #include "mozAutoDocUpdate.h"
 #include "nsMutationEvent.h"
 #include "nsPLDOMEvent.h"
+#include "nsWrapperCacheInlines.h"
 
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 bool nsDOMAttribute::sInitialized;
 
 nsDOMAttribute::nsDOMAttribute(nsDOMAttributeMap *aAttrMap,
                                already_AddRefed<nsINodeInfo> aNodeInfo,
--- a/content/base/src/nsDOMDocumentType.cpp
+++ b/content/base/src/nsDOMDocumentType.cpp
@@ -46,16 +46,17 @@
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsDOMString.h"
 #include "nsNodeInfoManager.h"
 #include "nsIDocument.h"
 #include "nsIXPConnect.h"
 #include "nsIDOMDocument.h"
 #include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
 
 nsresult
 NS_NewDOMDocumentType(nsIDOMDocumentType** aDocType,
                       nsNodeInfoManager *aNodeInfoManager,
                       nsIAtom *aName,
                       const nsAString& aPublicId,
                       const nsAString& aSystemId,
                       const nsAString& aInternalSubset)
--- a/content/base/src/nsDOMEventTargetWrapperCache.cpp
+++ b/content/base/src/nsDOMEventTargetWrapperCache.cpp
@@ -38,22 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsContentUtils.h"
 #include "nsDOMEventTargetWrapperCache.h"
 #include "nsIDocument.h"
 #include "nsIJSContextStack.h"
 #include "nsServiceManagerUtils.h"
 #include "nsDOMJSUtils.h"
-
-nsDOMEventTargetWrapperCache::~nsDOMEventTargetWrapperCache()
-{
-  nsISupports *supports = static_cast<nsIDOMEventTarget*>(this);
-  nsContentUtils::ReleaseWrapper(supports, this);
-}
+#include "nsWrapperCacheInlines.h"
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMEventTargetWrapperCache)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMEventTargetWrapperCache)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMEventTargetWrapperCache,
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -200,16 +200,17 @@
 #include "mozilla/dom/Link.h"
 #include "nsIHTMLDocument.h"
 #include "nsXULAppAPI.h"
 #include "nsDOMTouchEvent.h"
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
+#include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 
 #ifdef PR_LOGGING
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -61,16 +61,17 @@
 #include "nsNodeUtils.h"
 #include "nsBindingManager.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozAutoDocUpdate.h"
 #include "nsPLDOMEvent.h"
 
 #include "pldhash.h"
 #include "prprf.h"
+#include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 
 nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed<nsINodeInfo> aNodeInfo)
   : nsIContent(aNodeInfo)
 {
   NS_ABORT_IF_FALSE(mNodeInfo->NodeType() == nsIDOMNode::TEXT_NODE ||
                     mNodeInfo->NodeType() == nsIDOMNode::CDATA_SECTION_NODE ||
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -142,30 +142,43 @@
 
 #include "mozAutoDocUpdate.h"
 
 #include "nsCSSParser.h"
 #include "prprf.h"
 
 #include "nsSVGFeatures.h"
 #include "nsDOMMemoryReporter.h"
+#include "nsWrapperCacheInlines.h"
 
 #include "xpcpublic.h"
 
 using namespace mozilla::dom;
 namespace css = mozilla::css;
 
 NS_DEFINE_IID(kThisPtrOffsetsSID, NS_THISPTROFFSETS_SID);
 
 PRInt32 nsIContent::sTabFocusModel = eTabFocus_any;
 bool nsIContent::sTabFocusModelAppliesToXUL = false;
 PRUint32 nsMutationGuard::sMutationCount = 0;
 
 nsresult NS_NewContentIterator(nsIContentIterator** aInstancePtrResult);
 
+void
+nsWrapperCache::RemoveExpandoObject()
+{
+  JSObject *expando = GetExpandoObjectPreserveColor();
+  if (expando) {
+    JSCompartment *compartment = js::GetObjectCompartment(expando);
+    xpc::CompartmentPrivate *priv =
+      static_cast<xpc::CompartmentPrivate *>(js_GetCompartmentPrivate(compartment));
+    priv->RemoveDOMExpandoObject(expando);
+  }
+}
+
 //----------------------------------------------------------------------
 
 nsINode::nsSlots::~nsSlots()
 {
   if (mChildNodes) {
     mChildNodes->DropReference();
     NS_RELEASE(mChildNodes);
   }
@@ -1512,26 +1525,38 @@ nsIContent::GetBaseURI() const
     }
   }
 
   return base.forget();
 }
 
 //----------------------------------------------------------------------
 
-NS_IMPL_ADDREF(nsChildContentList)
-NS_IMPL_RELEASE(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsChildContentList)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsChildContentList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsChildContentList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsChildContentList)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_INTERFACE_TABLE_HEAD(nsChildContentList)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsChildContentList)
     NS_INTERFACE_TABLE_ENTRY(nsChildContentList, nsINodeList)
     NS_INTERFACE_TABLE_ENTRY(nsChildContentList, nsIDOMNodeList)
   NS_OFFSET_AND_INTERFACE_TABLE_END
   NS_OFFSET_AND_INTERFACE_TABLE_TO_MAP_SEGUE
+  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsChildContentList)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NodeList)
 NS_INTERFACE_MAP_END
 
 JSObject*
 nsChildContentList::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope)
 {
   return xpc::dom::NodeListBase::create(cx, scope, this);
 }
--- a/content/base/src/nsGenericElement.h
+++ b/content/base/src/nsGenericElement.h
@@ -98,17 +98,18 @@ class nsChildContentList : public nsINod
 {
 public:
   nsChildContentList(nsINode* aNode)
     : mNode(aNode)
   {
     SetIsProxy();
   }
 
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsChildContentList)
 
   // nsWrapperCache
   virtual JSObject* WrapObject(JSContext *cx, XPCWrappedNativeScope *scope);
 
   // nsIDOMNodeList interface
   NS_DECL_NSIDOMNODELIST
 
   // nsINodeList interface
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -59,17 +59,17 @@
 #endif
 #include "nsBindingManager.h"
 #include "nsGenericHTMLElement.h"
 #ifdef MOZ_MEDIA
 #include "nsHTMLMediaElement.h"
 #endif // MOZ_MEDIA
 #include "nsImageLoadingContent.h"
 #include "jsgc.h"
-#include "xpcpublic.h"
+#include "nsWrapperCacheInlines.h"
 
 using namespace mozilla::dom;
 
 // This macro expects the ownerDocument of content_ to be in scope as
 // |nsIDocument* doc|
 // NOTE: AttributeChildRemoved doesn't use this macro but has a very similar use.
 // If you change how this macro behave please update AttributeChildRemoved.
 #define IMPL_MUTATION_NOTIFICATION(func_, content_, params_)      \
@@ -606,16 +606,19 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
         // If reparenting moves us to a new compartment, preserving causes
         // problems. In that case, we release ourselves and re-preserve after
         // reparenting so we're sure to have the right JS object preserved.
         // We use a JSObject stack copy of the wrapper to protect it from GC
         // under ReparentWrappedNativeIfFound.
         if (aNode->PreservingWrapper()) {
           preservedWrapper = wrapper;
           nsContentUtils::ReleaseWrapper(aNode, aNode);
+          NS_ASSERTION(aNode->GetWrapper(),
+                       "ReleaseWrapper cleared our wrapper, this code needs to "
+                       "be changed to deal with that!");
         }
 
         nsCOMPtr<nsIXPConnectJSObjectHolder> oldWrapper;
         rv = xpc->ReparentWrappedNativeIfFound(aCx, wrapper, aNewScope, aNode,
                                                getter_AddRefs(oldWrapper));
 
         if (preservedWrapper) {
           nsContentUtils::PreserveWrapper(aNode, aNode);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -497,16 +497,18 @@
 #include "mozilla/dom/indexedDB/IDBIndex.h"
 #include "nsIIDBDatabaseException.h"
 
 #include "nsIDOMMediaQueryList.h"
 
 #include "nsDOMTouchEvent.h"
 #include "nsIDOMCustomEvent.h"
 
+#include "nsWrapperCacheInlines.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kDOMSOF_CID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID);
 
 static const char kDOMStringBundleURL[] =
   "chrome://global/locale/dom/dom.properties";
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -242,16 +242,17 @@
 
 #include "mozilla/dom/StructuredCloneTags.h"
 
 #include "nsRefreshDriver.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Telemetry.h"
 #include "nsLocation.h"
+#include "nsWrapperCacheInlines.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gDOMLeakPRLog;
 #endif
 
 static const char kStorageEnabled[] = "dom.storage.enabled";
 
 using namespace mozilla;
--- a/dom/base/nsWrapperCache.h
+++ b/dom/base/nsWrapperCache.h
@@ -10,17 +10,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is Gecko DOM code.
  *
  * The Initial Developer of the Original Code is
- * Mozilla Corporation.
+ *   Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *    Peter Van der Beken <peterv@propagandism.org>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -68,69 +68,62 @@ public:
 
   nsWrapperCache() : mWrapperPtrBits(0)
   {
   }
   ~nsWrapperCache()
   {
     NS_ASSERTION(!PreservingWrapper(),
                  "Destroying cache with a preserved wrapper!");
+    RemoveExpandoObject();
   }
 
   /**
    * This getter clears the gray bit before handing out the JSObject which means
    * that the object is guaranteed to be kept alive past the next CC.
    *
    * Implemented in xpcpublic.h because we have to include some JS headers that
    * don't play nicely with the rest of the codebase. Include xpcpublic.h if you
    * need to call this method.
    */
-  inline JSObject* GetWrapper() const;
+  JSObject* GetWrapper() const;
 
   /**
    * This getter does not change the color of the JSObject meaning that the
    * object returned is not guaranteed to be kept alive past the next CC.
    *
    * This should only be called if you are certain that the return value won't
    * be passed into a JS API function and that it won't be stored without being
    * rooted (or otherwise signaling the stored value to the CC).
    */
-  JSObject* GetWrapperPreserveColor() const
-  {
-    return reinterpret_cast<JSObject*>(mWrapperPtrBits & ~kWrapperBitMask);
-  }
+  JSObject* GetWrapperPreserveColor() const;
+
+  JSObject* GetExpandoObjectPreserveColor() const;
 
-  void SetWrapper(JSObject* aWrapper)
-  {
-    NS_ASSERTION(!PreservingWrapper(), "Clearing a preserved wrapper!");
-    mWrapperPtrBits = reinterpret_cast<PtrBits>(aWrapper) |
-                      (mWrapperPtrBits & WRAPPER_IS_PROXY);
-  }
+  void SetWrapper(JSObject* aWrapper);
 
-  void ClearWrapper()
-  {
-    NS_ASSERTION(!PreservingWrapper(), "Clearing a preserved wrapper!");
-    mWrapperPtrBits = 0;
-  }
+  void ClearWrapper();
+  void ClearWrapperIfProxy();
 
   bool PreservingWrapper()
   {
     return (mWrapperPtrBits & WRAPPER_BIT_PRESERVED) != 0;
   }
 
   void SetIsProxy()
   {
     mWrapperPtrBits |= WRAPPER_IS_PROXY;
   }
 
-  bool IsProxy()
+  bool IsProxy() const
   {
     return (mWrapperPtrBits & WRAPPER_IS_PROXY) != 0;
   }
 
+
   /**
    * Wrap the object corresponding to this wrapper cache.  If non-null is
    * returned, the object has already been stored in the wrapper cache.
    */
   virtual JSObject* WrapObject(JSContext *cx, XPCWrappedNativeScope *scope) {
     return nsnull;
   }
 
@@ -140,16 +133,28 @@ private:
   {
     if(aPreserve) {
       mWrapperPtrBits |= WRAPPER_BIT_PRESERVED;
     }
     else {
       mWrapperPtrBits &= ~WRAPPER_BIT_PRESERVED;
     }
   }
+  JSObject *GetJSObjectFromBits() const
+  {
+    return reinterpret_cast<JSObject*>(mWrapperPtrBits & ~kWrapperBitMask);
+  }
+  void SetWrapperBits(void *aWrapper)
+  {
+    mWrapperPtrBits = reinterpret_cast<PtrBits>(aWrapper) |
+                      (mWrapperPtrBits & WRAPPER_IS_PROXY);
+  }
+  void RemoveExpandoObject();
+
+  static JSObject *GetExpandoFromSlot(JSObject *obj);
 
   enum { WRAPPER_BIT_PRESERVED = 1 << 0 };
   enum { WRAPPER_IS_PROXY = 1 << 1 };
   enum { kWrapperBitMask = (WRAPPER_BIT_PRESERVED | WRAPPER_IS_PROXY) };
 
   PtrBits mWrapperPtrBits;
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/base/nsWrapperCacheInlines.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Gecko DOM code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *    Peter Van der Beken <peterv@propagandism.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsWrapperCacheInline_h___
+#define nsWrapperCacheInline_h___
+
+#include "nsWrapperCache.h"
+#include "xpcprivate.h"
+
+inline JSObject*
+nsWrapperCache::GetWrapperPreserveColor() const
+{
+  JSObject *obj = GetJSObjectFromBits();
+  return !IsProxy() || !obj || js::IsProxy(obj) ? obj : nsnull;
+}
+
+inline JSObject*
+nsWrapperCache::GetWrapper() const
+{
+    JSObject* obj = GetWrapperPreserveColor();
+    xpc_UnmarkGrayObject(obj);
+    return obj;
+}
+
+inline void
+nsWrapperCache::SetWrapper(JSObject* aWrapper)
+{
+    NS_ASSERTION(!PreservingWrapper(), "Clearing a preserved wrapper!");
+    NS_ASSERTION(aWrapper, "Use ClearWrapper!");
+
+    JSObject *obj = GetJSObjectFromBits();
+    if (obj && xpc::dom::isExpandoObject(obj)) {
+        NS_ASSERTION(xpc::dom::instanceIsProxy(aWrapper),
+                     "We have an expando but this isn't a DOM proxy?");
+        js::SetProxyExtra(aWrapper, xpc::dom::JSPROXYSLOT_EXPANDO,
+                          js::ObjectValue(*obj));
+    }
+
+    SetWrapperBits(aWrapper);
+}
+
+inline JSObject*
+nsWrapperCache::GetExpandoObjectPreserveColor() const
+{
+    JSObject *obj = GetJSObjectFromBits();
+    if (!obj) {
+        return NULL;
+    }
+
+    if (!IsProxy()) {
+        // If we support non-proxy dom binding objects then this should be:
+        //return xpc::dom::isExpandoObject(obj) ? obj : js::GetSlot(obj, EXPANDO_SLOT);
+        return NULL;
+    }
+
+    // FIXME unmark gray?
+    if (xpc::dom::instanceIsProxy(obj)) {
+        return GetExpandoFromSlot(obj);
+    }
+
+    return xpc::dom::isExpandoObject(obj) ? obj : NULL;
+}
+
+inline JSObject*
+nsWrapperCache::GetExpandoFromSlot(JSObject *obj)
+{
+    NS_ASSERTION(xpc::dom::instanceIsProxy(obj),
+                 "Asking for an expando but this isn't a DOM proxy?");
+    const js::Value &v = js::GetProxyExtra(obj, xpc::dom::JSPROXYSLOT_EXPANDO);
+    return v.isUndefined() ? NULL : v.toObjectOrNull();
+}
+
+inline void
+nsWrapperCache::ClearWrapper()
+{
+    NS_ASSERTION(!PreservingWrapper(), "Clearing a preserved wrapper!");
+    JSObject *obj = GetJSObjectFromBits();
+    if (!obj) {
+        return;
+    }
+
+    JSObject *expando;
+    if (xpc::dom::instanceIsProxy(obj)) {
+        expando = GetExpandoFromSlot(obj);
+    }
+    else {
+        // If we support non-proxy dom binding objects then this should be:
+        //expando = js::GetSlot(obj, EXPANDO_SLOT);
+        expando = NULL;
+    }
+
+    SetWrapperBits(expando);
+}
+
+inline void
+nsWrapperCache::ClearWrapperIfProxy()
+{
+    if (!IsProxy()) {
+        return;
+    }
+
+    RemoveExpandoObject();
+
+    SetWrapperBits(NULL);
+}
+
+#endif /* nsWrapperCache_h___ */
--- a/js/src/xpconnect/src/dombindings.cpp
+++ b/js/src/xpconnect/src/dombindings.cpp
@@ -42,16 +42,18 @@
 #include "xpcquickstubs.h"
 #include "XPCWrapper.h"
 #include "WrapperFactory.h"
 
 #include "nsIDOMNode.h"
 
 #include "nsDOMClassInfo.h"
 #include "nsGlobalWindow.h"
+#include "jsiter.h"
+#include "nsWrapperCacheInlines.h"
 
 extern XPCNativeInterface* interfaces[];
 
 using namespace js;
 
 namespace xpc {
 namespace dom {
 
@@ -134,25 +136,25 @@ NodeList<T>::getNodeList(JSObject *obj)
     return static_cast<T *>(js::GetProxyPrivate(obj).toPrivate());
 }
 
 template<class T>
 uint32
 NodeList<T>::getProtoShape(JSObject *obj)
 {
     JS_ASSERT(js::IsProxy(obj) && js::GetProxyHandler(obj) == &NodeList<T>::instance);
-    return js::GetProxyExtra(obj, 0).toPrivateUint32();
+    return js::GetProxyExtra(obj, JSPROXYSLOT_PROTOSHAPE).toPrivateUint32();
 }
 
 template<class T>
 void
 NodeList<T>::setProtoShape(JSObject *obj, uint32 shape)
 {
     JS_ASSERT(js::IsProxy(obj) && js::GetProxyHandler(obj) == &NodeList<T>::instance);
-    js::SetProxyExtra(obj, 0, PrivateUint32Value(shape));
+    js::SetProxyExtra(obj, JSPROXYSLOT_PROTOSHAPE, PrivateUint32Value(shape));
 }
 
 template<class T>
 bool
 NodeList<T>::instanceIsNodeListObject(JSContext *cx, JSObject *obj, JSObject *callee)
 {
     if (XPCWrapper::IsSecurityWrapper(obj)) {
         if (callee && js::GetObjectGlobal(obj) == js::GetObjectGlobal(callee)) {
@@ -299,22 +301,39 @@ NodeList<T>::create(JSContext *cx, XPCWr
     NS_ADDREF(aNodeList);
     setProtoShape(obj, -1);
 
     aWrapperCache->SetWrapper(obj);
 
     return obj;
 }
 
+static JSObject *
+getExpandoObject(JSObject *obj)
+{
+    NS_ASSERTION(instanceIsProxy(obj), "expected a DOM proxy object");
+    Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+    return v.isUndefined() ? NULL : v.toObjectOrNull();
+}
+
 template<class T>
 bool
 NodeList<T>::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy, jsid id, bool set,
                                       PropertyDescriptor *desc)
 {
-    // FIXME: expandos
+    JSObject *expando = getExpandoObject(proxy);
+    uintN flags = (set ? JSRESOLVE_ASSIGNING : 0) | JSRESOLVE_QUALIFIED;
+    if (expando && !JS_GetPropertyDescriptorById(cx, expando, id, flags, desc))
+        return false;
+    if (desc->obj) {
+        // Pretend the property lives on the wrapper.
+        desc->obj = proxy;
+        return true;
+    }
+
     bool isNumber;
     int32 index = nsDOMClassInfo::GetArrayIndexFromId(cx, id, &isNumber);
     if (isNumber && index >= 0) {
         T *nodeList = getNodeList(proxy);
         nsIContent *result = nodeList->GetNodeAt(PRUint32(index));
         if (result) {
             jsval v;
             if (!WrapObject(cx, proxy, result, result, &v))
@@ -342,45 +361,99 @@ NodeList<T>::getPropertyDescriptor(JSCon
     if (desc->obj)
         return true;
     if (WrapperFactory::IsXrayWrapper(proxy))
         return resolveNativeName(cx, proxy, id, desc);
     return JS_GetPropertyDescriptorById(cx, js::GetObjectProto(proxy), id, JSRESOLVE_QUALIFIED,
                                         desc);
 }
 
+JSClass ExpandoClass = {
+    "DOM proxy binding expando object",
+    JSCLASS_HAS_PRIVATE,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub
+};
+
+template<class T>
+JSObject *
+NodeList<T>::ensureExpandoObject(JSContext *cx, JSObject *obj)
+{
+    NS_ASSERTION(instanceIsProxy(obj), "expected a DOM proxy object");
+    JSObject *expando = getExpandoObject(obj);
+    if (!expando) {
+        expando = JS_NewObjectWithGivenProto(cx, &ExpandoClass, nsnull,
+                                             js::GetObjectParent(obj));
+        if (!expando)
+            return NULL;
+
+        JSCompartment *compartment = js::GetObjectCompartment(obj);
+        xpc::CompartmentPrivate *priv =
+            static_cast<xpc::CompartmentPrivate *>(js_GetCompartmentPrivate(compartment));
+        if (!priv->RegisterDOMExpandoObject(expando))
+            return NULL;
+
+        js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
+        expando->setPrivate(js::GetProxyPrivate(obj).toPrivate());
+    }
+    return expando;
+}
+
 template<class T>
 bool
 NodeList<T>::defineProperty(JSContext *cx, JSObject *proxy, jsid id,
                             PropertyDescriptor *desc)
 {
-    // FIXME: expandos
-    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);
 }
 
 template<class T>
 bool
 NodeList<T>::getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props)
 {
-    // FIXME: expandos
+    JSObject *expando = getExpandoObject(proxy);
+    if (expando && !GetPropertyNames(cx, expando, JSITER_OWNONLY | JSITER_HIDDEN, &props))
+        return false;
+
     PRUint32 length;
     getNodeList(proxy)->GetLength(&length);
     JS_ASSERT(int32(length) >= 0);
     for (int32 i = 0; i < int32(length); ++i) {
         if (!props.append(INT_TO_JSID(i)))
             return false;
     }
     return true;
 }
 
 template<class T>
 bool
 NodeList<T>::delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp)
 {
-    // FIXME: expandos
+    JSBool b = true;
+
+    JSObject *expando;
+    if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = getExpandoObject(proxy))) {
+        jsval v;
+        if (!JS_DeletePropertyById2(cx, expando, id, &v) ||
+            !JS_ValueToBoolean(cx, v, &b)) {
+            return false;
+        }
+    }
+
+    *bp = !!b;
     return true;
 }
 
 template<class T>
 bool
 NodeList<T>::enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props)
 {
     // FIXME: enumerate proto as well
@@ -394,42 +467,42 @@ NodeList<T>::fix(JSContext *cx, JSObject
     vp->setUndefined();
     return true;
 }
 
 template<class T>
 bool
 NodeList<T>::hasOwn(JSContext *cx, JSObject *proxy, jsid id, bool *bp)
 {
-    // FIXME: expandos
+    JSObject *expando = getExpandoObject(proxy);
+    if (expando) {
+        JSBool b = JS_TRUE;
+        JSBool ok = JS_HasPropertyById(cx, expando, id, &b);
+        *bp = !!b;
+        if (!ok || *bp)
+            return ok;
+    }
+
     bool isNumber;
     int32 index = nsDOMClassInfo::GetArrayIndexFromId(cx, id, &isNumber);
     if (isNumber && index >= 0) {
         if (getNodeList(proxy)->GetNodeAt(PRUint32(index))) {
             *bp = true;
             return true;
         }
     }
     *bp = false;
     return true;
 }
 
 template<class T>
 bool
 NodeList<T>::has(JSContext *cx, JSObject *proxy, jsid id, bool *bp)
 {
-    if (!hasOwn(cx, proxy, id, bp))
-        return false;
-    if (*bp)
-        return true;
-    JSBool found;
-    if (!JS_HasPropertyById(cx, js::GetObjectProto(proxy), id, &found))
-        return false;
-    *bp = !!found;
-    return true;
+    return ProxyHandler::has(cx, proxy, id, bp);
 }
 
 template<class T>
 bool
 NodeList<T>::cacheProtoShape(JSContext *cx, JSObject *proxy, JSObject *proto)
 {
     JSPropertyDescriptor desc;
     if (!JS_GetPropertyDescriptorById(cx, proto, nsDOMClassInfo::sLength_id, JSRESOLVE_QUALIFIED, &desc))
@@ -501,17 +574,25 @@ NodeList<T>::resolveNativeName(JSContext
 
     return true;
 }
 
 template<class T>
 bool
 NodeList<T>::get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, Value *vp)
 {
-    // FIXME: expandos
+    JSObject *expando = getExpandoObject(proxy);
+    if (expando) {
+        JSBool hasProp;
+        if (!JS_HasPropertyById(cx, expando, id, &hasProp))
+            return false;
+        if (hasProp)
+            return JS_GetPropertyById(cx, expando, id, vp);
+    }
+
     bool isNumber;
     int32 index = nsDOMClassInfo::GetArrayIndexFromId(cx, id, &isNumber);
     if (isNumber && index >= 0) {
         T *nodeList = getNodeList(proxy);
         nsIContent *result = nodeList->GetNodeAt(PRUint32(index));
         if (result)
             return WrapObject(cx, proxy, result, result, vp);
     }
@@ -538,28 +619,26 @@ NodeList<T>::get(JSContext *cx, JSObject
     }
 
     return JS_GetPropertyById(cx, proto, id, vp);
 }
 
 template<class T>
 bool
 NodeList<T>::set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, bool strict,
-              Value *vp)
+                 Value *vp)
 {
-    // FIXME: expandos
-    return true;
+    return ProxyHandler::set(cx, proxy, proxy, id, strict, vp);
 }
 
 template<class T>
 bool
 NodeList<T>::keys(JSContext *cx, JSObject *proxy, AutoIdVector &props)
 {
-    // FIXME: expandos
-    return getOwnPropertyNames(cx, proxy, props);
+    return ProxyHandler::keys(cx, proxy, props);
 }
 
 template<class T>
 bool
 NodeList<T>::iterate(JSContext *cx, JSObject *proxy, uintN flags, Value *vp)
 {
     JS_ReportError(cx, "FIXME");
     return false;
@@ -587,17 +666,17 @@ NodeList<nsINodeList>::obj_toString(JSCo
     return JS_NewStringCopyZ(cx, "[object NodeList]");
 }
 
 template<class T>
 void
 NodeList<T>::finalize(JSContext *cx, JSObject *proxy)
 {
     T *nodeList = getNodeList(proxy);
-    nsWrapperCache* cache;
+    nsWrapperCache *cache;
     CallQueryInterface(nodeList, &cache);
     if (cache) {
         cache->ClearWrapper();
     }
     NS_RELEASE(nodeList);
 }
 
 }
--- a/js/src/xpconnect/src/dombindings.h
+++ b/js/src/xpconnect/src/dombindings.h
@@ -37,31 +37,24 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef dombindings_h
 #define dombindings_h
 
 #include "jsapi.h"
 #include "jsproxy.h"
+#include "xpcpublic.h"
 
 class nsINodeList;
 class nsIHTMLCollection;
 
 namespace xpc {
 namespace dom {
 
-extern int HandlerFamily;
-inline void* ProxyFamily() { return &HandlerFamily; }
-inline bool instanceIsDOMProxy(JSObject *obj)
-{
-    return js::IsProxy(obj) &&
-           js::GetProxyHandler(obj)->family() == ProxyFamily();
-}
-
 class NodeListBase : public js::ProxyHandler {
 public:
     NodeListBase() : js::ProxyHandler(ProxyFamily()) {}
 
     static JSObject *create(JSContext *cx, XPCWrappedNativeScope *scope,
                             nsINodeList *aNodeList);
     static JSObject *create(JSContext *cx, XPCWrappedNativeScope *scope,
                             nsIHTMLCollection *aHTMLCollection,
@@ -85,16 +78,18 @@ class NodeList : public NodeListBase {
 
     static Methods sProtoMethods[];
 
     static bool instanceIsNodeListObject(JSContext *cx, JSObject *obj, JSObject *callee);
     static JSObject *getPrototype(JSContext *cx, XPCWrappedNativeScope *scope);
 
     static T *getNodeList(JSObject *obj);
 
+    static JSObject *ensureExpandoObject(JSContext *cx, JSObject *obj);
+
     static uint32 getProtoShape(JSObject *obj);
     static void setProtoShape(JSObject *obj, uint32 shape);
 
     static JSBool length_getter(JSContext *cx, JSObject *obj, jsid id, js::Value *vp);
     static JSBool item(JSContext *cx, uintN argc, jsval *vp);
     static JSBool namedItem(JSContext *cx, uintN argc, jsval *vp);
 
     static bool cacheProtoShape(JSContext *cx, JSObject *proxy, JSObject *proto);
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -62,16 +62,17 @@
 #include "XrayWrapper.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 
 #include "jsdIDebuggerService.h"
 
 #include "xpcquickstubs.h"
 #include "dombindings.h"
+#include "nsWrapperCacheInlines.h"
 
 NS_IMPL_THREADSAFE_ISUPPORTS7(nsXPConnect,
                               nsIXPConnect,
                               nsISupportsWeakReference,
                               nsIThreadObserver,
                               nsIJSRuntimeService,
                               nsIJSContextStack,
                               nsIThreadJSContextStack,
@@ -903,17 +904,17 @@ nsXPConnect::Traverse(void *p, nsCycleCo
     // XXX This test does seem fragile, we should probably whitelist classes
     //     that do hold a strong reference, but that might not be possible.
     else if(clazz->flags & JSCLASS_HAS_PRIVATE &&
             clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)
     {
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)");
         cb.NoteXPCOMChild(static_cast<nsISupports*>(xpc_GetJSPrivate(obj)));
     }
-    else if(xpc::dom::instanceIsDOMProxy(obj))
+    else if(xpc::dom::instanceIsProxy(obj))
     {
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "js::GetProxyPrivate(obj)");
         nsISupports *identity =
             static_cast<nsISupports*>(js::GetProxyPrivate(obj).toPrivate());
         cb.NoteXPCOMChild(identity);
     }
 
     return NS_OK;
--- a/js/src/xpconnect/src/qsgen.py
+++ b/js/src/xpconnect/src/qsgen.py
@@ -1696,16 +1696,17 @@ stubTopTemplate = '''\
 #include "prtypes.h"
 #include "nsID.h"
 #include "%s"
 #include "nscore.h"
 #include "nsCOMPtr.h"
 #include "nsDependentString.h"
 #include "xpcprivate.h"  // for XPCCallContext
 #include "xpcquickstubs.h"
+#include "nsWrapperCacheInlines.h"
 #include "jsbuiltins.h"
 '''
 
 def writeStubFile(filename, headerFilename, conf, interfaces):
     print "Creating stub file", filename
     make_targets.append(filename)
 
     f = open(filename, 'w')
--- a/js/src/xpconnect/src/xpcconvert.cpp
+++ b/js/src/xpconnect/src/xpcconvert.cpp
@@ -49,16 +49,17 @@
 #include "XPCWrapper.h"
 #include "nsJSPrincipals.h"
 #include "nsWrapperCache.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 #include "nsJSUtils.h"
 
 #include "dombindings.h"
+#include "nsWrapperCacheInlines.h"
 
 //#define STRICT_CHECK_OF_UNICODE
 #ifdef STRICT_CHECK_OF_UNICODE
 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF80))
 #else // STRICT_CHECK_OF_UNICODE
 #define ILLEGAL_RANGE(c) (0!=((c) & 0xFF00))
 #endif // STRICT_CHECK_OF_UNICODE
 
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -255,16 +255,17 @@ ContextCallback(JSContext *cx, uintN ope
     }
     return JS_TRUE;
 }
 
 xpc::CompartmentPrivate::~CompartmentPrivate()
 {
     delete waiverWrapperMap;
     delete expandoMap;
+    delete domExpandoMap;
     MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
 }
 
 static JSBool
 CompartmentCallback(JSContext *cx, JSCompartment *compartment, uintN op)
 {
     JS_ASSERT(op == JSCOMPARTMENT_DESTROY);
 
@@ -423,22 +424,32 @@ TraceExpandos(XPCWrappedNative *wn, JSOb
 {
     if(wn->IsWrapperExpired())
         return PL_DHASH_REMOVE;
     JS_CALL_OBJECT_TRACER(static_cast<JSTracer *>(aClosure), expando, "expando object");
     return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
+TraceDOMExpandos(nsPtrHashKey<JSObject> *expando, void *aClosure)
+{
+    JS_CALL_OBJECT_TRACER(static_cast<JSTracer *>(aClosure), expando->GetKey(),
+                          "DOM expando object");
+    return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
 TraceCompartment(xpc::PtrAndPrincipalHashKey *aKey, JSCompartment *compartment, void *aClosure)
 {
     xpc::CompartmentPrivate *priv = (xpc::CompartmentPrivate *)
         JS_GetCompartmentPrivate(static_cast<JSTracer *>(aClosure)->context, compartment);
     if (priv->expandoMap)
         priv->expandoMap->Enumerate(TraceExpandos, aClosure);
+    if (priv->domExpandoMap)
+        priv->domExpandoMap->EnumerateEntries(TraceDOMExpandos, aClosure);
     return PL_DHASH_NEXT;
 }
 
 void XPCJSRuntime::TraceXPConnectRoots(JSTracer *trc)
 {
     JSContext *iter = nsnull, *acx;
     while ((acx = JS_ContextIterator(GetJSRuntime(), &iter))) {
         JS_ASSERT(acx->hasRunOption(JSOPTION_UNROOTED_GLOBAL));
@@ -525,32 +536,42 @@ XPCJSRuntime::SuspectWrappedNative(JSCon
     // Only record objects that might be part of a cycle as roots, unless
     // the callback wants all traces (a debug feature).
     if(xpc_IsGrayGCThing(obj) || cb.WantAllTraces())
         cb.NoteRoot(nsIProgrammingLanguage::JAVASCRIPT, obj,
                     nsXPConnect::GetXPConnect());
 }
 
 static PLDHashOperator
-SuspectExpandos(XPCWrappedNative *wrapper, JSObject *&expando, void *arg)
+SuspectExpandos(XPCWrappedNative *wrapper, JSObject *expando, void *arg)
 {
     Closure* closure = static_cast<Closure*>(arg);
     XPCJSRuntime::SuspectWrappedNative(closure->cx, wrapper, *closure->cb);
 
     return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
+SuspectDOMExpandos(nsPtrHashKey<JSObject> *expando, void *arg)
+{
+    Closure *closure = static_cast<Closure*>(arg);
+    closure->cb->NoteXPCOMRoot(static_cast<nsISupports*>(expando->GetKey()->getPrivate()));
+    return PL_DHASH_NEXT;
+}
+
+static PLDHashOperator
 SuspectCompartment(xpc::PtrAndPrincipalHashKey *key, JSCompartment *compartment, void *arg)
 {
     Closure* closure = static_cast<Closure*>(arg);
     xpc::CompartmentPrivate *priv = (xpc::CompartmentPrivate *)
         JS_GetCompartmentPrivate(closure->cx, compartment);
     if (priv->expandoMap)
-        priv->expandoMap->Enumerate(SuspectExpandos, arg);
+        priv->expandoMap->EnumerateRead(SuspectExpandos, arg);
+    if (priv->domExpandoMap)
+        priv->domExpandoMap->EnumerateEntries(SuspectDOMExpandos, arg);
     return PL_DHASH_NEXT;
 }
 
 void
 XPCJSRuntime::AddXPConnectRoots(JSContext* cx,
                                 nsCycleCollectionTraversalCallback &cb)
 {
     // For all JS objects that are held by native objects but aren't held
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -4425,42 +4425,45 @@ namespace xpc {
 struct CompartmentPrivate
 {
     CompartmentPrivate(PtrAndPrincipalHashKey *key, bool wantXrays, bool cycleCollectionEnabled)
         : key(key),
           ptr(nsnull),
           wantXrays(wantXrays),
           cycleCollectionEnabled(cycleCollectionEnabled),
           waiverWrapperMap(nsnull),
-          expandoMap(nsnull)
+          expandoMap(nsnull),
+          domExpandoMap(nsnull)
     {
         MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
     }
 
     CompartmentPrivate(nsISupports *ptr, bool wantXrays, bool cycleCollectionEnabled)
         : key(nsnull),
           ptr(ptr),
           wantXrays(wantXrays),
           cycleCollectionEnabled(cycleCollectionEnabled),
           waiverWrapperMap(nsnull),
-          expandoMap(nsnull)
+          expandoMap(nsnull),
+          domExpandoMap(nsnull)
     {
         MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
     }
 
     ~CompartmentPrivate();
 
     // NB: key and ptr are mutually exclusive.
     nsAutoPtr<PtrAndPrincipalHashKey> key;
     nsCOMPtr<nsISupports> ptr;
     bool wantXrays;
     bool cycleCollectionEnabled;
     JSObject2JSObjectMap *waiverWrapperMap;
     // NB: we don't want this map to hold a strong reference to the wrapper.
     nsDataHashtable<nsPtrHashKey<XPCWrappedNative>, JSObject *> *expandoMap;
+    nsTHashtable<nsPtrHashKey<JSObject> > *domExpandoMap;
     nsCString location;
 
     bool RegisterExpandoObject(XPCWrappedNative *wn, JSObject *expando) {
         if (!expandoMap) {
             expandoMap = new nsDataHashtable<nsPtrHashKey<XPCWrappedNative>, JSObject *>();
             if (!expandoMap->Init(8))
                 return false;
         }
@@ -4483,16 +4486,32 @@ struct CompartmentPrivate
      * This lookup clears the gray bit before handing out the JSObject which
      * means that the object is guaranteed to be kept alive past the next CC.
      */
     JSObject *LookupExpandoObject(XPCWrappedNative *wn) {
         JSObject *obj = LookupExpandoObjectPreserveColor(wn);
         xpc_UnmarkGrayObject(obj);
         return obj;
     }
+
+    bool RegisterDOMExpandoObject(JSObject *expando) {
+        if (!domExpandoMap) {
+            domExpandoMap = new nsTHashtable<nsPtrHashKey<JSObject> >();
+            if(!domExpandoMap->Init(8))
+            {
+                domExpandoMap = nsnull;
+                return false;
+            }
+        }
+        return domExpandoMap->PutEntry(expando);
+    }
+    void RemoveDOMExpandoObject(JSObject *expando) {
+        if(domExpandoMap)
+            domExpandoMap->RemoveEntry(expando);
+    }
 };
 
 inline bool
 CompartmentParticipatesInCycleCollection(JSContext *cx, JSCompartment *compartment)
 {
     CompartmentPrivate *priv =
         static_cast<CompartmentPrivate *>(JS_GetCompartmentPrivate(cx, compartment));
     NS_ASSERTION(priv, "This should never be null!");
--- a/js/src/xpconnect/src/xpcpublic.h
+++ b/js/src/xpconnect/src/xpcpublic.h
@@ -40,16 +40,17 @@
 #ifndef xpcpublic_h
 #define xpcpublic_h
 
 #include "jsapi.h"
 #include "jsclass.h"
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jspubtd.h"
+#include "jsproxy.h"
 
 #include "nsISupports.h"
 #include "nsIPrincipal.h"
 #include "nsWrapperCache.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 
 class nsIPrincipal;
@@ -178,24 +179,16 @@ xpc_UnmarkGrayObjectRecursive(JSObject* 
 // be reached through it.
 inline void
 xpc_UnmarkGrayObject(JSObject *obj)
 {
     if(obj && xpc_IsGrayGCThing(obj))
         xpc_UnmarkGrayObjectRecursive(obj);
 }
 
-inline JSObject*
-nsWrapperCache::GetWrapper() const
-{
-  JSObject* obj = GetWrapperPreserveColor();
-  xpc_UnmarkGrayObject(obj);
-  return obj;
-}
-
 class nsIMemoryMultiReporterCallback;
 
 namespace mozilla {
 namespace xpconnect {
 namespace memory {
 
 struct CompartmentStats
 {
@@ -265,9 +258,33 @@ void
 ReportJSRuntimeStats(const IterateData &data, const nsACString &pathPrefix,
                      nsIMemoryMultiReporterCallback *callback,
                      nsISupports *closure);
 
 } // namespace memory
 } // namespace xpconnect
 } // namespace mozilla
 
+namespace xpc {
+namespace dom {
+
+extern int HandlerFamily;
+inline void* ProxyFamily() { return &HandlerFamily; }
+inline bool instanceIsProxy(JSObject *obj)
+{
+    return js::IsProxy(obj) &&
+           js::GetProxyHandler(obj)->family() == ProxyFamily();
+}
+extern JSClass ExpandoClass;
+inline bool isExpandoObject(JSObject *obj)
+{
+    return js::GetObjectJSClass(obj) == &ExpandoClass;
+}
+
+enum {
+    JSPROXYSLOT_PROTOSHAPE = 0,
+    JSPROXYSLOT_EXPANDO = 1
+};
+
+}
+}
+
 #endif
--- a/js/src/xpconnect/src/xpcwrappednative.cpp
+++ b/js/src/xpconnect/src/xpcwrappednative.cpp
@@ -39,17 +39,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* Wrapper object for reflecting native xpcom objects into JavaScript. */
 
 #include "xpcprivate.h"
 #include "nsCRT.h"
 #include "XPCWrapper.h"
-#include "nsWrapperCache.h"
+#include "nsWrapperCacheInlines.h"
 #include "xpclog.h"
 #include "jstl.h"
 #include "nsINode.h"
 #include "xpcquickstubs.h"
 #include "jsproxy.h"
 #include "AccessCheck.h"
 #include "WrapperFactory.h"
 #include "dombindings.h"
--- a/js/src/xpconnect/src/xpcwrappednativejsops.cpp
+++ b/js/src/xpconnect/src/xpcwrappednativejsops.cpp
@@ -38,16 +38,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */
 
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
+#include "nsWrapperCacheInlines.h"
 
 /***************************************************************************/
 
 // All of the exceptions thrown into JS from this file go through here.
 // That makes this a nice place to set a breakpoint.
 
 static JSBool Throw(uintN errNum, JSContext* cx)
 {
--- a/js/src/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/src/xpconnect/wrappers/WrapperFactory.cpp
@@ -251,17 +251,17 @@ GetWrappedNative(JSContext *cx, JSObject
 
 static bool
 CanXray(JSObject *obj, bool *proxy)
 {
     if (IS_WN_WRAPPER(obj) || js::GetObjectClass(obj)->ext.innerObject) {
         *proxy = false;
         return true;
     }
-    return (*proxy = dom::instanceIsDOMProxy(obj));
+    return (*proxy = dom::instanceIsProxy(obj));
 }
 
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent,
                        uintN flags)
 {
     NS_ASSERTION(!IsWrapper(obj) ||
                  GetProxyHandler(obj) == &WaiveXrayWrapperWrapper ||