Fix for bug 324871 (UserData can cause reference cycles). r/sr=sicking.
authorpeterv@propagandism.org
Sat, 12 May 2007 08:36:28 -0700
changeset 1391 61c90b558909b015d389e265b18defda07fefb66
parent 1390 40fddb2cf16d8fbcba5e62a6ccadfe860a1727b1
child 1392 df2ab24719a3794cfaa8287de4e4f5b4fd68070a
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs324871
milestone1.9a5pre
Fix for bug 324871 (UserData can cause reference cycles). r/sr=sicking.
content/base/public/nsContentUtils.h
content/base/public/nsIContent.h
content/base/src/nsContentUtils.cpp
content/base/src/nsDOMAttribute.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsGenericElement.cpp
content/base/src/nsNodeUtils.cpp
content/base/src/nsNodeUtils.h
content/base/src/nsPropertyTable.cpp
content/base/src/nsPropertyTable.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -864,35 +864,16 @@ public:
    * @param aLocalname localname of the node
    * @param aPrefix prefix of the node
    * @param aNamespaceID namespace of the node
    */
   static PRBool IsValidNodeName(nsIAtom *aLocalName, nsIAtom *aPrefix,
                                 PRInt32 aNamespaceID);
 
   /**
-   * Associate an object aData to aKey on node aNode. If aData is null any
-   * previously registered object and UserDataHandler associated to aKey on
-   * aNode will be removed.
-   * Should only be used to implement the DOM Level 3 UserData API.
-   *
-   * @param aNode canonical nsINode pointer of the node to add aData to
-   * @param aKey the key to associate the object to
-   * @param aData the object to associate to aKey on aNode (may be nulll)
-   * @param aHandler the UserDataHandler to call when the node is
-   *                 cloned/deleted/imported/renamed (may be nulll)
-   * @param aResult [out] the previously registered object for aKey on aNode, if
-   *                      any
-   * @return whether adding the object and UserDataHandler succeeded
-   */
-  static nsresult SetUserData(nsINode *aNode, nsIAtom *aKey, nsIVariant *aData,
-                              nsIDOMUserDataHandler *aHandler,
-                              nsIVariant **aResult);
-
-  /**
    * Creates a DocumentFragment from text using a context node to resolve
    * namespaces.
    *
    * @param aContextNode the node which is used to resolve namespaces
    * @param aFragment the string which is parsed to a DocumentFragment
    * @param aReturn [out] the created DocumentFragment
    */
   static nsresult CreateContextualFragment(nsIDOMNode* aContextNode,
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -834,20 +834,30 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIContent
 #define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_PRESERVED_WRAPPER      \
   {                                                              \
     nsISupports *preservedWrapper = nsnull;                      \
     if (tmp->GetOwnerDoc())                                      \
       preservedWrapper = tmp->GetOwnerDoc()->GetReference(tmp);  \
     cb.NoteXPCOMChild(preservedWrapper);                         \
   }
 
+#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_USERDATA \
+  if (tmp->HasProperties()) {                      \
+    nsNodeUtils::TraverseUserData(tmp, cb);        \
+  }
+
 #define NS_IMPL_CYCLE_COLLECTION_UNLINK_LISTENERMANAGER \
   if (tmp->HasFlag(NODE_HAS_LISTENERMANAGER)) {         \
     nsContentUtils::RemoveListenerManager(tmp);         \
     tmp->UnsetFlags(NODE_HAS_LISTENERMANAGER);          \
   }
 
 #define NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER \
   if (tmp->GetOwnerDoc())                                 \
     tmp->GetOwnerDoc()->RemoveReference(tmp);
 
+#define NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA \
+  if (tmp->HasProperties()) {                    \
+    nsNodeUtils::UnlinkUserData(tmp);            \
+  }
+
 
 #endif /* nsIContent_h___ */
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -3144,62 +3144,16 @@ nsContentUtils::IsValidNodeName(nsIAtom 
   // If the namespace is the XML namespace then the prefix can be anything.
   // If the namespace is not the XML namespace then the prefix must not be xml.
   return aPrefix != nsGkAtoms::xmlns &&
          (aNamespaceID == kNameSpaceID_XML || aPrefix != nsGkAtoms::xml);
 }
 
 /* static */
 nsresult
-nsContentUtils::SetUserData(nsINode *aNode, nsIAtom *aKey,
-                            nsIVariant *aData, nsIDOMUserDataHandler *aHandler,
-                            nsIVariant **aResult)
-{
-  *aResult = nsnull;
-
-  nsresult rv;
-  void *data;
-  if (aData) {
-    rv = aNode->SetProperty(DOM_USER_DATA, aKey, aData,
-                            nsPropertyTable::SupportsDtorFunc, PR_TRUE,
-                            &data);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    NS_ADDREF(aData);
-  }
-  else {
-    data = aNode->UnsetProperty(DOM_USER_DATA, aKey);
-  }
-
-  // Take over ownership of the old data from the property table.
-  nsCOMPtr<nsIVariant> oldData = dont_AddRef(NS_STATIC_CAST(nsIVariant*, data));
-
-  if (aData && aHandler) {
-    rv = aNode->SetProperty(DOM_USER_DATA_HANDLER, aKey, aHandler,
-                            nsPropertyTable::SupportsDtorFunc, PR_TRUE);
-    if (NS_FAILED(rv)) {
-      // We failed to set the handler, remove the data.
-      aNode->DeleteProperty(DOM_USER_DATA, aKey);
-
-      return rv;
-    }
-
-    NS_ADDREF(aHandler);
-  }
-  else {
-    aNode->DeleteProperty(DOM_USER_DATA_HANDLER, aKey);
-  }
-
-  oldData.swap(*aResult);
-
-  return NS_OK;
-}
-
-/* static */
-nsresult
 nsContentUtils::CreateContextualFragment(nsIDOMNode* aContextNode,
                                          const nsAString& aFragment,
                                          nsIDOMDocumentFragment** aReturn)
 {
   NS_ENSURE_ARG(aContextNode);
   *aReturn = nsnull;
 
   // Create a new parser for this entire operation
--- a/content/base/src/nsDOMAttribute.cpp
+++ b/content/base/src/nsDOMAttribute.cpp
@@ -72,22 +72,24 @@ nsDOMAttribute::nsDOMAttribute(nsDOMAttr
   // to drop our reference when it goes away.
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMAttribute)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMAttribute)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChild)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_USERDATA
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttribute)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChild)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 // QueryInterface implementation for nsDOMAttribute
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMAttribute)
   NS_INTERFACE_MAP_ENTRY(nsIDOMAttr)
   NS_INTERFACE_MAP_ENTRY(nsIAttribute)
   NS_INTERFACE_MAP_ENTRY(nsINode)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNode)
