Bug 1347823 - Part 2: Move HistoryTracker to nsSHistory and bind its event target to the TabGroup. r=smaug
authorSamael Wang <freesamael@gmail.com>
Thu, 27 Apr 2017 18:59:11 +0800
changeset 404569 f8bebf994dbfef3d091d61b9906854736cc12ffc
parent 404568 d2fbb471ba83f0f92d0d06e62311c0ff530794e6
child 404570 05a2a58f887ee8805336640d1e4c49845a475d31
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1347823
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1347823 - Part 2: Move HistoryTracker to nsSHistory and bind its event target to the TabGroup. r=smaug Convert the singleton HistoryTracker implementation to a per-nsSHistory based implementation so that it can be bound to a TabGroup. MozReview-Commit-ID: 7cMtArsO5lQ
docshell/shistory/nsISHEntry.idl
docshell/shistory/nsISHistoryInternal.idl
docshell/shistory/nsSHEntry.cpp
docshell/shistory/nsSHEntryShared.cpp
docshell/shistory/nsSHEntryShared.h
docshell/shistory/nsSHistory.cpp
docshell/shistory/nsSHistory.h
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -16,16 +16,17 @@ interface nsIMutableArray;
 interface nsILayoutHistoryState;
 interface nsIContentViewer;
 interface nsIURI;
 interface nsIInputStream;
 interface nsIDocShellTreeItem;
 interface nsIStructuredCloneContainer;
 interface nsIBFCacheEntry;
 interface nsIPrincipal;
+interface nsISHistory;
 
 %{C++
 #include "nsRect.h"
 class nsDocShellEditorData;
 class nsSHEntryShared;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
@@ -321,16 +322,21 @@ interface nsISHEntry : nsISupports
      */
     attribute nsIURI baseURI;
 
     /**
      * Sets/gets the current scroll restoration state,
      * 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)]
 interface nsISHEntryInternal : nsISupports
 {
     [notxpcom] void RemoveFromBFCacheAsync();
     [notxpcom] void RemoveFromBFCacheSync();
 
--- a/docshell/shistory/nsISHistoryInternal.idl
+++ b/docshell/shistory/nsISHistoryInternal.idl
@@ -92,16 +92,26 @@ interface nsISHistoryInternal: nsISuppor
    void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry);
 
    /**
     * Evict all the content viewers in this session history
     */
    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.
     *
     * @param aIndex
     *        Index to remove dynamic entries from. It will be passed to
     *        RemoveEntries as aStartIndex.
     * @param aContainer (optional)
     *        The container to start looking for dynamic entries. Only the
     *        dynamic descendants of the container will be removed. If not given,
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -957,8 +957,15 @@ nsSHEntry::GetLastTouched(uint32_t* aLas
 }
 
 NS_IMETHODIMP
 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;
+}
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -25,56 +25,19 @@
 namespace dom = mozilla::dom;
 
 namespace {
 
 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<nsSHEntryShared, 3> 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()
   : mDocShellID({0})
   , mLastTouched(0)
   , mID(gSHEntrySharedID++)
   , mViewerBounds(0, 0, 0, 0)
   , mIsFrameNavigation(false)
@@ -83,30 +46,16 @@ nsSHEntryShared::nsSHEntryShared()
   , mDynamicallyCreated(false)
   , mExpired(false)
 {
 }
 
 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<nsSHEntryShared, 3>::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();
   }
 }
 
 NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
 
 already_AddRefed<nsSHEntryShared>
