Bug 981900 - Add OnHistoryReplaceEntry to nsISHistoryListener to handle history.replaceState. r=smaug
authorSteven MacLeod <smacleod@mozilla.com>
Thu, 24 Apr 2014 15:12:15 -0400
changeset 180442 a38d0ab6d6b844c65448f601bd7b668f5f2d7cd5
parent 180441 9688e85bd87c14f571c15a15b074ae6b1ebfb2df
child 180443 031b49af61a68658a27cf1a5e76bb4081210ad8d
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewerssmaug
bugs981900
milestone31.0a1
Bug 981900 - Add OnHistoryReplaceEntry to nsISHistoryListener to handle history.replaceState. r=smaug
browser/components/sessionstore/content/content-sessionStore.js
browser/components/sessionstore/src/ContentRestore.jsm
browser/components/sessionstore/test/browser_sessionHistory.js
docshell/base/nsDocShell.cpp
docshell/shistory/public/nsISHistory.idl
docshell/shistory/public/nsISHistoryListener.idl
docshell/shistory/src/nsSHistory.cpp
docshell/test/browser/browser_bug422543.js
docshell/test/browser/browser_bug670318.js
embedding/tests/winEmbed/WebBrowserChrome.cpp
mobile/android/chrome/content/browser.js
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -281,16 +281,20 @@ let SessionHistoryListener = {
     return true;
   },
 
   OnHistoryReload: function (reloadURI, reloadFlags) {
     this.collect();
     return true;
   },
 