@@ -97,17 +99,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
                                  new nsNodeSupportsWeakRefTearoff(this))
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMAttr)
   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(Attr)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsDOMAttribute, nsIDOMAttr)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_FULL(nsDOMAttribute, nsIDOMAttr,
-                                      nsNodeUtils::LastRelease(this, PR_TRUE))
+                                      nsNodeUtils::LastRelease(this))
 
 void
 nsDOMAttribute::SetMap(nsDOMAttributeMap *aMap)
 {
   if (mAttrMap && !aMap && sInitialized) {
     // We're breaking a relationship with content and not getting a new one,
     // need to locally cache value. GetValue() does that.
     GetValue(mValue);
@@ -573,39 +575,23 @@ nsDOMAttribute::GetFeature(const nsAStri
                                               aFeature, aVersion, aReturn);
 }
 
 NS_IMETHODIMP
 nsDOMAttribute::SetUserData(const nsAString& aKey, nsIVariant* aData,
                             nsIDOMUserDataHandler* aHandler,
                             nsIVariant** aResult)
 {
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return nsContentUtils::SetUserData(this, key, aData, aHandler, aResult);
+  return nsNodeUtils::SetUserData(this, aKey, aData, aHandler, aResult);
 }
 
 NS_IMETHODIMP
 nsDOMAttribute::GetUserData(const nsAString& aKey, nsIVariant** aResult)
 {
-  nsIDocument *document = GetOwnerDoc();
-  NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  *aResult = NS_STATIC_CAST(nsIVariant*, GetProperty(DOM_USER_DATA, key));
-  NS_IF_ADDREF(*aResult);
-
-  return NS_OK;
+  return nsNodeUtils::GetUserData(this, aKey, aResult);
 }
 
 NS_IMETHODIMP
 nsDOMAttribute::GetIsId(PRBool* aReturn)
 {
   nsIContent* content = GetContentInternal();
   if (!content)
   {
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -852,21 +852,16 @@ nsDocument::~nsDocument()
   }
 
   if (mCSSLoader) {
     // Could be null here if Init() failed
     mCSSLoader->DropDocumentReference();
     NS_RELEASE(mCSSLoader);
   }
 
-  // We must delete properties before dropping document reference from
-  // NodeInfoManager, because nsNodeUtils::LastRelease can't remove properties
-  // when owner document is null.
-  mPropertyTable.DeleteAllProperties();
-
   // XXX Ideally we'd do this cleanup in the nsIDocument destructor.
   if (mNodeInfoManager) {
     mNodeInfoManager->DropDocumentReference();
     NS_RELEASE(mNodeInfoManager);
   }
 
   if (mAttrStyleSheet) {
     mAttrStyleSheet->SetOwningDocument(nsnull);
@@ -879,28 +874,16 @@ nsDocument::~nsDocument()
   NS_IF_RELEASE(mBindingManager);
 
   delete mHeaderData;
   delete mBoxObjectTable;
   delete mContentWrapperHash;
   nsLayoutStatics::Release();
 }
 
-void
-nsDocument::LastRelease()
-{
-  nsNodeUtils::LastRelease(this, PR_FALSE);
-  // Delete properties before starting to destruct the document.
-  // Some of the properties are bound to nsINode objects and the destructor
-  // functions of the properties may want to use the owner document of the
-  // nsINode.
-  mPropertyTable.DeleteAllProperties();
-  delete this;
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDOMDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDOMNSDocument)
   NS_INTERFACE_MAP_ENTRY(nsIDOMDocumentEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOM3DocumentEvent)
@@ -938,17 +921,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   }
   else
 NS_INTERFACE_MAP_END
 
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsDocument, nsIDocument)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS_WITH_DESTROY(nsDocument, 
                                                         nsIDocument,
-                                                        LastRelease())
+                                                        nsNodeUtils::LastRelease(this))
 
 
 PR_STATIC_CALLBACK(PLDHashOperator)
 SubDocTraverser(PLDHashTable *table, PLDHashEntryHdr *hdr, PRUint32 number,
                 void *arg)
 {
   SubDocMapEntry *entry = NS_STATIC_CAST(SubDocMapEntry*, hdr);
   nsCycleCollectionTraversalCallback *cb = 
@@ -1015,16 +998,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     return NS_OK;
   }
 
   // Traverse the mChildren nsAttrAndChildArray.
   for (PRInt32 indx = PRInt32(tmp->mChildren.ChildCount()); indx > 0; --indx) {
     cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1));
   }
 
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_USERDATA
+
   // Traverse all nsIDocument pointer members.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mBindingManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSecurityInfo)
 
   // Traverse all nsDocument nsCOMPtrs.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParser)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptGlobalObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mListenerManager)
