diff --git a/browser/components/sessionstore/src/nsSessionStore.js b/browser/components/sessionstore/src/nsSessionStore.js index 441220b424d..2ce3d47befd 100644 --- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -1920,7 +1920,9 @@ SessionStoreService.prototype = { catch (ex) { debug(ex); } } - entry.docIdentifier = aEntry.BFCacheEntry.ID; + if (aEntry.docIdentifier) { + entry.docIdentifier = aEntry.docIdentifier; + } if (aEntry.stateData != null) { entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); @@ -3022,11 +3024,16 @@ 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 @@ -3181,16 +3188,24 @@ SessionStoreService.prototype = { } if (aEntry.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; + // 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; } else { - shEntry.adoptBFCacheEntry(matchingEntry); + shEntry.docIdentifier = ident; } } @@ -3326,12 +3341,19 @@ 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 5c7e793e68a..5fe32e28b1b 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}) - // testURL?page2 (state object: {obj3:/^a$/}) <-- newest + // testURL (state object: null) <-- oldest + // testURL (state object: {obj1:1}) + // 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 5356e04fee6..31fa5af0240 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -69,7 +69,6 @@ #include "nsIAnimationFrameListener.h" #include "nsEventStates.h" #include "nsIStructuredCloneContainer.h" -#include "nsIBFCacheEntry.h" #include "nsDOMMemoryReporter.h" class nsIContent; @@ -126,8 +125,8 @@ class Element; } // namespace mozilla #define NS_IDOCUMENT_IID \ -{ 0x448c396a, 0x013c, 0x47b8, \ - { 0x95, 0xf4, 0x56, 0x68, 0x0f, 0x5f, 0x12, 0xf8 } } +{ 0x4114a7c7, 0xb2f4, 0x4dea, \ + { 0xac, 0x78, 0x20, 0xab, 0xda, 0x6f, 0xb2, 0xaf } } // Flag for AddStyleSheet(). #define NS_STYLESHEET_FROM_CATALOG (1 << 0) @@ -480,15 +479,11 @@ public: return GetBFCacheEntry() ? nsnull : mPresShell; } - void SetBFCacheEntry(nsIBFCacheEntry* aEntry) - { - mBFCacheEntry = aEntry; + void SetBFCacheEntry(nsISHEntry* aSHEntry) { + mSHEntry = aSHEntry; } - nsIBFCacheEntry* GetBFCacheEntry() const - { - return mBFCacheEntry; - } + nsISHEntry* GetBFCacheEntry() const { return mSHEntry; } /** * Return the parent document of this document. Will return null @@ -1791,9 +1786,9 @@ protected: AnimationListenerList mAnimationFrameListeners; - // 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; + // 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; // Our base target. nsString mBaseTarget; diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 81aa973394c..eae5e570c82 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4130,10 +4130,10 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const PRUnichar *aURL, mFailedLoadType = mLoadType; if (mLSHE) { - // 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(); + // 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(); } nsCAutoString url; @@ -4406,10 +4406,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 bfcache entry so this load is independent - // of all other loads. (This is important, in particular, for bugs 582795 - // and 585298.) - rv = shEntry->AbandonBFCacheEntry(); + // 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(); NS_ENSURE_SUCCESS(rv, rv); // @@ -8284,15 +8284,17 @@ nsDocShell::InternalLoad(nsIURI * aURI, NS_SUCCEEDED(splitRv2) && curBeforeHash.Equals(newBeforeHash); - // XXX rename - bool sameDocument = false; + bool sameDocIdent = false; if (mOSHE && aSHEntry) { // We're doing a history load. - mOSHE->SharesDocumentWith(aSHEntry, &sameDocument); + PRUint64 ourDocIdent, otherDocIdent; + mOSHE->GetDocIdentifier(&ourDocIdent); + aSHEntry->GetDocIdentifier(&otherDocIdent); + sameDocIdent = (ourDocIdent == otherDocIdent); #ifdef DEBUG - if (sameDocument) { + if (sameDocIdent) { nsCOMPtr currentPostData; mOSHE->GetPostData(getter_AddRefs(currentPostData)); NS_ASSERTION(currentPostData == aPostData, @@ -8315,7 +8317,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 = (sameDocument && mOSHE != aSHEntry) || + bool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) || (!aSHEntry && aPostData == nsnull && sameExceptHashes && !newHash.IsEmpty()); @@ -8366,6 +8368,7 @@ nsDocShell::InternalLoad(nsIURI * aURI, OnNewURI(aURI, nsnull, owner, mLoadType, true, true, true); nsCOMPtr postData; + PRUint64 docIdent = PRUint64(-1); nsCOMPtr cacheKey; if (mOSHE) { @@ -8379,12 +8382,8 @@ 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. - mLSHE->AdoptBFCacheEntry(mOSHE); } } @@ -8404,6 +8403,11 @@ 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 +8451,7 @@ nsDocShell::InternalLoad(nsIURI * aURI, } } - if (sameDocument) { + if (sameDocIdent) { // Set the doc's URI according to the new history entry's URI nsCOMPtr newURI; mOSHE->GetURI(getter_AddRefs(newURI)); @@ -8464,10 +8468,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 sameDocument is + // Need the doHashchange check here since sameDocIdent is // false if we're navigating to a new shentry (i.e. a aSHEntry // is null), such as when clicking a . - if (sameDocument || doHashchange) { + if (sameDocIdent || doHashchange) { window->DispatchSyncPopState(); } @@ -9250,11 +9254,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 document. + // history entry from any entries sharing its doc ident. PRUint32 responseStatus; nsresult rv = httpChannel->GetResponseStatus(&responseStatus); if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { - mLSHE->AbandonBFCacheEntry(); + mLSHE->SetUniqueDocIdentifier(); } } } @@ -9483,9 +9487,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 EvictOutOfRangeContentViewers 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 EvictContentViewers 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,9 +9669,11 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle, NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); - // 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), + // 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), NS_ERROR_FAILURE); // Set the new SHEntry's title (bug 655273). @@ -9713,7 +9719,7 @@ nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle, PRInt32 curIndex = -1; rv = rootSH->GetIndex(&curIndex); if (NS_SUCCEEDED(rv) && curIndex > -1) { - internalSH->EvictOutOfRangeContentViewers(curIndex); + internalSH->EvictContentViewers(curIndex - 1, curIndex); } } diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index 89030e8c450..dbd26a6a22c 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -66,7 +66,6 @@ // session history #include "nsSHEntry.h" -#include "nsSHEntryShared.h" #include "nsSHistory.h" #include "nsSHTransaction.h" @@ -88,15 +87,15 @@ Initialize() nsresult rv = nsSHistory::Startup(); NS_ENSURE_SUCCESS(rv, rv); - nsSHEntryShared::Startup(); - return NS_OK; + rv = nsSHEntry::Startup(); + return rv; } static void Shutdown() { nsSHistory::Shutdown(); - nsSHEntryShared::Shutdown(); + nsSHEntry::Shutdown(); gInitialized = false; } diff --git a/docshell/shistory/public/Makefile.in b/docshell/shistory/public/Makefile.in index 36e14517ada..434b098b1c9 100644 --- a/docshell/shistory/public/Makefile.in +++ b/docshell/shistory/public/Makefile.in @@ -56,7 +56,6 @@ 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 deleted file mode 100644 index 99d1e371f6f..00000000000 --- a/docshell/shistory/public/nsIBFCacheEntry.idl +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- 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 3daa1f1a030..155ae63e2f2 100644 --- a/docshell/shistory/public/nsISHEntry.idl +++ b/docshell/shistory/public/nsISHEntry.idl @@ -51,18 +51,15 @@ 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(6443FD72-A50F-4B8B-BB82-BB1FA04CB15D)] + +[scriptable, uuid(b92d403e-f5ec-4b81-b0e3-6e6c241cef2d)] interface nsISHEntry : nsIHistoryEntry { /** URI for the document */ @@ -143,6 +140,21 @@ 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; @@ -240,36 +252,6 @@ 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)] @@ -282,17 +264,6 @@ 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 1f5b0b68365..338acec2346 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(e27cf38e-c19f-4294-bd31-d7e0916e7fa2)] +[scriptable, uuid(2dede933-25e1-47a3-8f61-0127c785ea01)] interface nsISHistoryInternal: nsISupports { /** @@ -96,25 +96,20 @@ interface nsISHistoryInternal: nsISupports */ readonly attribute nsISHistoryListener listener; - /** - * 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 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 the content viewer associated with a bfcache entry + * Evict the content viewer associated with a session history entry * that has timed out. */ - void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry); + void evictExpiredContentViewerForEntry(in nsISHEntry 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 7d8eedde798..e1debfbd983 100644 --- a/docshell/shistory/src/Makefile.in +++ b/docshell/shistory/src/Makefile.in @@ -48,13 +48,10 @@ 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 c3ce68d4c81..baab34a27e0 100644 --- a/docshell/shistory/src/nsSHEntry.cpp +++ b/docshell/shistory/src/nsSHEntry.cpp @@ -36,6 +36,10 @@ * * ***** END LICENSE BLOCK ***** */ +#ifdef DEBUG_bryner +#define DEBUG_PAGE_CACHE +#endif + // Local Includes #include "nsSHEntry.h" #include "nsXPIDLString.h" @@ -44,44 +48,103 @@ #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 "nsSHEntryShared.h" -#include "nsILayoutHistoryState.h" -#include "nsIContentViewer.h" -#include "nsISupportsArray.h" +#include "nsIDocShell.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) - : mShared(other.mShared) - , mURI(other.mURI) + : 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) { } @@ -97,16 +160,37 @@ ClearParentPtr(nsISHEntry* aEntry, void* /* aData */) nsSHEntry::~nsSHEntry() { - // Null out the mParent pointers on all our kids. + StopTrackingEntry(this); + + // Since we never really remove kids from SHEntrys, we need to 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_ISUPPORTS4(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry, - nsISHEntryInternal) +NS_IMPL_ISUPPORTS5(nsSHEntry, nsISHContainer, nsISHEntry, nsIHistoryEntry, + nsIMutationObserver, nsISHEntryInternal) //***************************************************************************** // nsSHEntry: nsISHEntry @@ -167,13 +251,35 @@ NS_IMETHODIMP nsSHEntry::SetReferrerURI(nsIURI *aReferrerURI) NS_IMETHODIMP nsSHEntry::SetContentViewer(nsIContentViewer *aViewer) { - return mShared->SetContentViewer(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; } NS_IMETHODIMP nsSHEntry::GetContentViewer(nsIContentViewer **aResult) { - *aResult = mShared->mContentViewer; + *aResult = mContentViewer; NS_IF_ADDREF(*aResult); return NS_OK; } @@ -213,14 +319,14 @@ nsSHEntry::GetAnyContentViewer(nsISHEntry **aOwnerEntry, NS_IMETHODIMP nsSHEntry::SetSticky(bool aSticky) { - mShared->mSticky = aSticky; + mSticky = aSticky; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSticky(bool *aSticky) { - *aSticky = mShared->mSticky; + *aSticky = mSticky; return NS_OK; } @@ -259,18 +365,16 @@ NS_IMETHODIMP nsSHEntry::SetPostData(nsIInputStream* aPostData) NS_IMETHODIMP nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) { - *aResult = mShared->mLayoutHistoryState; + *aResult = mLayoutHistoryState; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) { - mShared->mLayoutHistoryState = aState; - if (mShared->mLayoutHistoryState) { - mShared->mLayoutHistoryState-> - SetScrollPositionOnly(!mShared->mSaveLayoutState); - } + mLayoutHistoryState = aState; + if (mLayoutHistoryState) + mLayoutHistoryState->SetScrollPositionOnly(!mSaveLayoutState); return NS_OK; } @@ -299,73 +403,91 @@ NS_IMETHODIMP nsSHEntry::SetID(PRUint32 aID) return NS_OK; } -nsSHEntryShared* nsSHEntry::GetSharedState() +NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult) { - return mShared; + *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; } NS_IMETHODIMP nsSHEntry::GetIsSubFrame(bool * aFlag) { - *aFlag = mShared->mIsFrameNavigation; + *aFlag = mIsFrameNavigation; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetIsSubFrame(bool aFlag) { - mShared->mIsFrameNavigation = aFlag; + mIsFrameNavigation = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetCacheKey(nsISupports** aResult) { - *aResult = mShared->mCacheKey; + *aResult = mCacheKey; NS_IF_ADDREF(*aResult); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetCacheKey(nsISupports* aCacheKey) { - mShared->mCacheKey = aCacheKey; + mCacheKey = aCacheKey; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSaveLayoutStateFlag(bool * aFlag) { - *aFlag = mShared->mSaveLayoutState; + *aFlag = mSaveLayoutState; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) { - mShared->mSaveLayoutState = aFlag; - if (mShared->mLayoutHistoryState) { - mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag); - } + mSaveLayoutState = aFlag; + if (mLayoutHistoryState) + mLayoutHistoryState->SetScrollPositionOnly(!aFlag); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetExpirationStatus(bool * aFlag) { - *aFlag = mShared->mExpired; + *aFlag = mExpired; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetExpirationStatus(bool aFlag) { - mShared->mExpired = aFlag; + mExpired = aFlag; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetContentType(nsACString& aContentType) { - aContentType = mShared->mContentType; + aContentType = mContentType; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetContentType(const nsACString& aContentType) { - mShared->mContentType = aContentType; + mContentType = aContentType; return NS_OK; } @@ -380,27 +502,26 @@ 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. - mShared->mIsFrameNavigation = false; + mIsFrameNavigation = false; // By default we save LayoutHistoryState - mShared->mSaveLayoutState = true; - mShared->mLayoutHistoryState = aLayoutHistoryState; + mSaveLayoutState = true; + mLayoutHistoryState = aLayoutHistoryState; //By default the page is not expired - mShared->mExpired = false; + mExpired = false; return NS_OK; } @@ -409,6 +530,8 @@ NS_IMETHODIMP nsSHEntry::Clone(nsISHEntry ** aResult) { *aResult = new nsSHEntry(*this); + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult); return NS_OK; } @@ -417,7 +540,7 @@ NS_IMETHODIMP nsSHEntry::GetParent(nsISHEntry ** aResult) { NS_ENSURE_ARG_POINTER(aResult); - *aResult = mShared->mParent; + *aResult = mParent; NS_IF_ADDREF(*aResult); return NS_OK; } @@ -430,95 +553,49 @@ nsSHEntry::SetParent(nsISHEntry * aParent) * * XXX this method should not be scriptable if this is the case!! */ - mShared->mParent = aParent; + mParent = aParent; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetWindowState(nsISupports *aState) { - mShared->mWindowState = aState; + mWindowState = aState; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetWindowState(nsISupports **aState) { - NS_IF_ADDREF(*aState = mShared->mWindowState); + NS_IF_ADDREF(*aState = mWindowState); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetViewerBounds(const nsIntRect &aBounds) { - mShared->mViewerBounds = aBounds; + mViewerBounds = aBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetViewerBounds(nsIntRect &aBounds) { - aBounds = mShared->mViewerBounds; + aBounds = mViewerBounds; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetOwner(nsISupports **aOwner) { - NS_IF_ADDREF(*aOwner = mShared->mOwner); + NS_IF_ADDREF(*aOwner = mOwner); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetOwner(nsISupports *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); + mOwner = aOwner; return NS_OK; } @@ -676,77 +753,256 @@ NS_IMETHODIMP nsSHEntry::AddChildShell(nsIDocShellTreeItem *aShell) { NS_ASSERTION(aShell, "Null child shell added to history entry"); - mShared->mChildShells.AppendObject(aShell); + mChildShells.AppendObject(aShell); return NS_OK; } NS_IMETHODIMP nsSHEntry::ChildShellAt(PRInt32 aIndex, nsIDocShellTreeItem **aShell) { - NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex)); + NS_IF_ADDREF(*aShell = mChildShells.SafeObjectAt(aIndex)); return NS_OK; } NS_IMETHODIMP nsSHEntry::ClearChildShells() { - mShared->mChildShells.Clear(); + mChildShells.Clear(); return NS_OK; } NS_IMETHODIMP nsSHEntry::GetRefreshURIList(nsISupportsArray **aList) { - NS_IF_ADDREF(*aList = mShared->mRefreshURIList); + NS_IF_ADDREF(*aList = mRefreshURIList); return NS_OK; } NS_IMETHODIMP nsSHEntry::SetRefreshURIList(nsISupportsArray *aList) { - mShared->mRefreshURIList = aList; + mRefreshURIList = aList; return NS_OK; } NS_IMETHODIMP nsSHEntry::SyncPresentationState() { - return mShared->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 +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() { - mShared->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(); + } } void nsSHEntry::RemoveFromBFCacheAsync() { - mShared->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. } nsDocShellEditorData* nsSHEntry::ForgetEditorData() { - // XXX jlebar Check how this is used. - return mShared->mEditorData.forget(); + return mEditorData.forget(); } void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) { - NS_ASSERTION(!(aData && mShared->mEditorData), + NS_ASSERTION(!(aData && mEditorData), "We're going to overwrite an owning ref!"); - if (mShared->mEditorData != aData) { - mShared->mEditorData = aData; - } + if (mEditorData != aData) + mEditorData = aData; } bool nsSHEntry::HasDetachedEditor() { - return mShared->mEditorData != nsnull; + return mEditorData != nsnull; } NS_IMETHODIMP @@ -767,7 +1023,7 @@ nsSHEntry::SetStateData(nsIStructuredCloneContainer *aContainer) NS_IMETHODIMP nsSHEntry::IsDynamicallyAdded(bool* aAdded) { - *aAdded = mShared->mDynamicallyCreated; + *aAdded = mDynamicallyCreated; return NS_OK; } @@ -790,14 +1046,14 @@ nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) NS_IMETHODIMP nsSHEntry::GetDocshellID(PRUint64* aID) { - *aID = mShared->mDocShellID; + *aID = mDocShellID; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetDocshellID(PRUint64 aID) { - mShared->mDocShellID = aID; + mDocShellID = aID; return NS_OK; } @@ -805,13 +1061,14 @@ nsSHEntry::SetDocshellID(PRUint64 aID) NS_IMETHODIMP nsSHEntry::GetLastTouched(PRUint32 *aLastTouched) { - *aLastTouched = mShared->mLastTouched; + *aLastTouched = mLastTouched; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetLastTouched(PRUint32 aLastTouched) { - mShared->mLastTouched = aLastTouched; + mLastTouched = aLastTouched; return NS_OK; } + diff --git a/docshell/shistory/src/nsSHEntry.h b/docshell/shistory/src/nsSHEntry.h index 4e3eeee29cd..cd44eb0d8e3 100644 --- a/docshell/shistory/src/nsSHEntry.h +++ b/docshell/shistory/src/nsSHEntry.h @@ -42,21 +42,28 @@ // 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" - -class nsSHEntryShared; +#include "nsRect.h" +#include "nsISupportsArray.h" +#include "nsIMutationObserver.h" +#include "nsExpirationTracker.h" +#include "nsDocShellEditorData.h" class nsSHEntry : public nsISHEntry, public nsISHContainer, + public nsIMutationObserver, public nsISHEntryInternal { public: @@ -68,30 +75,51 @@ 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(); - // 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 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; nsCOMPtr mStateData; }; diff --git a/docshell/shistory/src/nsSHEntryShared.cpp b/docshell/shistory/src/nsSHEntryShared.cpp deleted file mode 100644 index ba8089d4e6d..00000000000 --- a/docshell/shistory/src/nsSHEntryShared.cpp +++ /dev/null @@ -1,400 +0,0 @@ -/* ***** 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 deleted file mode 100644 index 792b7cabfc2..00000000000 --- a/docshell/shistory/src/nsSHEntryShared.h +++ /dev/null @@ -1,124 +0,0 @@ -/* ***** 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 c211dbdf2e5..4200307da8d 100644 --- a/docshell/shistory/src/nsSHistory.cpp +++ b/docshell/shistory/src/nsSHistory.cpp @@ -72,10 +72,12 @@ 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 }; @@ -88,46 +90,16 @@ 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, @@ -162,60 +134,15 @@ nsSHistoryObserver::Observe(nsISupports *aSubject, const char *aTopic, { if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { nsSHistory::UpdatePrefs(); - nsSHistory::GloballyEvictContentViewers(); + nsSHistory::EvictGlobalContentViewer(); } else if (!strcmp(aTopic, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID) || !strcmp(aTopic, "memory-pressure")) { - nsSHistory::GloballyEvictAllContentViewers(); + nsSHistory::EvictAllContentViewersGlobally(); } 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 //***************************************************************************** @@ -313,6 +240,7 @@ 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) { @@ -761,12 +689,12 @@ nsSHistory::GetListener(nsISHistoryListener ** aListener) } NS_IMETHODIMP -nsSHistory::EvictOutOfRangeContentViewers(PRInt32 aIndex) +nsSHistory::EvictContentViewers(PRInt32 aPreviousIndex, PRInt32 aIndex) { // Check our per SHistory object limit in the currently navigated SHistory - EvictOutOfRangeWindowContentViewers(aIndex); + EvictWindowContentViewers(aPreviousIndex, aIndex); // Check our total limit across all SHistory objects - GloballyEvictContentViewers(); + EvictGlobalContentViewer(); return NS_OK; } @@ -775,14 +703,7 @@ 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. - nsCOMPtr trans = mListRoot; - while (trans) { - EvictContentViewerForTransaction(trans); - - nsISHTransaction *temp = trans; - temp->GetNext(getter_AddRefs(trans)); - } - + EvictContentViewersInRange(0, mLength); return NS_OK; } @@ -914,78 +835,103 @@ nsSHistory::ReloadCurrentEntry() } void -nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex) +nsSHistory::EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex) { - // XXX rename method to EvictContentViewersExceptAroundIndex, or something. + // 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 + // + // 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. - // We need to release all content viewers that are no longer in the range - // - // 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.) - - if (aIndex < 0) { + // This can happen on the first load of a page in a particular window + if (aFromIndex < 0 || aToIndex < 0) { return; } - NS_ASSERTION(aIndex < mLength, "aIndex is out of range"); - if (aIndex >= mLength) { + NS_ASSERTION(aFromIndex < mLength, "aFromIndex is out of range"); + NS_ASSERTION(aToIndex < mLength, "aToIndex is out of range"); + if (aFromIndex >= mLength || aToIndex >= mLength) { return; } - // Calculate the range that's safe from eviction. - PRInt32 startSafeIndex = PR_MAX(0, aIndex - gHistoryMaxViewers); - PRInt32 endSafeIndex = PR_MIN(mLength, aIndex + gHistoryMaxViewers); + // 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); + } - 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; +#ifdef DEBUG nsCOMPtr trans; - GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans)); - for (PRUint32 i = startSafeIndex; trans && i <= endSafeIndex; i++) { - nsCOMPtr viewer = GetContentViewerForTransaction(trans); - safeViewers.AppendObject(viewer); + 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 - // Walk the SHistory list and evict any content viewers that aren't safe. - GetTransactionAtIndex(0, getter_AddRefs(trans)); - while (trans) { - nsCOMPtr viewer = GetContentViewerForTransaction(trans); - if (safeViewers.IndexOf(viewer) == -1) { - EvictContentViewerForTransaction(trans); + 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(); } nsISHTransaction *temp = trans; @@ -993,153 +939,138 @@ nsSHistory::EvictOutOfRangeWindowContentViewers(PRInt32 aIndex) } } -namespace { - -class TransactionAndDistance -{ -public: - TransactionAndDistance(nsISHTransaction *aTrans, PRUint32 aDist) - : mTransaction(aTrans) - , mDistance(aDist) - { - mViewer = GetContentViewerForTransaction(aTrans); - NS_ASSERTION(mViewer, "Transaction should have a content viewer"); - - nsCOMPtr shentry; - mTransaction->GetSHEntry(getter_AddRefs(shentry)); - - nsCOMPtr shentryInternal = do_QueryInterface(shentry); - if (shentryInternal) { - shentryInternal->GetLastTouched(&mLastTouched); - } else { - NS_WARNING("Can't cast to nsISHEntryInternal?"); - mLastTouched = 0; - } - } - - 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 +// static void -nsSHistory::GloballyEvictContentViewers() +nsSHistory::EvictGlobalContentViewer() { - // 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. + // 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)); - nsTArray transactions; + 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)); - 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; + PRUint32 entryLastTouched = 0; + if (gOptimizeEviction) { + nsCOMPtr entryInternal = do_QueryInterface(entry); + if (entryInternal) { + // Find when this entry was last activated + entryInternal->GetLastTouched(&entryLastTouched); } } - // 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); +#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++; - nsISHTransaction *temp = trans; - temp->GetNext(getter_AddRefs(trans)); + // 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)); } - // 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)); - } +#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 - // 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 (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 - // 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(); + // 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(); - for (PRInt32 i = transactions.Length() - 1; - i >= sHistoryMaxTotalViewers; --i) { - - EvictContentViewerForTransaction(transactions[i].mTransaction); - - } + // 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; + } + } else { + // couldn't find a content viewer to evict, so we are done + shouldTryEviction = false; + } + } // while shouldTryEviction } -nsresult -nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) +NS_IMETHODIMP +nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) { PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers); PRInt32 endIndex = NS_MIN(mLength - 1, @@ -1151,11 +1082,8 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) for (i = startIndex; trans && i <= endIndex; ++i) { nsCOMPtr entry; trans->GetSHEntry(getter_AddRefs(entry)); - - // Does entry have the same BFCacheEntry as the argument to this method? - if (entry->HasBFCacheEntry(aEntry)) { + if (entry == aEntry) break; - } nsISHTransaction *temp = trans; temp->GetNext(getter_AddRefs(trans)); @@ -1163,13 +1091,21 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) if (i > endIndex) return NS_OK; - if (i == mIndex) { - NS_WARNING("How did the current SHEntry expire?"); + NS_ASSERTION(i != mIndex, "How did the current session entry expire?"); + if (i == mIndex) 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; } @@ -1180,11 +1116,11 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) //static void -nsSHistory::GloballyEvictAllContentViewers() +nsSHistory::EvictAllContentViewersGlobally() { PRInt32 maxViewers = sHistoryMaxTotalViewers; sHistoryMaxTotalViewers = 0; - GloballyEvictContentViewers(); + EvictGlobalContentViewer(); sHistoryMaxTotalViewers = maxViewers; } diff --git a/docshell/shistory/src/nsSHistory.h b/docshell/shistory/src/nsSHistory.h index 7bf6d69c045..1efeb8cfa20 100644 --- a/docshell/shistory/src/nsSHistory.h +++ b/docshell/shistory/src/nsSHistory.h @@ -101,11 +101,12 @@ protected: nsresult PrintHistory(); #endif - // 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(); + // 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(); // 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 e8ce5c3e5ab..272973d9dd3 100644 --- a/docshell/test/Makefile.in +++ b/docshell/test/Makefile.in @@ -119,7 +119,6 @@ _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 179e094633c..d45e07cd90c 100644 --- a/docshell/test/chrome/bug396519_window.xul +++ b/docshell/test/chrome/bug396519_window.xul @@ -50,9 +50,6 @@ const LISTEN_EVENTS = ["pageshow"]; - const Cc = Components.classes; - const Ci = Components.interfaces; - var gBrowser; var gTestCount = 0; var gTestsIterator; @@ -99,23 +96,6 @@ 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 deleted file mode 100644 index c8f3765135b..00000000000 --- a/docshell/test/test_bfcache_plus_hash.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - Test for Bug 646641 - - - - - -Mozilla Bug 646641 -

- -
-
-
- - diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index b89467a5f43..e5d75092a49 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -218,9 +218,12 @@ public: // First check if the document the IDBDatabase is part of is bfcached nsCOMPtr ownerDoc = database->GetOwnerDocument(); - nsIBFCacheEntry* bfCacheEntry; - if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) { - bfCacheEntry->RemoveFromBFCacheSync(); + nsISHEntry* shEntry; + if (ownerDoc && (shEntry = ownerDoc->GetBFCacheEntry())) { + nsCOMPtr sheInternal = do_QueryInterface(shEntry); + if (sheInternal) { + sheInternal->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 e5ae9c16e2b..9fa34d761c2 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->EvictOutOfRangeContentViewers(loadedIndex); + historyInt->EvictContentViewers(prevIndex, loadedIndex); } } } diff --git a/mobile/app/mobile.js b/mobile/app/mobile.js index 95606de5e0f..8f377b1c38f 100644 --- a/mobile/app/mobile.js +++ b/mobile/app/mobile.js @@ -140,6 +140,7 @@ 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 aa4f52529c8..a64e91883de 100644 --- a/mobile/chrome/content/bindings/browser.js +++ b/mobile/chrome/content/bindings/browser.js @@ -268,13 +268,8 @@ let WebNavigation = { if (aEntry.docshellID) shEntry.docshellID = aEntry.docshellID; - 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.stateData) + shEntry.stateData = aEntry.stateData; if (aEntry.scroll) { let scrollPos = aEntry.scroll.split(","); @@ -283,15 +278,23 @@ let WebNavigation = { } if (aEntry.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; + // 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; } else { - shEntry.adoptBFCacheEntry(matchingEntry); + shEntry.docIdentifier = ident; } } @@ -376,12 +379,11 @@ let WebNavigation = { } catch (e) { dump(e); } } - entry.docIdentifier = aEntry.BFCacheEntry.ID; + if (aEntry.docIdentifier) + entry.docIdentifier = aEntry.docIdentifier; - if (aEntry.stateData != null) { - entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); - entry.structuredCloneVersion = aEntry.stateData.formatVersion; - } + if (aEntry.stateData) + entry.stateData = aEntry.stateData; if (!(aEntry instanceof Ci.nsISHContainer)) return entry; diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 648b704a122..728fd7c8384 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -109,6 +109,8 @@ 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