From c2c495c2efbc5e49207291948b64df66866a9e25 Mon Sep 17 00:00:00 2001 From: "jonas@sicking.cc" Date: Mon, 7 May 2007 16:45:25 -0700 Subject: [PATCH] Don't attempt to cycle collect documents, windows and elements that are currently being viewed. r=bz, sr=jst b=378987 --- content/base/public/nsIDocument.h | 22 ++ content/base/public/nsINode.h | 9 +- content/base/src/Makefile.in | 3 + content/base/src/nsCCUncollectableMarker.cpp | 210 +++++++++++++++++++ content/base/src/nsCCUncollectableMarker.h | 62 ++++++ content/base/src/nsDocument.cpp | 5 + content/base/src/nsGenericElement.cpp | 8 + content/base/src/nsNodeUtils.cpp | 1 + content/xul/content/src/nsXULElement.cpp | 3 + dom/src/base/Makefile.in | 1 + dom/src/base/nsGlobalWindow.cpp | 6 + layout/build/nsLayoutStatics.cpp | 7 + xpcom/base/nsCycleCollector.cpp | 8 + 13 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 content/base/src/nsCCUncollectableMarker.cpp create mode 100644 content/base/src/nsCCUncollectableMarker.h diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 0a7894a04fb3..378512f9c659 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -853,6 +853,24 @@ public: */ virtual PRBool MutationEventBeingDispatched() = 0; + /** + * Marks as not-going-to-be-collected for the given generation of + * cycle collection. + */ + void MarkUncollectableForCCGeneration(PRUint32 aGeneration) + { + mMarkedCCGeneration = aGeneration; + } + + /** + * Gets the cycle collector generation this document is marked for. + */ + PRUint32 GetMarkedCCGeneration() + { + return mMarkedCCGeneration; + } + + protected: ~nsIDocument() { @@ -925,6 +943,10 @@ protected: // if this document is part of a multipart document, // the ID can be used to distinguish it from the other parts. PRUint32 mPartID; + + // Cycle collector generation in which we're certain that this document + // won't be collected + PRUint32 mMarkedCCGeneration; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID) diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index 66c4833c3b28..d95b4dd8ac89 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -82,11 +82,16 @@ class nsNodeSupportsWeakRefTearoff; // NOTE: Should only be used on nsIContent nodes #define NODE_MAY_HAVE_FRAME 0x00000020U +// Whether the 'in doc' flag is faked to true for +// this node. This should only ever be set on XUL +// elements. +#define NODE_HAS_FAKED_INDOC 0x00000040U + // Four bits for the script-type ID -#define NODE_SCRIPT_TYPE_OFFSET 6 +#define NODE_SCRIPT_TYPE_OFFSET 7 // Remaining bits are node type specific. -#define NODE_TYPE_SPECIFIC_BITS_OFFSET 0x0a +#define NODE_TYPE_SPECIFIC_BITS_OFFSET 0x0b // Useful macro for getting a node given an nsIContent and an nsIDocument // Returns the first argument cast to nsINode if it is non-null, otherwise diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index a88535f5f724..a6be900eebc0 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -77,6 +77,8 @@ REQUIRES = xpcom \ rdf \ xultmpl \ util \ + appshell \ + shistory \ $(NULL) EXPORTS = \ @@ -100,6 +102,7 @@ CPPSRCS = \ nsAtomListUtils.cpp \ nsAttrAndChildArray.cpp \ nsAttrValue.cpp \ + nsCCUncollectableMarker.cpp \ nsCommentNode.cpp \ nsContentAreaDragDrop.cpp \ nsContentIterator.cpp \ diff --git a/content/base/src/nsCCUncollectableMarker.cpp b/content/base/src/nsCCUncollectableMarker.cpp new file mode 100644 index 000000000000..63303f810f0c --- /dev/null +++ b/content/base/src/nsCCUncollectableMarker.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 mozilla.org code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonas Sicking (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +#include "nsCCUncollectableMarker.h" +#include "nsIObserverService.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsServiceManagerUtils.h" +#include "nsIDOMDocument.h" +#include "nsIContentViewer.h" +#include "nsIDocument.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "nsIWebNavigation.h" +#include "nsISHistory.h" +#include "nsISHEntry.h" +#include "nsISHContainer.h" +#include "nsIWindowWatcher.h" + +static PRBool sInited = 0; +PRUint32 nsCCUncollectableMarker::sGeneration = 0; + +NS_IMPL_ISUPPORTS1(nsCCUncollectableMarker, nsIObserver) + +/* static */ +nsresult +nsCCUncollectableMarker::Init() +{ + if (sInited) { + return NS_OK; + } + + nsCOMPtr marker = new nsCCUncollectableMarker; + NS_ENSURE_TRUE(marker, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv; + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // This makes the observer service hold an owning reference to the marker + rv = obs->AddObserver(marker, "cycle-collector-begin", PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + sInited = PR_TRUE; + + return NS_OK; +} + +void +MarkContentViewer(nsIContentViewer* aViewer) +{ + if (!aViewer) { + return; + } + + nsCOMPtr domDoc; + aViewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc = do_QueryInterface(domDoc); + if (doc) { + doc->MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); + } +} + +void MarkDocShell(nsIDocShellTreeNode* aNode); + +void +MarkSHEntry(nsISHEntry* aSHEntry) +{ + if (!aSHEntry) { + return; + } + + nsCOMPtr cview; + aSHEntry->GetContentViewer(getter_AddRefs(cview)); + MarkContentViewer(cview); + + nsCOMPtr child; + PRInt32 i = 0; + while (NS_SUCCEEDED(aSHEntry->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + MarkDocShell(child); + } + + nsCOMPtr shCont = do_QueryInterface(aSHEntry); + PRInt32 count; + shCont->GetChildCount(&count); + for (i = 0; i < count; ++i) { + nsCOMPtr childEntry; + shCont->GetChildAt(i, getter_AddRefs(childEntry)); + MarkSHEntry(childEntry); + } + +} + +void +MarkDocShell(nsIDocShellTreeNode* aNode) +{ + nsCOMPtr shell = do_QueryInterface(aNode); + if (!shell) { + return; + } + + nsCOMPtr cview; + shell->GetContentViewer(getter_AddRefs(cview)); + MarkContentViewer(cview); + + nsCOMPtr webNav = do_QueryInterface(shell); + nsCOMPtr history; + webNav->GetSessionHistory(getter_AddRefs(history)); + if (history) { + PRInt32 i, historyCount; + history->GetCount(&historyCount); + for (i = 0; i < historyCount; ++i) { + nsCOMPtr historyEntry; + history->GetEntryAtIndex(i, PR_FALSE, getter_AddRefs(historyEntry)); + nsCOMPtr shEntry = do_QueryInterface(historyEntry); + + MarkSHEntry(shEntry); + } + } + + PRInt32 i, childCount; + aNode->GetChildCount(&childCount); + for (i = 0; i < childCount; ++i) { + nsCOMPtr child; + aNode->GetChildAt(i, getter_AddRefs(child)); + MarkDocShell(child); + } +} + +void +MarkWindowList(nsISimpleEnumerator* aWindowList) +{ + nsCOMPtr iter; + while (NS_SUCCEEDED(aWindowList->GetNext(getter_AddRefs(iter))) && + iter) { + nsCOMPtr window = do_QueryInterface(iter); + if (window) { + nsCOMPtr rootDocShell = + do_QueryInterface(window->GetDocShell()); + + MarkDocShell(rootDocShell); + } + } +} + +nsresult +nsCCUncollectableMarker::Observe(nsISupports* aSubject, const char* aTopic, + const PRUnichar* aData) +{ + // Increase generation to effectivly unmark all current objects + ++sGeneration; + + // Iterate all toplevel windows + nsresult rv; + nsCOMPtr windowList; + nsCOMPtr med = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); + if (med) { + rv = med->GetEnumerator(nsnull, getter_AddRefs(windowList)); + NS_ENSURE_SUCCESS(rv, rv); + + MarkWindowList(windowList); + } + + nsCOMPtr ww = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + if (ww) { + rv = ww->GetWindowEnumerator(getter_AddRefs(windowList)); + NS_ENSURE_SUCCESS(rv, rv); + + MarkWindowList(windowList); + } +} + diff --git a/content/base/src/nsCCUncollectableMarker.h b/content/base/src/nsCCUncollectableMarker.h new file mode 100644 index 000000000000..822ff75ea68a --- /dev/null +++ b/content/base/src/nsCCUncollectableMarker.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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 mozilla.org code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jonas Sicking (Original Author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +#include "nsIObserver.h" + +class nsCCUncollectableMarker : public nsIObserver +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + /** + * Inits a global nsCCUncollectableMarker. Should only be called once. + */ + static nsresult Init(); + + /** + * Checks if we're collecting during a given generation + */ + static PRBool InGeneration(PRUint32 aGeneration) { + return aGeneration == sGeneration; + } + + static PRUint32 sGeneration; + +private: + nsCCUncollectableMarker() {}; + +}; diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index bd271cb88a25..efc3bc91fdc9 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -153,6 +153,7 @@ static NS_DEFINE_CID(kDOMEventGroupCID, NS_DOMEVENTGROUP_CID); #include "nsIJSContextStack.h" #include "nsIXPConnect.h" #include "nsCycleCollector.h" +#include "nsCCUncollectableMarker.h" #ifdef MOZ_LOGGING // so we can get logging even in release builds @@ -1010,6 +1011,10 @@ LinkMapTraverser(nsUint32ToContentHashEntry* aEntry, void* userArg) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDocument) + if (nsCCUncollectableMarker::InGeneration(tmp->GetMarkedCCGeneration())) { + return NS_OK; + } + // Traverse the mChildren nsAttrAndChildArray. for (PRInt32 indx = PRInt32(tmp->mChildren.ChildCount()); indx > 0; --indx) { cb.NoteXPCOMChild(tmp->mChildren.ChildAt(indx - 1)); diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index d80dfd5deb81..7cfdfa8acfbc 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -117,6 +117,7 @@ #include "nsCycleCollectionParticipant.h" +#include "nsCCUncollectableMarker.h" #ifdef MOZ_SVG PRBool NS_SVG_TestFeature(const nsAString &fstr); @@ -3007,6 +3008,13 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGenericElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGenericElement) + nsIDocument* currentDoc = tmp->GetCurrentDoc(); + if (currentDoc && !tmp->HasFlag(NODE_HAS_FAKED_INDOC) && + nsCCUncollectableMarker::InGeneration( + currentDoc->GetMarkedCCGeneration())) { + return NS_OK; + } + nsIDocument* ownerDoc = tmp->GetOwnerDoc(); if (ownerDoc) { ownerDoc->BindingManager()->Traverse(tmp, cb); diff --git a/content/base/src/nsNodeUtils.cpp b/content/base/src/nsNodeUtils.cpp index c38e8f6b8858..76d499dd6200 100755 --- a/content/base/src/nsNodeUtils.cpp +++ b/content/base/src/nsNodeUtils.cpp @@ -547,6 +547,7 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, PRBool aClone, PRBool aDeep, nsXULElement *xulElem = NS_STATIC_CAST(nsXULElement*, elem); if (!xulElem->mPrototype || xulElem->IsInDoc()) { clone->mParentPtrBits |= nsINode::PARENT_BIT_INDOCUMENT; + clone->SetFlags(NODE_HAS_FAKED_INDOC); } } #endif diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index 02101ec97af2..38a4700d9000 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -867,6 +867,9 @@ nsXULElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, // Being added to a document. mParentPtrBits |= PARENT_BIT_INDOCUMENT; + + // Unset this flag since we now really are in a document. + UnsetFlags(NODE_HAS_FAKED_INDOC); } // Now recurse into our kids diff --git a/dom/src/base/Makefile.in b/dom/src/base/Makefile.in index 3c13bdb04636..6cbe78e84836 100644 --- a/dom/src/base/Makefile.in +++ b/dom/src/base/Makefile.in @@ -123,6 +123,7 @@ LOCAL_INCLUDES = \ -I$(topsrcdir)/content/xbl/src \ -I$(topsrcdir)/content/xul/document/src \ -I$(topsrcdir)/content/events/src \ + -I$(topsrcdir)/content/base/src \ $(NULL) DEFINES += -D_IMPL_NS_LAYOUT diff --git a/dom/src/base/nsGlobalWindow.cpp b/dom/src/base/nsGlobalWindow.cpp index 74234aa9da42..883675352b17 100644 --- a/dom/src/base/nsGlobalWindow.cpp +++ b/dom/src/base/nsGlobalWindow.cpp @@ -75,6 +75,7 @@ #include "nsContentCID.h" #include "nsLayoutStatics.h" #include "nsCycleCollector.h" +#include "nsCCUncollectableMarker.h" // Interfaces Needed #include "nsIWidget.h" @@ -718,6 +719,11 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsGlobalWindow, NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGlobalWindow) + if (tmp->mDoc && nsCCUncollectableMarker::InGeneration( + tmp->mDoc->GetMarkedCCGeneration())) { + return NS_OK; + } + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOpener) diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 19ad9a7d9460..cfeb9f067554 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -78,6 +78,7 @@ #include "txMozillaXSLTProcessor.h" #include "nsDOMStorage.h" #include "nsCellMap.h" +#include "nsCCUncollectableMarker.h" #ifdef MOZ_XUL #include "nsXULContentUtils.h" @@ -209,6 +210,12 @@ nsLayoutStatics::Initialize() return rv; } + rv = nsCCUncollectableMarker::Init(); + if (NS_FAILED(rv)) { + NS_ERROR("Could not initialize nsCCUncollectableMarker"); + return rv; + } + return NS_OK; } diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp index 7a6b4766ed29..83c7b7f0b1f7 100644 --- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -138,6 +138,8 @@ #include "prtime.h" #include "nsPrintfCString.h" #include "nsTArray.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" #include #ifdef WIN32 @@ -1917,6 +1919,12 @@ nsCycleCollector::Collect(PRUint32 aTryCollections) PRTime start = PR_Now(), now; #endif + nsCOMPtr obs = + do_GetService("@mozilla.org/observer-service;1"); + if (obs) { + obs->NotifyObservers(nsnull, "cycle-collector-begin", nsnull); + } + while (aTryCollections > 0) { // This triggers a JS GC. Our caller assumes we always trigger at // least one JS GC -- they rely on this fact to avoid redundant JS