@@ -1069,16 +1054,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
   // Unlink the mChildren nsAttrAndChildArray.
   for (PRInt32 indx = PRInt32(tmp->mChildren.ChildCount()) - 1; 
        indx >= 0; --indx) {
     tmp->mChildren.ChildAt(indx)->UnbindFromTree();
     tmp->mChildren.RemoveChildAt(indx);
   }
 
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
+
   // Unlink any associated preserved wrapper.
   tmp->RemoveReference(tmp);
 
   tmp->mParentDocument = nsnull;
   tmp->mRootContent = nsnull;
 
   // nsDocument has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
@@ -4162,37 +4149,24 @@ nsDocument::GetFeature(const nsAString& 
 }
 
 NS_IMETHODIMP
 nsDocument::SetUserData(const nsAString &aKey,
                         nsIVariant *aData,
                         nsIDOMUserDataHandler *aHandler,
                         nsIVariant **aResult)
 {
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return nsContentUtils::SetUserData(this, key, aData, aHandler, aResult);
+  return nsNodeUtils::SetUserData(this, aKey, aData, aHandler, aResult);
 }
 
 NS_IMETHODIMP
 nsDocument::GetUserData(const nsAString &aKey,
                         nsIVariant **aResult)
 {
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  *aResult = NS_STATIC_CAST(nsIVariant*, GetProperty(DOM_USER_DATA, key));
-  NS_IF_ADDREF(*aResult);
-
-  return NS_OK;
+  return nsNodeUtils::GetUserData(this, aKey, aResult);
 }
 
 NS_IMETHODIMP
 nsDocument::LookupPrefix(const nsAString& aNamespaceURI,
                          nsAString& aPrefix)
 {
   nsCOMPtr<nsIDOM3Node> root(do_QueryInterface(mRootContent));
   if (root) {
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -718,18 +718,16 @@ protected:
   
 #ifdef DEBUG
   void VerifyRootContentState();
 #endif
 
   nsDocument(const char* aContentType);
   virtual ~nsDocument();
 
-  void LastRelease();
-
   nsCString mReferrer;
   nsString mLastModified;
 
   nsVoidArray mCharSetObservers;
 
   PLDHashTable *mSubDocuments;
 
   // Array of owning references to all children
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -75,21 +75,23 @@ nsGenericDOMDataNode::~nsGenericDOMDataN
   NS_PRECONDITION(!IsInDoc(),
                   "Please remove this from the document properly");
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericDOMDataNode)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGenericDOMDataNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_USERDATA
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericDOMDataNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGenericDOMDataNode)
   NS_INTERFACE_MAP_ENTRY(nsIContent)
   NS_INTERFACE_MAP_ENTRY(nsINode)
   NS_INTERFACE_MAP_ENTRY(nsPIDOMEventTarget)
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIDOMEventReceiver,
@@ -103,17 +105,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
                                  new nsNodeSupportsWeakRefTearoff(this))
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIDOM3Node, new nsNode3Tearoff(this))
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsGenericDOMDataNode, nsIContent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_FULL(nsGenericDOMDataNode, nsIContent,
-                                      nsNodeUtils::LastRelease(this, PR_TRUE))
+                                      nsNodeUtils::LastRelease(this))
 
 
 nsresult
 nsGenericDOMDataNode::GetNodeValue(nsAString& aNodeValue)
 {
   return GetData(aNodeValue);
 }
 
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -520,41 +520,24 @@ nsNode3Tearoff::GetFeature(const nsAStri
 }
 
 NS_IMETHODIMP
 nsNode3Tearoff::SetUserData(const nsAString& aKey,
                             nsIVariant* aData,
                             nsIDOMUserDataHandler* aHandler,
                             nsIVariant** aResult)
 {
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return nsContentUtils::SetUserData(mContent, key, aData, aHandler, aResult);
+  return nsNodeUtils::SetUserData(mContent, aKey, aData, aHandler, aResult);
 }
 
 NS_IMETHODIMP
 nsNode3Tearoff::GetUserData(const nsAString& aKey,
                             nsIVariant** aResult)
 {
-  nsIDocument *document = mContent->GetOwnerDoc();
-  NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
-  if (!key) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  *aResult = NS_STATIC_CAST(nsIVariant*,
-                            mContent->GetProperty(DOM_USER_DATA, key));
-  NS_IF_ADDREF(*aResult);
-
-  return NS_OK;
+  return nsNodeUtils::GetUserData(mContent, aKey, aResult);
 }
 
 NS_IMETHODIMP
 nsNode3Tearoff::LookupPrefix(const nsAString& aNamespaceURI,
                              nsAString& aPrefix)
 {
   SetDOMStringToNull(aPrefix);
 
@@ -2982,16 +2965,17 @@ nsGenericElement::doRemoveChild(nsIDOMNo
 //----------------------------------------------------------------------
 
 // nsISupports implementation
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericElement)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_USERDATA
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 
   // Unlink child content (and unbind our subtree).
   {
     PRUint32 i;
     PRUint32 kids = tmp->mAttrsAndChildren.ChildCount();
     for (i = kids; i > 0; i--) {
       tmp->mAttrsAndChildren.ChildAt(i-1)->UnbindFromTree();
@@ -3016,16 +3000,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   }
 
   nsIDocument* ownerDoc = tmp->GetOwnerDoc();
   if (ownerDoc) {
     ownerDoc->BindingManager()->Traverse(tmp, cb);
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_LISTENERMANAGER
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_USERDATA
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_PRESERVED_WRAPPER
 
   if (tmp->HasProperties() && tmp->IsNodeOfType(nsINode::eXUL)) {
     nsISupports* property =
       NS_STATIC_CAST(nsISupports*,
                      tmp->GetProperty(nsGkAtoms::contextmenulistener));
     cb.NoteXPCOMChild(property);
     property = NS_STATIC_CAST(nsISupports*,
@@ -3070,17 +3055,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
                                  new nsNodeSupportsWeakRefTearoff(this))
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsGenericElement, nsIContent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS_WITH_DESTROY(nsGenericElement, 
 							nsIContent,
-							nsNodeUtils::LastRelease(this, PR_TRUE))
+							nsNodeUtils::LastRelease(this))
 
 nsresult
 nsGenericElement::PostQueryInterface(REFNSIID aIID, void** aInstancePtr)
 {
   nsIDocument *document = GetOwnerDoc();
   if (document) {
     return document->BindingManager()->GetBindingImplementation(this, aIID,
                                                                 aInstancePtr);
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -169,73 +169,49 @@ nsNodeUtils::ParentChainChanged(nsIConte
     NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(
         slots->mMutationObservers,
         nsIMutationObserver,
         ParentChainChanged,
         (aContent));
   }
 }
 
-struct nsHandlerData
-{
-  PRUint16 mOperation;
-  nsCOMPtr<nsIDOMNode> mSource, mDest;
-};
-
-static void
-CallHandler(void *aObject, nsIAtom *aKey, void *aHandler, void *aData)
-{
-  nsHandlerData *handlerData = NS_STATIC_CAST(nsHandlerData*, aData);
-  nsCOMPtr<nsIDOMUserDataHandler> handler =
-    NS_STATIC_CAST(nsIDOMUserDataHandler*, aHandler);
-
-  nsINode *node = NS_STATIC_CAST(nsINode*, aObject);
-  nsCOMPtr<nsIVariant> data =
-    NS_STATIC_CAST(nsIVariant*, node->GetProperty(DOM_USER_DATA, aKey));
-  NS_ASSERTION(data, "Handler without data?");
-
-  nsAutoString key;
-  aKey->ToString(key);
-  handler->Handle(handlerData->mOperation, key, data, handlerData->mSource,
-                  handlerData->mDest);
-}
-
 void
-nsNodeUtils::LastRelease(nsINode* aNode, PRBool aDelete)
+nsNodeUtils::LastRelease(nsINode* aNode)
 {
   nsINode::nsSlots* slots = aNode->GetExistingSlots();
   if (slots) {
     if (!slots->mMutationObservers.IsEmpty()) {
       NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(slots->mMutationObservers,
                                          nsIMutationObserver,
                                          NodeWillBeDestroyed, (aNode));
     }
 
     PtrBits flags = slots->mFlags | NODE_DOESNT_HAVE_SLOTS;
     delete slots;
     aNode->mFlagsOrSlots = flags;
   }
 
   // Kill properties first since that may run external code, so we want to
   // be in as complete state as possible at that time.
-  if (aNode->HasProperties()) {
+  if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+    // Delete all properties before tearing down the document. Some of the
+    // properties are bound to nsINode objects and the destructor functions of
+    // the properties may want to use the owner document of the nsINode.
+    NS_STATIC_CAST(nsIDocument*, aNode)->PropertyTable()->DeleteAllProperties();
+  }
+  else if (aNode->HasProperties()) {
     // Strong reference to the document so that deleting properties can't
     // delete the document.
     nsCOMPtr<nsIDocument> document = aNode->GetOwnerDoc();
     if (document) {
-      nsHandlerData handlerData;
-      handlerData.mOperation = nsIDOMUserDataHandler::NODE_DELETED;
-
-      nsPropertyTable *table = document->PropertyTable();
-
-      table->Enumerate(aNode, DOM_USER_DATA_HANDLER, CallHandler, &handlerData);
-      table->DeleteAllPropertiesFor(aNode);
+      document->PropertyTable()->DeleteAllPropertiesFor(aNode);
     }
-    aNode->UnsetFlags(NODE_HAS_PROPERTIES);
   }
+  aNode->UnsetFlags(NODE_HAS_PROPERTIES);
 
   if (aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
 #ifdef DEBUG
     if (nsContentUtils::IsInitialized()) {
       nsCOMPtr<nsIEventListenerManager> manager;
       nsContentUtils::GetListenerManager(aNode, PR_FALSE,
                                          getter_AddRefs(manager));
       if (!manager) {
@@ -244,19 +220,118 @@ nsNodeUtils::LastRelease(nsINode* aNode,
       }
     }
 #endif
 
     nsContentUtils::RemoveListenerManager(aNode);
     aNode->UnsetFlags(NODE_HAS_LISTENERMANAGER);
   }
 
-  if (aDelete) {
-    delete aNode;
+  delete aNode;
+}
+
+static nsresult
+SetUserDataProperty(PRUint16 aCategory, nsINode *aNode, nsIAtom *aKey,
+                    nsISupports* aValue, void** aOldValue)
+{
+  nsresult rv = aNode->SetProperty(aCategory, aKey, aValue,
+                                   nsPropertyTable::SupportsDtorFunc, PR_TRUE,
+                                   aOldValue);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Property table owns it now.
+  NS_ADDREF(aValue);
+
+  return NS_OK;
+}
+
+/* static */
+nsresult
+nsNodeUtils::SetUserData(nsINode *aNode, const nsAString &aKey,
+                         nsIVariant *aData, nsIDOMUserDataHandler *aHandler,
+                         nsIVariant **aResult)
+{
+  *aResult = nsnull;
+
+  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
+  if (!key) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsresult rv;
+  void *data;
+  if (aData) {
+    rv = SetUserDataProperty(DOM_USER_DATA, aNode, key, aData, &data);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  else {
+    data = aNode->UnsetProperty(DOM_USER_DATA, key);
   }
+
+  // Take over ownership of the old data from the property table.
+  nsCOMPtr<nsIVariant> oldData = dont_AddRef(NS_STATIC_CAST(nsIVariant*, data));
+
+  if (aData && aHandler) {
+    nsCOMPtr<nsIDOMUserDataHandler> oldHandler;
+    rv = SetUserDataProperty(DOM_USER_DATA_HANDLER, aNode, key, aHandler,
+                             getter_AddRefs(oldHandler));
+    if (NS_FAILED(rv)) {
+      // We failed to set the handler, remove the data.
+      aNode->DeleteProperty(DOM_USER_DATA, key);
+
+      return rv;
+    }
+  }
+  else {
+    aNode->DeleteProperty(DOM_USER_DATA_HANDLER, key);
+  }
+
+  oldData.swap(*aResult);
+
+  return NS_OK;
+}
+
+/* static */
+nsresult
+nsNodeUtils::GetUserData(nsINode *aNode, const nsAString &aKey,
+                         nsIVariant **aResult)
+{
+  nsCOMPtr<nsIAtom> key = do_GetAtom(aKey);
+  if (!key) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  *aResult = NS_STATIC_CAST(nsIVariant*,
+                            aNode->GetProperty(DOM_USER_DATA, key));
+  NS_IF_ADDREF(*aResult);
+
+  return NS_OK;
+}
+
+struct nsHandlerData
+{
+  PRUint16 mOperation;
+  nsCOMPtr<nsIDOMNode> mSource, mDest;
+};
+
+static void
+CallHandler(void *aObject, nsIAtom *aKey, void *aHandler, void *aData)
+{
+  nsHandlerData *handlerData = NS_STATIC_CAST(nsHandlerData*, aData);
+  nsCOMPtr<nsIDOMUserDataHandler> handler =
+    NS_STATIC_CAST(nsIDOMUserDataHandler*, aHandler);
+  nsINode *node = NS_STATIC_CAST(nsINode*, aObject);
+  nsCOMPtr<nsIVariant> data =
+    NS_STATIC_CAST(nsIVariant*, node->GetProperty(DOM_USER_DATA, aKey));
+  NS_ASSERTION(data, "Handler without data?");
+
+  nsAutoString key;
+  aKey->ToString(key);
+  handler->Handle(handlerData->mOperation, key, data, handlerData->mSource,
+                  handlerData->mDest);
 }
 
 /* static */
 nsresult
 nsNodeUtils::CallUserDataHandlers(nsCOMArray<nsINode> &aNodesWithProperties,
                                   nsIDocument *aOwnerDocument,
                                   PRUint16 aOperation, PRBool aCloned)
 {
@@ -288,16 +363,40 @@ nsNodeUtils::CallUserDataHandlers(nsCOMA
 
     table->Enumerate(nodeWithProperties, DOM_USER_DATA_HANDLER, CallHandler,
                      &handlerData);
   }
 
   return NS_OK;
 }
 
+static void
+NoteUserData(void *aObject, nsIAtom *aKey, void *aXPCOMChild, void *aData)
+{
+  nsCycleCollectionTraversalCallback* cb =
+    NS_STATIC_CAST(nsCycleCollectionTraversalCallback*, aData);
+  cb->NoteXPCOMChild(NS_STATIC_CAST(nsISupports*, aXPCOMChild));
+}
+
+/* static */
+void
+nsNodeUtils::TraverseUserData(nsINode* aNode,
+                              nsCycleCollectionTraversalCallback &aCb)
+{
+  nsIDocument* ownerDoc = aNode->GetOwnerDoc();
+  if (!ownerDoc) {
+    return;
+  }
+
+  nsPropertyTable *table = ownerDoc->PropertyTable();
+
+  table->Enumerate(aNode, DOM_USER_DATA, NoteUserData, &aCb);
+  table->Enumerate(aNode, DOM_USER_DATA_HANDLER, NoteUserData, &aCb);
+}
+
 /* static */
 nsresult
 nsNodeUtils::CloneNodeImpl(nsINode *aNode, PRBool aDeep, nsIDOMNode **aResult)
 {
   *aResult = nsnull;
 
   nsCOMPtr<nsIDOMNode> newNode;
   nsCOMArray<nsINode> nodesWithProperties;
@@ -558,8 +657,25 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
       ok = ok && aNodesWithProperties.AppendObject(clone);
     }
 
     NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
   }
 
   return clone ? CallQueryInterface(clone, aResult) : NS_OK;
 }
+
+
+/* static */
+void
+nsNodeUtils::UnlinkUserData(nsINode *aNode)
+{
+  NS_ASSERTION(aNode->HasProperties(), "Call to UnlinkUserData not needed.");
+
+  // Strong reference to the document so that deleting properties can't
+  // delete the document.
+  nsCOMPtr<nsIDocument> document = aNode->GetOwnerDoc();
+  if (document) {
+    document->PropertyTable()->DeleteAllPropertiesFor(aNode, DOM_USER_DATA);
+    document->PropertyTable()->DeleteAllPropertiesFor(aNode,
+                                                      DOM_USER_DATA_HANDLER);
+  }
+}
--- a/content/base/src/nsNodeUtils.h
+++ b/content/base/src/nsNodeUtils.h
@@ -41,17 +41,21 @@
 #include "nsDOMAttributeMap.h"
 #include "nsIDOMNode.h"
 #include "nsIMutationObserver.h"
 
 struct JSContext;
 struct JSObject;
 class nsINode;
 class nsNodeInfoManager;
+class nsIVariant;
+class nsIDOMUserDataHandler;
 template<class E> class nsCOMArray;
+struct nsCycleCollectionTraversalCallback;
+struct CharacterDataChangeInfo;
 
 class nsNodeUtils
 {
 public:
   /**
    * Send CharacterDataChanged notifications to nsIMutationObservers.
    * @param aContent  Node whose data changed
    * @param aInfo     Struct with information details about the change
@@ -107,19 +111,18 @@ public:
    * @param aContent  The piece of content that had its parent changed.
    * @see nsIMutationObserver::ParentChainChanged
    */
   static void ParentChainChanged(nsIContent *aContent);
 
   /**
    * To be called when reference count of aNode drops to zero.
    * @param aNode The node which is going to be deleted.
-   * @param aDelete If PR_TRUE, calling this method also deletes aNode.
    */
-  static void LastRelease(nsINode* aNode, PRBool aDelete);
+  static void LastRelease(nsINode* aNode);
 
   /**
    * Clones aNode, its attributes and, if aDeep is PR_TRUE, its descendant nodes
    * If aNewNodeInfoManager is not null, it is used to create new nodeinfos for
    * the clones. aNodesWithProperties will be filled with all the nodes that
    * have properties, and every node in it will be followed by its clone.
    *
    * @param aNode Node to clone.
@@ -171,16 +174,49 @@ public:
   {
     nsCOMPtr<nsIDOMNode> dummy;
     return CloneAndAdopt(aNode, PR_FALSE, PR_TRUE, aNewNodeInfoManager, aCx,
                          aOldScope, aNewScope, aNodesWithProperties,
                          nsnull, getter_AddRefs(dummy));
   }
 
   /**
+   * Associate an object aData to aKey on node aNode. If aData is null any
+   * previously registered object and UserDataHandler associated to aKey on
+   * aNode will be removed.
+   * Should only be used to implement the DOM Level 3 UserData API.
+   *
+   * @param aNode canonical nsINode pointer of the node to add aData to
+   * @param aKey the key to associate the object to
+   * @param aData the object to associate to aKey on aNode (may be nulll)
+   * @param aHandler the UserDataHandler to call when the node is
+   *                 cloned/deleted/imported/renamed (may be nulll)
+   * @param aResult [out] the previously registered object for aKey on aNode, if
+   *                      any
+   * @return whether adding the object and UserDataHandler succeeded
+   */
+  static nsresult SetUserData(nsINode *aNode, const nsAString &aKey,
+                              nsIVariant *aData,
+                              nsIDOMUserDataHandler *aHandler,
+                              nsIVariant **aResult);
+
+  /**
+   * Get the UserData object registered for a Key on node aNode, if any.
+   * Should only be used to implement the DOM Level 3 UserData API.
+   *
+   * @param aNode canonical nsINode pointer of the node to get UserData for
+   * @param aKey the key to get UserData for
+   * @param aResult [out] the previously registered object for aKey on aNode, if
+   *                      any
+   * @return whether getting the object and UserDataHandler succeeded
+   */
+  static nsresult GetUserData(nsINode *aNode, const nsAString &aKey,
+                              nsIVariant **aResult);
+
+  /**
    * Call registered userdata handlers for operation aOperation for the nodes in
    * aNodesWithProperties. If aCloned is PR_TRUE aNodesWithProperties should
    * contain both the original and the cloned nodes (and only the userdata
    * handlers registered for the original nodes will be called).
    *
    * @param aNodesWithProperties Contains the nodes that might have properties
    *                             registered on them. If aCloned is PR_TRUE every
    *                             one of those nodes should be immediately
@@ -190,26 +226,43 @@ public:
    * @param aCloned If PR_TRUE aNodesWithProperties will contain both original
    *                and cloned nodes.
    */
   static nsresult CallUserDataHandlers(nsCOMArray<nsINode> &aNodesWithProperties,
                                        nsIDocument *aOwnerDocument,
                                        PRUint16 aOperation, PRBool aCloned);
 
   /**
+   * Helper for the cycle collector to traverse the DOM UserData and
+   * UserDataHandlers for aNode.
+   *
+   * @param aNode the node to traverse UserData and UserDataHandlers for
+   * @param aCb the cycle collection callback
+   */
+  static void TraverseUserData(nsINode* aNode,
+                               nsCycleCollectionTraversalCallback &aCb);
+
+  /**
    * A basic implementation of the DOM cloneNode method. Calls nsINode::Clone to
    * do the actual cloning of the node.
    *
    * @param aNode the node to clone
    * @param aDeep if true all descendants will be cloned too
    * @param aResult the clone
    */
   static nsresult CloneNodeImpl(nsINode *aNode, PRBool aDeep,
                                 nsIDOMNode **aResult);
 
+  /**
+   * Release the UserData and UserDataHandlers for aNode.
+   *
+   * @param aNode the node to release the UserData and UserDataHandlers for
+   */
+  static void UnlinkUserData(nsINode *aNode);
+
 private:
   friend PLDHashOperator PR_CALLBACK
     AdoptFunc(nsAttrHashKey::KeyType aKey, nsIDOMNode *aData, void* aUserArg);
 
   /**
    * Walks aNode, its attributes and, if aDeep is PR_TRUE, its descendant nodes.
    * If aClone is PR_TRUE the nodes will be cloned. If aNewNodeInfoManager is
    * not null, it is used to create new nodeinfos for the nodes. Also reparents
--- a/content/base/src/nsPropertyTable.cpp
+++ b/content/base/src/nsPropertyTable.cpp
@@ -111,16 +111,26 @@ nsPropertyTable::DeleteAllProperties()
 void
 nsPropertyTable::DeleteAllPropertiesFor(nsPropertyOwner aObject)
 {
   for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
     prop->DeletePropertyFor(aObject);
   }
 }
 
+void
+nsPropertyTable::DeleteAllPropertiesFor(nsPropertyOwner aObject,
+                                        PRUint16 aCategory)
+{
+  for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
+    if (prop->mCategory == aCategory)
+      prop->DeletePropertyFor(aObject);
+  }
+}
+
 nsresult
 nsPropertyTable::TransferOrDeleteAllPropertiesFor(nsPropertyOwner aObject,
                                                   nsPropertyTable *aOtherTable)
 {
   nsresult rv = NS_OK;
   for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
     if (prop->mTransfer) {
       PropertyListMapEntry *entry = NS_STATIC_CAST(PropertyListMapEntry*,
--- a/content/base/src/nsPropertyTable.h
+++ b/content/base/src/nsPropertyTable.h
@@ -228,16 +228,23 @@ class nsPropertyTable
 
   /**
    * Deletes all of the properties for object |aObject|, calling the
    * destructor function for each property.
    */
   NS_HIDDEN_(void) DeleteAllPropertiesFor(nsPropertyOwner aObject);
 
   /**
+   * Deletes all of the properties in category |aCategory| for object |aObject|,
+   * calling the destructor function for each property.
+   */
+  NS_HIDDEN_(void) DeleteAllPropertiesFor(nsPropertyOwner aObject,
+                                          PRUint16 aCategory);
+
+  /**
    * Transfers all properties for object |aObject| that were set with the
    * |aTransfer| argument as PR_TRUE to |aTable|. Deletes the other properties
    * for object |aObject|, calling the destructor function for each property.
    * If transfering a property fails, this deletes all the properties for
    * object |aObject|.
    */
   NS_HIDDEN_(nsresult)
     TransferOrDeleteAllPropertiesFor(nsPropertyOwner aObject,