Bug 704623, part 1. Track orphan DOM nodes so that they can be reported in about:memory. r=smaug
authorJohnny Stenback <jst@mozilla.com>
Tue, 14 Feb 2012 15:13:19 -0800
changeset 89735 67d25814ec449f95d0bd94af8b3afd08b06174ba
parent 89734 1aa0d78efa8d355427f64e74420e3649998387e1
child 89736 c06cc2fd3395a57b4b158ca579efd87d9bfc7cf9
push id783
push userlsblakk@mozilla.com
push dateTue, 24 Apr 2012 17:33:42 +0000
treeherdermozilla-beta@11faed19f136 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs704623
milestone13.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 704623, part 1. Track orphan DOM nodes so that they can be reported in about:memory. r=smaug
content/base/public/nsINode.h
content/base/src/nsAttrAndChildArray.cpp
content/base/src/nsContentUtils.cpp
content/base/src/nsDOMAttribute.cpp
content/base/src/nsDocument.cpp
content/base/src/nsGenericElement.cpp
dom/base/nsGlobalWindow.cpp
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -306,28 +306,34 @@ public:
   NS_DECL_DOM_MEMORY_REPORTER_SIZEOF
 
   friend class nsNodeUtils;
   friend class nsNodeWeakReference;
   friend class nsNodeSupportsWeakRefTearoff;
   friend class nsAttrAndChildArray;
 
 #ifdef MOZILLA_INTERNAL_API
+  static nsINode *sOrphanNodeHead;
+
   nsINode(already_AddRefed<nsINodeInfo> aNodeInfo)
   : mNodeInfo(aNodeInfo),
     mParent(nsnull),
     mFlags(0),
-    mBoolFlags(0),
-    mNextSibling(nsnull),
-    mPreviousSibling(nsnull),
+    mBoolFlags(1 << NodeIsOrphan),
+    mNextOrphanNode(sOrphanNodeHead->mNextOrphanNode),
+    mPreviousOrphanNode(sOrphanNodeHead),
     mFirstChild(nsnull),
     mSlots(nsnull)
   {
+    NS_ASSERTION(GetBoolFlag(NodeIsOrphan),
+                 "mBoolFlags not initialized correctly!");
+
+    mNextOrphanNode->mPreviousOrphanNode = this;
+    sOrphanNodeHead->mNextOrphanNode = this;
   }
-
 #endif
 
   virtual ~nsINode();
 
   /**
    * Bit-flags to pass (or'ed together) to IsNodeOfType()
    */
   enum {
@@ -1098,18 +1104,73 @@ public:
     return NS_OK;
   }
   nsresult LookupNamespaceURI(const nsAString& aNamespacePrefix,
                               nsAString& aNamespaceURI);
 
   nsresult IsEqualNode(nsIDOMNode* aOther, bool* aReturn);
   bool IsEqualTo(nsINode* aOther);
 
-  nsIContent* GetNextSibling() const { return mNextSibling; }
-  nsIContent* GetPreviousSibling() const { return mPreviousSibling; }
+  nsIContent* GetNextSibling() const
+  {
+    return NS_UNLIKELY(IsOrphan()) ? nsnull : mNextSibling;
+  }
+
+  nsIContent* GetPreviousSibling() const
+  {
+    return NS_UNLIKELY(IsOrphan()) ? nsnull : mPreviousSibling;
+  }
+
+  // Returns true if this node is an orphan node
+  bool IsOrphan() const
+  {
+#ifdef MOZILLA_INTERNAL_API
+    NS_ASSERTION(this != sOrphanNodeHead, "Orphan node head orphan check?!");
+#endif
+
+    return GetBoolFlag(NodeIsOrphan);
+  }
+
+#ifdef MOZILLA_INTERNAL_API
+  // Mark this node as an orphan node. This marking is only relevant
+  // for this node itself, not its children. Its children are not
+  // considered orphan until they themselves are removed from their
+  // parent and get marked as orphans.
+  void MarkAsOrphan()
+  {
+    NS_ASSERTION(!IsOrphan(), "Orphan node orphaned again?");
+    NS_ASSERTION(this != sOrphanNodeHead, "Orphan node head orphaned?!");
+
+    mNextOrphanNode = sOrphanNodeHead->mNextOrphanNode;
+    mPreviousOrphanNode = sOrphanNodeHead;
+    mNextOrphanNode->mPreviousOrphanNode = this;
+    sOrphanNodeHead->mNextOrphanNode = this;
+
+    SetBoolFlag(NodeIsOrphan);
+  }
+
+  // Unmark this node as an orphan node. Do this before inserting this
+  // node into a parent or otherwise associating it with some other
+  // owner.
+  void MarkAsNonOrphan()
+  {
+    NS_ASSERTION(IsOrphan(), "Non-orphan node un-orphaned");
+    NS_ASSERTION(this != sOrphanNodeHead, "Orphan node head unorphaned?!");
+    NS_ASSERTION(!mParent, "Must not have a parent here!");
+
+    mPreviousOrphanNode->mNextOrphanNode = mNextOrphanNode;
+    mNextOrphanNode->mPreviousOrphanNode = mPreviousOrphanNode;
+    mPreviousOrphanNode = nsnull;
+    mNextOrphanNode = nsnull;
+
+    ClearBoolFlag(NodeIsOrphan);
+  }
+#endif
+
+  static void Init();
 
   /**
    * Get the next node in the pre-order tree traversal of the DOM.  If
    * aRoot is non-null, then it must be an ancestor of |this|
    * (possibly equal to |this|) and only nodes that are descendants of
    * aRoot, not including aRoot itself, will be returned.  Returns
    * null if there are no more nodes to traverse.
    */
