Bug 395045. Expire cached content-viewers after they've been unused for 20-30 minutes. r+sr+a=bz
authorroc+@cs.cmu.edu
Fri, 21 Sep 2007 02:19:59 -0700
changeset 6187 7c9aae671bfb9a998c6999aa1b3dd196006d6992
parent 6186 0242373f16053f89bd3c6fae8cc1eb4a41b6d2d4
child 6188 155ad2e1913a442fee25a9d28ebb17f52936a183
push idunknown
push userunknown
push dateunknown
bugs395045
milestone1.9a8pre
Bug 395045. Expire cached content-viewers after they've been unused for 20-30 minutes. r+sr+a=bz
docshell/build/nsDocShellModule.cpp
docshell/shistory/public/nsISHistoryInternal.idl
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/shistory/src/nsSHistory.cpp
docshell/shistory/src/nsSHistory.h
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -75,22 +75,26 @@ Initialize(nsIModule* aSelf)
 {
   NS_PRECONDITION(!gInitialized, "docshell module already initialized");
   if (gInitialized) {
     return NS_OK;
   }
   gInitialized = PR_TRUE;
 
   nsresult rv = nsSHistory::Startup();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = nsSHEntry::Startup();
   return rv;
 }
 
 PR_STATIC_CALLBACK(void)
 Shutdown(nsIModule* aSelf)
 {
+  nsSHEntry::Shutdown();
   gInitialized = PR_FALSE;
 }
 
 // docshell
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebShell, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init)
 
--- a/docshell/shistory/public/nsISHistoryInternal.idl
+++ b/docshell/shistory/public/nsISHistoryInternal.idl
@@ -41,22 +41,23 @@
 #include "nsISHEntry.idl"
 #include "nsISHTransaction.idl"
 
 interface nsISHistoryListener;
 interface nsIDocShell;
 
 %{C++
 #define NS_SHISTORY_INTERNAL_CID \
-{0x5b4cba4c, 0xbf67, 0x499a, {0xae, 0x2c, 0x3f, 0x76, 0x65, 0x6f, 0x4a, 0x4e}}
+{ 0x9c47c121, 0x1c6e, 0x4d8f, \
+  { 0xb9, 0x04, 0x3a, 0xc9, 0x68, 0x11, 0x6e, 0x88 } }
 
 #define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1"
 %}
 
-[scriptable, uuid(df8788d6-c0ed-4517-b47e-c719afc94284)]
+[scriptable, uuid(9c47c121-1c6e-4d8f-b904-3ac968116e88)]
 interface nsISHistoryInternal: nsISupports
 {
   /**
    * Add a new Entry to the History List
    * @param aEntry - The entry to add
    * @param aPersist - If true this specifies that the entry should persist
    * in the list.  If false, this means that when new entries are added
    * this element will not appear in the session history list.
@@ -93,9 +94,15 @@ interface nsISHistoryInternal: nsISuppor
   /**
    * Evict content viewers until the number of content viewers per tab
    * is no more than gHistoryMaxViewers.  Also, count
    * total number of content viewers globally and evict one if we are over
    * our total max.  This is always called in Show(), after we destroy
    * the previous viewer.
    */
    void evictContentViewers(in long previousIndex, in long index);
+   
+   /**
+    * Evict the content viewer associated with a session history entry
+    * that has timed out.
+    */
+   void evictExpiredContentViewerForEntry(in nsISHEntry aEntry);
 };
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -46,24 +46,63 @@
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsIDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsISHistory.h"
+#include "nsISHistoryInternal.h"
 
+// Hardcode this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS 30*60
+
+typedef nsExpirationTracker<nsSHEntry,3> 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;
 
+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() 
   : mLoadType(0)
   , mID(gEntryID++)
   , mPageIdentifier(mID)
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mIsFrameNavigation(PR_FALSE)
   , mSaveLayoutState(PR_TRUE)