@@ -127,18 +76,19 @@ nsSHEntryShared::Duplicate(nsSHEntryShar
   newEntry->mLastTouched = aEntry->mLastTouched;
 
   return newEntry.forget();
 }
 
 void
 nsSHEntryShared::RemoveFromExpirationTracker()
 {
-  if (gHistoryTracker && GetExpirationState()->IsTracked()) {
-    gHistoryTracker->RemoveObject(this);
+  nsCOMPtr<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
+  if (shistory && GetExpirationState()->IsTracked()) {
+    shistory->RemoveFromExpirationTracker(this);
   }
 }
 
 nsresult
 nsSHEntryShared::SyncPresentationState()
 {
   if (mContentViewer && mWindowState) {
     // If we have a content viewer and a window state, we should be ok.
@@ -169,59 +119,36 @@ nsSHEntryShared::DropPresentationState()
   mSticky = true;
   mWindowState = nullptr;
   mViewerBounds.SetRect(0, 0, 0, 0);
   mChildShells.Clear();
   mRefreshURIList = nullptr;
   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<nsIDocShell> container;
-  mContentViewer->GetContainer(getter_AddRefs(container));
-  nsCOMPtr<nsIDocShellTreeItem> 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<nsIDocShellTreeItem> root;
-  treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
-  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
-  nsCOMPtr<nsISHistory> history;
-  webNav->GetSessionHistory(getter_AddRefs(history));
-  nsCOMPtr<nsISHistoryInternal> 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) {
-    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<nsISHistoryInternal> shistory = do_QueryReferent(mSHistory);
+    if (shistory) {
+      shistory->AddToExpirationTracker(this);
+    }
 
     nsCOMPtr<nsIDOMDocument> 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);
--- a/docshell/shistory/nsSHEntryShared.h
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -10,16 +10,17 @@
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsExpirationTracker.h"
 #include "nsIBFCacheEntry.h"
 #include "nsIMutationObserver.h"
 #include "nsRect.h"
 #include "nsString.h"
+#include "nsWeakPtr.h"
 
 #include "mozilla/Attributes.h"
 
 class nsSHEntry;
 class nsISHEntry;
 class nsIDocument;
 class nsIContentViewer;
 class nsIDocShellTreeItem;
@@ -49,22 +50,19 @@ public:
 
   nsExpirationState *GetExpirationState() { return &mExpirationState; }
 
 private:
   ~nsSHEntryShared();
 
   friend class nsSHEntry;
 
-  friend class HistoryTracker;
-
   static already_AddRefed<nsSHEntryShared> 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
@@ -84,17 +82,20 @@ private:
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIDocument> mDocument;
   nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
   nsCOMPtr<nsISupports> mWindowState;
   nsIntRect mViewerBounds;
   nsCOMPtr<nsIMutableArray> mRefreshURIList;
   nsExpirationState mExpirationState;
   nsAutoPtr<nsDocShellEditorData> mEditorData;
+  nsWeakPtr mSHistory;
 
   bool mIsFrameNavigation;
   bool mSaveLayoutState;
   bool mSticky;
   bool mDynamicallyCreated;
+
+  // This flag is about necko cache, not bfcache.
   bool mExpired;
 };
 
 #endif
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -27,21 +27,26 @@
 #include "prsystem.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MathAlgorithms.h"
 #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,
   PREF_SHISTORY_MAX_TOTAL_VIEWERS,
   nullptr
 };
 
 static int32_t gHistoryMaxSize = 50;
@@ -246,16 +251,17 @@ nsSHistory::~nsSHistory()
 NS_IMPL_ADDREF(nsSHistory)
 NS_IMPL_RELEASE(nsSHistory)
 
 NS_INTERFACE_MAP_BEGIN(nsSHistory)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
   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
 uint32_t
 nsSHistory::CalcMaxTotalViewers()
 {
   // Calculate an estimate of how many ContentViewers we should cache based
   // on RAM.  This assumes that the average ContentViewer is 4MB (conservative)
@@ -374,16 +380,18 @@ nsSHistory::Shutdown()
 /* Add an entry to the History list at mIndex and
  * increment the index to point to the new entry
  */
 NS_IMETHODIMP
 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) {
     nsID docshellID = mRootDocShell->HistoryID();
     aSHEntry->SetDocshellID(&docshellID);
   }
 
   nsCOMPtr<nsISHTransaction> currentTxn;
@@ -1291,16 +1299,40 @@ nsSHistory::EvictExpiredContentViewerFor
     return NS_OK;
   }
 
   EvictContentViewerForTransaction(trans);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry)
+{
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  if (!mHistoryTracker || !entry) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mHistoryTracker->AddObject(entry);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry)
+{
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(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
 // infrequently -- only when the disk or memory cache is cleared.
 
 // static
 void
 nsSHistory::GloballyEvictAllContentViewers()
@@ -1891,16 +1923,33 @@ nsSHistory::InitiateLoad(nsISHEntry* aFr
                            nsIWebNavigation::LOAD_FLAGS_NONE, false);
 
 }
 
 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<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow();
+    if (!win) {
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    RefPtr<mozilla::dom::TabGroup> tabGroup = win->TabGroup();
+    mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
+      this,
+      mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+                                    CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
+      tabGroup->EventTargetFor(mozilla::TaskCategory::Other));
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator)
 {
   NS_ENSURE_ARG_POINTER(aEnumerator);
   RefPtr<nsSHEnumerator> iterator = new nsSHEnumerator(this);
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -11,32 +11,61 @@
 #include "nsExpirationTracker.h"
 #include "nsIPartialSHistoryListener.h"
 #include "nsISHistory.h"
 #include "nsISHistoryInternal.h"
 #include "nsISimpleEnumerator.h"
 #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;
 class nsSHistoryObserver;
 class nsISHEntry;
 class nsISHTransaction;
 
 class nsSHistory final : public mozilla::LinkedListElement<nsSHistory>,
                          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<nsSHEntryShared, 3>
+  {
+  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
   NS_DECL_NSISHISTORYINTERNAL
   NS_DECL_NSIWEBNAVIGATION
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
@@ -82,16 +111,19 @@ private:
   nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                  uint32_t aHistCmd);
 
   // aIndex is the index of the transaction which may be removed.
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
+  // Track all bfcache entries and evict on expiration.
+  mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
   nsCOMPtr<nsISHTransaction> mListRoot;
   int32_t mIndex;
   int32_t mLength;
   int32_t mRequestedIndex;
 
   // The number of entries before this session history object.
   int32_t mGlobalIndexOffset;