From f1b1dbc9b2b345eb9a551c1c0d5be0592d4ea45f Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Fri, 21 Oct 2011 11:26:34 -0400 Subject: [PATCH] Bug 683777 - In an attempt to fix a crash in nsDocShell::InternalLoad, re-land bug 646641 with an extra null-check. r=smaug --- .../sessionstore/src/nsSessionStore.js | 40 +- .../test/browser/browser_500328.js | 8 +- content/base/public/nsIDocument.h | 21 +- docshell/base/nsDocShell.cpp | 67 +-- docshell/build/nsDocShellModule.cpp | 7 +- docshell/shistory/public/Makefile.in | 1 + docshell/shistory/public/nsIBFCacheEntry.idl | 47 ++ docshell/shistory/public/nsISHEntry.idl | 63 +- .../shistory/public/nsISHistoryInternal.idl | 27 +- docshell/shistory/src/Makefile.in | 3 + docshell/shistory/src/nsSHEntry.cpp | 497 ++++------------ docshell/shistory/src/nsSHEntry.h | 64 +- docshell/shistory/src/nsSHEntryShared.cpp | 400 +++++++++++++ docshell/shistory/src/nsSHEntryShared.h | 124 ++++ docshell/shistory/src/nsSHistory.cpp | 548 ++++++++++-------- docshell/shistory/src/nsSHistory.h | 11 +- docshell/test/Makefile.in | 1 + docshell/test/chrome/bug396519_window.xul | 20 + docshell/test/test_bfcache_plus_hash.html | 120 ++++ dom/indexedDB/IndexedDatabaseManager.cpp | 9 +- layout/base/nsDocumentViewer.cpp | 2 +- mobile/app/mobile.js | 1 - mobile/chrome/content/bindings/browser.js | 42 +- modules/libpref/src/init/all.js | 2 - 24 files changed, 1312 insertions(+), 813 deletions(-) create mode 100644 docshell/shistory/public/nsIBFCacheEntry.idl create mode 100644 docshell/shistory/src/nsSHEntryShared.cpp create mode 100644 docshell/shistory/src/nsSHEntryShared.h create mode 100644 docshell/test/test_bfcache_plus_hash.html diff --git a/browser/components/sessionstore/src/nsSessionStore.js b/browser/components/sessionstore/src/nsSessionStore.js index 2ce3d47befd..441220b424d 100644 --- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -1920,9 +1920,7 @@ SessionStoreService.prototype = { catch (ex) { debug(ex); } } - if (aEntry.docIdentifier) { - entry.docIdentifier = aEntry.docIdentifier; - } + entry.docIdentifier = aEntry.BFCacheEntry.ID; if (aEntry.stateData != null) { entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); @@ -3024,16 +3022,11 @@ SessionStoreService.prototype = { browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank")); // Attach data that will be restored on "load" event, after tab is restored. if (activeIndex > -1) { - let curSHEntry = browser.webNavigation.sessionHistory. - getEntryAtIndex(activeIndex, false). - QueryInterface(Ci.nsISHEntry); - // restore those aspects of the currently active documents which are not // preserved in the plain history entries (mainly scroll state and text data) browser.__SS_restore_data = tabData.entries[activeIndex] || {}; browser.__SS_restore_pageStyle = tabData.pageStyle || ""; browser.__SS_restore_tab = aTab; - browser.__SS_restore_docIdentifier = curSHEntry.docIdentifier; didStartLoad = true; try { // In order to work around certain issues in session history, we need to @@ -3188,24 +3181,16 @@ SessionStoreService.prototype = { } if (aEntry.docIdentifier) { - // Get a new document identifier for this entry to ensure that history - // entries after a session restore are considered to have different - // documents from the history entries before the session restore. - // Document identifiers are 64-bit ints, so JS will loose precision and - // start assigning all entries the same doc identifier if these ever get - // large enough. - // - // It's a potential security issue if document identifiers aren't - // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees - // that we won't re-use a doc identifier within a given instance of the - // application. - let ident = aDocIdentMap[aEntry.docIdentifier]; - if (!ident) { - shEntry.setUniqueDocIdentifier(); - aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier; + // If we have a serialized document identifier, try to find an SHEntry + // which matches that doc identifier and adopt that SHEntry's + // BFCacheEntry. If we don't find a match, insert shEntry as the match + // for the document identifier. + let matchingEntry = aDocIdentMap[aEntry.docIdentifier]; + if (!matchingEntry) { + aDocIdentMap[aEntry.docIdentifier] = shEntry; } else { - shEntry.docIdentifier = ident; + shEntry.adoptBFCacheEntry(matchingEntry); } } @@ -3341,19 +3326,12 @@ SessionStoreService.prototype = { aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle"; } - if (aBrowser.__SS_restore_docIdentifier) { - let sh = aBrowser.webNavigation.sessionHistory; - sh.getEntryAtIndex(sh.index, false).QueryInterface(Ci.nsISHEntry). - docIdentifier = aBrowser.__SS_restore_docIdentifier; - } - // notify the tabbrowser that this document has been completely restored this._sendTabRestoredNotification(aBrowser.__SS_restore_tab); delete aBrowser.__SS_restore_data; delete aBrowser.__SS_restore_pageStyle; delete aBrowser.__SS_restore_tab; - delete aBrowser.__SS_restore_docIdentifier; }, /** diff --git a/browser/components/sessionstore/test/browser/browser_500328.js b/browser/components/sessionstore/test/browser/browser_500328.js index 5fe32e28b1b..5c7e793e68a 100644 --- a/browser/components/sessionstore/test/browser/browser_500328.js +++ b/browser/components/sessionstore/test/browser/browser_500328.js @@ -116,13 +116,13 @@ function test() { // After these push/replaceState calls, the window should have three // history entries: - // testURL (state object: null) <-- oldest - // testURL (state object: {obj1:1}) - // page2 (state object: {obj3:/^a$/}) <-- newest + // testURL (state object: null) <-- oldest + // testURL (state object: {obj1:1}) + // testURL?page2 (state object: {obj3:/^a$/}) <-- newest let contentWindow = tab.linkedBrowser.contentWindow; let history = contentWindow.history; history.pushState({obj1:1}, "title-obj1"); - history.pushState({obj2:2}, "title-obj2", "page2"); + history.pushState({obj2:2}, "title-obj2", "?page2"); history.replaceState({obj3:/^a$/}, "title-obj3"); let state = ss.getTabState(tab); diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 38d5bc0e3ea..f20d48390b3 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -69,6 +69,7 @@ #include "nsIAnimationFrameListener.h" #include "nsEventStates.h" #include "nsIStructuredCloneContainer.h" +#include "nsIBFCacheEntry.h" #include "nsDOMMemoryReporter.h" class nsIContent; @@ -125,8 +126,8 @@ class Element; } // namespace mozilla #define NS_IDOCUMENT_IID \ -{ 0x4114a7c7, 0xb2f4, 0x4dea, \ - { 0xac, 0x78, 0x20, 0xab, 0xda, 0x6f, 0xb2, 0xaf } } +{ 0x448c396a, 0x013c, 0x47b8, \ + { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } } // Flag for AddStyleSheet(). #define NS_STYLESHEET_FROM_CATALOG (1 << 0) @@ -479,11 +480,15 @@ public: return GetBFCacheEntry() ? nsnull : mPresShell; } - void SetBFCacheEntry(nsISHEntry* aSHEntry) { - mSHEntry = aSHEntry; + void SetBFCacheEntry(nsIBFCacheEntry* aEntry) + { + mBFCacheEntry = aEntry; } - nsISHEntry* GetBFCacheEntry() const { return mSHEntry; } + nsIBFCacheEntry* GetBFCacheEntry() const + { + return mBFCacheEntry; + } /** * Return the parent document of this document. Will return null @@ -1786,9 +1791,9 @@ protected: AnimationListenerList mAnimationFrameListeners; - // The session history entry in which we're currently bf-cached. Non-null - // if and only if we're currently in the bfcache. - nsISHEntry* mSHEntry; + // This object allows us to evict ourself from the back/forward cache. The + // pointer is non-null iff we're currently in the bfcache. + nsIBFCacheEntry *mBFCacheEntry; // Our base target. nsString mBaseTarget; diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 9916a6de0b2..38f2ac4f4f5 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4126,10 +4126,10 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const PRUnichar *aURL, mFailedLoadType = mLoadType; if (mLSHE) { - // If we don't give mLSHE a new doc identifier here, when we go back or - // forward to another SHEntry with the same doc identifier, the error - // page will persist. - mLSHE->SetUniqueDocIdentifier(); + // Abandon mLSHE's BFCache entry and create a new one. This way, if + // we go back or forward to another SHEntry with the same doc + // identifier, the error page won't persist. + mLSHE->AbandonBFCacheEntry(); } nsCAutoString url; @@ -4402,10 +4402,10 @@ nsDocShell::LoadPage(nsISupports *aPageDescriptor, PRUint32 aDisplayType) nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry)); NS_ENSURE_SUCCESS(rv, rv); - // Give our cloned shEntry a new document identifier so this load is - // independent of all other loads. (This is important, in particular, - // for bugs 582795 and 585298.) - rv = shEntry->SetUniqueDocIdentifier(); + // Give our cloned shEntry a new bfcache entry so this load is independent + // of all other loads. (This is important, in particular, for bugs 582795 + // and 585298.) + rv = shEntry->AbandonBFCacheEntry(); NS_ENSURE_SUCCESS(rv, rv); // @@ -8280,17 +8280,15 @@ nsDocShell::InternalLoad(nsIURI * aURI, NS_SUCCEEDED(splitRv2) && curBeforeHash.Equals(newBeforeHash); - bool sameDocIdent = false; + // XXX rename + bool sameDocument = false; if (mOSHE && aSHEntry) { // We're doing a history load. - PRUint64 ourDocIdent, otherDocIdent; - mOSHE->GetDocIdentifier(&ourDocIdent); - aSHEntry->GetDocIdentifier(&otherDocIdent); - sameDocIdent = (ourDocIdent == otherDocIdent); + mOSHE->SharesDocumentWith(aSHEntry, &sameDocument); #ifdef DEBUG - if (sameDocIdent) { + if (sameDocument) { nsCOMPtr currentPostData; mOSHE->GetPostData(getter_AddRefs(currentPostData)); NS_ASSERTION(currentPostData == aPostData, @@ -8313,7 +8311,7 @@ nsDocShell::InternalLoad(nsIURI * aURI, // The restriction tha the SHEntries in (a) must be different ensures // that history.go(0) and the like trigger full refreshes, rather than // short-circuited loads. - bool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) || + bool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) || (!aSHEntry && aPostData == nsnull && sameExceptHashes && !newHash.IsEmpty()); @@ -8364,7 +8362,6 @@ nsDocShell::InternalLoad(nsIURI * aURI, OnNewURI(aURI, nsnull, owner, mLoadType, true, true, true); nsCOMPtr postData; - PRUint64 docIdent = PRUint64(-1); nsCOMPtr cacheKey; if (mOSHE) { @@ -8378,8 +8375,13 @@ nsDocShell::InternalLoad(nsIURI * aURI, // wouldn't want here. if (aLoadType & LOAD_CMD_NORMAL) { mOSHE->GetPostData(getter_AddRefs(postData)); - mOSHE->GetDocIdentifier(&docIdent); mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); + + // Link our new SHEntry to the old SHEntry's back/forward + // cache data, since the two SHEntries correspond to the + // same document. + if (mLSHE) + mLSHE->AdoptBFCacheEntry(mOSHE); } } @@ -8399,11 +8401,6 @@ nsDocShell::InternalLoad(nsIURI * aURI, // cache first if (cacheKey) mOSHE->SetCacheKey(cacheKey); - - // Propagate our document identifier to the new mOSHE so that - // we'll know it's related by an anchor navigation or pushState. - if (docIdent != PRUint64(-1)) - mOSHE->SetDocIdentifier(docIdent); } /* restore previous position of scroller(s), if we're moving @@ -8447,7 +8444,7 @@ nsDocShell::InternalLoad(nsIURI * aURI, } } - if (sameDocIdent) { + if (sameDocument) { // Set the doc's URI according to the new history entry's URI nsCOMPtr newURI; mOSHE->GetURI(getter_AddRefs(newURI)); @@ -8464,10 +8461,10 @@ nsDocShell::InternalLoad(nsIURI * aURI, // Dispatch the popstate and hashchange events, as appropriate. nsCOMPtr window = do_QueryInterface(mScriptGlobal); if (window) { - // Need the doHashchange check here since sameDocIdent is + // Need the doHashchange check here since sameDocument is // false if we're navigating to a new shentry (i.e. a aSHEntry // is null), such as when clicking a . - if (sameDocIdent || doHashchange) { + if (sameDocument || doHashchange) { window->DispatchSyncPopState(); } @@ -9250,11 +9247,11 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, nsISupports* aOwner, } // If the response status indicates an error, unlink this session - // history entry from any entries sharing its doc ident. + // history entry from any entries sharing its document. PRUint32 responseStatus; nsresult rv = httpChannel->GetResponseStatus(&responseStatus); if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { - mLSHE->SetUniqueDocIdentifier(); + mLSHE->AbandonBFCacheEntry(); } } } @@ -9483,9 +9480,9 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle, // It's important that this function not run arbitrary scripts after step 1 // and before completing step 5. For example, if a script called // history.back() before we completed step 5, bfcache might destroy an - // active content viewer. Since EvictContentViewers at the end of step 5 - // might run script, we can't just put a script blocker around the critical - // section. + // active content viewer. Since EvictOutOfRangeContentViewers at the end of + // step 5 might run script, we can't just put a script blocker around the + // critical section. // // Note that we completely ignore the aTitle parameter. @@ -9665,11 +9662,9 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle, NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); - // Set the new SHEntry's document identifier, if we can. - PRUint64 ourDocIdent; - NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent), - NS_ERROR_FAILURE); - NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent), + // Link the new SHEntry to the old SHEntry's BFCache entry, since the + // two entries correspond to the same document. + NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), NS_ERROR_FAILURE); // Set the new SHEntry's title (bug 655273). @@ -9715,7 +9710,7 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle, PRInt32 curIndex = -1; rv = rootSH->GetIndex(&curIndex); if (NS_SUCCEEDED(rv) && curIndex > -1) { - internalSH->EvictContentViewers(curIndex - 1, curIndex); + internalSH->EvictOutOfRangeContentViewers(curIndex); } } diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index dbd26a6a22c..89030e8c450 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -66,6 +66,7 @@ // session history #include "nsSHEntry.h" +#include "nsSHEntryShared.h" #include "nsSHistory.h" #include "nsSHTransaction.h" @@ -87,15 +88,15 @@ Initialize() nsresult rv = nsSHistory::Startup(); NS_ENSURE_SUCCESS(rv, rv); - rv = nsSHEntry::Startup(); - return rv; + nsSHEntryShared::Startup(); + return NS_OK; } static void Shutdown() { nsSHistory::Shutdown(); - nsSHEntry::Shutdown(); + nsSHEntryShared::Shutdown(); gInitialized = false; } diff --git a/docshell/shistory/public/Makefile.in b/docshell/shistory/public/Makefile.in index 434b098b1c9..36e14517ada 100644 --- a/docshell/shistory/public/Makefile.in +++ b/docshell/shistory/public/Makefile.in @@ -56,6 +56,7 @@ XPIDLSRCS = \ nsISHContainer.idl \ nsISHTransaction.idl \ nsISHistoryInternal.idl \ + nsIBFCacheEntry.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/docshell/shistory/public/nsIBFCacheEntry.idl b/docshell/shistory/public/nsIBFCacheEntry.idl new file mode 100644 index 00000000000..99d1e371f6f --- /dev/null +++ b/docshell/shistory/public/nsIBFCacheEntry.idl @@ -0,0 +1,47 @@ +/* -*- 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 the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * 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 "nsISupports.idl" + +/** + * This interface lets you evict a document from the back/forward cache. + */ +[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)] +interface nsIBFCacheEntry : nsISupports +{ + void RemoveFromBFCacheSync(); + void RemoveFromBFCacheAsync(); + + readonly attribute unsigned long long ID; +}; diff --git a/docshell/shistory/public/nsISHEntry.idl b/docshell/shistory/public/nsISHEntry.idl index 155ae63e2f2..3daa1f1a030 100644 --- a/docshell/shistory/public/nsISHEntry.idl +++ b/docshell/shistory/public/nsISHEntry.idl @@ -51,15 +51,18 @@ interface nsIInputStream; interface nsIDocShellTreeItem; interface nsISupportsArray; interface nsIStructuredCloneContainer; +interface nsIBFCacheEntry; + %{C++ struct nsIntRect; class nsDocShellEditorData; +class nsSHEntryShared; %} [ref] native nsIntRect(nsIntRect); [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData); +[ptr] native nsSHEntryShared(nsSHEntryShared); - -[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)] +[scriptable, uuid(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)] interface nsISHEntry : nsIHistoryEntry { /** URI for the document */ @@ -140,21 +143,6 @@ interface nsISHEntry : nsIHistoryEntry */ attribute unsigned long ID; - /** - * docIdentifier is an integer that should be the same for two entries - * attached to the same docshell if and only if the two entries are entries - * for the same document. In practice, two entries A and B will have the - * same docIdentifier if we arrived at B by clicking an anchor link in A or - * if B was created by A's calling history.pushState(). - */ - attribute unsigned long long docIdentifier; - - /** - * Changes this entry's doc identifier to a new value which is unique - * among those of all other entries. - */ - void setUniqueDocIdentifier(); - /** attribute to set and get the cache key for the entry */ attribute nsISupports cacheKey; @@ -252,6 +240,36 @@ interface nsISHEntry : nsIHistoryEntry * The history ID of the docshell. */ attribute unsigned long long docshellID; + + readonly attribute nsIBFCacheEntry BFCacheEntry; + + /** + * Does this SHEntry point to the given BFCache entry? If so, evicting + * the BFCache entry will evict the SHEntry, since the two entries + * correspond to the same document. + */ + [notxpcom, noscript] + boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry); + + /** + * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to + * aEntry's BFCacheEntry. + */ + void adoptBFCacheEntry(in nsISHEntry aEntry); + + /** + * Create a new BFCache entry and drop our reference to our old one. This + * call unlinks this SHEntry from any other SHEntries for its document. + */ + void abandonBFCacheEntry(); + + /** + * Does this SHEntry correspond to the same document as aEntry? This is + * true iff the two SHEntries have the same BFCacheEntry. So in + * particular, sharesDocumentWith(aEntry) is guaranteed to return true if + * it's preceeded by a call to adoptBFCacheEntry(aEntry). + */ + boolean sharesDocumentWith(in nsISHEntry aEntry); }; [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)] @@ -264,6 +282,17 @@ interface nsISHEntryInternal : nsISupports * A number that is assigned by the sHistory when the entry is activated */ attribute unsigned long lastTouched; + + /** + * Some state, particularly that related to the back/forward cache, is + * shared between SHEntries which correspond to the same document. This + * method gets a pointer to that shared state. + * + * This shared state is the SHEntry's BFCacheEntry. So + * hasBFCacheEntry(getSharedState()) is guaranteed to return true. + */ + [noscript, notxpcom] + nsSHEntryShared getSharedState(); }; %{ C++ diff --git a/docshell/shistory/public/nsISHistoryInternal.idl b/docshell/shistory/public/nsISHistoryInternal.idl index 338acec2346..1f5b0b68365 100644 --- a/docshell/shistory/public/nsISHistoryInternal.idl +++ b/docshell/shistory/public/nsISHistoryInternal.idl @@ -57,7 +57,7 @@ struct nsTArrayDefaultAllocator; [ref] native nsDocshellIDArray(nsTArray); -[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)] +[scriptable, uuid(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)] interface nsISHistoryInternal: nsISupports { /** @@ -96,20 +96,25 @@ interface nsISHistoryInternal: nsISupports */ readonly attribute nsISHistoryListener listener; - /** - * 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(in long previousIndex, in long index); + /** + * Evict content viewers which don't lie in the "safe" range around aIndex. + * In practice, this should leave us with no more than gHistoryMaxViewers + * viewers associated with this SHistory object. + * + * Also make sure that the total number of content viewers in all windows is + * not greater than our global max; if it is, evict viewers as appropriate. + * + * @param aIndex - The index around which the "safe" range is centered. In + * general, if you just navigated the history, aIndex should be the index + * history was navigated to. + */ + void evictOutOfRangeContentViewers(in long aIndex); /** - * Evict the content viewer associated with a session history entry + * Evict the content viewer associated with a bfcache entry * that has timed out. */ - void evictExpiredContentViewerForEntry(in nsISHEntry aEntry); + void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry); /** * Evict all the content viewers in this session history diff --git a/docshell/shistory/src/Makefile.in b/docshell/shistory/src/Makefile.in index e1debfbd983..7d8eedde798 100644 --- a/docshell/shistory/src/Makefile.in +++ b/docshell/shistory/src/Makefile.in @@ -48,10 +48,13 @@ LIBRARY_NAME = shistory_s FORCE_STATIC_LIB = 1 LIBXUL_LIBRARY = 1 +EXPORTS = nsSHEntryShared.h \ + $(NULL) CPPSRCS = nsSHEntry.cpp \ nsSHTransaction.cpp \ nsSHistory.cpp \ + nsSHEntryShared.cpp \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/docshell/shistory/src/nsSHEntry.cpp b/docshell/shistory/src/nsSHEntry.cpp index baab34a27e0..c3ce68d4c81 100644 --- a/docshell/shistory/src/nsSHEntry.cpp +++ b/docshell/shistory/src/nsSHEntry.cpp @@ -36,10 +36,6 @@ * * ***** END LICENSE BLOCK ***** */ -#ifdef DEBUG_bryner -#define DEBUG_PAGE_CACHE -#endif - // Local Includes #include "nsSHEntry.h" #include "nsXPIDLString.h" @@ -48,103 +44,44 @@ #include "nsIDocShellTreeItem.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" -#include "nsAutoPtr.h" -#include "nsThreadUtils.h" -#include "nsIWebNavigation.h" #include "nsISHistory.h" #include "nsISHistoryInternal.h" #include "nsDocShellEditorData.h" -#include "nsIDocShell.h" +#include "nsSHEntryShared.h" +#include "nsILayoutHistoryState.h" +#include "nsIContentViewer.h" +#include "nsISupportsArray.h" namespace dom = mozilla::dom; -// Hardcode this to time out unused content viewers after 30 minutes -#define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60 - -typedef nsExpirationTracker HistoryTrackerBase; -class HistoryTracker : public HistoryTrackerBase { -public: - // Expire cached contentviewers after 20-30 minutes in the cache. - HistoryTracker() : HistoryTrackerBase((CONTENT_VIEWER_TIMEOUT_SECONDS/2)*1000) {} - -protected: - virtual void NotifyExpired(nsSHEntry* aObj) { - RemoveObject(aObj); - aObj->Expire(); - } -}; - -static HistoryTracker *gHistoryTracker = nsnull; static PRUint32 gEntryID = 0; -static PRUint64 gEntryDocIdentifier = 0; - -nsresult nsSHEntry::Startup() -{ - gHistoryTracker = new HistoryTracker(); - return gHistoryTracker ? NS_OK : NS_ERROR_OUT_OF_MEMORY; -} - -void nsSHEntry::Shutdown() -{ - delete gHistoryTracker; - gHistoryTracker = nsnull; -} - -static void StopTrackingEntry(nsSHEntry *aEntry) -{ - if (aEntry->GetExpirationState()->IsTracked()) { - gHistoryTracker->RemoveObject(aEntry); - } -} //***************************************************************************** //*** nsSHEntry: Object Management //***************************************************************************** -nsSHEntry::nsSHEntry() +nsSHEntry::nsSHEntry() : mLoadType(0) , mID(gEntryID++) - , mDocIdentifier(gEntryDocIdentifier++) , mScrollPositionX(0) , mScrollPositionY(0) , mURIWasModified(false) - , mIsFrameNavigation(false) - , mSaveLayoutState(true) - , mExpired(false) - , mSticky(true) - , mDynamicallyCreated(false) - , mParent(nsnull) - , mViewerBounds(0, 0, 0, 0) - , mDocShellID(0) - , mLastTouched(0) { + mShared = new nsSHEntryShared(); } nsSHEntry::nsSHEntry(const nsSHEntry &other) - : mURI(other.mURI) + : mShared(other.mShared) + , mURI(other.mURI) , mReferrerURI(other.mReferrerURI) - // XXX why not copy mDocument? , mTitle(other.mTitle) , mPostData(other.mPostData) - , mLayoutHistoryState(other.mLayoutHistoryState) , mLoadType(0) // XXX why not copy? , mID(other.mID) - , mDocIdentifier(other.mDocIdentifier) , mScrollPositionX(0) // XXX why not copy? , mScrollPositionY(0) // XXX why not copy? , mURIWasModified(other.mURIWasModified) - , mIsFrameNavigation(other.mIsFrameNavigation) - , mSaveLayoutState(other.mSaveLayoutState) - , mExpired(other.mExpired) - , mSticky(true) - , mDynamicallyCreated(other.mDynamicallyCreated) - // XXX why not copy mContentType? - , mCacheKey(other.mCacheKey) - , mParent(other.mParent) - , mViewerBounds(0, 0, 0, 0) - , mOwner(other.mOwner) - , mDocShellID(other.mDocShellID) , mStateData(other.mStateData) { } @@ -160,37 +97,16 @@ ClearParentPtr(nsISHEntry* aEntry, void* /* aData */) nsSHEntry::~nsSHEntry() { - StopTrackingEntry(this); - - // Since we never really remove kids from SHEntrys, we need to null - // out the mParent pointers on all our kids. + // Null out the mParent pointers on all our kids. mChildren.EnumerateForwards(ClearParentPtr, nsnull); - mChildren.Clear(); - - if (mContentViewer) { - // RemoveFromBFCacheSync is virtual, so call the nsSHEntry version - // explicitly - nsSHEntry::RemoveFromBFCacheSync(); - } - - mEditorData = nsnull; - -#ifdef DEBUG - // This is not happening as far as I can tell from breakpad as of early November 2007 - nsExpirationTracker::Iterator iterator(gHistoryTracker); - nsSHEntry* elem; - while ((elem = iterator.Next()) != nsnull) { - NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); - } -#endif } //***************************************************************************** // nsSHEntry: nsISupports //***************************************************************************** -NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry, - nsIMutationObserver, nsISHEntryInternal) +NS_IMPL_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry, + nsISHEntryInternal) //***************************************************************************** // nsSHEntry: nsISHEntry @@ -251,35 +167,13 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(nsIURI *aReferrerURI) NS_IMETHODIMP nsSHEntry::SetContentViewer(nsIContentViewer *aViewer) { - NS_PRECONDITION(!aViewer || !mContentViewer, "SHEntry already contains viewer"); - - if (mContentViewer || !aViewer) { - DropPresentationState(); - } - - mContentViewer = aViewer; - - if (mContentViewer) { - gHistoryTracker->AddObject(this); - - nsCOMPtr domDoc; - mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); - // Store observed document in strong pointer in case it is removed from - // the contentviewer - mDocument = do_QueryInterface(domDoc); - if (mDocument) { - mDocument->SetBFCacheEntry(this); - mDocument->AddMutationObserver(this); - } - } - - return NS_OK; + return mShared->SetContentViewer(aViewer); } NS_IMETHODIMP nsSHEntry::GetContentViewer(nsIContentViewer **aResult) { - *aResult = mContentViewer; + *aResult = mShared->mContentViewer; NS_IF_ADDREF(*aResult); return NS_OK; } @@ -319,14 +213,14 @@ nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry, NS_IMETHODIMP nsSHEntry::SetSticky(bool aSticky) { - mSticky = aSticky; + mShared->mSticky = aSticky; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSticky(bool *aSticky) { - *aSticky = mSticky; + *aSticky = mShared->mSticky; return NS_OK; } @@ -365,16 +259,18 @@ NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData) NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) { - *aResult = mLayoutHistoryState; + *aResult = mShared->mLayoutHistoryState; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) { - mLayoutHistoryState = aState; - if (mLayoutHistoryState) - mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState); + mShared->mLayoutHistoryState = aState; + if (mShared->mLayoutHistoryState) { + mShared->mLayoutHistoryState-> + SetScrollPositionOnly(!mShared->mSaveLayoutState); + } return NS_OK; } @@ -403,91 +299,73 @@ NS_IMETHODIMP nsSHEntry::SetID(PRUint32 aID) return NS_OK; } -NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult) +nsSHEntryShared* nsSHEntry::GetSharedState() { - *aResult = mDocIdentifier; - return NS_OK; -} - -NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier) -{ - // This ensures that after a session restore, gEntryDocIdentifier is greater - // than all SHEntries' docIdentifiers, which ensures that we'll never repeat - // a doc identifier. - if (aDocIdentifier >= gEntryDocIdentifier) - gEntryDocIdentifier = aDocIdentifier + 1; - - mDocIdentifier = aDocIdentifier; - return NS_OK; -} - -NS_IMETHODIMP nsSHEntry::SetUniqueDocIdentifier() -{ - mDocIdentifier = gEntryDocIdentifier++; - return NS_OK; + return mShared; } NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool * aFlag) { - *aFlag = mIsFrameNavigation; + *aFlag = mShared->mIsFrameNavigation; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool aFlag) { - mIsFrameNavigation = aFlag; + mShared->mIsFrameNavigation = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult) { - *aResult = mCacheKey; + *aResult = mShared->mCacheKey; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey) { - mCacheKey = aCacheKey; + mShared->mCacheKey = aCacheKey; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag) { - *aFlag = mSaveLayoutState; + *aFlag = mShared->mSaveLayoutState; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) { - mSaveLayoutState = aFlag; - if (mLayoutHistoryState) - mLayoutHistoryState->SetScrollPositionOnly(!aFlag); + mShared->mSaveLayoutState = aFlag; + if (mShared->mLayoutHistoryState) { + mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag); + } return NS_OK; } NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag) { - *aFlag = mExpired; + *aFlag = mShared->mExpired; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool aFlag) { - mExpired = aFlag; + mShared->mExpired = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType) { - aContentType = mContentType; + aContentType = mShared->mContentType; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType) { - mContentType = aContentType; + mShared->mContentType = aContentType; return NS_OK; } @@ -502,26 +380,27 @@ nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle, mURI = aURI; mTitle = aTitle; mPostData = aInputStream; - mCacheKey = aCacheKey; - mContentType = aContentType; - mOwner = aOwner; - mDocShellID = aDocShellID; - mDynamicallyCreated = aDynamicCreation; // Set the LoadType by default to loadHistory during creation mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory; + mShared->mCacheKey = aCacheKey; + mShared->mContentType = aContentType; + mShared->mOwner = aOwner; + mShared->mDocShellID = aDocShellID; + mShared->mDynamicallyCreated = aDynamicCreation; + // By default all entries are set false for subframe flag. // nsDocShell::CloneAndReplace() which creates entries for // all subframe navigations, sets the flag to true. - mIsFrameNavigation = false; + mShared->mIsFrameNavigation = false; // By default we save LayoutHistoryState - mSaveLayoutState = true; - mLayoutHistoryState = aLayoutHistoryState; + mShared->mSaveLayoutState = true; + mShared->mLayoutHistoryState = aLayoutHistoryState; //By default the page is not expired - mExpired = false; + mShared->mExpired = false; return NS_OK; } @@ -530,8 +409,6 @@ NS_IMETHODIMP nsSHEntry::Clone(nsISHEntry ** aResult) { *aResult = new nsSHEntry(*this); - if (!*aResult) - return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult); return NS_OK; } @@ -540,7 +417,7 @@ NS_IMETHODIMP nsSHEntry::GetParent(nsISHEntry ** aResult) { NS_ENSURE_ARG_POINTER(aResult); - *aResult = mParent; + *aResult = mShared->mParent; NS_IF_ADDREF(*aResult); return NS_OK; } @@ -553,49 +430,95 @@ nsSHEntry::SetParent(nsISHEntry * aParent) * * XXX this method should not be scriptable if this is the case!! */ - mParent = aParent; + mShared->mParent = aParent; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetWindowState(nsISupports *aState) { - mWindowState = aState; + mShared->mWindowState = aState; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetWindowState(nsISupports **aState) { - NS_IF_ADDREF(*aState = mWindowState); + NS_IF_ADDREF(*aState = mShared->mWindowState); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetViewerBounds(const nsIntRect &aBounds) { - mViewerBounds = aBounds; + mShared->mViewerBounds = aBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetViewerBounds(nsIntRect &aBounds) { - aBounds = mViewerBounds; + aBounds = mShared->mViewerBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetOwner(nsISupports **aOwner) { - NS_IF_ADDREF(*aOwner = mOwner); + NS_IF_ADDREF(*aOwner = mShared->mOwner); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetOwner(nsISupports *aOwner) { - mOwner = aOwner; + mShared->mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry **aEntry) +{ + NS_ENSURE_ARG_POINTER(aEntry); + NS_IF_ADDREF(*aEntry = mShared); + return NS_OK; +} + +bool +nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry *aEntry) +{ + return static_cast(mShared) == aEntry; +} + +NS_IMETHODIMP +nsSHEntry::AdoptBFCacheEntry(nsISHEntry *aEntry) +{ + nsCOMPtr shEntry = do_QueryInterface(aEntry); + NS_ENSURE_STATE(shEntry); + + nsSHEntryShared *shared = shEntry->GetSharedState(); + NS_ENSURE_STATE(shared); + + mShared = shared; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SharesDocumentWith(nsISHEntry *aEntry, bool *aOut) +{ + NS_ENSURE_ARG_POINTER(aOut); + + nsCOMPtr internal = do_QueryInterface(aEntry); + NS_ENSURE_STATE(internal); + + *aOut = mShared == internal->GetSharedState(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::AbandonBFCacheEntry() +{ + mShared = nsSHEntryShared::Duplicate(mShared); return NS_OK; } @@ -753,256 +676,77 @@ NS_IMETHODIMP nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell) { NS_ASSERTION(aShell, "Null child shell added to history entry"); - mChildShells.AppendObject(aShell); + mShared->mChildShells.AppendObject(aShell); return NS_OK; } NS_IMETHODIMP nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell) { - NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex)); + NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex)); return NS_OK; } NS_IMETHODIMP nsSHEntry::ClearChildShells() { - mChildShells.Clear(); + mShared->mChildShells.Clear(); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetRefreshURIList(nsISupportsArray **aList) { - NS_IF_ADDREF(*aList = mRefreshURIList); + NS_IF_ADDREF(*aList = mShared->mRefreshURIList); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetRefreshURIList(nsISupportsArray *aList) { - mRefreshURIList = aList; + mShared->mRefreshURIList = aList; return NS_OK; } NS_IMETHODIMP nsSHEntry::SyncPresentationState() { - if (mContentViewer && mWindowState) { - // If we have a content viewer and a window state, we should be ok. - return NS_OK; - } - - DropPresentationState(); - - return NS_OK; + return mShared->SyncPresentationState(); } -void -nsSHEntry::DropPresentationState() -{ - nsRefPtr kungFuDeathGrip = this; - - if (mDocument) { - mDocument->SetBFCacheEntry(nsnull); - mDocument->RemoveMutationObserver(this); - mDocument = nsnull; - } - if (mContentViewer) - mContentViewer->ClearHistoryEntry(); - - StopTrackingEntry(this); - mContentViewer = nsnull; - mSticky = true; - mWindowState = nsnull; - mViewerBounds.SetRect(0, 0, 0, 0); - mChildShells.Clear(); - mRefreshURIList = nsnull; - mEditorData = nsnull; -} - -void -nsSHEntry::Expire() -{ - // This entry has timed out. If we still have a content viewer, we need to - // get it evicted. - if (!mContentViewer) - return; - nsCOMPtr container; - mContentViewer->GetContainer(getter_AddRefs(container)); - nsCOMPtr treeItem = do_QueryInterface(container); - if (!treeItem) - return; - // 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) - return; - historyInt->EvictExpiredContentViewerForEntry(this); -} - -//***************************************************************************** -// nsSHEntry: nsIMutationObserver -//***************************************************************************** - -void -nsSHEntry::NodeWillBeDestroyed(const nsINode* aNode) -{ - NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); -} - -void -nsSHEntry::CharacterDataWillChange(nsIDocument* aDocument, - nsIContent* aContent, - CharacterDataChangeInfo* aInfo) -{ -} - -void -nsSHEntry::CharacterDataChanged(nsIDocument* aDocument, - nsIContent* aContent, - CharacterDataChangeInfo* aInfo) -{ - RemoveFromBFCacheAsync(); -} - -void -nsSHEntry::AttributeWillChange(nsIDocument* aDocument, - dom::Element* aContent, - PRInt32 aNameSpaceID, - nsIAtom* aAttribute, - PRInt32 aModType) -{ -} - -void -nsSHEntry::AttributeChanged(nsIDocument* aDocument, - dom::Element* aElement, - PRInt32 aNameSpaceID, - nsIAtom* aAttribute, - PRInt32 aModType) -{ - RemoveFromBFCacheAsync(); -} - -void -nsSHEntry::ContentAppended(nsIDocument* aDocument, - nsIContent* aContainer, - nsIContent* aFirstNewContent, - PRInt32 /* unused */) -{ - RemoveFromBFCacheAsync(); -} - -void -nsSHEntry::ContentInserted(nsIDocument* aDocument, - nsIContent* aContainer, - nsIContent* aChild, - PRInt32 /* unused */) -{ - RemoveFromBFCacheAsync(); -} - -void -nsSHEntry::ContentRemoved(nsIDocument* aDocument, - nsIContent* aContainer, - nsIContent* aChild, - PRInt32 aIndexInContainer, - nsIContent* aPreviousSibling) -{ - RemoveFromBFCacheAsync(); -} - -void -nsSHEntry::ParentChainChanged(nsIContent *aContent) -{ -} - -class DestroyViewerEvent : public nsRunnable -{ -public: - DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument) - : mViewer(aViewer), - mDocument(aDocument) - {} - - NS_IMETHOD Run() - { - if (mViewer) - mViewer->Destroy(); - return NS_OK; - } - - nsCOMPtr mViewer; - nsCOMPtr mDocument; -}; - void nsSHEntry::RemoveFromBFCacheSync() { - NS_ASSERTION(mContentViewer && mDocument, - "we're not in the bfcache!"); - - nsCOMPtr viewer = mContentViewer; - DropPresentationState(); - - // Warning! The call to DropPresentationState could have dropped the last - // reference to this nsSHEntry, so no accessing members beyond here. - - if (viewer) { - viewer->Destroy(); - } + mShared->RemoveFromBFCacheSync(); } void nsSHEntry::RemoveFromBFCacheAsync() { - NS_ASSERTION(mContentViewer && mDocument, - "we're not in the bfcache!"); - - // Release the reference to the contentviewer asynchronously so that the - // document doesn't get nuked mid-mutation. - - nsCOMPtr evt = - new DestroyViewerEvent(mContentViewer, mDocument); - nsresult rv = NS_DispatchToCurrentThread(evt); - if (NS_FAILED(rv)) { - NS_WARNING("failed to dispatch DestroyViewerEvent"); - } - else { - // Drop presentation. Also ensures that we don't post more then one - // PLEvent. Only do this if we succeeded in posting the event since - // otherwise the document could be torn down mid mutation causing crashes. - DropPresentationState(); - } - // Warning! The call to DropPresentationState could have dropped the last - // reference to this nsSHEntry, so no accessing members beyond here. + mShared->RemoveFromBFCacheAsync(); } nsDocShellEditorData* nsSHEntry::ForgetEditorData() { - return mEditorData.forget(); + // XXX jlebar Check how this is used. + return mShared->mEditorData.forget(); } void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) { - NS_ASSERTION(!(aData && mEditorData), + NS_ASSERTION(!(aData && mShared->mEditorData), "We're going to overwrite an owning ref!"); - if (mEditorData != aData) - mEditorData = aData; + if (mShared->mEditorData != aData) { + mShared->mEditorData = aData; + } } bool nsSHEntry::HasDetachedEditor() { - return mEditorData != nsnull; + return mShared->mEditorData != nsnull; } NS_IMETHODIMP @@ -1023,7 +767,7 @@ nsSHEntry::SetStateData(nsIStructuredCloneContainer *aContainer) NS_IMETHODIMP nsSHEntry::IsDynamicallyAdded(bool* aAdded) { - *aAdded = mDynamicallyCreated; + *aAdded = mShared->mDynamicallyCreated; return NS_OK; } @@ -1046,14 +790,14 @@ nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) NS_IMETHODIMP nsSHEntry::GetDocshellID(PRUint64* aID) { - *aID = mDocShellID; + *aID = mShared->mDocShellID; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetDocshellID(PRUint64 aID) { - mDocShellID = aID; + mShared->mDocShellID = aID; return NS_OK; } @@ -1061,14 +805,13 @@ nsSHEntry::SetDocshellID(PRUint64 aID) NS_IMETHODIMP nsSHEntry::GetLastTouched(PRUint32 *aLastTouched) { - *aLastTouched = mLastTouched; + *aLastTouched = mShared->mLastTouched; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLastTouched(PRUint32 aLastTouched) { - mLastTouched = aLastTouched; + mShared->mLastTouched = aLastTouched; return NS_OK; } - diff --git a/docshell/shistory/src/nsSHEntry.h b/docshell/shistory/src/nsSHEntry.h index cd44eb0d8e3..4e3eeee29cd 100644 --- a/docshell/shistory/src/nsSHEntry.h +++ b/docshell/shistory/src/nsSHEntry.h @@ -42,28 +42,21 @@ // Helper Classes #include "nsCOMPtr.h" +#include "nsAutoPtr.h" #include "nsCOMArray.h" #include "nsString.h" -#include "nsAutoPtr.h" // Interfaces needed -#include "nsIContentViewer.h" #include "nsIInputStream.h" -#include "nsILayoutHistoryState.h" #include "nsISHEntry.h" #include "nsISHContainer.h" #include "nsIURI.h" -#include "nsIEnumerator.h" #include "nsIHistoryEntry.h" -#include "nsRect.h" -#include "nsISupportsArray.h" -#include "nsIMutationObserver.h" -#include "nsExpirationTracker.h" -#include "nsDocShellEditorData.h" + +class nsSHEntryShared; class nsSHEntry : public nsISHEntry, public nsISHContainer, - public nsIMutationObserver, public nsISHEntryInternal { public: @@ -75,51 +68,30 @@ public: NS_DECL_NSISHENTRY NS_DECL_NSISHENTRYINTERNAL NS_DECL_NSISHCONTAINER - NS_DECL_NSIMUTATIONOBSERVER void DropPresentationState(); - void Expire(); - - nsExpirationState *GetExpirationState() { return &mExpirationState; } - static nsresult Startup(); static void Shutdown(); private: ~nsSHEntry(); - nsCOMPtr mURI; - nsCOMPtr mReferrerURI; - nsCOMPtr mContentViewer; - nsCOMPtr mDocument; // document currently being observed - nsString mTitle; - nsCOMPtr mPostData; - nsCOMPtr mLayoutHistoryState; - nsCOMArray mChildren; - PRUint32 mLoadType; - PRUint32 mID; - PRInt64 mDocIdentifier; - PRInt32 mScrollPositionX; - PRInt32 mScrollPositionY; - PRPackedBool mURIWasModified; - PRPackedBool mIsFrameNavigation; - PRPackedBool mSaveLayoutState; - PRPackedBool mExpired; - PRPackedBool mSticky; - PRPackedBool mDynamicallyCreated; - nsCString mContentType; - nsCOMPtr mCacheKey; - nsISHEntry * mParent; // weak reference - nsCOMPtr mWindowState; - nsIntRect mViewerBounds; - nsCOMArray mChildShells; - nsCOMPtr mRefreshURIList; - nsCOMPtr mOwner; - nsExpirationState mExpirationState; - nsAutoPtr mEditorData; - PRUint64 mDocShellID; - PRUint32 mLastTouched; + // We share the state in here with other SHEntries which correspond to the + // same document. + nsRefPtr mShared; + + // See nsSHEntry.idl for comments on these members. + nsCOMPtr mURI; + nsCOMPtr mReferrerURI; + nsString mTitle; + nsCOMPtr mPostData; + PRUint32 mLoadType; + PRUint32 mID; + PRInt32 mScrollPositionX; + PRInt32 mScrollPositionY; + nsCOMArray mChildren; + bool mURIWasModified; nsCOMPtr mStateData; }; diff --git a/docshell/shistory/src/nsSHEntryShared.cpp b/docshell/shistory/src/nsSHEntryShared.cpp new file mode 100644 index 00000000000..ba8089d4e6d --- /dev/null +++ b/docshell/shistory/src/nsSHEntryShared.cpp @@ -0,0 +1,400 @@ +/* ***** 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 the Mozilla Foundation. + * + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Justin Lebar + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 "nsSHEntryShared.h" +#include "nsISHistory.h" +#include "nsISHistoryInternal.h" +#include "nsIDocument.h" +#include "nsIWebNavigation.h" +#include "nsIContentViewer.h" +#include "nsIDocShellTreeItem.h" +#include "nsISupportsArray.h" +#include "nsDocShellEditorData.h" +#include "nsThreadUtils.h" +#include "nsILayoutHistoryState.h" +#include "prprf.h" + +namespace dom = mozilla::dom; + +namespace { + +PRUint64 gSHEntrySharedID = 0; + +} // anonymous namespace + +// Hardcode this to time out unused content viewers after 30 minutes +// XXX jlebar shouldn't this be a pref? +#define CONTENT_VIEWER_TIMEOUT_SECONDS (30*60) + +typedef nsExpirationTracker HistoryTrackerBase; +class HistoryTracker : public HistoryTrackerBase { +public: + // Expire cached contentviewers after 20-30 minutes in the cache. + HistoryTracker() + : HistoryTrackerBase(1000 * CONTENT_VIEWER_TIMEOUT_SECONDS / 2) + { + } + +protected: + virtual void NotifyExpired(nsSHEntryShared *aObj) { + RemoveObject(aObj); + aObj->Expire(); + } +}; + +static HistoryTracker *gHistoryTracker = nsnull; + +void +nsSHEntryShared::Startup() +{ + gHistoryTracker = new HistoryTracker(); +} + +void +nsSHEntryShared::Shutdown() +{ + delete gHistoryTracker; + gHistoryTracker = nsnull; +} + +nsSHEntryShared::nsSHEntryShared() + : mDocShellID(0) + , mParent(nsnull) + , mIsFrameNavigation(false) + , mSaveLayoutState(true) + , mSticky(true) + , mDynamicallyCreated(false) + , mLastTouched(0) + , mID(gSHEntrySharedID++) + , mExpired(false) + , mViewerBounds(0, 0, 0, 0) +{ +} + +nsSHEntryShared::~nsSHEntryShared() +{ + RemoveFromExpirationTracker(); + +#ifdef DEBUG + // Check that we're not still on track to expire. We shouldn't be, because + // we just removed ourselves! + nsExpirationTracker::Iterator + iterator(gHistoryTracker); + + nsSHEntryShared *elem; + while ((elem = iterator.Next()) != nsnull) { + NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); + } +#endif + + if (mContentViewer) { + RemoveFromBFCacheSync(); + } +} + +NS_IMPL_ISUPPORTS2(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) + +already_AddRefed +nsSHEntryShared::Duplicate(nsSHEntryShared *aEntry) +{ + nsRefPtr newEntry = new nsSHEntryShared(); + + newEntry->mDocShellID = aEntry->mDocShellID; + newEntry->mChildShells.AppendObjects(aEntry->mChildShells); + newEntry->mOwner = aEntry->mOwner; + newEntry->mParent = aEntry->mParent; + newEntry->mContentType.Assign(aEntry->mContentType); + newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation; + newEntry->mSaveLayoutState = aEntry->mSaveLayoutState; + newEntry->mSticky = aEntry->mSticky; + newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated; + newEntry->mCacheKey = aEntry->mCacheKey; + newEntry->mLastTouched = aEntry->mLastTouched; + + return newEntry.forget(); +} + +void nsSHEntryShared::RemoveFromExpirationTracker() +{ + if (GetExpirationState()->IsTracked()) { + gHistoryTracker->RemoveObject(this); + } +} + +nsresult +nsSHEntryShared::SyncPresentationState() +{ + if (mContentViewer && mWindowState) { + // If we have a content viewer and a window state, we should be ok. + return NS_OK; + } + + DropPresentationState(); + + return NS_OK; +} + +void +nsSHEntryShared::DropPresentationState() +{ + nsRefPtr kungFuDeathGrip = this; + + if (mDocument) { + mDocument->SetBFCacheEntry(nsnull); + mDocument->RemoveMutationObserver(this); + mDocument = nsnull; + } + if (mContentViewer) { + mContentViewer->ClearHistoryEntry(); + } + + RemoveFromExpirationTracker(); + mContentViewer = nsnull; + mSticky = true; + mWindowState = nsnull; + mViewerBounds.SetRect(0, 0, 0, 0); + mChildShells.Clear(); + mRefreshURIList = nsnull; + mEditorData = nsnull; +} + +void +nsSHEntryShared::Expire() +{ + // This entry has timed out. If we still have a content viewer, we need to + // evict it. + if (!mContentViewer) { + return; + } + nsCOMPtr container; + mContentViewer->GetContainer(getter_AddRefs(container)); + nsCOMPtr treeItem = do_QueryInterface(container); + if (!treeItem) { + return; + } + // 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) { + return; + } + historyInt->EvictExpiredContentViewerForEntry(this); +} + +nsresult +nsSHEntryShared::SetContentViewer(nsIContentViewer *aViewer) +{ + NS_PRECONDITION(!aViewer || !mContentViewer, + "SHEntryShared already contains viewer"); + + if (mContentViewer || !aViewer) { + DropPresentationState(); + } + + mContentViewer = aViewer; + + if (mContentViewer) { + gHistoryTracker->AddObject(this); + + nsCOMPtr domDoc; + mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + // Store observed document in strong pointer in case it is removed from + // the contentviewer + mDocument = do_QueryInterface(domDoc); + if (mDocument) { + mDocument->SetBFCacheEntry(this); + mDocument->AddMutationObserver(this); + } + } + + return NS_OK; +} + +nsresult +nsSHEntryShared::RemoveFromBFCacheSync() +{ + NS_ASSERTION(mContentViewer && mDocument, + "we're not in the bfcache!"); + + nsCOMPtr viewer = mContentViewer; + DropPresentationState(); + + // Warning! The call to DropPresentationState could have dropped the last + // reference to this object, so don't access members beyond here. + + if (viewer) { + viewer->Destroy(); + } + + return NS_OK; +} + +class DestroyViewerEvent : public nsRunnable +{ +public: + DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument) + : mViewer(aViewer), + mDocument(aDocument) + {} + + NS_IMETHOD Run() + { + if (mViewer) { + mViewer->Destroy(); + } + return NS_OK; + } + + nsCOMPtr mViewer; + nsCOMPtr mDocument; +}; + +nsresult +nsSHEntryShared::RemoveFromBFCacheAsync() +{ + NS_ASSERTION(mContentViewer && mDocument, + "we're not in the bfcache!"); + + // Release the reference to the contentviewer asynchronously so that the + // document doesn't get nuked mid-mutation. + + nsCOMPtr evt = + new DestroyViewerEvent(mContentViewer, mDocument); + nsresult rv = NS_DispatchToCurrentThread(evt); + if (NS_FAILED(rv)) { + NS_WARNING("failed to dispatch DestroyViewerEvent"); + } else { + // Drop presentation. Only do this if we succeeded in posting the event + // since otherwise the document could be torn down mid-mutation, causing + // crashes. + DropPresentationState(); + } + + // Careful! The call to DropPresentationState could have dropped the last + // reference to this nsSHEntryShared, so don't access members beyond here. + + return NS_OK; +} + +nsresult +nsSHEntryShared::GetID(PRUint64 *aID) +{ + *aID = mID; + return NS_OK; +} + +//***************************************************************************** +// nsSHEntryShared: nsIMutationObserver +//***************************************************************************** + +void +nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode) +{ + NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); +} + +void +nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ +} + +void +nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument, + dom::Element* aContent, + PRInt32 aNameSpaceID, + nsIAtom* aAttribute, + PRInt32 aModType) +{ +} + +void +nsSHEntryShared::AttributeChanged(nsIDocument* aDocument, + dom::Element* aElement, + PRInt32 aNameSpaceID, + nsIAtom* aAttribute, + PRInt32 aModType) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + PRInt32 /* unused */) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + PRInt32 /* unused */) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + PRInt32 aIndexInContainer, + nsIContent* aPreviousSibling) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ParentChainChanged(nsIContent *aContent) +{ +} diff --git a/docshell/shistory/src/nsSHEntryShared.h b/docshell/shistory/src/nsSHEntryShared.h new file mode 100644 index 00000000000..792b7cabfc2 --- /dev/null +++ b/docshell/shistory/src/nsSHEntryShared.h @@ -0,0 +1,124 @@ +/* ***** 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 the Mozilla Foundation. + * + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Justin Lebar + * + * Alternatively, the contents of this file may be used under the terms of + * either 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 ***** */ + +#ifndef nsSHEntryShared_h__ +#define nsSHEntryShared_h__ + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsIBFCacheEntry.h" +#include "nsIMutationObserver.h" +#include "nsExpirationTracker.h" +#include "nsRect.h" + +class nsSHEntry; +class nsISHEntry; +class nsIDocument; +class nsIContentViewer; +class nsIDocShellTreeItem; +class nsILayoutHistoryState; +class nsISupportsArray; +class nsDocShellEditorData; + +// A document may have multiple SHEntries, either due to hash navigations or +// calls to history.pushState. SHEntries corresponding to the same document +// share many members; in particular, they share state related to the +// back/forward cache. +// +// nsSHEntryShared is the vehicle for this sharing. +class nsSHEntryShared : public nsIBFCacheEntry, + public nsIMutationObserver +{ + public: + static void Startup(); + static void Shutdown(); + + nsSHEntryShared(); + ~nsSHEntryShared(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMUTATIONOBSERVER + NS_DECL_NSIBFCACHEENTRY + + private: + friend class nsSHEntry; + + friend class HistoryTracker; + friend class nsExpirationTracker; + nsExpirationState *GetExpirationState() { return &mExpirationState; } + + static already_AddRefed Duplicate(nsSHEntryShared *aEntry); + + void RemoveFromExpirationTracker(); + void Expire(); + nsresult SyncPresentationState(); + void DropPresentationState(); + + nsresult SetContentViewer(nsIContentViewer *aViewer); + + // See nsISHEntry.idl for an explanation of these members. + + // These members are copied by nsSHEntryShared::Duplicate(). If you add a + // member here, be sure to update the Duplicate() implementation. + PRUint64 mDocShellID; + nsCOMArray mChildShells; + nsCOMPtr mOwner; + nsISHEntry* mParent; + nsCString mContentType; + bool mIsFrameNavigation; + bool mSaveLayoutState; + bool mSticky; + bool mDynamicallyCreated; + nsCOMPtr mCacheKey; + PRUint32 mLastTouched; + + // These members aren't copied by nsSHEntryShared::Duplicate() because + // they're specific to a particular content viewer. + PRUint64 mID; + nsCOMPtr mContentViewer; + nsCOMPtr mDocument; + nsCOMPtr mLayoutHistoryState; + bool mExpired; + nsCOMPtr mWindowState; + nsIntRect mViewerBounds; + nsCOMPtr mRefreshURIList; + nsExpirationState mExpirationState; + nsAutoPtr mEditorData; +}; + +#endif diff --git a/docshell/shistory/src/nsSHistory.cpp b/docshell/shistory/src/nsSHistory.cpp index 4200307da8d..c211dbdf2e5 100644 --- a/docshell/shistory/src/nsSHistory.cpp +++ b/docshell/shistory/src/nsSHistory.cpp @@ -72,12 +72,10 @@ using namespace mozilla; #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" -#define PREF_SHISTORY_OPTIMIZE_EVICTION "browser.sessionhistory.optimize_eviction" static const char* kObservedPrefs[] = { PREF_SHISTORY_SIZE, PREF_SHISTORY_MAX_TOTAL_VIEWERS, - PREF_SHISTORY_OPTIMIZE_EVICTION, nsnull }; @@ -90,16 +88,46 @@ static PRCList gSHistoryList; // means we will calculate how many viewers to cache based on total memory PRInt32 nsSHistory::sHistoryMaxTotalViewers = -1; -// Whether we should optimize the search for which entry to evict, -// by evicting older entries first. See entryLastTouched in -// nsSHistory::EvictGlobalContentViewer(). -// NB: After 4.0, we should remove this option and the corresponding -// pref - optimization should always be used -static bool gOptimizeEviction = false; // A counter that is used to be able to know the order in which // entries were touched, so that we can evict older entries first. static PRUint32 gTouchCounter = 0; +static PRLogModuleInfo* gLogModule = PR_LOG_DEFINE("nsSHistory"); +#define LOG(format) PR_LOG(gLogModule, PR_LOG_DEBUG, format) + +// This macro makes it easier to print a log message which includes a URI's +// spec. Example use: +// +// nsIURI *uri = [...]; +// LOG_SPEC(("The URI is %s.", _spec), uri); +// +#define LOG_SPEC(format, uri) \ + PR_BEGIN_MACRO \ + if (PR_LOG_TEST(gLogModule, PR_LOG_DEBUG)) { \ + nsCAutoString _specStr(NS_LITERAL_CSTRING("(null)"));\ + if (uri) { \ + uri->GetSpec(_specStr); \ + } \ + const char* _spec = _specStr.get(); \ + LOG(format); \ + } \ + PR_END_MACRO + +// This macro makes it easy to log a message including an SHEntry's URI. +// For example: +// +// nsCOMPtr shentry = [...]; +// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); +// +#define LOG_SHENTRY_SPEC(format, shentry) \ + PR_BEGIN_MACRO \ + if (PR_LOG_TEST(gLogModule, PR_LOG_DEBUG)) { \ + nsCOMPtr uri; \ + shentry->GetURI(getter_AddRefs(uri)); \ + LOG_SPEC(format, uri); \ + } \ + PR_END_MACRO + enum HistCmd{ HIST_CMD_BACK, HIST_CMD_FORWARD, @@ -134,15 +162,60 @@ nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic, { if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { nsSHistory::UpdatePrefs(); - nsSHistory::EvictGlobalContentViewer(); + nsSHistory::GloballyEvictContentViewers(); } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) || !strcmp(aTopic, "memory-pressure")) { - nsSHistory::EvictAllContentViewersGlobally(); + nsSHistory::GloballyEvictAllContentViewers(); } return NS_OK; } +namespace { + +already_AddRefed +GetContentViewerForTransaction(nsISHTransaction *aTrans) +{ + nsCOMPtr entry; + aTrans->GetSHEntry(getter_AddRefs(entry)); + if (!entry) { + return nsnull; + } + + nsCOMPtr ownerEntry; + nsCOMPtr viewer; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); + return viewer.forget(); +} + +void +EvictContentViewerForTransaction(nsISHTransaction *aTrans) +{ + nsCOMPtr entry; + aTrans->GetSHEntry(getter_AddRefs(entry)); + nsCOMPtr viewer; + nsCOMPtr ownerEntry; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); + if (viewer) { + NS_ASSERTION(ownerEntry, + "Content viewer exists but its SHEntry is null"); + + LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " + "owning SHEntry 0x%p at %s.", + viewer.get(), ownerEntry.get(), _spec), ownerEntry); + + // Drop the presentation state before destroying the viewer, so that + // document teardown is able to correctly persist the state. + ownerEntry->SetContentViewer(nsnull); + ownerEntry->SyncPresentationState(); + viewer->Destroy(); + } +} + +} // anonymous namespace + //***************************************************************************** //*** nsSHistory: Object Management //***************************************************************************** @@ -240,7 +313,6 @@ nsSHistory::UpdatePrefs() Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, &sHistoryMaxTotalViewers); - Preferences::GetBool(PREF_SHISTORY_OPTIMIZE_EVICTION, &gOptimizeEviction); // If the pref is negative, that means we calculate how many viewers // we think we should cache, based on total memory if (sHistoryMaxTotalViewers < 0) { @@ -689,12 +761,12 @@ nsSHistory::GetListener(nsISHistoryListener ** aListener) } NS_IMETHODIMP -nsSHistory::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex) +nsSHistory::EvictOutOfRangeContentViewers(PRInt32 aIndex) { // Check our per SHistory object limit in the currently navigated SHistory - EvictWindowContentViewers(aPreviousIndex, aIndex); + EvictOutOfRangeWindowContentViewers(aIndex); // Check our total limit across all SHistory objects - EvictGlobalContentViewer(); + GloballyEvictContentViewers(); return NS_OK; } @@ -703,7 +775,14 @@ nsSHistory::EvictAllContentViewers() { // XXXbz we don't actually do a good job of evicting things as we should, so // we might have viewers quite far from mIndex. So just evict everything. - EvictContentViewersInRange(0, mLength); + nsCOMPtr trans = mListRoot; + while (trans) { + EvictContentViewerForTransaction(trans); + + nsISHTransaction *temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + return NS_OK; } @@ -835,103 +914,78 @@ nsSHistory::ReloadCurrentEntry() } void -nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) +nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex) { - // 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. Existing content viewers - // should be in the window from - // aFromIndex - gHistoryMaxViewers to aFromIndex + gHistoryMaxViewers + // XXX rename method to EvictContentViewersExceptAroundIndex, or something. + + // We need to release all content viewers that are no longer in the range // - // We make the assumption that entries outside this range have no viewers so - // that we don't have to walk the whole entire session history checking for - // content viewers. + // aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers + // + // to ensure that this SHistory object isn't responsible for more than + // gHistoryMaxViewers content viewers. But our job is complicated by the + // fact that two transactions which are related by either hash navigations or + // history.pushState will have the same content viewer. + // + // To illustrate the issue, suppose gHistoryMaxViewers = 3 and we have four + // linked transactions in our history. Suppose we then add a new content + // viewer and call into this function. So the history looks like: + // + // A A A A B + // + * + // + // where the letters are content viewers and + and * denote the beginning and + // end of the range aIndex +/- gHistoryMaxViewers. + // + // Although one copy of the content viewer A exists outside the range, we + // don't want to evict A, because it has other copies in range! + // + // We therefore adjust our eviction strategy to read: + // + // Evict each content viewer outside the range aIndex -/+ + // gHistoryMaxViewers, unless that content viewer also appears within the + // range. + // + // (Note that it's entirely legal to have two copies of one content viewer + // separated by a different content viewer -- call pushState twice, go back + // once, and refresh -- so we can't rely on identical viewers only appearing + // adjacent to one another.) - // This can happen on the first load of a page in a particular window - if (aFromIndex < 0 || aToIndex < 0) { + if (aIndex < 0) { return; } - NS_ASSERTION(aFromIndex < mLength, "aFromIndex is out of range"); - NS_ASSERTION(aToIndex < mLength, "aToIndex is out of range"); - if (aFromIndex >= mLength || aToIndex >= mLength) { + NS_ASSERTION(aIndex < mLength, "aIndex is out of range"); + if (aIndex >= mLength) { 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 = NS_MAX(0, aFromIndex - gHistoryMaxViewers); - } else { // going backward - startIndex = aToIndex + gHistoryMaxViewers + 1; - if (startIndex >= mLength) { - return; - } - endIndex = NS_MIN(mLength, aFromIndex + gHistoryMaxViewers + 1); - } + // Calculate the range that's safe from eviction. + PRInt32 startSafeIndex = PR_MAX(0, aIndex - gHistoryMaxViewers); + PRInt32 endSafeIndex = PR_MIN(mLength, aIndex + gHistoryMaxViewers); -#ifdef DEBUG + LOG(("EvictOutOfRangeWindowContentViewers(index=%d), " + "mLength=%d. Safe range [%d, %d]", + aIndex, mLength, startSafeIndex, endSafeIndex)); + + // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be + // evicted. Collect a set of them so we don't accidentally evict one of them + // if it appears outside this range. + nsCOMArray safeViewers; nsCOMPtr trans; + GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans)); + for (PRUint32 i = startSafeIndex; trans && i <= endSafeIndex; i++) { + nsCOMPtr viewer = GetContentViewerForTransaction(trans); + safeViewers.AppendObject(viewer); + nsISHTransaction *temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + + // Walk the SHistory list and evict any content viewers that aren't safe. GetTransactionAtIndex(0, getter_AddRefs(trans)); - - // Walk the full session history and check that entries outside the window - // around aFromIndex have no content viewers - for (PRInt32 i = 0; trans && i < mLength; ++i) { - if (i < aFromIndex - gHistoryMaxViewers || - i > aFromIndex + gHistoryMaxViewers) { - nsCOMPtr entry; - trans->GetSHEntry(getter_AddRefs(entry)); - nsCOMPtr viewer; - nsCOMPtr ownerEntry; - entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), - getter_AddRefs(viewer)); - NS_WARN_IF_FALSE(!viewer, - "ContentViewer exists outside gHistoryMaxViewer range"); - } - - nsISHTransaction *temp = trans; - temp->GetNext(getter_AddRefs(trans)); - } -#endif - - EvictContentViewersInRange(startIndex, endIndex); -} - -void -nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd) -{ - nsCOMPtr trans; - GetTransactionAtIndex(aStart, getter_AddRefs(trans)); - - for (PRInt32 i = aStart; trans && i < aEnd; ++i) { - nsCOMPtr entry; - trans->GetSHEntry(getter_AddRefs(entry)); - nsCOMPtr 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; - ownerEntry->GetURI(getter_AddRefs(uri)); - nsCAutoString spec; - if (uri) - uri->GetSpec(spec); - - printf("per SHistory limit: evicting content viewer: %s\n", spec.get()); -#endif - - // Drop the presentation state before destroying the viewer, so that - // document teardown is able to correctly persist the state. - ownerEntry->SetContentViewer(nsnull); - ownerEntry->SyncPresentationState(); - viewer->Destroy(); + while (trans) { + nsCOMPtr viewer = GetContentViewerForTransaction(trans); + if (safeViewers.IndexOf(viewer) == -1) { + EvictContentViewerForTransaction(trans); } nsISHTransaction *temp = trans; @@ -939,138 +993,153 @@ nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd) } } -// static -void -nsSHistory::EvictGlobalContentViewer() +namespace { + +class TransactionAndDistance { - // 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. - bool shouldTryEviction = 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; - PRUint32 candidateLastTouched = 0; - nsCOMPtr evictFromSHE; - nsCOMPtr evictViewer; - PRInt32 totalContentViewers = 0; - nsSHistory* shist = static_cast - (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 = NS_MAX(0, shist->mIndex - gHistoryMaxViewers); - PRInt32 endIndex = NS_MIN(shist->mLength - 1, - shist->mIndex + gHistoryMaxViewers); - nsCOMPtr trans; - shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); +public: + TransactionAndDistance(nsISHTransaction *aTrans, PRUint32 aDist) + : mTransaction(aTrans) + , mDistance(aDist) + { + mViewer = GetContentViewerForTransaction(aTrans); + NS_ASSERTION(mViewer, "Transaction should have a content viewer"); - for (PRInt32 i = startIndex; trans && i <= endIndex; ++i) { - nsCOMPtr entry; - trans->GetSHEntry(getter_AddRefs(entry)); - nsCOMPtr viewer; - nsCOMPtr ownerEntry; - entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), - getter_AddRefs(viewer)); + nsCOMPtr shentry; + mTransaction->GetSHEntry(getter_AddRefs(shentry)); - PRUint32 entryLastTouched = 0; - if (gOptimizeEviction) { - nsCOMPtr entryInternal = do_QueryInterface(entry); - if (entryInternal) { - // Find when this entry was last activated - entryInternal->GetLastTouched(&entryLastTouched); - } - } - -#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 = NS_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 this entry is further away from focus than any previously found - // or at the same distance but it is longer time since it was activated - // then take this entry as the new candiate for eviction - if (distance > distanceFromFocus || (distance == distanceFromFocus && candidateLastTouched > entryLastTouched)) { - -#ifdef DEBUG_PAGE_CACHE - printf("Choosing as new eviction candidate: %s\n", spec.get()); -#endif - candidateLastTouched = entryLastTouched; - distanceFromFocus = distance; - evictFromSHE = ownerEntry; - evictViewer = viewer; - } - } - nsISHTransaction* temp = trans; - temp->GetNext(getter_AddRefs(trans)); - } - shist = static_cast(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 - - // Drop the presentation state before destroying the viewer, so that - // document teardown is able to correctly persist the state. - evictFromSHE->SetContentViewer(nsnull); - evictFromSHE->SyncPresentationState(); - evictViewer->Destroy(); - - // 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 = false; - } + nsCOMPtr shentryInternal = do_QueryInterface(shentry); + if (shentryInternal) { + shentryInternal->GetLastTouched(&mLastTouched); } else { - // couldn't find a content viewer to evict, so we are done - shouldTryEviction = false; + NS_WARNING("Can't cast to nsISHEntryInternal?"); + mLastTouched = 0; } - } // while shouldTryEviction + } + + bool operator<(const TransactionAndDistance &aOther) const + { + // Compare distances first, and fall back to last-accessed times. + if (aOther.mDistance != this->mDistance) { + return this->mDistance < aOther.mDistance; + } + + return this->mLastTouched < aOther.mLastTouched; + } + + bool operator==(const TransactionAndDistance &aOther) const + { + // This is a little silly; we need == so the default comaprator can be + // instantiated, but this function is never actually called when we sort + // the list of TransactionAndDistance objects. + return aOther.mDistance == this->mDistance && + aOther.mLastTouched == this->mLastTouched; + } + + nsCOMPtr mTransaction; + nsCOMPtr mViewer; + PRUint32 mLastTouched; + PRInt32 mDistance; +}; + +} // anonymous namespace + +//static +void +nsSHistory::GloballyEvictContentViewers() +{ + // First, collect from each SHistory object the transactions which have a + // cached content viewer. Associate with each transaction its distance from + // its SHistory's current index. + + nsTArray transactions; + + nsSHistory *shist = static_cast(PR_LIST_HEAD(&gSHistoryList)); + while (shist != &gSHistoryList) { + + // Maintain a list of the transactions which have viewers and belong to + // this particular shist object. We'll add this list to the global list, + // |transactions|, eventually. + nsTArray shTransactions; + + // Content viewers are likely to exist only within shist->mIndex -/+ + // gHistoryMaxViewers, so only search within that range. + // + // A content viewer might exist outside that range due to either: + // + // * history.pushState or hash navigations, in which case a copy of the + // content viewer should exist within the range, or + // + // * bugs which cause us not to call nsSHistory::EvictContentViewers() + // often enough. Once we do call EvictContentViewers() for the + // SHistory object in question, we'll do a full search of its history + // and evict the out-of-range content viewers, so we don't bother here. + // + PRInt32 startIndex = NS_MAX(0, shist->mIndex - gHistoryMaxViewers); + PRInt32 endIndex = NS_MIN(shist->mLength - 1, + shist->mIndex + gHistoryMaxViewers); + nsCOMPtr trans; + shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); + for (PRInt32 i = startIndex; trans && i <= endIndex; i++) { + nsCOMPtr contentViewer = + GetContentViewerForTransaction(trans); + + if (contentViewer) { + // Because one content viewer might belong to multiple SHEntries, we + // have to search through shTransactions to see if we already know + // about this content viewer. If we find the viewer, update its + // distance from the SHistory's index and continue. + bool found = false; + for (PRUint32 j = 0; j < shTransactions.Length(); j++) { + TransactionAndDistance &container = shTransactions[j]; + if (container.mViewer == contentViewer) { + container.mDistance = PR_MIN(container.mDistance, + PR_ABS(i - shist->mIndex)); + found = true; + break; + } + } + + // If we didn't find a TransactionAndDistance for this content viewer, make a new + // one. + if (!found) { + TransactionAndDistance container(trans, PR_ABS(i - shist->mIndex)); + shTransactions.AppendElement(container); + } + } + + nsISHTransaction *temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + + // We've found all the transactions belonging to shist which have viewers. + // Add those transactions to our global list and move on. + transactions.AppendElements(shTransactions); + shist = static_cast(PR_NEXT_LINK(shist)); + } + + // We now have collected all cached content viewers. First check that we + // have enough that we actually need to evict some. + if ((PRInt32)transactions.Length() <= sHistoryMaxTotalViewers) { + return; + } + + // If we need to evict, sort our list of transactions and evict the largest + // ones. (We could of course get better algorithmic complexity here by using + // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, + // so let's not worry about it.) + transactions.Sort(); + + for (PRInt32 i = transactions.Length() - 1; + i >= sHistoryMaxTotalViewers; --i) { + + EvictContentViewerForTransaction(transactions[i].mTransaction); + + } } -NS_IMETHODIMP -nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) +nsresult +nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) { PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers); PRInt32 endIndex = NS_MIN(mLength - 1, @@ -1082,8 +1151,11 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) for (i = startIndex; trans && i <= endIndex; ++i) { nsCOMPtr entry; trans->GetSHEntry(getter_AddRefs(entry)); - if (entry == aEntry) + + // Does entry have the same BFCacheEntry as the argument to this method? + if (entry->HasBFCacheEntry(aEntry)) { break; + } nsISHTransaction *temp = trans; temp->GetNext(getter_AddRefs(trans)); @@ -1091,21 +1163,13 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) if (i > endIndex) return NS_OK; - NS_ASSERTION(i != mIndex, "How did the current session entry expire?"); - if (i == mIndex) + if (i == mIndex) { + NS_WARNING("How did the current SHEntry expire?"); return NS_OK; - - // We evict content viewers for the expired entry and any other entries that - // we would have to go through the expired entry to get to (i.e. the entries - // that have the expired entry between them and the current entry). Those - // other entries should have timed out already, actually, but this is just - // to be on the safe side. - if (i < mIndex) { - EvictContentViewersInRange(startIndex, i + 1); - } else { - EvictContentViewersInRange(i, endIndex + 1); } - + + EvictContentViewerForTransaction(trans); + return NS_OK; } @@ -1116,11 +1180,11 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) //static void -nsSHistory::EvictAllContentViewersGlobally() +nsSHistory::GloballyEvictAllContentViewers() { PRInt32 maxViewers = sHistoryMaxTotalViewers; sHistoryMaxTotalViewers = 0; - EvictGlobalContentViewer(); + GloballyEvictContentViewers(); sHistoryMaxTotalViewers = maxViewers; } diff --git a/docshell/shistory/src/nsSHistory.h b/docshell/shistory/src/nsSHistory.h index 1efeb8cfa20..7bf6d69c045 100644 --- a/docshell/shistory/src/nsSHistory.h +++ b/docshell/shistory/src/nsSHistory.h @@ -101,12 +101,11 @@ protected: nsresult PrintHistory(); #endif - // Evict the viewers at indices between aStartIndex and aEndIndex, - // including aStartIndex but not aEndIndex. - void EvictContentViewersInRange(PRInt32 aStartIndex, PRInt32 aEndIndex); - void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex); - static void EvictGlobalContentViewer(); - static void EvictAllContentViewersGlobally(); + // Evict content viewers in this window which don't lie in the "safe" range + // around aIndex. + void EvictOutOfRangeWindowContentViewers(PRInt32 aIndex); + static void GloballyEvictContentViewers(); + static void GloballyEvictAllContentViewers(); // Calculates a max number of total // content viewers to cache, based on amount of total memory diff --git a/docshell/test/Makefile.in b/docshell/test/Makefile.in index 272973d9dd3..e8ce5c3e5ab 100644 --- a/docshell/test/Makefile.in +++ b/docshell/test/Makefile.in @@ -119,6 +119,7 @@ _TEST_FILES = \ test_bug669671.html \ file_bug669671.sjs \ test_bug675587.html \ + test_bfcache_plus_hash.html \ test_bug680257.html \ file_bug680257.html \ $(NULL) diff --git a/docshell/test/chrome/bug396519_window.xul b/docshell/test/chrome/bug396519_window.xul index d45e07cd90c..179e094633c 100644 --- a/docshell/test/chrome/bug396519_window.xul +++ b/docshell/test/chrome/bug396519_window.xul @@ -50,6 +50,9 @@ const LISTEN_EVENTS = ["pageshow"]; + const Cc = Components.classes; + const Ci = Components.interfaces; + var gBrowser; var gTestCount = 0; var gTestsIterator; @@ -96,6 +99,23 @@ QueryInterface(Components.interfaces.nsISHEntry); is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount); } + + // Make sure none of the SHEntries share bfcache entries with one + // another. + for (var i = 0; i < history.count; i++) { + for (var j = 0; j < history.count; j++) { + if (j == i) + continue; + + let shentry1 = history.getEntryAtIndex(i, false) + .QueryInterface(Ci.nsISHEntry); + let shentry2 = history.getEntryAtIndex(j, false) + .QueryInterface(Ci.nsISHEntry); + ok(!shentry1.sharesDocumentWith(shentry2), + 'Test ' + gTestCount + ': shentry[' + i + "] shouldn't " + + "share document with shentry[" + j + ']'); + } + } } else { is(history.count, gExpected.length, "Wrong history length in test "+gTestCount); diff --git a/docshell/test/test_bfcache_plus_hash.html b/docshell/test/test_bfcache_plus_hash.html new file mode 100644 index 00000000000..c8f3765135b --- /dev/null +++ b/docshell/test/test_bfcache_plus_hash.html @@ -0,0 +1,120 @@ + + + + + Test for Bug 646641 + + + + + +Mozilla Bug 646641 +