@@ -1246,16 +1307,18 @@ private:
     NodeIsCCBlackTree,
     // Maybe set if the node is a root of a subtree 
     // which needs to be kept in the purple buffer.
     NodeIsPurpleRoot,
     // Set if the node has an explicit base URI stored
     NodeHasExplicitBaseURI,
     // Set if the element has some style states locked
     ElementHasLockedStyleStates,
+    // Set if the node is an orphan node.
+    NodeIsOrphan,
     // Guard value
     BooleanFlagCount
   };
 
   void SetBoolFlag(BooleanFlag name, bool value) {
     PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
     mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
   }
@@ -1461,18 +1524,34 @@ protected:
 
   PRUint32 mFlags;
 
 private:
   // Boolean flags.
   PRUint32 mBoolFlags;
 
 protected:
-  nsIContent* mNextSibling;
-  nsIContent* mPreviousSibling;
+  union {
+    // mNextSibling is used when this node is part of a DOM tree
+    nsIContent* mNextSibling;
+
+    // mNextOrphanNode is used when this is in the linked list of
+    // orphan nodes.
+    nsINode *mNextOrphanNode;
+  };
+
+  union {
+    // mPreviousSibling is used when this node is part of a DOM tree
+    nsIContent* mPreviousSibling;
+
+    // mPreviousOrphanNode is used when this is in the linked list of
+    // orphan nodes.
+    nsINode* mPreviousOrphanNode;
+  };
+
   nsIContent* mFirstChild;
 
   // Storage for more members that are usually not needed; allocated lazily.
   nsSlots* mSlots;
 };
 
 
 extern const nsIID kThisPtrOffsetsSID;
