From 51055b1e7a032bcd28de8e81848ccff3f7dd38f4 Mon Sep 17 00:00:00 2001 From: Johnny Stenback Date: Tue, 14 Feb 2012 15:13:19 -0800 Subject: [PATCH] Bug 704623, part 1. Track orphan DOM nodes so that they can be reported in about:memory. r=smaug --- content/base/public/nsINode.h | 95 ++++++++++++++++++++++-- content/base/src/nsAttrAndChildArray.cpp | 29 ++++++-- content/base/src/nsContentUtils.cpp | 2 + content/base/src/nsDOMAttribute.cpp | 4 + content/base/src/nsDocument.cpp | 3 +- content/base/src/nsGenericElement.cpp | 26 ++++++- dom/base/nsGlobalWindow.cpp | 16 +++- 7 files changed, 155 insertions(+), 20 deletions(-) diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index a8c0e7a3fa4..ed8f904d576 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -311,18 +311,24 @@ public: friend class nsAttrAndChildArray; #ifdef MOZILLA_INTERNAL_API + static nsINode *sOrphanNodeHead; + nsINode(already_AddRefed 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(); @@ -1103,8 +1109,63 @@ public: 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 @@ -1251,6 +1312,8 @@ private: NodeHasExplicitBaseURI, // Set if the element has some style states locked ElementHasLockedStyleStates, + // Set if the node is an orphan node. + NodeIsOrphan, // Guard value BooleanFlagCount }; @@ -1466,8 +1529,24 @@ private: 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. diff --git a/content/base/src/nsAttrAndChildArray.cpp b/content/base/src/nsAttrAndChildArray.cpp index ed6e7f1f0d6..e04d331caeb 100644 --- a/content/base/src/nsAttrAndChildArray.cpp +++ b/content/base/src/nsAttrAndChildArray.cpp @@ -232,13 +232,19 @@ nsAttrAndChildArray::TakeChildAt(PRUint32 aPos) PRUint32 childCount = ChildCount(); void** pos = mImpl->mBuffer + AttrSlotsSize() + aPos; nsIContent* child = static_cast(*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); @@ -657,8 +663,12 @@ nsAttrAndChildArray::Clear() // 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 @@ -668,7 +678,6 @@ nsAttrAndChildArray::Clear() // 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); } @@ -822,8 +831,16 @@ 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); diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index cf0d7a0a8ce..d68d1cc0186 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -362,6 +362,8 @@ nsContentUtils::Init() return NS_OK; } + nsINode::Init(); + nsresult rv = NS_GetNameSpaceManager(&sNameSpaceManager); NS_ENSURE_SUCCESS(rv, rv); diff --git a/content/base/src/nsDOMAttribute.cpp b/content/base/src/nsDOMAttribute.cpp index b14467b7c91..56fd4fbe3fc 100644 --- a/content/base/src/nsDOMAttribute.cpp +++ b/content/base/src/nsDOMAttribute.cpp @@ -91,6 +91,7 @@ nsDOMAttribute::~nsDOMAttribute() { if (mChild) { static_cast(mChild)->UnbindFromAttribute(); + mChild->MarkAsOrphan(); NS_RELEASE(mChild); mFirstChild = nsnull; } @@ -121,6 +122,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMAttribute) nsINode::Unlink(tmp); if (tmp->mChild) { static_cast(tmp->mChild)->UnbindFromAttribute(); + tmp->mChild->MarkAsOrphan(); NS_RELEASE(tmp->mChild); tmp->mFirstChild = nsnull; } @@ -726,6 +728,7 @@ nsDOMAttribute::EnsureChildState() if (!value.IsEmpty()) { NS_NewTextNode(&mChild, mNodeInfo->NodeInfoManager()); + mChild->MarkAsNonOrphan(); static_cast(mChild)->BindToAttribute(this); mFirstChild = mChild; @@ -793,5 +796,6 @@ nsDOMAttribute::doRemoveChild(bool aNotify) } child->UnbindFromAttribute(); + child->MarkAsOrphan(); } diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 2134071fde2..cf4614d9520 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1579,7 +1579,8 @@ nsDocument::~nsDocument() nsCycleCollector_DEBUG_wasFreed(static_cast(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; diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index 6011cc04915..7c7f74107b2 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -213,9 +213,16 @@ nsINode::nsSlots::Unlink() //---------------------------------------------------------------------- +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* @@ -3228,7 +3235,7 @@ nsGenericElement::UnbindFromTree(bool aDeep, bool aNullParent) // 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) { @@ -3822,7 +3829,22 @@ 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(&orphanNodeListHead[0]); + + sOrphanNodeHead->mNextOrphanNode = sOrphanNodeHead; + sOrphanNodeHead->mPreviousOrphanNode = sOrphanNodeHead; + + sOrphanNodeHead->mFirstChild = reinterpret_cast(0xdeadbeef); + sOrphanNodeHead->mParent = reinterpret_cast(0xdeadbeef); +} + +// static nsresult nsGenericElement::DispatchEvent(nsPresContext* aPresContext, nsEvent* aEvent, diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index bef91ea49da..589f9a0c893 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1049,6 +1049,10 @@ nsGlobalWindow::~nsGlobalWindow() } } + if (IsInnerWindow() && mDocument) { + mDoc->MarkAsOrphan(); + } + mDocument = nsnull; // Forces Release mDoc = nsnull; @@ -1311,6 +1315,8 @@ nsGlobalWindow::FreeInnerObjects(bool aClearScope) // Remember the document's principal. mDocumentPrincipal = mDoc->NodePrincipal(); + + mDoc->MarkAsOrphan(); } #ifdef DEBUG @@ -2262,13 +2268,14 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, 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; @@ -2387,6 +2394,9 @@ nsGlobalWindow::InnerSetNewDocument(nsIDocument* aDocument) } #endif + MOZ_ASSERT(aDocument->IsOrphan(), "New document must be orphan!"); + aDocument->MarkAsNonOrphan(); + mDocument = do_QueryInterface(aDocument); mDoc = aDocument; mLocalStorage = nsnull;