Bug 602256: Using history.pushState in a page breaks history tracking for inner frames. r=bz, a=blocks-betaN
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 03 Feb 2011 09:27:39 -0800
changeset 61863 4b90fd0c1c4d249436d38b6a07a7e77e1fb79c90
parent 61862 f00b81064d570e372b70ac673ca4b00c40556f2e
child 61864 69f3807d4bb498a6bc9ffedbc8e2a02b7e92425b
push idunknown
push userunknown
push dateunknown
reviewersbz, blocks-betaN
bugs602256
milestone2.0b12pre
Bug 602256: Using history.pushState in a page breaks history tracking for inner frames. r=bz, a=blocks-betaN
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/shistory/src/nsSHEntry.cpp
dom/tests/mochitest/general/Makefile.in
dom/tests/mochitest/general/historyframes.html
dom/tests/mochitest/general/test_framedhistoryframes.html
dom/tests/mochitest/general/test_windowedhistoryframes.html
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3364,16 +3364,17 @@ nsDocShell::AddChildSHEntry(nsISHEntry *
         NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
 
         nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE));
         if (currentEntry) {
             PRUint32 cloneID = 0;
             nsCOMPtr<nsISHEntry> nextEntry;
             aCloneRef->GetID(&cloneID);
             rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry,
+                                 loadType == LOAD_PUSHSTATE,
                                  getter_AddRefs(nextEntry));
 
             if (NS_SUCCEEDED(rv)) {
                 nsCOMPtr<nsISHistoryInternal>
                     shPrivate(do_QueryInterface(mSessionHistory));
                 NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE);
                 rv = shPrivate->AddEntry(nextEntry, PR_TRUE);
             }
@@ -9913,16 +9914,26 @@ nsDocShell::AddToSessionHistory(nsIURI *
         if (expTime <=  now)
             expired = PR_TRUE;
     }
     if (expired)
         entry->SetExpirationStatus(PR_TRUE);
 
 
     if (root == static_cast<nsIDocShellTreeItem *>(this) && mSessionHistory) {
+        // Bug 629559: Detect if this is an anchor navigation and clone the
+        // session history in that case too
+        if (mLoadType == LOAD_PUSHSTATE) {
+            PRUint32 cloneID;
+            mOSHE->GetID(&cloneID);
+            nsCOMPtr<nsISHEntry> newEntry;
+            CloneAndReplace(mOSHE, this, cloneID, entry, PR_TRUE, getter_AddRefs(newEntry));
+            NS_ASSERTION(entry == newEntry, "The new session history should be in the new entry");
+        }
+
         // This is the root docshell
         if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {            
             // Replace current entry in session history.
             PRInt32  index = 0;   
             mSessionHistory->GetIndex(&index);
             nsCOMPtr<nsISHistoryInternal>   shPrivate(do_QueryInterface(mSessionHistory));
             // Replace the current entry with the new entry
             if (shPrivate)
@@ -10126,22 +10137,24 @@ nsDocShell::WalkHistoryEntries(nsISHEntr
 
     return NS_OK;
 }
 
 // callback data for WalkHistoryEntries
 struct NS_STACK_CLASS CloneAndReplaceData
 {
     CloneAndReplaceData(PRUint32 aCloneID, nsISHEntry *aReplaceEntry,
-                        nsISHEntry *aDestTreeParent)
+                        PRBool aCloneChildren, nsISHEntry *aDestTreeParent)
         : cloneID(aCloneID),
+          cloneChildren(aCloneChildren),
           replaceEntry(aReplaceEntry),
           destTreeParent(aDestTreeParent) { }
 
     PRUint32              cloneID;
+    PRBool                cloneChildren;
     nsISHEntry           *replaceEntry;
     nsISHEntry           *destTreeParent;
     nsCOMPtr<nsISHEntry>  resultEntry;
 };
 
 /* static */ nsresult
 nsDocShell::CloneAndReplaceChild(nsISHEntry *aEntry, nsDocShell *aShell,
                                  PRInt32 aEntryIndex, void *aData)
@@ -10161,30 +10174,41 @@ nsDocShell::CloneAndReplaceChild(nsISHEn
       }
       return NS_OK;
     }
     
     PRUint32 srcID;
     aEntry->GetID(&srcID);
 
     if (srcID == cloneID) {
-        // Just replace the entry, and don't walk the children.
+        // Replace the entry
         dest = replaceEntry;
         dest->SetIsSubFrame(PR_TRUE);
+
+        if (data->cloneChildren) {
+            // Walk the children
+            CloneAndReplaceData childData(cloneID, replaceEntry,
+                                          data->cloneChildren, dest);
+            result = WalkHistoryEntries(aEntry, aShell,
+                                        CloneAndReplaceChild, &childData);
+            if (NS_FAILED(result))
+                return result;
+        }
     } else {
         // Clone the SHEntry...
         result = aEntry->Clone(getter_AddRefs(dest));
         if (NS_FAILED(result))
             return result;
 
         // This entry is for a subframe navigation
         dest->SetIsSubFrame(PR_TRUE);
 
         // Walk the children
-        CloneAndReplaceData childData(cloneID, replaceEntry, dest);
+        CloneAndReplaceData childData(cloneID, replaceEntry,
+                                      data->cloneChildren, dest);
         result = WalkHistoryEntries(aEntry, aShell,
                                     CloneAndReplaceChild, &childData);
         if (NS_FAILED(result))
             return result;
 
         if (aShell)
             aShell->SwapHistoryEntries(aEntry, dest);
     }
