Bug 704623, part 1. Track orphan DOM nodes so that they can be reported in about:memory. r=smaug

This commit is contained in:
Johnny Stenback 2012-02-14 15:13:19 -08:00
parent 0fb7cea14a
commit 742aa9d9eb
7 changed files with 155 additions and 20 deletions

View File

@ -311,18 +311,24 @@ public:
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();
@ -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.

View File

@ -232,13 +232,19 @@ nsAttrAndChildArray::TakeChildAt(PRUint32 aPos)
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);
@ -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);

View File

@ -362,6 +362,8 @@ nsContentUtils::Init()
return NS_OK;
}
nsINode::Init();
nsresult rv = NS_GetNameSpaceManager(&sNameSpaceManager);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -91,6 +91,7 @@ nsDOMAttribute::~nsDOMAttribute()
{
if (mChild) {
static_cast<nsTextNode*>(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<nsTextNode*>(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<nsTextNode*>(mChild)->BindToAttribute(this);
mFirstChild = mChild;
@ -793,5 +796,6 @@ nsDOMAttribute::doRemoveChild(bool aNotify)
}
child->UnbindFromAttribute();
child->MarkAsOrphan();
}

View File

@ -1579,7 +1579,8 @@ nsDocument::~nsDocument()
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;

View File

@ -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<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,

View File

@ -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;