--- a/content/base/src/nsAttrAndChildArray.cpp
+++ b/content/base/src/nsAttrAndChildArray.cpp
@@ -227,23 +227,29 @@ nsAttrAndChildArray::RemoveChildAt(PRUin
 already_AddRefed<nsIContent>
 nsAttrAndChildArray::TakeChildAt(PRUint32 aPos)
 {
   NS_ASSERTION(aPos < ChildCount(), "out-of-bounds");
 
   PRUint32 childCount = ChildCount();
   void** pos = mImpl->mBuffer + AttrSlotsSize() + aPos;
   nsIContent* child = static_cast<nsIContent*>(*pos);
+
+  MOZ_ASSERT(!child->IsOrphan(), "Child should not be an orphan here");
+
   if (child->mPreviousSibling) {
     child->mPreviousSibling->mNextSibling = child->mNextSibling;
   }
   if (child->mNextSibling) {
     child->mNextSibling->mPreviousSibling = child->mPreviousSibling;
   }
-  child->mPreviousSibling = child->mNextSibling = nsnull;
+
+  // Mark the child as an orphan now that it's no longer associated
+  // with its old parent.
+  child->MarkAsOrphan();
 
   memmove(pos, pos + 1, (childCount - aPos - 1) * sizeof(nsIContent*));
   SetChildCount(childCount - 1);
 
   return child;
 }
 
 PRInt32
@@ -652,28 +658,31 @@ nsAttrAndChildArray::Clear()
 
   nsAutoScriptBlocker scriptBlocker;
   PRUint32 end = slotCount * ATTRSIZE + ChildCount();
   for (i = slotCount * ATTRSIZE; i < end; ++i) {
     nsIContent* child = static_cast<nsIContent*>(mImpl->mBuffer[i]);
     // making this false so tree teardown doesn't end up being
     // O(N*D) (number of nodes times average depth of tree).
     child->UnbindFromTree(false); // XXX is it better to let the owner do this?
-    // Make sure to unlink our kids from each other, since someone
-    // else could stil be holding references to some of them.
+    // Mark the child as an orphan now that it's no longer a child of
+    // its old parent, and make sure to unlink our kids from each
+    // other, since someone else could stil be holding references to
+    // some of them.
+
+    child->MarkAsOrphan();
 
     // XXXbz We probably can't push this assignment down into the |aNullParent|
     // case of UnbindFromTree because we still need the assignment in
     // RemoveChildAt.  In particular, ContentRemoved fires between
     // RemoveChildAt and UnbindFromTree, and in ContentRemoved the sibling
     // chain needs to be correct.  Though maybe we could set the prev and next
     // to point to each other but keep the kid being removed pointing to them
     // through ContentRemoved so consumers can find where it used to be in the
     // list?
-    child->mPreviousSibling = child->mNextSibling = nsnull;
     NS_RELEASE(child);
   }
 
   SetAttrSlotAndChildCount(0, 0);
 }
 
 PRUint32
 nsAttrAndChildArray::NonMappedAttrCount() const
@@ -817,18 +826,26 @@ nsAttrAndChildArray::AddAttrSlot()
 
   return true;
 }
 
 inline void
 nsAttrAndChildArray::SetChildAtPos(void** aPos, nsIContent* aChild,
                                    PRUint32 aIndex, PRUint32 aChildCount)
 {
-  NS_PRECONDITION(!aChild->GetNextSibling(), "aChild with next sibling?");
-  NS_PRECONDITION(!aChild->GetPreviousSibling(), "aChild with prev sibling?");
+  MOZ_ASSERT(aChild->IsOrphan(), "aChild should be an orphan here");
+
+  NS_PRECONDITION(aChild->IsOrphan() || !aChild->GetNextSibling(),
+                  "aChild should be orphan and have no next sibling!");
+  NS_PRECONDITION(aChild->IsOrphan() || !aChild->GetPreviousSibling(),
+                  "aChild should be orphan and have no prev sibling!");
+
+  // Unmark this child as an orphan now that it's a child of its new
+  // parent.
+  aChild->MarkAsNonOrphan();
 
   *aPos = aChild;
   NS_ADDREF(aChild);
   if (aIndex != 0) {
     nsIContent* previous = static_cast<nsIContent*>(*(aPos - 1));
     aChild->mPreviousSibling = previous;
     previous->mNextSibling = aChild;
   }
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -357,16 +357,18 @@ nsresult
 nsContentUtils::Init()
 {
   if (sInitialized) {
     NS_WARNING("Init() called twice");
 
     return NS_OK;
   }
 
+  nsINode::Init();
+
   nsresult rv = NS_GetNameSpaceManager(&sNameSpaceManager);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsXPConnect* xpconnect = nsXPConnect::GetXPConnect();
   NS_ENSURE_TRUE(xpconnect, NS_ERROR_FAILURE);
 
   sXPConnect = xpconnect;
   sThreadJSContextStack = xpconnect;
--- a/content/base/src/nsDOMAttribute.cpp
+++ b/content/base/src/nsDOMAttribute.cpp
@@ -86,16 +86,17 @@ nsDOMAttribute::nsDOMAttribute(nsDOMAttr
     content->AddMutationObserver(this);
   }
 }
 
 nsDOMAttribute::~nsDOMAttribute()
 {
   if (mChild) {
     static_cast<nsTextNode*>(mChild)->UnbindFromAttribute();
+    mChild->MarkAsOrphan();
     NS_RELEASE(mChild);
     mFirstChild = nsnull;
   }
 
   nsIContent* content = GetContentInternal();
   if (content) {
     content->RemoveMutationObserver(this);
   }
@@ -116,16 +117,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMAttribute)
   nsINode::Trace(tmp, aCallback, aClosure);
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttribute)
   nsINode::Unlink(tmp);
   if (tmp->mChild) {
     static_cast<nsTextNode*>(tmp->mChild)->UnbindFromAttribute();
+    tmp->mChild->MarkAsOrphan();
     NS_RELEASE(tmp->mChild);
     tmp->mFirstChild = nsnull;
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 DOMCI_NODE_DATA(Attr, nsDOMAttribute)
 
 // QueryInterface implementation for nsDOMAttribute
@@ -721,16 +723,17 @@ nsDOMAttribute::EnsureChildState()
   NS_PRECONDITION(!mChild, "Someone screwed up");
 
   nsAutoString value;
   GetValue(value);
 
   if (!value.IsEmpty()) {
     NS_NewTextNode(&mChild, mNodeInfo->NodeInfoManager());
 
+    mChild->MarkAsNonOrphan();
     static_cast<nsTextNode*>(mChild)->BindToAttribute(this);
     mFirstChild = mChild;
 
     mChild->SetText(value, false);
   }
 }
 
 void