+  OnHistoryReplaceEntry: function (index) {
+    this.collect();
+  },
+
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsISHistoryListener,
     Ci.nsISupportsWeakReference
   ])
 };
 
 /**
  * Listens for scroll position changes. Whenever the user scrolls the top-most
--- a/browser/components/sessionstore/src/ContentRestore.jsm
+++ b/browser/components/sessionstore/src/ContentRestore.jsm
@@ -364,16 +364,17 @@ HistoryListener.prototype = {
     this.webNavigation.sessionHistory.removeSHistoryListener(this);
   },
 
   OnHistoryNewEntry: function(newURI) {},
   OnHistoryGoBack: function(backURI) { return true; },
   OnHistoryGoForward: function(forwardURI) { return true; },
   OnHistoryGotoIndex: function(index, gotoURI) { return true; },
   OnHistoryPurge: function(numEntries) { return true; },
+  OnHistoryReplaceEntry: function(index) {},
 
   OnHistoryReload: function(reloadURI, reloadFlags) {
     this.callback();
 
     // Cancel the load.
     return false;
   },
 }
--- a/browser/components/sessionstore/test/browser_sessionHistory.js
+++ b/browser/components/sessionstore/test/browser_sessionHistory.js
@@ -215,21 +215,21 @@ add_task(function test_pushstate_replace
 
   // Check that we have added the history entry.
   SyncHandlers.get(browser).flush();
   let {entries} = JSON.parse(ss.getTabState(tab));
   is(entries.length, 2, "there is another shistory entry");
   is(entries[1].url, "http://example.com/test-entry/", "url is correct");
 
   // Disabled until replaceState invalidation is supported. See Bug 967028.
-  // browser.messageManager.
-  //   sendAsyncMessage("ss-test:historyReplaceState", {url: 'test-entry2/'});
-  // yield promiseContentMessage(browser, "ss-test:historyReplaceState");
+  browser.messageManager.
+    sendAsyncMessage("ss-test:historyReplaceState", {url: 'test-entry2/'});
+  yield promiseContentMessage(browser, "ss-test:historyReplaceState");
 
-  // // Check that we have modified the history entry.
-  // SyncHandlers.get(browser).flush();
-  // let {entries} = JSON.parse(ss.getTabState(tab));
-  // is(entries.length, 2, "there is still two shistory entries");
-  // is(entries[1].url, "http://example.com/test-entry/test-entry2/", "url is correct");
+  // Check that we have modified the history entry.
+  SyncHandlers.get(browser).flush();
+  let {entries} = JSON.parse(ss.getTabState(tab));
+  is(entries.length, 2, "there is still two shistory entries");
+  is(entries[1].url, "http://example.com/test-entry/test-entry2/", "url is correct");
 
   // Cleanup.
   gBrowser.removeTab(tab);
 });
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10866,31 +10866,41 @@ nsDocShell::AddState(JS::Handle<JS::Valu
     // set URIWasModified to true for the current SHEntry (bug 669671).
     bool sameExceptHashes = true, oldURIWasModified = false;
     newURI->EqualsExceptRef(currentURI, &sameExceptHashes);
     oldOSHE->GetURIWasModified(&oldURIWasModified);
     newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified);
 
     // Step 5: If aReplace is false, indicating that we're doing a pushState
     // rather than a replaceState, notify bfcache that we've added a page to
-    // the history so it can evict content viewers if appropriate.
+    // the history so it can evict content viewers if appropriate. Otherwise
+    // call ReplaceEntry so that we notify nsIHistoryListeners that an entry
+    // was replaced.
+    nsCOMPtr<nsISHistory> rootSH;
+    GetRootSessionHistory(getter_AddRefs(rootSH));
+    NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED);
+
+    nsCOMPtr<nsISHistoryInternal> internalSH =
+        do_QueryInterface(rootSH);
+    NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED);
+
     if (!aReplace) {
-        nsCOMPtr<nsISHistory> rootSH;
-        GetRootSessionHistory(getter_AddRefs(rootSH));
-        NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED);
-
-        nsCOMPtr<nsISHistoryInternal> internalSH =
-            do_QueryInterface(rootSH);
-        NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED);
-
         int32_t curIndex = -1;
         rv = rootSH->GetIndex(&curIndex);
         if (NS_SUCCEEDED(rv) && curIndex > -1) {
             internalSH->EvictOutOfRangeContentViewers(curIndex);
         }
+    } else {
+        nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);
+
+        int32_t index = -1;
+        rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
+        if (NS_SUCCEEDED(rv) && index > -1) {
+            internalSH->ReplaceEntry(index, rootSHEntry);
+        }
     }
 
     // Step 6: If the document's URI changed, update document's URI and update
     // global history.
     //
     // We need to call FireOnLocationChange so that the browser's address bar
     // gets updated and the back button is enabled, but we only need to
     // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
--- a/docshell/shistory/public/nsISHistory.idl
+++ b/docshell/shistory/public/nsISHistory.idl
@@ -17,22 +17,22 @@ interface nsISimpleEnumerator;
  * session history component must create a instance of it and set
  * it in the nsIWebNavigation object.
  * This interface is accessible from javascript.
  */
  
 
 %{C++
 #define NS_SHISTORY_CID \
-{0x7294fe9c, 0x14d8, 0x11d5, {0x98, 0x82, 0x00, 0xC0, 0x4f, 0xa0, 0x2f, 0x40}}
+{0x7b807041, 0xe60a, 0x4384, {0x93, 0x5f, 0xaf, 0x30, 0x61, 0xd8, 0xb8, 0x15}}
 
 #define NS_SHISTORY_CONTRACTID "@mozilla.org/browser/shistory;1"
 %}
 