@@ -104,16 +143,18 @@ ClearParentPtr(nsISHEntry* aEntry, void*
   if (aEntry) {
     aEntry->SetParent(nsnull);
   }
   return PR_TRUE;
 }
 
 nsSHEntry::~nsSHEntry()
 {
+  StopTrackingEntry(this);
+
   // Since we never really remove kids from SHEntrys, we need to null
   // out the mParent pointers on all our kids.
   mChildren.EnumerateForwards(ClearParentPtr, nsnull);
   mChildren.Clear();
 
   nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
   DropPresentationState();
   if (viewer) {
@@ -188,16 +229,18 @@ nsSHEntry::SetContentViewer(nsIContentVi
     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->SetShellsHidden(PR_TRUE);
       mDocument->AddMutationObserver(this);
     }
+    
+    gHistoryTracker->AddObject(this);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::GetContentViewer(nsIContentViewer **aResult)
 {
@@ -632,24 +675,50 @@ nsSHEntry::DropPresentationState()
   if (mDocument) {
     mDocument->SetShellsHidden(PR_FALSE);
     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;
 }
 
+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<nsISupports> 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);
+}
+
 //*****************************************************************************
 //    nsSHEntry: nsIMutationObserver
 //*****************************************************************************
 
 void
 nsSHEntry::NodeWillBeDestroyed(const nsINode* aNode)
 {
   NS_NOTREACHED("Document destroyed while we're holding a strong ref to it");
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -53,16 +53,17 @@
 #include "nsISHEntry.h"
 #include "nsISHContainer.h"
 #include "nsIURI.h"
 #include "nsIEnumerator.h"
 #include "nsIHistoryEntry.h"
 #include "nsRect.h"
 #include "nsSupportsArray.h"
 #include "nsIMutationObserver.h"
+#include "nsExpirationTracker.h"
 
 class nsSHEntry : public nsISHEntry,
                   public nsISHContainer,
                   public nsIMutationObserver
 {
 public: 
   nsSHEntry();
   nsSHEntry(const nsSHEntry &other);
@@ -70,16 +71,23 @@ public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIHISTORYENTRY
   NS_DECL_NSISHENTRY
   NS_DECL_NSISHCONTAINER
   NS_DECL_NSIMUTATIONOBSERVER
 
   void DropPresentationState();
 
+  void Expire();
+  
+  nsExpirationState *GetExpirationState() { return &mExpirationState; }
+  
+  static nsresult Startup();
+  static void Shutdown();
+  
 private:
   ~nsSHEntry();
   void DocumentMutated();
 
   nsCOMPtr<nsIURI>                mURI;
   nsCOMPtr<nsIURI>                mReferrerURI;
   nsCOMPtr<nsIContentViewer>      mContentViewer;
   nsCOMPtr<nsIDocument>           mDocument; // document currently being observed
@@ -99,11 +107,12 @@ private:
   nsCString                       mContentType;
   nsCOMPtr<nsISupports>           mCacheKey;
   nsISHEntry *                    mParent;  // weak reference
   nsCOMPtr<nsISupports>           mWindowState;
   nsRect                          mViewerBounds;
   nsCOMArray<nsIDocShellTreeItem> mChildShells;
   nsCOMPtr<nsISupportsArray>      mRefreshURIList;
   nsCOMPtr<nsISupports>           mOwner;
+  nsExpirationState               mExpirationState;
 };
 
 #endif /* nsSHEntry_h */
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -784,21 +784,27 @@ nsSHistory::EvictWindowContentViewers(PR
     startIndex = PR_MAX(0, aFromIndex - gHistoryMaxViewers);
   } else { // going backward
     startIndex = aToIndex + gHistoryMaxViewers + 1;
     if (startIndex >= mLength) {
       return;
     }
     endIndex = PR_MIN(mLength, aFromIndex + gHistoryMaxViewers);
   }
+  
+  EvictContentViewersInRange(startIndex, endIndex);
+}
 
+void
+nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd)
+{
   nsCOMPtr<nsISHTransaction> trans;
-  GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
+  GetTransactionAtIndex(aStart, getter_AddRefs(trans));
 
-  for (PRInt32 i = startIndex; i < endIndex; ++i) {
+  for (PRInt32 i = aStart; i < aEnd; ++i) {
     nsCOMPtr<nsISHEntry> entry;
     trans->GetSHEntry(getter_AddRefs(entry));
     nsCOMPtr<nsIContentViewer> viewer;
     nsCOMPtr<nsISHEntry> ownerEntry;
     entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
                                getter_AddRefs(viewer));
     if (viewer) {
       NS_ASSERTION(ownerEntry,
@@ -936,16 +942,56 @@ nsSHistory::EvictGlobalContentViewer()
       }
     } else {
       // couldn't find a content viewer to evict, so we are done
       shouldTryEviction = PR_FALSE;
     }
   }  // while shouldTryEviction
 }
 
+NS_IMETHODIMP
+nsSHistory::EvictExpiredContentViewerForEntry(nsISHEntry *aEntry)
+{
+  PRInt32 startIndex = PR_MAX(0, mIndex - gHistoryMaxViewers);
+  PRInt32 endIndex = PR_MIN(mLength - 1,
+                            mIndex + gHistoryMaxViewers);
+  nsCOMPtr<nsISHTransaction> trans;
+  GetTransactionAtIndex(startIndex, getter_AddRefs(trans));
+
+  PRInt32 i;
+  for (i = startIndex; i <= endIndex; ++i) {
+    nsCOMPtr<nsISHEntry> entry;
+    trans->GetSHEntry(getter_AddRefs(entry));
+    if (entry == aEntry)
+      break;
+
+    nsISHTransaction *temp = trans;
+    temp->GetNext(getter_AddRefs(trans));
+  }
+  if (i > endIndex)
+    return NS_OK;
+  
+  NS_ASSERTION(i != mIndex, "How did the current session entry expire?");
+  if (i == mIndex)
+    return NS_OK;
+  
+  // We evict content viewers for the expired entry and any other entries that
+  // we would have to go through the expired entry to get to (i.e. the entries
+  // that have the expired entry between them and the current entry). Those
+  // other entries should have timed out already, actually, but this is just
+  // to be on the safe side.
+  if (i < mIndex) {
+    EvictContentViewersInRange(startIndex, i + 1);
+  } else {
+    EvictContentViewersInRange(i, endIndex + 1);
+  }
+  
+  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::EvictAllContentViewers()
--- a/docshell/shistory/src/nsSHistory.h
+++ b/docshell/shistory/src/nsSHistory.h
@@ -94,16 +94,17 @@ protected:
    nsresult InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType);
 
    NS_IMETHOD LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 histCmd);
 	
 #ifdef DEBUG
    nsresult PrintHistory();
 #endif
 
+  void EvictContentViewersInRange(PRInt32 aStartIndex, PRInt32 aEndIndex);
   void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex);
   static void EvictGlobalContentViewer();
   static void EvictAllContentViewers();
 
   // Calculates a max number of total
   // content viewers to cache, based on amount of total memory
   static PRUint32 CalcMaxTotalViewers();