Bug 610417 - Optimize eviction in bfcache. r=smaug a=blocking-fennec
authorAlon Zakai <azakai@mozilla.com>
Wed, 19 Jan 2011 11:57:36 -0800
changeset 60876 7756b033258fe3f3079dd8c416e63d3eb1afd0b1
parent 60875 754ea885a0f87af2f0cfe1e8684db67f5603657b
child 60878 f4db6f267ddec2944daf4613ac5ca088b0bed8fb
push idunknown
push userunknown
push dateunknown
reviewerssmaug, blocking-fennec
bugs610417
milestone2.0b10pre
Bug 610417 - Optimize eviction in bfcache. r=smaug a=blocking-fennec
browser/app/profile/firefox.js
docshell/shistory/public/nsISHEntry.idl
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/shistory/src/nsSHistory.cpp
modules/libpref/src/init/all.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -902,16 +902,18 @@ pref("browser.safebrowsing.enabled", fal
 pref("browser.safebrowsing.malware.enabled", false);
 
 // don't check for default browser
 pref("browser.shell.checkDefaultBrowser", false);
 
 // disable bfcache for memory
 pref("browser.sessionhistory.max_total_viewers", 0);
 
+pref("browser.sessionhistory.optimize_eviction", false);
+
 // tweak default content sink prefs
 pref("content.sink.interactive_deflect_count", 10); /* default 0 */
 pref("content.sink.perf_deflect_count", 50); /* default 200 */
 pref("content.sink.interactive_parse_time", 5000); /* default 3000 */
 pref("content.sink.perf_parse_time", 150000); /* default 360000 */
 pref("content.sink.pending_event_mode", 0); /* default 1 */
 pref("content.sink.event_probe_rate", 1); /* default 1 */
 pref("content.sink.interactive_time", 750000); /* default 750000 */
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -245,21 +245,26 @@ interface nsISHEntry : nsIHistoryEntry
     boolean hasDynamicallyAddedChild();
 
     /**
      * The history ID of the docshell.
      */
     attribute unsigned long long docshellID;
 };
 
