From 04a6be5beba622c344df20b40d2db36bda6bfcdb Mon Sep 17 00:00:00 2001 From: Justin Lebar Date: Mon, 9 May 2011 17:59:49 -0400 Subject: [PATCH] Bug 646641 - Part 1: SHEntries for the same document should share bfcache state. r=smaug --- .../sessionstore/src/nsSessionStore.js | 56 +- .../test/browser/browser_500328.js | 8 +- content/base/public/nsIDocument.h | 23 +- docshell/base/nsDocShell.cpp | 58 +- docshell/build/nsDocShellModule.cpp | 7 +- docshell/shistory/public/Makefile.in | 1 + docshell/shistory/public/nsIBFCacheEntry.idl | 45 ++ docshell/shistory/public/nsISHEntry.idl | 63 ++- .../shistory/public/nsISHistoryInternal.idl | 6 +- docshell/shistory/src/Makefile.in | 3 + docshell/shistory/src/nsSHEntry.cpp | 497 +++++------------- docshell/shistory/src/nsSHEntry.h | 64 +-- docshell/shistory/src/nsSHEntryShared.cpp | 348 ++++++++++++ docshell/shistory/src/nsSHEntryShared.h | 88 ++++ docshell/shistory/src/nsSHistory.cpp | 11 +- docshell/test/Makefile.in | 1 + docshell/test/test_bfcache_plus_hash.html | 121 +++++ dom/indexedDB/IndexedDatabaseManager.cpp | 9 +- 18 files changed, 879 insertions(+), 530 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 3a16e04c16b3..9ce49f5e07f0 100644 --- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -1673,9 +1673,13 @@ SessionStoreService.prototype = { } else if (history && history.count > 0) { try { + // bfCacheEntryMap maps a bfCacheEntry to a unique number. It also + // contains a field 'max' which contains the maximum number in the map. + let bfCacheEntryMap = {'max': 0}; for (var j = 0; j < history.count; j++) { let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false), - aFullData, aTab.pinned); + aFullData, aTab.pinned, + bfCacheEntryMap); tabData.entries.push(entry); } // If we make it through the for loop, then we're ok and we should clear @@ -1764,10 +1768,14 @@ SessionStoreService.prototype = { * always return privacy sensitive data (use with care) * @param aIsPinned * the tab is pinned and should be treated differently for privacy + * @param aBFCacheEntryMap + * a dictionary mapping BFCacheEntries to integers. This dictionary + * must also map 'max' to the largest number in the dictionary. * @returns object */ _serializeHistoryEntry: - function sss_serializeHistoryEntry(aEntry, aFullData, aIsPinned) { + function sss_serializeHistoryEntry(aEntry, aFullData, + aIsPinned, aBFCacheEntryMap) { var entry = { url: aEntry.URI.spec }; try { @@ -1858,8 +1866,14 @@ SessionStoreService.prototype = { catch (ex) { debug(ex); } } - if (aEntry.docIdentifier) { - entry.docIdentifier = aEntry.docIdentifier; + if (aBFCacheEntryMap[aEntry.BFCacheEntry]) { + entry.docIdentifier = aBFCacheEntryMap[aEntry.BFCacheEntry]; + } + else { + let id = aBFCacheEntryMap['max'] + 1; + entry.docIdentifier = id; + aBFCacheEntryMap['max'] = id; + aBFCacheEntryMap[aEntry.BFCacheEntry] = id; } if (aEntry.stateData != null) { @@ -1877,7 +1891,7 @@ SessionStoreService.prototype = { var child = aEntry.GetChildAt(i); if (child) { entry.children.push(this._serializeHistoryEntry(child, aFullData, - aIsPinned)); + aIsPinned, aBFCacheEntryMap)); } else { // to maintain the correct frame order, insert a dummy entry entry.children.push({ url: "about:blank" }); @@ -2960,7 +2974,6 @@ SessionStoreService.prototype = { 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 { @@ -3113,24 +3126,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); } } @@ -3266,19 +3271,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 5fe32e28b1b6..5c7e793e68a4 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 736af834966f..7a881bc5361f 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,9 +126,9 @@ class Element; } // namespace mozilla -#define NS_IDOCUMENT_IID \ -{ 0xfac563fb, 0x2b6a, 0x4ac8, \ - { 0x85, 0xf7, 0xd5, 0x14, 0x4b, 0x3e, 0xce, 0x78 } } +#define NS_IDOCUMENT_IID \ +{ 0x455e4d79, 0x756b, 0x4f73, \ + { 0x95, 0xea, 0x3f, 0xf6, 0x0c, 0x6a, 0x8c, 0xa6 } } // Flag for AddStyleSheet(). #define NS_STYLESHEET_FROM_CATALOG (1 << 0) @@ -480,11 +481,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 @@ -1738,9 +1743,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 b2e5aaf33dab..7096fdc7a18f 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4136,10 +4136,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; @@ -4412,10 +4412,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); // @@ -8403,17 +8403,15 @@ nsDocShell::InternalLoad(nsIURI * aURI, NS_SUCCEEDED(splitRv2) && curBeforeHash.Equals(newBeforeHash); - PRBool sameDocIdent = PR_FALSE; + // XXX rename + PRBool sameDocument = PR_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, @@ -8436,7 +8434,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. - PRBool doShortCircuitedLoad = (sameDocIdent && mOSHE != aSHEntry) || + PRBool doShortCircuitedLoad = (sameDocument && mOSHE != aSHEntry) || (!aSHEntry && aPostData == nsnull && sameExceptHashes && !newHash.IsEmpty()); @@ -8487,7 +8485,6 @@ nsDocShell::InternalLoad(nsIURI * aURI, OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE, PR_TRUE, PR_TRUE); nsCOMPtr postData; - PRUint64 docIdent = PRUint64(-1); nsCOMPtr cacheKey; if (mOSHE) { @@ -8501,8 +8498,12 @@ 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); } } @@ -8522,11 +8523,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 @@ -8570,7 +8566,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)); @@ -8587,10 +8583,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(); } @@ -9420,11 +9416,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(); } } } @@ -9835,11 +9831,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). diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index c9b190f01732..e1afa1e6e93c 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 = PR_FALSE; } diff --git a/docshell/shistory/public/Makefile.in b/docshell/shistory/public/Makefile.in index 434b098b1c90..36e14517ada2 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 000000000000..a01e2dcebad5 --- /dev/null +++ b/docshell/shistory/public/nsIBFCacheEntry.idl @@ -0,0 +1,45 @@ +/* -*- 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(27A326F6-5D66-463D-8CDC-49394A8CB7D6)] +interface nsIBFCacheEntry : nsISupports +{ + void RemoveFromBFCacheSync(); + void RemoveFromBFCacheAsync(); +}; diff --git a/docshell/shistory/public/nsISHEntry.idl b/docshell/shistory/public/nsISHEntry.idl index 155ae63e2f29..3daa1f1a0306 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 338acec23468..7c461f8a35b7 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(AFE77866-8859-4492-B131-4C58167E1630)] interface nsISHistoryInternal: nsISupports { /** @@ -106,10 +106,10 @@ interface nsISHistoryInternal: nsISupports void evictContentViewers(in long previousIndex, in long index); /** - * 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 e1debfbd983c..7d8eedde7983 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 1f6c1de258b1..136b001cde61 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(PR_FALSE) - , mIsFrameNavigation(PR_FALSE) - , mSaveLayoutState(PR_TRUE) - , mExpired(PR_FALSE) - , mSticky(PR_TRUE) - , mDynamicallyCreated(PR_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(PR_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(PRBool aSticky) { - mSticky = aSticky; + mShared->mSticky = aSticky; return NS_OK; } NS_IMETHODIMP nsSHEntry::GetSticky(PRBool *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(PRBool * aFlag) { - *aFlag = mIsFrameNavigation; + *aFlag = mShared->mIsFrameNavigation; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetIsSubFrame(PRBool 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(PRBool * aFlag) { - *aFlag = mSaveLayoutState; + *aFlag = mShared->mSaveLayoutState; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetSaveLayoutStateFlag(PRBool 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(PRBool * aFlag) { - *aFlag = mExpired; + *aFlag = mShared->mExpired; return NS_OK; } NS_IMETHODIMP nsSHEntry::SetExpirationStatus(PRBool 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 = PR_FALSE; + mShared->mIsFrameNavigation = PR_FALSE; // By default we save LayoutHistoryState - mSaveLayoutState = PR_TRUE; - mLayoutHistoryState = aLayoutHistoryState; + mShared->mSaveLayoutState = PR_TRUE; + mShared->mLayoutHistoryState = aLayoutHistoryState; //By default the page is not expired - mExpired = PR_FALSE; + mShared->mExpired = PR_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; +} + +PRBool +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, PRBool *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 = PR_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; + } } PRBool nsSHEntry::HasDetachedEditor() { - return mEditorData != nsnull; + return mShared->mEditorData != nsnull; } NS_IMETHODIMP @@ -1023,7 +767,7 @@ nsSHEntry::SetStateData(nsIStructuredCloneContainer *aContainer) NS_IMETHODIMP nsSHEntry::IsDynamicallyAdded(PRBool* aAdded) { - *aAdded = mDynamicallyCreated; + *aAdded = mShared->mDynamicallyCreated; return NS_OK; } @@ -1046,14 +790,14 @@ nsSHEntry::HasDynamicallyAddedChild(PRBool* 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 cd44eb0d8e3c..ded9c796cadd 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; + PRPackedBool mURIWasModified; nsCOMPtr mStateData; }; diff --git a/docshell/shistory/src/nsSHEntryShared.cpp b/docshell/shistory/src/nsSHEntryShared.cpp new file mode 100644 index 000000000000..a181827db925 --- /dev/null +++ b/docshell/shistory/src/nsSHEntryShared.cpp @@ -0,0 +1,348 @@ +#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" + +namespace dom = mozilla::dom; + +// 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(PR_FALSE) + , mSaveLayoutState(PR_TRUE) + , mSticky(PR_TRUE) + , mDynamicallyCreated(PR_FALSE) + , mLastTouched(0) + , mExpired(PR_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 = PR_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; +} + +//***************************************************************************** +// 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 000000000000..9f1ccbefcba7 --- /dev/null +++ b/docshell/shistory/src/nsSHEntryShared.h @@ -0,0 +1,88 @@ + +#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 SetDocIdentifier(PRUint64 aDocIdentifier); + + 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; + PRPackedBool mIsFrameNavigation; + PRPackedBool mSaveLayoutState; + PRPackedBool mSticky; + PRPackedBool mDynamicallyCreated; + nsCOMPtr mCacheKey; + PRUint32 mLastTouched; + + // These members aren't copied by nsSHEntryShared::Duplicate() because + // they're specific to a particular content viewer. + nsCOMPtr mContentViewer; + nsCOMPtr mDocument; + nsCOMPtr mLayoutHistoryState; + PRPackedBool 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 d8b43c330ddb..78998aef00d3 100644 --- a/docshell/shistory/src/nsSHistory.cpp +++ b/docshell/shistory/src/nsSHistory.cpp @@ -1081,8 +1081,8 @@ nsSHistory::EvictGlobalContentViewer() } // while shouldTryEviction } -NS_IMETHODIMP -nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) +nsresult +nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry *aEntry) { PRInt32 startIndex = NS_MAX(0, mIndex - gHistoryMaxViewers); PRInt32 endIndex = NS_MIN(mLength - 1, @@ -1094,8 +1094,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)); @@ -1117,7 +1120,7 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry) } else { EvictContentViewersInRange(i, endIndex + 1); } - + return NS_OK; } diff --git a/docshell/test/Makefile.in b/docshell/test/Makefile.in index 408272f975f0..f6e2fc101ef2 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 \ $(NULL) ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa) diff --git a/docshell/test/test_bfcache_plus_hash.html b/docshell/test/test_bfcache_plus_hash.html new file mode 100644 index 000000000000..d75a4d8272e8 --- /dev/null +++ b/docshell/test/test_bfcache_plus_hash.html @@ -0,0 +1,121 @@ + + + + + Test for Bug 646641 + + + + + + +Mozilla Bug 646641 +

+ +
+
+
+ + diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index 0cd5b428f141..42c355b2b034 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -173,12 +173,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;