+ +
+
+
+ + diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index e4265bbbfd0..e611fa5676e 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -228,12 +228,9 @@ public: // First check if the document the IDBDatabase is part of is bfcached nsCOMPtr ownerDoc = database->GetOwnerDocument(); - nsISHEntry* shEntry; - if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) { - nsCOMPtr sheInternal = do_QueryInterface(shEntry); - if (sheInternal) { - sheInternal->RemoveFromBFCacheSync(); - } + nsIBFCacheEntry* bfCacheEntry; + if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) { + bfCacheEntry->RemoveFromBFCacheSync(); NS_ASSERTION(database->IsClosed(), "Kicking doc out of bfcache should have closed database"); continue; diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 9fa34d761c2..e5ae9c16e2b 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1959,7 +1959,7 @@ DocumentViewerImpl::Show(void) printf("About to evict content viewers: prev=%d, loaded=%d\n", prevIndex, loadedIndex); #endif - historyInt->EvictContentViewers(prevIndex, loadedIndex); + historyInt->EvictOutOfRangeContentViewers(loadedIndex); } } } diff --git a/mobile/app/mobile.js b/mobile/app/mobile.js index b5a63a80922..07f29aaf691 100644 --- a/mobile/app/mobile.js +++ b/mobile/app/mobile.js @@ -140,7 +140,6 @@ pref("browser.display.remotetabs.timeout", 10); /* session history */ pref("browser.sessionhistory.max_total_viewers", 1); pref("browser.sessionhistory.max_entries", 50); -pref("browser.sessionhistory.optimize_eviction", true); /* session store */ pref("browser.sessionstore.resume_session_once", false); diff --git a/mobile/chrome/content/bindings/browser.js b/mobile/chrome/content/bindings/browser.js index a64e91883de..aa4f52529c8 100644 --- a/mobile/chrome/content/bindings/browser.js +++ b/mobile/chrome/content/bindings/browser.js @@ -268,8 +268,13 @@ let WebNavigation = { if (aEntry.docshellID) shEntry.docshellID = aEntry.docshellID; - if (aEntry.stateData) - shEntry.stateData = aEntry.stateData; + if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) { + shEntry.stateData = + Cc["@mozilla.org/docshell/structured-clone-container;1"]. + createInstance(Ci.nsIStructuredCloneContainer); + + shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion); + } if (aEntry.scroll) { let scrollPos = aEntry.scroll.split(","); @@ -278,23 +283,15 @@ let WebNavigation = { } if (aEntry.docIdentifier) { - // Get a new document identifier for this entry to ensure that history - // entries after a session restore are considered to have different - // documents from the history entries before the session restore. - // Document identifiers are 64-bit ints, so JS will loose precision and - // start assigning all entries the same doc identifier if these ever get - // large enough. - // - // It's a potential security issue if document identifiers aren't - // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees - // that we won't re-use a doc identifier within a given instance of the - // application. - let ident = aDocIdentMap[aEntry.docIdentifier]; - if (!ident) { - shEntry.setUniqueDocIdentifier(); - aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier; + // If we have a serialized document identifier, try to find an SHEntry + // which matches that doc identifier and adopt that SHEntry's + // BFCacheEntry. If we don't find a match, insert shEntry as the match + // for the document identifier. + let matchingEntry = aDocIdentMap[aEntry.docIdentifier]; + if (!matchingEntry) { + aDocIdentMap[aEntry.docIdentifier] = shEntry; } else { - shEntry.docIdentifier = ident; + shEntry.adoptBFCacheEntry(matchingEntry); } } @@ -379,11 +376,12 @@ let WebNavigation = { } catch (e) { dump(e); } } - if (aEntry.docIdentifier) - entry.docIdentifier = aEntry.docIdentifier; + entry.docIdentifier = aEntry.BFCacheEntry.ID; - if (aEntry.stateData) - entry.stateData = aEntry.stateData; + if (aEntry.stateData != null) { + entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); + entry.structuredCloneVersion = aEntry.stateData.formatVersion; + } if (!(aEntry instanceof Ci.nsISHContainer)) return entry; diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 9d7ca6b4f2f..6f73ab32c20 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -109,8 +109,6 @@ pref("dom.enable_performance", true); // of content viewers to cache based on the amount of available memory. pref("browser.sessionhistory.max_total_viewers", -1); -pref("browser.sessionhistory.optimize_eviction", true); - pref("ui.use_native_colors", true); pref("ui.click_hold_context_menus", false); pref("browser.display.use_document_fonts", 1); // 0 = never, 1 = quick, 2 = always