-[scriptable, uuid(b4440e2e-0fc2-11e3-971f-59e799890b3c)]
+[scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
 interface nsISHistory: nsISupports
 {
   /**
    * A readonly property of the interface that returns 
    * the number of toplevel documents currently available
    * in session history.
    */
    readonly attribute long count;
@@ -142,9 +142,21 @@ interface nsISHistory: nsISupports
    * @see nsISimpleEnumerator
    * @see nsISHEntry
    * @see QueryInterface()
    * @see do_QueryInterface()
    */
    readonly attribute nsISimpleEnumerator SHistoryEnumerator;
 
    void reloadCurrentEntry();
+
+   /**
+   * Called to obtain the index to a given history entry.
+   *
+   * @param aEntry            The entry to obtain the index of.
+   *
+   * @return                  <code>NS_OK</code> index for the history entry
+   *                          is obtained successfully.
+   *                          <code>NS_ERROR_FAILURE</code> Error in obtaining
+   *                          index for the given history entry.
+   */
+   long getIndexOfEntry(in nsISHEntry aEntry);
 };
--- a/docshell/shistory/public/nsISHistoryListener.idl
+++ b/docshell/shistory/public/nsISHistoryListener.idl
@@ -16,17 +16,17 @@ interface nsIURI;
  * A session history listener will be notified when pages are added, removed
  * and loaded from session history. It can prevent any action (except adding
  * a new session history entry) from happening by returning false from the
  * corresponding callback method.
  *
  * A session history listener can be registered on a particular nsISHistory
  * instance via the nsISHistory::addSHistoryListener() method.
  */
-[scriptable, uuid(3b07f591-e8e1-11d4-9882-00c04fa02f40)]
+[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)]
 interface nsISHistoryListener : nsISupports 
 {
   /**
    * Called when a new document is added to session history. New documents are
    * added to session history by docshell when new pages are loaded in a frame
    * or content area, for example via nsIWebNavigation::loadURI()
    *
    * @param aNewURI     The URI of the document to be added to session history.
@@ -82,9 +82,19 @@ interface nsISHistoryListener : nsISuppo
    * from history, to erase evidence of prior page loads, etc.
    *
    * To purge documents from session history call nsISHistory::PurgeHistory()
    *
    * @param aNumEntries   The number of entries to be removed from session history.
    * @return              Whether the operation can proceed.
    */
    boolean OnHistoryPurge(in long aNumEntries);
+
+  /**
+   * Called when an entry is replaced in the session history. Entries are
+   * replaced when navigating away from non-persistent history entries (such as
+   * about pages) and when history.replaceState is called.
+   *
+   * @param aIndex        The index in session history of the entry being
+  *                       replaced
+   */
+   void OnHistoryReplaceEntry(in long aIndex);
 };
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -410,28 +410,30 @@ nsSHistory::AddEntry(nsISHEntry * aSHEnt
 
   if(mListRoot)
     GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
 
   bool currentPersist = true;
   if(currentTxn)
     currentTxn->GetPersist(&currentPersist);
 
+  int32_t currentIndex = mIndex;
+
   if(!currentPersist)
   {
+    NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex));
     NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry),NS_ERROR_FAILURE);
     currentTxn->SetPersist(aPersist);
     return NS_OK;
   }
 
   nsCOMPtr<nsISHTransaction> txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID));
   NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIURI> uri;
-  int32_t currentIndex = mIndex;
   aSHEntry->GetURI(getter_AddRefs(uri));
   NOTIFY_LISTENERS(OnHistoryNewEntry, (uri));
 
   // If a listener has changed mIndex, we need to get currentTxn again,
   // otherwise we'll be left at an inconsistent state (see bug 320742)
   if (currentIndex != mIndex) {
     GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
   }
@@ -550,16 +552,60 @@ nsSHistory::GetTransactionAtIndex(int32_
     }  //NS_SUCCEEDED
     else 
       return NS_ERROR_FAILURE;
   }  // while 
   
   return NS_OK;
 }
 
+
+/* Get the index of a given entry */
+NS_IMETHODIMP
+nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult) {
+  NS_ENSURE_ARG(aSHEntry);
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = -1;
+
+  if (mLength <= 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsISHTransaction> currentTxn;
+  int32_t cnt = 0;
+
+  nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn));
+  if (NS_FAILED(rv) || !currentTxn) {
+    return NS_ERROR_FAILURE;
+  }
+
+  while (true) {
+    nsCOMPtr<nsISHEntry> entry;
+    rv = currentTxn->GetSHEntry(getter_AddRefs(entry));
+    if (NS_FAILED(rv) || !entry) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (aSHEntry == entry) {
+      *aResult = cnt;
+      break;
+    }
+
+    rv = currentTxn->GetNext(getter_AddRefs(currentTxn));
+    if (NS_FAILED(rv) || !currentTxn) {
+      return NS_ERROR_FAILURE;
+    }
+
+    cnt++;
+  }
+
+  return NS_OK;
+}
+
+
 #ifdef DEBUG
 nsresult
 nsSHistory::PrintHistory()
 {
 
   nsCOMPtr<nsISHTransaction>   txn;
   int32_t index = 0;
   nsresult rv;
@@ -729,16 +775,18 @@ nsSHistory::ReplaceEntry(int32_t aIndex,
 
   if (!mListRoot) // Session History is not initialised.
     return NS_ERROR_FAILURE;
 
   rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn));
 
   if(currentTxn)
   {
+    NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex));
+
     // Set the replacement entry in the transaction
     rv = currentTxn->SetSHEntry(aReplaceEntry);
     rv = currentTxn->SetPersist(true);
   }
   return rv;
 }
 
 NS_IMETHODIMP
