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
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;