diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl index 0dc66bddcb10..b0b8b8b7eaac 100644 --- a/docshell/shistory/nsISHEntry.idl +++ b/docshell/shistory/nsISHEntry.idl @@ -21,6 +21,7 @@ interface nsIDocShellTreeItem; interface nsIStructuredCloneContainer; interface nsIBFCacheEntry; interface nsIPrincipal; +interface nsISHistory; %{C++ #include "nsRect.h" @@ -326,6 +327,11 @@ interface nsISHEntry : nsISupports * if true == "manual", false == "auto". */ attribute boolean scrollRestorationIsManual; + + /** + * Set the session history it belongs to. It's only set on root entries. + */ + [noscript] void setSHistory(in nsISHistory aSHistory); }; [scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)] diff --git a/docshell/shistory/nsISHistoryInternal.idl b/docshell/shistory/nsISHistoryInternal.idl index ad113d32e65d..ae0a1facf073 100644 --- a/docshell/shistory/nsISHistoryInternal.idl +++ b/docshell/shistory/nsISHistoryInternal.idl @@ -96,6 +96,16 @@ interface nsISHistoryInternal: nsISupports */ void evictAllContentViewers(); + /** + * Add a BFCache entry to expiration tracker so it gets evicted on expiration. + */ + void addToExpirationTracker(in nsIBFCacheEntry aEntry); + + /** + * Remove a BFCache entry from expiration tracker. + */ + void removeFromExpirationTracker(in nsIBFCacheEntry aEntry); + /** * Remove dynamic entries found at given index. * diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp index 463b9db26d1d..e7fe22c3cfff 100644 --- a/docshell/shistory/nsSHEntry.cpp +++ b/docshell/shistory/nsSHEntry.cpp @@ -962,3 +962,10 @@ nsSHEntry::SetLastTouched(uint32_t aLastTouched) mShared->mLastTouched = aLastTouched; return NS_OK; } + +NS_IMETHODIMP +nsSHEntry::SetSHistory(nsISHistory* aSHistory) +{ + mShared->mSHistory = do_GetWeakReference(aSHistory); + return NS_OK; +} diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp index 84a69822a0e5..523b490ccdd9 100644 --- a/docshell/shistory/nsSHEntryShared.cpp +++ b/docshell/shistory/nsSHEntryShared.cpp @@ -30,46 +30,9 @@ uint64_t gSHEntrySharedID = 0; } // namespace -#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout" -// Default this to time out unused content viewers after 30 minutes -#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) - -typedef nsExpirationTracker HistoryTrackerBase; -class HistoryTracker final : public HistoryTrackerBase -{ -public: - explicit HistoryTracker(uint32_t aTimeout) - : HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker") - { - } - -protected: - virtual void NotifyExpired(nsSHEntryShared* aObj) - { - RemoveObject(aObj); - aObj->Expire(); - } -}; - -static HistoryTracker* gHistoryTracker = nullptr; - -void -nsSHEntryShared::EnsureHistoryTracker() -{ - if (!gHistoryTracker) { - // nsExpirationTracker doesn't allow one to change the timer period, - // so just set it once when the history tracker is used for the first time. - gHistoryTracker = new HistoryTracker( - mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS, - CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT)); - } -} - void nsSHEntryShared::Shutdown() { - delete gHistoryTracker; - gHistoryTracker = nullptr; } nsSHEntryShared::nsSHEntryShared() @@ -88,20 +51,6 @@ nsSHEntryShared::nsSHEntryShared() nsSHEntryShared::~nsSHEntryShared() { RemoveFromExpirationTracker(); - -#ifdef DEBUG - if (gHistoryTracker) { - // 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()) != nullptr) { - NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); - } - } -#endif - if (mContentViewer) { RemoveFromBFCacheSync(); } @@ -132,8 +81,9 @@ nsSHEntryShared::Duplicate(nsSHEntryShared* aEntry) void nsSHEntryShared::RemoveFromExpirationTracker() { - if (gHistoryTracker && GetExpirationState()->IsTracked()) { - gHistoryTracker->RemoveObject(this); + nsCOMPtr shistory = do_QueryReferent(mSHistory); + if (shistory && GetExpirationState()->IsTracked()) { + shistory->RemoveFromExpirationTracker(this); } } @@ -174,34 +124,6 @@ nsSHEntryShared::DropPresentationState() mEditorData = nullptr; } -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) { @@ -215,8 +137,13 @@ nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) mContentViewer = aViewer; if (mContentViewer) { - EnsureHistoryTracker(); - gHistoryTracker->AddObject(this); + // mSHistory is only set for root entries, but in general bfcache only + // applies to root entries as well. BFCache for subframe navigation has been + // disabled since 2005 in bug 304860. + nsCOMPtr shistory = do_QueryReferent(mSHistory); + if (shistory) { + shistory->AddToExpirationTracker(this); + } nsCOMPtr domDoc; mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h index 74ffeb5b7ffa..6f6caca01f75 100644 --- a/docshell/shistory/nsSHEntryShared.h +++ b/docshell/shistory/nsSHEntryShared.h @@ -15,6 +15,7 @@ #include "nsIMutationObserver.h" #include "nsRect.h" #include "nsString.h" +#include "nsWeakPtr.h" #include "mozilla/Attributes.h" @@ -54,12 +55,9 @@ private: friend class nsSHEntry; - friend class HistoryTracker; - static already_AddRefed Duplicate(nsSHEntryShared* aEntry); void RemoveFromExpirationTracker(); - void Expire(); nsresult SyncPresentationState(); void DropPresentationState(); @@ -89,11 +87,14 @@ private: nsCOMPtr mRefreshURIList; nsExpirationState mExpirationState; nsAutoPtr mEditorData; + nsWeakPtr mSHistory; bool mIsFrameNavigation; bool mSaveLayoutState; bool mSticky; bool mDynamicallyCreated; + + // This flag is about necko cache, not bfcache. bool mExpired; }; diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp index 1fb22213fd09..25249fe63298 100644 --- a/docshell/shistory/nsSHistory.cpp +++ b/docshell/shistory/nsSHistory.cpp @@ -32,11 +32,16 @@ #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" +#include "mozilla/dom/TabGroup.h" using namespace mozilla; #define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" #define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" +#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout" + +// Default this to time out unused content viewers after 30 minutes +#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) static const char* kObservedPrefs[] = { PREF_SHISTORY_SIZE, @@ -251,6 +256,7 @@ NS_INTERFACE_MAP_BEGIN(nsSHistory) NS_INTERFACE_MAP_ENTRY(nsISHistory) NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END // static @@ -379,6 +385,8 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) { NS_ENSURE_ARG(aSHEntry); + aSHEntry->SetSHistory(this); + // If we have a root docshell, update the docshell id of the root shentry to // match the id of that docshell if (mRootDocShell) { @@ -1296,6 +1304,30 @@ nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry) return NS_OK; } +NS_IMETHODIMP +nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry) +{ + RefPtr entry = static_cast(aEntry); + if (!mHistoryTracker || !entry) { + return NS_ERROR_FAILURE; + } + + mHistoryTracker->AddObject(entry); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry) +{ + RefPtr entry = static_cast(aEntry); + if (!mHistoryTracker || !entry) { + return NS_ERROR_FAILURE; + } + + mHistoryTracker->RemoveObject(entry); + return NS_OK; +} + // Evicts all content viewers in all history objects. This is very // inefficient, because it requires a linear search through all SHistory // objects for each viewer to be evicted. However, this method is called @@ -1896,6 +1928,23 @@ NS_IMETHODIMP nsSHistory::SetRootDocShell(nsIDocShell* aDocShell) { mRootDocShell = aDocShell; + + // Init mHistoryTracker on setting mRootDocShell so we can bind its event + // target to the tabGroup. + if (mRootDocShell) { + nsCOMPtr win = mRootDocShell->GetWindow(); + if (!win) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr tabGroup = win->TabGroup(); + mHistoryTracker = mozilla::MakeUnique( + this, + mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS, + CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT), + tabGroup->EventTargetFor(mozilla::TaskCategory::Other)); + } + return NS_OK; } diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h index b8c2d644ec9a..2cee92d7bd15 100644 --- a/docshell/shistory/nsSHistory.h +++ b/docshell/shistory/nsSHistory.h @@ -16,9 +16,10 @@ #include "nsIWebNavigation.h" #include "nsSHEntryShared.h" #include "nsTObserverArray.h" -#include "nsWeakPtr.h" +#include "nsWeakReference.h" #include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" class nsIDocShell; class nsSHEnumerator; @@ -29,9 +30,37 @@ class nsISHTransaction; class nsSHistory final : public mozilla::LinkedListElement, public nsISHistory, public nsISHistoryInternal, - public nsIWebNavigation + public nsIWebNavigation, + public nsSupportsWeakReference { public: + + // The timer based history tracker is used to evict bfcache on expiration. + class HistoryTracker final : public nsExpirationTracker + { + public: + explicit HistoryTracker(nsSHistory* aSHistory, + uint32_t aTimeout, + nsIEventTarget* aEventTarget) + : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker", aEventTarget) + { + MOZ_ASSERT(aSHistory); + mSHistory = aSHistory; + } + + protected: + virtual void NotifyExpired(nsSHEntryShared* aObj) + { + RemoveObject(aObj); + mSHistory->EvictExpiredContentViewerForEntry(aObj); + } + + private: + // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker + // so it's safe to use raw pointer here. + nsSHistory* mSHistory; + }; + nsSHistory(); NS_DECL_ISUPPORTS NS_DECL_NSISHISTORY @@ -87,6 +116,9 @@ private: // otherwise comparison is done to aIndex - 1. bool RemoveDuplicate(int32_t aIndex, bool aKeepNext); + // Track all bfcache entries and evict on expiration. + mozilla::UniquePtr mHistoryTracker; + nsCOMPtr mListRoot; int32_t mIndex; int32_t mLength;