--- a/docshell/test/browser/browser_bug422543.js
+++ b/docshell/test/browser/browser_bug422543.js
@@ -34,16 +34,18 @@ SHistoryListener.prototype = {
     return this.retval;
   },
 
   OnHistoryReload: function (aReloadURI, aReloadFlags) {
     this.last = "reload";
     return this.retval;
   },
 
+  OnHistoryReplaceEntry: function (aIndex) {},
+
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISHistoryListener,
                                          Ci.nsISupportsWeakReference])
 };
 
 let gFirstListener = new SHistoryListener();
 let gSecondListener = new SHistoryListener();
 
 function test() {
--- a/docshell/test/browser/browser_bug670318.js
+++ b/docshell/test/browser/browser_bug670318.js
@@ -34,16 +34,17 @@ function test() {
       return true;
     },
 
     OnHistoryReload: function () true,
     OnHistoryGoBack: function () true,
     OnHistoryGoForward: function () true,
     OnHistoryGotoIndex: function () true,
     OnHistoryPurge: function () true,
+    OnHistoryReplaceEntry: function () true,
 
     QueryInterface: XPCOMUtils.generateQI([Ci.nsISHistoryListener,
                                            Ci.nsISupportsWeakReference])
   };
 
   let tab = gBrowser.loadOneTab(URL, {inBackground: false});
   let browser = tab.linkedBrowser;
   let history = browser.sessionHistory;
--- a/embedding/tests/winEmbed/WebBrowserChrome.cpp
+++ b/embedding/tests/winEmbed/WebBrowserChrome.cpp
@@ -361,16 +361,22 @@ WebBrowserChrome::OnHistoryReload(nsIURI
 NS_IMETHODIMP
 WebBrowserChrome::OnHistoryPurge(int32_t aNumEntries, bool *aContinue)
 {
     // For now let the operation continue
     *aContinue = false;
     return SendHistoryStatusMessage(nullptr, "purge", aNumEntries);
 }
 
+NS_IMETHODIMP
+WebBrowserChrome::OnHistoryReplaceEntry(int32_t aIndex)
+{
+    return SendHistoryStatusMessage(nullptr, "replace", aIndex);
+}
+
 static void
 AppendIntToCString(int32_t info1, nsCString& aResult)
 {
   char intstr[10];
   _snprintf(intstr, sizeof(intstr) - 1, "%i", info1);
   intstr[sizeof(intstr) - 1] = '\0';
   aResult.Append(intstr);
 }
@@ -433,16 +439,21 @@ WebBrowserChrome::SendHistoryStatusMessa
         status.Append(" Url: ");
         status.Append(uriSpec);
     }
     else if (!(strcmp(operation, "purge")))
     {
         AppendIntToCString(info1, status);
         status.Append(" purged from Session History");
     }
+    else if (!(strcmp(operation, "replace")))
+    {
+        status.Assign("Replacing HistoryIndex: ");
+        AppentIntToCString(info1, status);
+    }
 
     nsString wstatus;
     NS_CStringToUTF16(status, NS_CSTRING_ENCODING_UTF8, wstatus);
     WebBrowserChromeUI::UpdateStatusBarText(this, wstatus.get());
 
     return NS_OK;
 }
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4245,16 +4245,21 @@ Tab.prototype = {
     return true;
   },
 
   OnHistoryPurge: function(aNumEntries) {
     this._sendHistoryEvent("Purge", { numEntries: aNumEntries });
     return true;
   },
 
+  OnHistoryReplaceEntry: function(aIndex) {
+    // we don't do anything with this, so don't propogate it
+    // for now anyway.
+  },
+
   get metadata() {
     return ViewportHandler.getMetadataForDocument(this.browser.contentDocument);
   },
 
   /** Update viewport when the metadata changes. */
   updateViewportMetadata: function updateViewportMetadata(aMetadata, aInitialLoad) {
     if (Services.prefs.getBoolPref("browser.ui.zoom.force-user-scalable")) {
       aMetadata.allowZoom = true;