@@ -10196,22 +10220,23 @@ nsDocShell::CloneAndReplaceChild(nsISHEn
     return result;
 }
 
 /* static */ nsresult
 nsDocShell::CloneAndReplace(nsISHEntry *aSrcEntry,
                                    nsDocShell *aSrcShell,
                                    PRUint32 aCloneID,
                                    nsISHEntry *aReplaceEntry,
+                                   PRBool aCloneChildren,
                                    nsISHEntry **aResultEntry)
 {
     NS_ENSURE_ARG_POINTER(aResultEntry);
     NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
 
-    CloneAndReplaceData data(aCloneID, aReplaceEntry, nsnull);
+    CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nsnull);
     nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
 
     data.resultEntry.swap(*aResultEntry);
     return rv;
 }
 
 void
 nsDocShell::SwapHistoryEntries(nsISHEntry *aOldEntry, nsISHEntry *aNewEntry)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -378,20 +378,23 @@ protected:
     NS_IMETHOD PersistLayoutHistoryState();
 
     // Clone a session history tree for subframe navigation.
     // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
     // for the entry with id |aCloneID|, which will be replaced with
     // |aReplaceEntry|.  |aSrcShell| is a (possibly null) docshell which
     // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
     // have that pointer updated to point to the cloned history entry.