@@ -788,10 +791,11 @@ nsDOMAttribute::doRemoveChild(bool aNoti
   NS_RELEASE(mChild);
   mFirstChild = nsnull;
 
   if (aNotify) {
     nsNodeUtils::AttributeChildRemoved(this, child);
   }
 
   child->UnbindFromAttribute();
+  child->MarkAsOrphan();
 }
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1574,17 +1574,18 @@ nsDocument::~nsDocument()
     PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
            ("DOCUMENT %p destroyed", this));
 #endif
 
 #ifdef DEBUG
   nsCycleCollector_DEBUG_wasFreed(static_cast<nsIDocument*>(this));
 #endif
 
-  NS_ASSERTION(!mIsShowing, "Destroying a currently-showing document");
+  NS_ASSERTION(!mIsShowing, "Deleting a currently-showing document");
+  NS_ASSERTION(IsOrphan(), "Deleted document not an orphan?");
 
   mInDestructor = true;
   mInUnlinkOrDeletion = true;
 
   // Clear mObservers to keep it in sync with the mutationobserver list
   mObservers.Clear();
 
   if (mStyleSheetSetList) {
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -208,19 +208,26 @@ nsINode::nsSlots::Unlink()
   if (mChildNodes) {
     mChildNodes->DropReference();
     NS_RELEASE(mChildNodes);
   }
 }
 
 //----------------------------------------------------------------------
 
