From 6ba0ca043f99b7ba2659611aea0677cbe23e70de Mon Sep 17 00:00:00 2001 From: "bryner%brianryner.com" Date: Fri, 23 Sep 2005 18:16:40 +0000 Subject: [PATCH] Add a global limit to the number of cached content viewers that scales with the amount of physical memory. Patch by Marria Nazif . Bug 292965, r=biesi, sr=me. --- browser/app/profile/firefox.js | 1 - camino/resources/application/all-camino.js | 3 - docshell/base/nsDocShell.cpp | 84 ++++- docshell/base/nsDocShell.h | 5 + docshell/base/nsIDocShell.idl | 18 +- docshell/base/nsWebShell.cpp | 11 +- docshell/build/nsDocShellModule.cpp | 29 +- docshell/shistory/public/nsISHEntry.idl | 7 +- .../shistory/public/nsISHistoryInternal.idl | 21 +- docshell/shistory/src/nsSHEntry.cpp | 32 ++ docshell/shistory/src/nsSHistory.cpp | 328 ++++++++++++++++-- docshell/shistory/src/nsSHistory.h | 24 +- layout/base/nsDocumentViewer.cpp | 38 +- modules/libpref/src/init/all.js | 3 + xpfe/bootstrap/browser-prefs.js | 1 - 15 files changed, 529 insertions(+), 76 deletions(-) diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 2aa1aab166b8..7ae08306b74e 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -221,7 +221,6 @@ pref("browser.search.update", true); pref("browser.history.grouping", "day"); pref("browser.sessionhistory.max_entries", 50); -pref("browser.sessionhistory.max_viewers", 3); // handle external links // 0=default window, 1=current window/tab, 2=new window, 3=new tab in most recent window diff --git a/camino/resources/application/all-camino.js b/camino/resources/application/all-camino.js index fb22a894400b..0f8374c185fd 100644 --- a/camino/resources/application/all-camino.js +++ b/camino/resources/application/all-camino.js @@ -127,6 +127,3 @@ pref("browser.tabs.loadInBackground", false); // use the html network errors rather than sheets pref("browser.xul.error_pages.enabled", true); - -// turn on bfcache optimizations -pref("browser.sessionhistory.max_viewers", 3); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 78da3a907d8a..11f2f159604c 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -264,6 +264,8 @@ nsDocShell::nsDocShell(): mMarginHeight(0), mItemType(typeContent), mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto), + mPreviousTransIndex(-1), + mLoadedTransIndex(-1), mEditorData(nsnull), mTreeOwner(nsnull), mChromeEventHandler(nsnull) @@ -1721,6 +1723,36 @@ nsDocShell::SetUseErrorPages(PRBool aUseErrorPages) return NS_OK; } +NS_IMETHODIMP +nsDocShell::GetPreviousTransIndex(PRInt32 *aPreviousTransIndex) +{ + *aPreviousTransIndex = mPreviousTransIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadedTransIndex(PRInt32 *aLoadedTransIndex) +{ + *aLoadedTransIndex = mLoadedTransIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::HistoryPurged(PRInt32 aNumEntries) +{ + mPreviousTransIndex = PR_MAX(-1, mPreviousTransIndex - aNumEntries); + + PRInt32 count = mChildList.Count(); + for (PRInt32 i = 0; i < count; ++i) { + nsCOMPtr shell = do_QueryInterface(ChildAt(i)); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + //***************************************************************************** // nsDocShell::nsIDocShellTreeItem //***************************************************************************** @@ -2647,12 +2679,30 @@ nsDocShell::DoAddChildSHEntry(nsISHEntry* aNewEntry, PRInt32 aChildOffset) * for this subframe in the CloneAndReplace function. */ + // In this case, we will end up calling AddEntry, which increases the + // current index by 1 + nsCOMPtr rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + rootSH->GetIndex(&mPreviousTransIndex); + } + nsresult rv; nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); if (parent) { rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset); } + + + if (rootSH) { + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif + } + return rv; } @@ -4908,10 +4958,15 @@ nsDocShell::CanSavePresentation(PRUint32 aLoadType, // Avoid doing the work of saving the presentation state in the case where // the content viewer cache is disabled. - PRInt32 maxViewers = 0; - mPrefs->GetIntPref("browser.sessionhistory.max_viewers", &maxViewers); - if (maxViewers == 0) + nsCOMPtr rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + nsCOMPtr shistInt(do_QueryInterface(rootSH)); + PRInt32 maxViewers; + shistInt->GetHistoryMaxTotalViewers(&maxViewers); + if (maxViewers == 0) return PR_FALSE; + } // Don't cache the content viewer if we're in a subframe and the subframe // pref is disabled. @@ -5264,7 +5319,13 @@ nsDocShell::RestoreFromHistory() GetRootSessionHistory(getter_AddRefs(rootSH)); if (rootSH) { nsCOMPtr hist = do_QueryInterface(rootSH); + rootSH->GetIndex(&mPreviousTransIndex); hist->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif } // Rather than call Embed(), we will retrieve the viewer from the session @@ -5332,7 +5393,7 @@ nsDocShell::RestoreFromHistory() if (mContentViewer) { mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nsnull); - mContentViewer->Destroy(); + viewer->SetPreviousViewer(mContentViewer); } mContentViewer.swap(viewer); @@ -7267,8 +7328,15 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, // SH. if (rootSH && (mLoadType & LOAD_CMD_HISTORY)) { nsCOMPtr shInternal(do_QueryInterface(rootSH)); - if (shInternal) + if (shInternal) { + rootSH->GetIndex(&mPreviousTransIndex); shInternal->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } } PRBool onLocationChangeNeeded = SetCurrentURI(aURI, aChannel, aFireOnLocationChange); @@ -7478,7 +7546,13 @@ nsDocShell::AddToSessionHistory(nsIURI * aURI, nsCOMPtr shPrivate(do_QueryInterface(mSessionHistory)); NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + mSessionHistory->GetIndex(&mPreviousTransIndex); rv = shPrivate->AddEntry(entry, shouldPersist); + mSessionHistory->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif } } else { diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 5b0f7cde3db9..2f171c980103 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -594,6 +594,11 @@ protected: // Somebody give me better name nsCOMPtr mLSHE; + // Index into the SHTransaction list, indicating the previous and current + // transaction at the time that this DocShell begins to load + PRInt32 mPreviousTransIndex; + PRInt32 mLoadedTransIndex; + // Editor stuff nsDocShellEditorData* mEditorData; // editor data, if any diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index f172f89f4813..02e2b5c94f09 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -66,7 +66,7 @@ interface nsISHEntry; interface nsILayoutHistoryState; interface nsISecureBrowserUI; -[scriptable, uuid(cfe93f8d-bd92-4e77-a9b7-c37296fe0986)] +[scriptable, uuid(9f0c7461-b9a4-47f6-b88c-421dce1bce66)] interface nsIDocShell : nsISupports { /** @@ -387,5 +387,21 @@ interface nsIDocShell : nsISupports /* attribute to access whether error pages are enabled */ attribute boolean useErrorPages; + + /** + * Keeps track of the previous SHTransaction index and the current + * SHTransaction index at the time that the doc shell begins to load. + * Used for ContentViewer eviction. + */ + readonly attribute long previousTransIndex; + readonly attribute long loadedTransIndex; + + /** + * Notification that entries have been removed from the beginning of a + * nsSHistory which has this as its rootDocShell. + * + * @param numEntries - The number of entries removed + */ + void historyPurged(in long numEntries); }; diff --git a/docshell/base/nsWebShell.cpp b/docshell/base/nsWebShell.cpp index 62822da570b2..75ae7b6f291c 100644 --- a/docshell/base/nsWebShell.cpp +++ b/docshell/base/nsWebShell.cpp @@ -902,8 +902,15 @@ nsresult nsWebShell::EndPageLoad(nsIWebProgress *aProgress, if (rootSH && (mLoadType & LOAD_CMD_HISTORY)) { nsCOMPtr shInternal(do_QueryInterface(rootSH)); - if (shInternal) - shInternal->UpdateIndex(); + if (shInternal) { + rootSH->GetIndex(&mPreviousTransIndex); + shInternal->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } } // Make it look like we really did honestly finish loading the diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index b5ef6dc5c3f3..0a3dceae9e18 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -62,6 +62,29 @@ #include "nsGlobalHistoryAdapter.h" #include "nsGlobalHistory2Adapter.h" +static PRBool gInitialized = PR_FALSE; + +// The one time initialization for this module +// static +PR_STATIC_CALLBACK(nsresult) +Initialize(nsIModule* aSelf) +{ + NS_PRECONDITION(!gInitialized, "docshell module already initialized"); + if (gInitialized) { + return NS_OK; + } + gInitialized = PR_TRUE; + + nsresult rv = nsSHistory::Startup(); + return rv; +} + +PR_STATIC_CALLBACK(void) +Shutdown(nsIModule* aSelf) +{ + gInitialized = PR_FALSE; +} + // docshell NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebShell, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup) @@ -83,7 +106,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsInternetConfigService) // session history NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction) -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSHistory, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory) // Currently no-one is instantiating docshell's directly because // nsWebShell is still our main "shell" class. nsWebShell is a subclass @@ -154,5 +177,5 @@ static const nsModuleComponentInfo gDocShellModuleInfo[] = { // "docshell provider" to illustrate that this thing really *should* // be dispensing docshells rather than webshells. -NS_IMPL_NSGETMODULE(docshell_provider, gDocShellModuleInfo) - +NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(docshell_provider, gDocShellModuleInfo, + Initialize, Shutdown) diff --git a/docshell/shistory/public/nsISHEntry.idl b/docshell/shistory/public/nsISHEntry.idl index 17af9654397f..05c8a388cea4 100644 --- a/docshell/shistory/public/nsISHEntry.idl +++ b/docshell/shistory/public/nsISHEntry.idl @@ -55,7 +55,7 @@ struct nsRect; %} [ref] native nsRect(nsRect); -[scriptable, uuid(a19c4489-4a71-42e1-b150-61366547030d)] +[scriptable, uuid(542a98b9-2889-4922-aaf4-02b6056f4136)] interface nsISHEntry : nsIHistoryEntry { /** URI for the document */ @@ -177,6 +177,11 @@ interface nsISHEntry : nsIHistoryEntry /** Attribute that indicates if this entry is for a subframe navigation */ void setIsSubFrame(in boolean aFlag); + + /** Return any content viewer present in or below this node in the + nsSHEntry tree. This will differ from contentViewer in the case + where a child nsSHEntry has the content viewer for this tree. */ + nsIContentViewer getAnyContentViewer(out nsISHEntry ownerEntry); }; diff --git a/docshell/shistory/public/nsISHistoryInternal.idl b/docshell/shistory/public/nsISHistoryInternal.idl index d4f5272611e6..1c46b1d910d7 100644 --- a/docshell/shistory/public/nsISHistoryInternal.idl +++ b/docshell/shistory/public/nsISHistoryInternal.idl @@ -51,7 +51,7 @@ interface nsIDocShell; #define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1" %} -[scriptable, uuid(DD335421-B8B8-11d3-BDC8-0050040A9B44)] +[scriptable, uuid(494fac3c-64f4-41b8-b209-b4ada899613b)] interface nsISHistoryInternal: nsISupports { /** @@ -91,10 +91,19 @@ interface nsISHistoryInternal: nsISupports readonly attribute nsISHistoryListener listener; /** - * Evict content viewers until the number of content viewers is no more than - * browser.sessionhistory.max_viewers. This is done automatically by - * updateIndex(), but should be called explicitly if a new history entry - * is added and later has a content viewer set. + * Evict content viewers until the number of content viewers per tab + * is no more than gHistoryMaxViewers. Also, count + * total number of content viewers globally and evict one if we are over + * our total max. This is always called in Show(), after we destroy + * the previous viewer. */ - void evictContentViewers(); + void evictContentViewers(in long previousIndex, in long index); + + /** + * Max number of total cached content viewers. If the pref + * browser.sessionhistory.max_total_viewers is negative, then + * this value is calculated based on the total amount of memory. + * Otherwise, it comes straight from the pref. + */ + readonly attribute long historyMaxTotalViewers; }; diff --git a/docshell/shistory/src/nsSHEntry.cpp b/docshell/shistory/src/nsSHEntry.cpp index 361ca62a568b..74122e477923 100644 --- a/docshell/shistory/src/nsSHEntry.cpp +++ b/docshell/shistory/src/nsSHEntry.cpp @@ -199,6 +199,38 @@ nsSHEntry::GetContentViewer(nsIContentViewer **aResult) return NS_OK; } +NS_IMETHODIMP +nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry, + nsIContentViewer **aResult) +{ + // Find a content viewer in the root node or any of its children, + // assuming that there is only one content viewer total in any one + // nsSHEntry tree + GetContentViewer(aResult); + if (*aResult) { +#ifdef DEBUG_PAGE_CACHE + printf("Found content viewer\n"); +#endif + *aOwnerEntry = this; + NS_ADDREF(*aOwnerEntry); + return NS_OK; + } + // The root SHEntry doesn't have a ContentViewer, so check child nodes + for (PRInt32 i = 0; i < mChildren.Count(); i++) { + nsISHEntry* child = mChildren[i]; + if (child) { +#ifdef DEBUG_PAGE_CACHE + printf("Evaluating SHEntry child %d\n", i); +#endif + child->GetAnyContentViewer(aOwnerEntry, aResult); + if (*aResult) { + return NS_OK; + } + } + } + return NS_OK; +} + NS_IMETHODIMP nsSHEntry::SetSticky(PRBool aSticky) { diff --git a/docshell/shistory/src/nsSHistory.cpp b/docshell/shistory/src/nsSHistory.cpp index da2cebb2cfcf..361b73cc83e6 100644 --- a/docshell/shistory/src/nsSHistory.cpp +++ b/docshell/shistory/src/nsSHistory.cpp @@ -56,12 +56,24 @@ #include "nsIPrefService.h" #include "nsIURI.h" #include "nsIContentViewer.h" +#include "nsIPrefBranch2.h" +#include "prclist.h" + +// For calculating max history entries and max cachable contentviewers +#include "nspr.h" +#include // for log() #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" -#define PREF_SHISTORY_VIEWERS "browser.sessionhistory.max_viewers" +#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" static PRInt32 gHistoryMaxSize = 50; -static PRInt32 gHistoryMaxViewers = 0; +// Max viewers allowed per SHistory objects +static const PRInt32 gHistoryMaxViewers = 3; +// List of all SHistory objects, used for content viewer cache eviction +static PRCList gSHistoryList; +// Max viewers allowed total, across all SHistory objects - negative default +// means we will calculate how many viewers to cache based on total memory +PRInt32 nsSHistory::sHistoryMaxTotalViewers = -1; enum HistCmd{ HIST_CMD_BACK, @@ -70,17 +82,58 @@ enum HistCmd{ HIST_CMD_RELOAD } ; +//***************************************************************************** +//*** nsSHistoryPrefObserver +//***************************************************************************** + +class nsSHistoryPrefObserver : public nsIObserver +{ + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsSHistoryPrefObserver() {} + +protected: + ~nsSHistoryPrefObserver() {} +}; + +NS_IMPL_ISUPPORTS1(nsSHistoryPrefObserver, nsIObserver) + +NS_IMETHODIMP +nsSHistoryPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, + const PRUnichar *aData) +{ + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr prefs = do_QueryInterface(aSubject); + prefs->GetIntPref(PREF_SHISTORY_MAX_TOTAL_VIEWERS, + &nsSHistory::sHistoryMaxTotalViewers); + if (nsSHistory::sHistoryMaxTotalViewers < 0) { + nsSHistory::sHistoryMaxTotalViewers = nsSHistory::GetMaxTotalViewers(); + } + + nsSHistory::EvictGlobalContentViewer(); + } + + return NS_OK; +} + //***************************************************************************** //*** nsSHistory: Object Management //***************************************************************************** nsSHistory::nsSHistory() : mListRoot(nsnull), mIndex(-1), mLength(0), mRequestedIndex(-1) { + // Add this new SHistory object to the list + PR_APPEND_LINK(this, &gSHistoryList); } nsSHistory::~nsSHistory() { + // Remove this SHistory object from the list + PR_REMOVE_LINK(this); } //***************************************************************************** @@ -101,11 +154,71 @@ NS_INTERFACE_MAP_END // nsSHistory: nsISHistory //***************************************************************************** -/* - * Init method to get pref settings - */ NS_IMETHODIMP -nsSHistory::Init() +nsSHistory::GetHistoryMaxTotalViewers(PRInt32 *max) +{ + *max = sHistoryMaxTotalViewers; + return NS_OK; +} + +// static +PRUint32 +nsSHistory::GetMaxTotalViewers() +{ + // Calculate an estimate of how many ContentViewers we should cache based + // on RAM. This assumes that the average ContentViewer is 4MB (conservative) + // and caps the max at 8 ContentViewers + // + // TODO: Should we split the cache memory betw. ContentViewer caching and + // nsCacheService? + // + // RAM ContentViewers + // ----------------------- + // 32 Mb 0 + // 64 Mb 1 + // 128 Mb 2 + // 256 Mb 3 + // 512 Mb 5 + // 1024 Mb 8 + // 2048 Mb 8 + // 4096 Mb 8 + PRUint64 bytes = PR_GetPhysicalMemorySize(); + + if (LL_IS_ZERO(bytes)) + return 0; + + // Conversion from unsigned int64 to double doesn't work on all platforms. + // We need to truncate the value at LL_MAXINT to make sure we don't + // overflow. + if (LL_CMP(bytes, >, LL_MAXINT)) + bytes = LL_MAXINT; + + PRUint64 kbytes; + LL_SHR(kbytes, bytes, 10); + + double kBytesD; + LL_L2D(kBytesD, (PRInt64) kbytes); + + // This is essentially the same calculation as for nsCacheService, + // except that we divide the final memory calculation by 4, since + // we assume each ContentViewer takes on average 4MB + PRUint32 viewers = 0; + double x = log(kBytesD)/log(2.0) - 14; + if (x > 0) { + viewers = (PRUint32)(x * x - x + 2.001); // add .001 for rounding + viewers /= 4; + } + + // Cap it off at 8 max + if (viewers > 8) { + viewers = 8; + } + return viewers; +} + +// static +nsresult +nsSHistory::Startup() { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { @@ -118,17 +231,32 @@ nsSHistory::Init() if (defaultBranch) { defaultBranch->GetIntPref(PREF_SHISTORY_SIZE, &gHistoryMaxSize); } - - // The size of the content viewer cache does not suffer from this problem, - // so we allow it to be overridden by user prefs. - nsCOMPtr branch = do_QueryInterface(prefs); - if (branch) - branch->GetIntPref(PREF_SHISTORY_VIEWERS, &gHistoryMaxViewers); + + // Allow the user to override the max total number of cached viewers, + // but keep the per SHistory cached viewer limit constant + nsCOMPtr branch = do_QueryInterface(prefs); + if (branch) { + branch->GetIntPref(PREF_SHISTORY_MAX_TOTAL_VIEWERS, + &sHistoryMaxTotalViewers); + nsSHistoryPrefObserver* obs = new nsSHistoryPrefObserver(); + if (!obs) { + return NS_ERROR_OUT_OF_MEMORY; + } + branch->AddObserver(PREF_SHISTORY_MAX_TOTAL_VIEWERS, + obs, PR_FALSE); + } } + // If the pref is negative, that means we calculate how many viewers + // we think we should cache, based on total memory + if (sHistoryMaxTotalViewers < 0) { + sHistoryMaxTotalViewers = GetMaxTotalViewers(); + } + + // Initialize the global list of all SHistory objects + PR_INIT_CLIST(&gSHistoryList); return NS_OK; } - /* Add an entry to the History list at mIndex and * increment the index to point to the new entry */ @@ -222,7 +350,6 @@ nsSHistory::GetEntryAtIndex(PRInt32 aIndex, PRBool aModifyIndex, nsISHEntry** aR if (NS_SUCCEEDED(rv) && (*aResult)) { // Set mIndex to the requested index, if asked to do so.. if (aModifyIndex) { - EvictContentViewers(mIndex, aIndex); mIndex = aIndex; } } //entry @@ -424,6 +551,10 @@ nsSHistory::PurgeHistory(PRInt32 aEntries) if (mIndex < -1) { mIndex = -1; } + + if (mRootDocShell) + mRootDocShell->HistoryPurged(cnt); + return NS_OK; } @@ -493,10 +624,12 @@ nsSHistory::GetListener(nsISHistoryListener ** aListener) } NS_IMETHODIMP -nsSHistory::EvictContentViewers() +nsSHistory::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex) { - // This is called after a new entry has been appended to the end of the list. - EvictContentViewers(mIndex - 1, mIndex); + // Check our per SHistory object limit in the currently navigated SHistory + EvictWindowContentViewers(aPreviousIndex, aIndex); + // Check our total limit across all SHistory objects + EvictGlobalContentViewer(); return NS_OK; } @@ -608,43 +741,60 @@ nsSHistory::Reload(PRUint32 aReloadFlags) } void -nsSHistory::EvictContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) +nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) { - // To enforce the limit on cached content viewers, we need to release all - // of the content viewers that are no longer in the "window" that now - // ends/begins at aToIndex. + // To enforce the per SHistory object limit on cached content viewers, we + // need to release all of the content viewers that are no longer in the + // "window" that now ends/begins at aToIndex. + // This can happen on the first load of a page in a particular window + if (aFromIndex < 0 || aToIndex < 0) { + return; + } + + // These indices give the range of SHEntries whose content viewers will be + // evicted PRInt32 startIndex, endIndex; if (aToIndex > aFromIndex) { // going forward + endIndex = aToIndex - gHistoryMaxViewers; + if (endIndex <= 0) { + return; + } startIndex = PR_MAX(0, aFromIndex - gHistoryMaxViewers); - endIndex = PR_MAX(0, aToIndex - gHistoryMaxViewers); } else { // going backward - startIndex = PR_MIN(mLength - 1, aToIndex + gHistoryMaxViewers); - endIndex = PR_MIN(mLength - 1, aFromIndex + gHistoryMaxViewers); + startIndex = aToIndex + gHistoryMaxViewers + 1; + if (startIndex >= mLength) { + return; + } + endIndex = PR_MIN(mLength, aFromIndex + gHistoryMaxViewers); } nsCOMPtr trans; GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); - for (PRInt32 i = startIndex; trans && i < endIndex; ++i) { + for (PRInt32 i = startIndex; i < endIndex; ++i) { nsCOMPtr entry; trans->GetSHEntry(getter_AddRefs(entry)); nsCOMPtr viewer; - entry->GetContentViewer(getter_AddRefs(viewer)); + nsCOMPtr ownerEntry; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); if (viewer) { + NS_ASSERTION(ownerEntry, + "ContentViewer exists but its SHEntry is null"); #ifdef DEBUG_PAGE_CACHE nsCOMPtr uri; - entry->GetURI(getter_AddRefs(uri)); + ownerEntry->GetURI(getter_AddRefs(uri)); nsCAutoString spec; if (uri) uri->GetSpec(spec); - printf("Evicting content viewer: %s\n", spec.get()); + printf("per SHistory limit: evicting content viewer: %s\n", spec.get()); #endif viewer->Destroy(); - entry->SetContentViewer(nsnull); - entry->SyncPresentationState(); + ownerEntry->SetContentViewer(nsnull); + ownerEntry->SyncPresentationState(); } nsISHTransaction *temp = trans; @@ -652,15 +802,125 @@ nsSHistory::EvictContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) } } +// static +void +nsSHistory::EvictGlobalContentViewer() +{ + // true until the total number of content viewers is <= total max + // The usual case is that we only need to evict one content viewer. + // However, if somebody resets the pref value, we might occasionally + // need to evict more than one. + PRBool shouldTryEviction = PR_TRUE; + while (shouldTryEviction) { + // Walk through our list of SHistory objects, looking for content + // viewers in the possible active window of all of the SHEntry objects. + // Keep track of the SHEntry object that has a ContentViewer and is + // farthest from the current focus in any SHistory object. The + // ContentViewer associated with that SHEntry will be evicted + PRInt32 distanceFromFocus = 0; + nsCOMPtr evictFromSHE; + nsCOMPtr evictViewer; + PRInt32 totalContentViewers = 0; + nsSHistory* shist = NS_STATIC_CAST(nsSHistory*, + PR_LIST_HEAD(&gSHistoryList)); + while (shist != &gSHistoryList) { + // Calculate the window of SHEntries that could possibly have a content + // viewer. There could be up to gHistoryMaxViewers content viewers, + // but we don't know whether they are before or after the mIndex position + // in the SHEntry list. Just check both sides, to be safe. + PRInt32 startIndex = PR_MAX(0, shist->mIndex - gHistoryMaxViewers); + PRInt32 endIndex = PR_MIN(shist->mLength - 1, + shist->mIndex + gHistoryMaxViewers); + nsCOMPtr trans; + shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); + + for (PRInt32 i = startIndex; i <= endIndex; ++i) { + nsCOMPtr entry; + trans->GetSHEntry(getter_AddRefs(entry)); + nsCOMPtr viewer; + nsCOMPtr ownerEntry; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); + +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr uri; + if (ownerEntry) { + ownerEntry->GetURI(getter_AddRefs(uri)); + } else { + entry->GetURI(getter_AddRefs(uri)); + } + nsCAutoString spec; + if (uri) { + uri->GetSpec(spec); + printf("Considering for eviction: %s\n", spec.get()); + } +#endif + + // This SHEntry has a ContentViewer, so check how far away it is from + // the currently used SHEntry within this SHistory object + if (viewer) { + PRInt32 distance = PR_ABS(shist->mIndex - i); + +#ifdef DEBUG_PAGE_CACHE + printf("Has a cached content viewer: %s\n", spec.get()); + printf("mIndex: %d i: %d\n", shist->mIndex, i); +#endif + totalContentViewers++; + if (distance > distanceFromFocus) { + +#ifdef DEBUG_PAGE_CACHE + printf("Choosing as new eviction candidate: %s\n", spec.get()); +#endif + + distanceFromFocus = distance; + evictFromSHE = ownerEntry; + evictViewer = viewer; + } + } + nsISHTransaction* temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + shist = NS_STATIC_CAST(nsSHistory*, PR_NEXT_LINK(shist)); + } + +#ifdef DEBUG_PAGE_CACHE + printf("Distance from focus: %d\n", distanceFromFocus); + printf("Total max viewers: %d\n", sHistoryMaxTotalViewers); + printf("Total number of viewers: %d\n", totalContentViewers); +#endif + + if (totalContentViewers > sHistoryMaxTotalViewers && evictViewer) { +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr uri; + evictFromSHE->GetURI(getter_AddRefs(uri)); + nsCAutoString spec; + if (uri) { + uri->GetSpec(spec); + printf("Evicting content viewer: %s\n", spec.get()); + } +#endif + + evictViewer->Destroy(); + evictFromSHE->SetContentViewer(nsnull); + evictFromSHE->SyncPresentationState(); + + // If we only needed to evict one content viewer, then we are done. + // Otherwise, continue evicting until we reach the max total limit. + if (totalContentViewers - sHistoryMaxTotalViewers == 1) { + shouldTryEviction = PR_FALSE; + } + } else { + // couldn't find a content viewer to evict, so we are done + shouldTryEviction = PR_FALSE; + } + } // while shouldTryEviction +} + NS_IMETHODIMP nsSHistory::UpdateIndex() { // Update the actual index with the right value. if (mIndex != mRequestedIndex && mRequestedIndex != -1) { - // We've just finished a history navigation (back or forward), so enforce - // the max number of content viewers. - - EvictContentViewers(mIndex, mRequestedIndex); mIndex = mRequestedIndex; } diff --git a/docshell/shistory/src/nsSHistory.h b/docshell/shistory/src/nsSHistory.h index 2d21404a978a..cb14ebddc84a 100644 --- a/docshell/shistory/src/nsSHistory.h +++ b/docshell/shistory/src/nsSHistory.h @@ -52,10 +52,16 @@ #include "nsISimpleEnumerator.h" #include "nsISHistoryListener.h" #include "nsIHistoryEntry.h" +#include "nsIObserver.h" + +// Needed to maintain global list of all SHistory objects +#include "prclist.h" class nsIDocShell; class nsSHEnumerator; -class nsSHistory: public nsISHistory, +class nsSHistoryPrefObserver; +class nsSHistory: public PRCList, + public nsISHistory, public nsISHistoryInternal, public nsIWebNavigation { @@ -67,11 +73,17 @@ public: NS_DECL_NSISHISTORYINTERNAL NS_DECL_NSIWEBNAVIGATION - NS_IMETHOD Init(); + // One time initialization method called upon docshell module construction + static nsresult Startup(); + + // Calculates a max number of total + // content viewers to cache, based on amount of total memory + static PRUint32 GetMaxTotalViewers(); protected: virtual ~nsSHistory(); friend class nsSHEnumerator; + friend class nsSHistoryPrefObserver; // Could become part of nsIWebNavigation NS_IMETHOD GetEntryAtIndex(PRInt32 aIndex, PRBool aModifyIndex, nsISHEntry** aResult); @@ -85,7 +97,8 @@ protected: nsresult PrintHistory(); #endif - void EvictContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex); + void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex); + static void EvictGlobalContentViewer(); protected: nsCOMPtr mListRoot; @@ -96,6 +109,9 @@ protected: nsWeakPtr mListener; // Weak reference. Do not refcount this. nsIDocShell * mRootDocShell; + + // Max viewers allowed total, across all SHistory objects + static PRInt32 sHistoryMaxTotalViewers; }; //***************************************************************************** //*** nsSHEnumerator: Object Management @@ -117,6 +133,4 @@ private: nsSHistory * mSHistory; }; - - #endif /* nsSHistory */ diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 55443ea528fd..78fe492c1a03 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1381,20 +1381,6 @@ DocumentViewerImpl::Destroy() mSHEntry = nsnull; - // If we put ourselves into session history, make sure there aren't - // too many content viewers around. Note: if max_viewers is set to 0, - // this can reenter Destroy() and dispose of this content viewer! - - nsCOMPtr webNav = do_QueryReferent(mContainer); - if (webNav) { - nsCOMPtr history; - webNav->GetSessionHistory(getter_AddRefs(history)); - nsCOMPtr historyInt = do_QueryInterface(history); - if (historyInt) { - historyInt->EvictContentViewers(); - } - } - // Break the link from the document/presentation to the docshell, so that // link traversals cannot affect the currently-loaded document. // When the presentation is restored, Open() and InitInternal() will reset @@ -1731,6 +1717,30 @@ DocumentViewerImpl::Show(void) nsCOMPtr prevViewer(mPreviousViewer); mPreviousViewer = nsnull; prevViewer->Destroy(); + + // Make sure we don't have too many cached ContentViewers + nsCOMPtr treeItem = do_QueryReferent(mContainer); + if (treeItem) { + // We need to find the root DocShell since only that object has an + // SHistory and we need the SHistory to evict content viewers + nsCOMPtr root; + treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root)); + nsCOMPtr webNav = do_QueryInterface(root); + nsCOMPtr history; + webNav->GetSessionHistory(getter_AddRefs(history)); + nsCOMPtr historyInt = do_QueryInterface(history); + if (historyInt) { + PRInt32 prevIndex,loadedIndex; + nsCOMPtr docShell = do_QueryInterface(treeItem); + docShell->GetPreviousTransIndex(&prevIndex); + docShell->GetLoadedTransIndex(&loadedIndex); +#ifdef DEBUG_PAGE_CACHE + printf("About to evict content viewers: prev=%d, loaded=%d\n", + prevIndex, loadedIndex); +#endif + historyInt->EvictContentViewers(prevIndex, loadedIndex); + } + } } if (mWindow) { diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index ee080e0c7274..7c7f663bedca 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -62,6 +62,9 @@ pref("browser.cache.memory.enable", true); pref("browser.cache.disk_cache_ssl", false); // 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically pref("browser.cache.check_doc_frequency", 3); +// Fastback caching - if this pref is negative, then we calculate the number +// of content viewers to cache based on the amount of available memory. +pref("browser.sessionhistory.max_total_viewers", -1); pref("browser.display.use_document_fonts", 1); // 0 = never, 1 = quick, 2 = always pref("browser.display.use_document_colors", true); diff --git a/xpfe/bootstrap/browser-prefs.js b/xpfe/bootstrap/browser-prefs.js index cd9c632b5bb5..aaa1bbd9d59e 100644 --- a/xpfe/bootstrap/browser-prefs.js +++ b/xpfe/bootstrap/browser-prefs.js @@ -113,7 +113,6 @@ pref("browser.search.param.Google.1.default", "chrome://navigator/content/search pref("browser.history.grouping", "day"); pref("browser.sessionhistory.max_entries", 50); -pref("browser.sessionhistory.max_viewers", 3); // Tabbed browser pref("browser.tabs.loadDivertedInBackground", false);