Bug 534178, remove dublicate nsSHTransactions, r=bz, a=blocking
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sun, 05 Sep 2010 21:10:35 +0300
changeset 52044 b909c38edaf5c672bfc6a0937d3e625655a2c6f5
parent 52043 08aab2f522283641fe37567f5c2201a5ff3ce490
child 52045 e44c15a9941e99d0f190398fb5890ea66729443c
push idunknown
push userunknown
push dateunknown
reviewersbz, blocking
bugs534178
milestone2.0b6pre
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 534178, remove dublicate nsSHTransactions, r=bz, a=blocking
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/shistory/src/nsSHTransaction.cpp
docshell/shistory/src/nsSHistory.cpp
docshell/shistory/src/nsSHistory.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2161,16 +2161,47 @@ nsDocShell::HistoryPurged(PRInt32 aNumEn
         if (shell) {
             shell->HistoryPurged(aNumEntries);
         }
     }
 
     return NS_OK;
 }
 
+nsresult
+nsDocShell::HistoryTransactionRemoved(PRInt32 aIndex)
+{
+    // These indices are used for fastback cache eviction, to determine
+    // which session history entries are candidates for content viewer
+    // eviction.  We need to adjust by the number of entries that we
+    // just purged from history, so that we look at the right session history
+    // entries during eviction.
+    if (aIndex == mPreviousTransIndex) {
+        mPreviousTransIndex = -1;
+    } else if (aIndex < mPreviousTransIndex) {
+        --mPreviousTransIndex;
+    }
+    if (mLoadedTransIndex == aIndex) {
+        mLoadedTransIndex = 0;
+    } else if (aIndex < mLoadedTransIndex) {
+        --mLoadedTransIndex;
+    }
+                            
+    PRInt32 count = mChildList.Count();
+    for (PRInt32 i = 0; i < count; ++i) {
+        nsCOMPtr<nsIDocShell> shell = do_QueryInterface(ChildAt(i));
+        if (shell) {
+            static_cast<nsDocShell*>(shell.get())->
+                HistoryTransactionRemoved(aIndex);
+        }
+    }
+
+    return NS_OK;
+}
+
 static
 nsresult
 GetPrincipalDomain(nsIPrincipal* aPrincipal, nsACString& aDomain)
 {
   aDomain.Truncate();
 
   nsCOMPtr<nsIURI> codebaseURI;
   nsresult rv = aPrincipal->GetDomain(getter_AddRefs(codebaseURI));
@@ -9623,17 +9654,17 @@ nsDocShell::AddState(nsIVariant *aData, 
     // since SetCurrentURI will call FireOnLocationChange for us.
     if (!equalURIs) {
         SetCurrentURI(newURI, nsnull, PR_TRUE);
         document->SetDocumentURI(newURI);
 
         AddURIVisit(newURI, oldURI, oldURI, 0);
     }
     else {
-        FireOnLocationChange(this, nsnull, mCurrentURI);
+        FireDummyOnLocationChange();
     }
 
     return NS_OK;
 }
 
 PRBool
 nsDocShell::ShouldAddToSessionHistory(nsIURI * aURI)
 {
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -264,16 +264,23 @@ public:
     // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
     // the timer involved out of mRefreshURIList if it's there.
     // aTimer must not be null.
     nsresult ForceRefreshURIFromTimer(nsIURI * aURI, PRInt32 aDelay,
                                       PRBool aMetaRefresh, nsITimer* aTimer);
 
     friend class OnLinkClickEvent;
 
+    // We need dummy OnLocationChange in some cases to update the UI.
+    void FireDummyOnLocationChange()
+    {
+      FireOnLocationChange(this, nsnull, mCurrentURI);
+    }
+
+    nsresult HistoryTransactionRemoved(PRInt32 aIndex);
 protected:
     // Object Management
     virtual ~nsDocShell();
     virtual void DestroyChildren();
 
     // Content Viewer Management
     NS_IMETHOD EnsureContentViewer();
     // aPrincipal can be passed in if the caller wants.  If null is
--- a/docshell/shistory/src/nsSHTransaction.cpp
+++ b/docshell/shistory/src/nsSHTransaction.cpp
@@ -106,20 +106,22 @@ nsSHTransaction::GetNext(nsISHTransactio
    NS_IF_ADDREF(*aResult);
    return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsSHTransaction::SetNext(nsISHTransaction * aNext)
 {
-   NS_ENSURE_SUCCESS(aNext->SetPrev(this), NS_ERROR_FAILURE);
+  if (aNext) {
+    NS_ENSURE_SUCCESS(aNext->SetPrev(this), NS_ERROR_FAILURE);
+  }
 
-   mNext = aNext;
-   return NS_OK;
+  mNext = aNext;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHTransaction::SetPrev(nsISHTransaction * aPrev)
 {
 	/* This is weak reference to parent. Do not Addref it */
      mPrev = aPrev;
 	 return NS_OK;
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -57,16 +57,17 @@
 #include "nsIURI.h"
 #include "nsIContentViewer.h"
 #include "nsICacheService.h"
 #include "nsIObserverService.h"
 #include "prclist.h"
 #include "mozilla/Services.h"
 #include "nsTArray.h"
 #include "nsCOMArray.h"
+#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"
 
@@ -1132,23 +1133,101 @@ PRBool RemoveChildEntries(nsISHistory* a
                           nsTArray<PRUint64>& aEntryIDs)
 {
   nsCOMPtr<nsIHistoryEntry> rootHE;
   aHistory->GetEntryAtIndex(aIndex, PR_FALSE, getter_AddRefs(rootHE));
   nsCOMPtr<nsISHContainer> root = do_QueryInterface(rootHE);
   return root ? RemoveFromSessionHistoryContainer(root, aEntryIDs) : PR_FALSE;
 }
 
+PRBool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2)
+{
+  if (!aEntry1 && !aEntry2) {
+    return PR_TRUE;
+  }
+  if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
+    return PR_FALSE;
+  }
+  PRUint32 id1, id2;
+  aEntry1->GetID(&id1);
+  aEntry2->GetID(&id2);
+  if (id1 != id2) {
+    return PR_FALSE;
+  }
+
+  nsCOMPtr<nsISHContainer> container1 = do_QueryInterface(aEntry1);
+  nsCOMPtr<nsISHContainer> container2 = do_QueryInterface(aEntry2);
+  PRInt32 count1, count2;
+  container1->GetChildCount(&count1);
+  container2->GetChildCount(&count2);
+  // We allow null entries in the end of the child list.
+  PRInt32 count = PR_MAX(count1, count2);
+  for (PRInt32 i = 0; i < count; ++i) {
+    nsCOMPtr<nsISHEntry> child1, child2;
+    container1->GetChildAt(i, getter_AddRefs(child1));
+    container2->GetChildAt(i, getter_AddRefs(child2));
+    if (!IsSameTree(child1, child2)) {
+      return PR_FALSE;
+    }
+  }
+  
+  return PR_TRUE;
+}
+
+PRBool
+nsSHistory::RemoveDuplicate(PRInt32 aIndex)
+{
+  NS_ASSERTION(aIndex > 0, "aIndex must be > 0!");
+  nsCOMPtr<nsIHistoryEntry> rootHE1, rootHE2;
+  GetEntryAtIndex(aIndex, PR_FALSE, getter_AddRefs(rootHE1));
+  GetEntryAtIndex(aIndex - 1, PR_FALSE, getter_AddRefs(rootHE2));
+  nsCOMPtr<nsISHEntry> root1 = do_QueryInterface(rootHE1);
+  nsCOMPtr<nsISHEntry> root2 = do_QueryInterface(rootHE2);
+  if (IsSameTree(root1, root2)) {
+    nsCOMPtr<nsISHTransaction> txToRemove, txToKeep, txNext;
+    GetTransactionAtIndex(aIndex, getter_AddRefs(txToRemove));
+    GetTransactionAtIndex(aIndex - 1, getter_AddRefs(txToKeep));
+    NS_ENSURE_TRUE(txToRemove, PR_FALSE);
+    NS_ENSURE_TRUE(txToKeep, PR_FALSE);
+    txToRemove->GetNext(getter_AddRefs(txNext));
+    txToRemove->SetNext(nsnull);
+    txToRemove->SetPrev(nsnull);
+    // If txNext is non-null, this will set txNext's .prev
+    txToKeep->SetNext(txNext);
+    static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex);
+    if (mIndex >= aIndex) {
+      mIndex = mIndex - 1;
+    }
+    --mLength;
+    return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 NS_IMETHODIMP_(void)
 nsSHistory::RemoveEntries(nsTArray<PRUint64>& aIDs, PRInt32 aStartIndex)
 {
   PRInt32 index = aStartIndex;
   while(index >= 0 && RemoveChildEntries(this, --index, aIDs));
+  // Nothing was removed from minIndex if it is > 0!
+  PRInt32 minIndex = index >= 0 ? index : 0;
   index = aStartIndex;
   while(index >= 0 && RemoveChildEntries(this, index++, aIDs));
+  
+  // We need to remove duplicate nsSHEntry trees.
+  PRBool didRemove = PR_FALSE;
+  while (index && index > minIndex) {
+    didRemove = RemoveDuplicate(index--) || didRemove;
+  }
+  if (didRemove && mRootDocShell) {
+    nsRefPtr<nsIRunnable> ev =
+      NS_NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell),
+                           &nsDocShell::FireDummyOnLocationChange);
+    NS_DispatchToCurrentThread(ev);
+  }
 }
 
 void
 nsSHistory::RemoveDynEntries(PRInt32 aOldIndex, PRInt32 aNewIndex)
 {
   // Search for the entries which are in the current index,
   // but not in the new one.
   nsCOMPtr<nsISHEntry> originalSH;
--- a/docshell/shistory/src/nsSHistory.h
+++ b/docshell/shistory/src/nsSHistory.h
@@ -111,16 +111,19 @@ protected:
   // Calculates a max number of total
   // content viewers to cache, based on amount of total memory
   static PRUint32 CalcMaxTotalViewers();
 
   void RemoveDynEntries(PRInt32 aOldIndex, PRInt32 aNewIndex);
 
   nsresult LoadNextPossibleEntry(PRInt32 aNewIndex, long aLoadType, PRUint32 aHistCmd);
 protected:
+  // Note, aIndex must be > 0, since it is compared to aIndex - 1.
+  PRBool RemoveDuplicate(PRInt32 aIndex);
+
   nsCOMPtr<nsISHTransaction> mListRoot;
   PRInt32 mIndex;
   PRInt32 mLength;
   PRInt32 mRequestedIndex;
   // Session History listener
   nsWeakPtr mListener;
   // Weak reference. Do not refcount this.
   nsIDocShell *  mRootDocShell;