+nsINode *nsINode::sOrphanNodeHead = nsnull;
+
 nsINode::~nsINode()
 {
   NS_ASSERTION(!HasSlots(), "nsNodeUtils::LastRelease was not called?");
+
+  MOZ_ASSERT(IsOrphan(), "Node should be orphan by the time it's deleted!");
+
+  mPreviousOrphanNode->mNextOrphanNode = mNextOrphanNode;
+  mNextOrphanNode->mPreviousOrphanNode = mPreviousOrphanNode;
 }
 
 void*
 nsINode::GetProperty(PRUint16 aCategory, nsIAtom *aPropertyName,
                      nsresult *aStatus) const
 {
   return OwnerDoc()->PropertyTable(aCategory)->GetProperty(this, aPropertyName,
                                                            aStatus);
@@ -3223,17 +3230,17 @@ nsGenericElement::UnbindFromTree(bool aD
     DeleteProperty(nsGkAtoms::transitionsProperty);
     DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
     DeleteProperty(nsGkAtoms::animationsProperty);
   }
 
   // Unset this since that's what the old code effectively did.
   UnsetFlags(NODE_FORCE_XBL_BINDINGS);
-  
+
 #ifdef MOZ_XUL
   nsXULElement* xulElem = nsXULElement::FromContent(this);
   if (xulElem) {
     xulElem->SetXULBindingParent(nsnull);
   }
   else
 #endif
   {
@@ -3817,17 +3824,32 @@ nsGenericElement::GetTextContent(nsAStri
 }
 
 NS_IMETHODIMP
 nsGenericElement::SetTextContent(const nsAString& aTextContent)
 {
   return nsContentUtils::SetNodeTextContent(this, aTextContent, false);
 }
 
-/* static */
+// static
+void
+nsINode::Init()
+{
+  // Allocate static storage for the head of the list of orphan nodes
+  static MOZ_ALIGNED_DECL(char orphanNodeListHead[sizeof(nsINode)], 8);
+  sOrphanNodeHead = reinterpret_cast<nsINode *>(&orphanNodeListHead[0]);
+
+  sOrphanNodeHead->mNextOrphanNode = sOrphanNodeHead;
+  sOrphanNodeHead->mPreviousOrphanNode = sOrphanNodeHead;
+
+  sOrphanNodeHead->mFirstChild = reinterpret_cast<nsIContent *>(0xdeadbeef);
+  sOrphanNodeHead->mParent = reinterpret_cast<nsIContent *>(0xdeadbeef);
+}
+
+// static
 nsresult
 nsGenericElement::DispatchEvent(nsPresContext* aPresContext,
                                 nsEvent* aEvent,
                                 nsIContent* aTarget,
                                 bool aFullDispatch,
                                 nsEventStatus* aStatus)
 {
   NS_PRECONDITION(aTarget, "Must have target");
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1044,16 +1044,20 @@ nsGlobalWindow::~nsGlobalWindow()
     // If our outer window's inner window is this window, null out the
     // outer window's reference to this window that's being deleted.
     nsGlobalWindow *outer = GetOuterWindowInternal();
     if (outer && outer->mInnerWindow == this) {
       outer->mInnerWindow = nsnull;
     }
   }
 
+  if (IsInnerWindow() && mDocument) {
+    mDoc->MarkAsOrphan();
+  }
+
   mDocument = nsnull;           // Forces Release
   mDoc = nsnull;
 
   NS_ASSERTION(!mArguments, "mArguments wasn't cleaned up properly!");
 
   CleanUp(true);
 
 #ifdef DEBUG
@@ -1306,16 +1310,18 @@ nsGlobalWindow::FreeInnerObjects(bool aC
     mNavigator = nsnull;
   }
 
   if (mDocument) {
     NS_ASSERTION(mDoc, "Why is mDoc null?");
 
     // Remember the document's principal.
     mDocumentPrincipal = mDoc->NodePrincipal();
+
+    mDoc->MarkAsOrphan();
   }
 
 #ifdef DEBUG
   if (mDocument)
     nsCycleCollector_DEBUG_shouldBeFreed(nsCOMPtr<nsISupports>(do_QueryInterface(mDocument)));
 #endif
 
   // Remove our reference to the document and the document principal.
@@ -2257,23 +2263,24 @@ nsGlobalWindow::SetNewDocument(nsIDocume
   // alive etc.
 
   if ((!reUseInnerWindow || aDocument != oldDoc) && !aState) {
     nsCOMPtr<nsIHTMLDocument> html_doc(do_QueryInterface(mDocument));
     nsWindowSH::InstallGlobalScopePolluter(cx, newInnerWindow->mJSObject,
                                            html_doc);
   }
 
-  if (aDocument) {
-    aDocument->SetScriptGlobalObject(newInnerWindow);
-  }
+  aDocument->SetScriptGlobalObject(newInnerWindow);
 
   if (!aState) {
     if (reUseInnerWindow) {
       if (newInnerWindow->mDoc != aDocument) {
+        newInnerWindow->mDoc->MarkAsOrphan();
+        aDocument->MarkAsNonOrphan();
+
         newInnerWindow->mDocument = do_QueryInterface(aDocument);
         newInnerWindow->mDoc = aDocument;
 
         // We're reusing the inner window for a new document. In this
         // case we don't clear the inner window's scope, but we must
         // make sure the cached document property gets updated.
 
         // XXXmarkh - tell other languages about this?
@@ -2382,16 +2389,19 @@ nsGlobalWindow::InnerSetNewDocument(nsID
     nsIURI *uri = aDocument->GetDocumentURI();
     nsCAutoString spec;
     if (uri)
       uri->GetSpec(spec);
     PR_LogPrint("DOMWINDOW %p SetNewDocument %s", this, spec.get());
   }
 #endif
 
+  MOZ_ASSERT(aDocument->IsOrphan(), "New document must be orphan!");
+  aDocument->MarkAsNonOrphan();
+
   mDocument = do_QueryInterface(aDocument);
   mDoc = aDocument;
   mLocalStorage = nsnull;
   mSessionStorage = nsnull;
 
 #ifdef DEBUG
   mLastOpenedURI = aDocument->GetDocumentURI();
 #endif