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 id18523
push userdtownsend@mozilla.com
push dateThu, 03 Feb 2011 17:37:03 +0000
treeherdermozilla-central@9c815db836e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, blocks-betaN
bugs602256
milestone2.0b12pre
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 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>