+    // If aCloneChildren is true then the children of the entry with id
+    // |aCloneID| will be cloned into |aReplaceEntry|.
     static nsresult CloneAndReplace(nsISHEntry *aSrcEntry,
                                     nsDocShell *aSrcShell,
                                     PRUint32 aCloneID,
                                     nsISHEntry *aReplaceEntry,
+                                    PRBool aCloneChildren,
                                     nsISHEntry **aDestEntry);
 
     // Child-walking callback for CloneAndReplace
     static nsresult CloneAndReplaceChild(nsISHEntry *aEntry,
                                          nsDocShell *aShell,
                                          PRInt32 aChildIndex, void *aData);
 
     nsresult GetRootSessionHistory(nsISHistory ** aReturn);
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -139,16 +139,17 @@ nsSHEntry::nsSHEntry(const nsSHEntry &ot
   , mExpired(other.mExpired)
   , mSticky(PR_TRUE)
   , mDynamicallyCreated(other.mDynamicallyCreated)
   // XXX why not copy mContentType?
   , mCacheKey(other.mCacheKey)
   , mParent(other.mParent)
   , mViewerBounds(0, 0, 0, 0)
   , mOwner(other.mOwner)
+  , mStateData(other.mStateData)
   , mDocShellID(other.mDocShellID)
 {
 }
 
 static PRBool
 ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
 {
   if (aEntry) {
--- a/dom/tests/mochitest/general/Makefile.in
+++ b/dom/tests/mochitest/general/Makefile.in
@@ -42,27 +42,30 @@ VPATH		= @srcdir@
 relativesrcdir  = dom/tests/mochitest/general
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		497633.html \
 		489127.html \
+		historyframes.html \
 		test_497898.html \
 		test_bug504220.html \
 		test_consoleAPI.html \
 		test_domWindowUtils.html \
 		test_domWindowUtils_scrollXY.html \
 		test_innerScreen.xul \
 		test_offsets.html \
 		test_offsets.js \
 		test_offsets.xul \
 		test_windowProperties.html \
 		test_clipboard_events.html \
 		test_focusrings.xul \
 		test_nodesFromRect.html \
 		test_frameElementWrapping.html \
 		file_frameElementWrapping.html \
+		test_framedhistoryframes.html \
+		test_windowedhistoryframes.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/historyframes.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+  <title>Test for Bug 602256</title>
+</head>
+<body onload="SimpleTest.executeSoon(run_test)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<div id="content">
+  <iframe id="iframe" src="data:text/html,<p%20id='text'>Start</p>"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+var testWin = window.opener ? window.opener : window.parent;
+
+var SimpleTest = testWin.SimpleTest;
+function is() { testWin.is.apply(testWin, arguments); }
+
+var gFrame = null;
+
+var gState = null;
+
+window.addEventListener("popstate", function(aEvent) {
+  gState = aEvent.state;
+}, false);
+
+function waitForLoad(aCallback) {
+  function listener() {
+    gFrame.removeEventListener("load", listener, false);
+    SimpleTest.executeSoon(aCallback);
+  }
+
+  gFrame.addEventListener("load", listener, false);
+}
+
+function loadContent(aURL, aCallback) {
+  waitForLoad(aCallback);
+
+  gFrame.src = aURL;
+}
+
+function getURL() {
+  return gFrame.contentDocument.documentURI;
+}
+
+function getContent() {
+  return gFrame.contentDocument.getElementById("text").textContent;
+}
+
+var START = "data:text/html,<p%20id='text'>Start</p>";
+var URL1 = "data:text/html,<p%20id='text'>Test1</p>";
+var URL2 = "data:text/html,<p%20id='text'>Test2</p>";
+
+function run_test() {
+  window.history.pushState("START", window.location);
+
+  gFrame = document.getElementById("iframe");
+
+  test_basic_inner_navigation();
+}
+
+function end_test() {
+  testWin.done();
+}
+
+function test_basic_inner_navigation() {
+  // Navigate the inner frame a few times
+  loadContent(URL1, function() {
+    is(getURL(), URL1, "URL should be correct");
+    is(getContent(), "Test1", "Page should be correct");
+
+    loadContent(URL2, function() {
+      is(getURL(), URL2, "URL should be correct");
+      is(getContent(), "Test2", "Page should be correct");
+
+      // Test that history is working
+      waitForLoad(function() {
+        is(getURL(), URL1, "URL should be correct");
+        is(getContent(), "Test1", "Page should be correct");
+
+        waitForLoad(function() {
+          is(getURL(), URL2, "URL should be correct");
+          is(getContent(), "Test2", "Page should be correct");
+
+          test_state_navigation();
+        });
+        window.history.forward();
+      });
+      window.history.back();
+    });
+  });
+}
+
+function test_state_navigation() {
+  window.history.pushState("STATE1", window.location);
+
+  is(getURL(), URL2, "URL should be correct");
+  is(getContent(), "Test2", "Page should be correct");
+
+  window.history.pushState("STATE2", window.location);
+
+  is(getURL(), URL2, "URL should be correct");
+  is(getContent(), "Test2", "Page should be correct");
+
+  window.history.back();
+
+  is(gState, "STATE1", "State should be correct");
+  is(getURL(), URL2, "URL should be correct");
+  is(getContent(), "Test2", "Page should be correct");
+
+  window.history.forward();
+
+  is(gState, "STATE2", "State should be correct");
+  is(getURL(), URL2, "URL should be correct");
+  is(getContent(), "Test2", "Page should be correct");
+
+  window.history.back();
+  window.history.back();
+
+  is(gState, "START", "State should be correct");
+  is(getURL(), URL2, "URL should be correct");
+  is(getContent(), "Test2", "Page should be correct");
+
+  waitForLoad(function() {
+    is(getURL(), URL1, "URL should be correct");
+    is(getContent(), "Test1", "Page should be correct");
+
+    waitForLoad(function() {
+      is(gState, "START", "State should be correct");
+      is(getURL(), START, "URL should be correct");
+      is(getContent(), "Start", "Page should be correct");
+
+      end_test();
+    });
+
+    window.history.back();
+
+    is(gState, "START", "State should be correct");
+  });
+
+  window.history.back();
+  is(gState, "START", "State should be correct");
+}
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_framedhistoryframes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+  <title>Test for Bug 602256</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content">
+  <iframe id="iframe" src="historyframes.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_windowedhistoryframes.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+  <title>Test for Bug 602256</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+  subWin.close();
+  SimpleTest.finish();
+}
+
+var subWin = window.open("historyframes.html", "_blank");
+
+</script>
+</pre>
+</body>
+</html>