-[scriptable, uuid(e0b0ac6d-cb29-4be2-b685-1755d6301a69)]
+[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)]
 interface nsISHEntryInternal : nsISupports
 {
     [notxpcom] void RemoveFromBFCacheAsync();
     [notxpcom] void RemoveFromBFCacheSync();
+
+    /**
+     * A number that is assigned by the sHistory when the entry is activated
+     */
+    attribute unsigned long lastTouched;
 };
 
 %{ C++
 // {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}
 #define NS_SHENTRY_CID \
 {0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
 
 #define NS_SHENTRY_CONTRACTID \
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -112,16 +112,17 @@ nsSHEntry::nsSHEntry()
   , 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)
 {
 }
 
 nsSHEntry::nsSHEntry(const nsSHEntry &other)
   : mURI(other.mURI)
   , mReferrerURI(other.mReferrerURI)
   // XXX why not copy mDocument?
   , mTitle(other.mTitle)
@@ -993,8 +994,23 @@ nsSHEntry::GetDocshellID(PRUint64* aID)
 
 NS_IMETHODIMP
 nsSHEntry::SetDocshellID(PRUint64 aID)
 {
   mDocShellID = aID;
   return NS_OK;
 }
 
+
+NS_IMETHODIMP
+nsSHEntry::GetLastTouched(unsigned int *aLastTouched)
+{
+  *aLastTouched = mLastTouched;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLastTouched(unsigned int aLastTouched)
+{
+  mLastTouched = aLastTouched;
+  return NS_OK;
+}
+
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -115,11 +115,12 @@ private:
   nsIntRect                       mViewerBounds;
   nsCOMArray<nsIDocShellTreeItem> mChildShells;
   nsCOMPtr<nsISupportsArray>      mRefreshURIList;
   nsCOMPtr<nsISupports>           mOwner;
   nsExpirationState               mExpirationState;
   nsAutoPtr<nsDocShellEditorData> mEditorData;
   nsString                        mStateData;
   PRUint64                        mDocShellID;
+  PRUint32                        mLastTouched;
 };
 
 #endif /* nsSHEntry_h */
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -65,26 +65,37 @@
 #include "nsDocShell.h"
 
 // For calculating max history entries and max cachable contentviewers
 #include "nspr.h"
 #include <math.h>  // for log()
 
 #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 PRInt32  gHistoryMaxSize = 50;
 // Max viewers allowed per SHistory objects
 static const PRInt32  gHistoryMaxViewers = 3;
 // List of all SHistory objects, used for content viewer cache eviction
 static PRCList gSHistoryList;
 // Max viewers allowed total, across all SHistory objects - negative default
 // 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 PRBool gOptimizeEviction = PR_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;
+
 enum HistCmd{
   HIST_CMD_BACK,
   HIST_CMD_FORWARD,
   HIST_CMD_GOTOINDEX,
   HIST_CMD_RELOAD
 } ;
 
 //*****************************************************************************
@@ -217,16 +228,18 @@ nsSHistory::CalcMaxTotalViewers()
 
 // static
 void
 nsSHistory::UpdatePrefs(nsIPrefBranch *aPrefBranch)
 {
   aPrefBranch->GetIntPref(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
   aPrefBranch->GetIntPref(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
                           &sHistoryMaxTotalViewers);
+  aPrefBranch->GetBoolPref(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) {
     sHistoryMaxTotalViewers = CalcMaxTotalViewers();
   }
 }
 
 // static
@@ -260,16 +273,18 @@ nsSHistory::Startup()
     if (branch) {
       nsSHistoryObserver* obs = new nsSHistoryObserver();
       if (!obs) {
         return NS_ERROR_OUT_OF_MEMORY;
       }
       branch->AddObserver(PREF_SHISTORY_SIZE, obs, PR_FALSE);
       branch->AddObserver(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
                           obs, PR_FALSE);
+      branch->AddObserver(PREF_SHISTORY_OPTIMIZE_EVICTION,
+                          obs, PR_FALSE);
 
       nsCOMPtr<nsIObserverService> obsSvc =
         mozilla::services::GetObserverService();
       if (obsSvc) {
         // Observe empty-cache notifications so tahat clearing the disk/memory
         // cache will also evict all content viewers.
         obsSvc->AddObserver(obs,
                             NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID, PR_FALSE);
@@ -932,16 +947,17 @@ nsSHistory::EvictGlobalContentViewer()
   PRBool shouldTryEviction = PR_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<nsISHEntry> evictFromSHE;
     nsCOMPtr<nsIContentViewer> evictViewer;
     PRInt32 totalContentViewers = 0;
     nsSHistory* shist = static_cast<nsSHistory*>
                                    (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,
@@ -956,16 +972,25 @@ nsSHistory::EvictGlobalContentViewer()
       for (PRInt32 i = startIndex; i <= endIndex; ++i) {
         nsCOMPtr<nsISHEntry> entry;
         trans->GetSHEntry(getter_AddRefs(entry));
         nsCOMPtr<nsIContentViewer> viewer;
         nsCOMPtr<nsISHEntry> ownerEntry;
         entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
                                    getter_AddRefs(viewer));
 
+        PRUint32 entryLastTouched = 0;
+        if (gOptimizeEviction) {
+          nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(entry);
+          if (entryInternal) {
+            // Find when this entry was last activated
+            entryInternal->GetLastTouched(&entryLastTouched);
+          }
+        }
+
 #ifdef DEBUG_PAGE_CACHE
         nsCOMPtr<nsIURI> uri;
         if (ownerEntry) {
           ownerEntry->GetURI(getter_AddRefs(uri));
         } else {
           entry->GetURI(getter_AddRefs(uri));
         }
         nsCAutoString spec;
@@ -980,22 +1005,26 @@ nsSHistory::EvictGlobalContentViewer()
         if (viewer) {
           PRInt32 distance = PR_ABS(shist->mIndex - i);
           
 #ifdef DEBUG_PAGE_CACHE
           printf("Has a cached content viewer: %s\n", spec.get());
           printf("mIndex: %d i: %d\n", shist->mIndex, i);
 #endif
           totalContentViewers++;
-          if (distance > distanceFromFocus) {
-            
+
+          // 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));
       }
@@ -1399,31 +1428,37 @@ nsSHistory::LoadNextPossibleEntry(PRInt3
   }
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsSHistory::LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 aHistCmd)
 {
   nsCOMPtr<nsIDocShell> docShell;
-  nsCOMPtr<nsISHEntry> shEntry;
   // Keep note of requested history index in mRequestedIndex.
   mRequestedIndex = aIndex;
 
   nsCOMPtr<nsISHEntry> prevEntry;
   GetEntryAtIndex(mIndex, PR_FALSE, getter_AddRefs(prevEntry));
 
   nsCOMPtr<nsISHEntry> nextEntry;
   GetEntryAtIndex(mRequestedIndex, PR_FALSE, getter_AddRefs(nextEntry));
   nsCOMPtr<nsIHistoryEntry> nHEntry(do_QueryInterface(nextEntry));
   if (!nextEntry || !prevEntry || !nHEntry) {
     mRequestedIndex = -1;
     return NS_ERROR_FAILURE;
   }
 
+  // Remember that this entry is getting loaded at this point in the sequence
+  nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(nextEntry);
+
+  if (entryInternal) {
+    entryInternal->SetLastTouched(++gTouchCounter);
+  }
+
   // Send appropriate listener notifications
   PRBool canNavigate = PR_TRUE;
   // Get the uri for the entry we are about to visit
   nsCOMPtr<nsIURI> nextURI;
   nHEntry->GetURI(getter_AddRefs(nextURI));
 
   if(mListener) {
     nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -100,16 +100,18 @@ pref("offline-apps.quota.warn",         
 pref("dom.indexedDB.enabled", true);
 // Space to allow indexedDB databases before prompting (in MB).
 pref("dom.indexedDB.warningQuota", 50);
 
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_total_viewers", -1);
 
+pref("browser.sessionhistory.optimize_eviction", false);
+
 pref("ui.use_native_colors", true);
 pref("ui.use_native_popup_windows", false);
 pref("ui.click_hold_context_menus", false);
 pref("browser.display.use_document_fonts",  1);  // 0 = never, 1 = quick, 2 = always
 pref("browser.display.use_document_colors", true);
 pref("browser.display.use_system_colors",   false);
 pref("browser.display.foreground_color",    "#000000");
 pref("browser.display.background_color",    "#FFFFFF");