Bug 635844 part 2: Update pushState to latest agreed behavior. r=jlebar a=beltzner/jst
authorJonas Sicking <jonas@sicking.cc>
Mon, 28 Feb 2011 23:08:56 -0800
changeset 63196 9e139c2c0c25bcee4d9cb59cd33483297bc42db2
parent 63195 d34e0e81327a059a1bb6773522bb1d13ef517a37
child 63197 a7b9ace8b3a36f3738d3ec89cb02aa619724f429
push id19071
push usersicking@mozilla.com
push dateTue, 01 Mar 2011 07:09:43 +0000
treeherdermozilla-central@a7b9ace8b3a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, beltzner, jst
bugs635844
milestone2.0b13pre
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 635844 part 2: Update pushState to latest agreed behavior. r=jlebar a=beltzner/jst
browser/components/sessionstore/test/browser/browser_500328.js
docshell/base/nsDocShell.cpp
docshell/test/file_bug590573_1.html
docshell/test/file_bug590573_2.html
docshell/test/test_bug590573.html
dom/base/nsDOMClassInfo.cpp
dom/base/nsHistory.cpp
dom/base/nsHistory.h
dom/interfaces/base/nsIDOMHistory.idl
dom/interfaces/core/nsIDOMNSDocument.idl
dom/tests/mochitest/whatwg/file_bug500328_1.html
dom/tests/mochitest/whatwg/test_bug500328.html
layout/base/nsDocumentViewer.cpp
--- a/browser/components/sessionstore/test/browser/browser_500328.js
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -39,22 +39,22 @@ function checkState(tab) {
   // from the popState event are as we expect them to be.
   //
   // We also add a node to the document's body when after going back and make
   // sure it's still there after we go forward -- this is to test that the two
   // history entries correspond to the same document.
 
   let popStateCount = 0;
 
-  tab.linkedBrowser.addEventListener("popstate", function(aEvent) {
+  let handler = function(aEvent) {
     let contentWindow = tab.linkedBrowser.contentWindow;
     if (popStateCount == 0) {
       popStateCount++;
-      ok(aEvent.state, "Event should have a state property.");
-      is(JSON.stringify(aEvent.state), JSON.stringify({obj1:1}),
+      //ok(aEvent.state, "Event should have a state property.");
+      is(JSON.stringify(tab.linkedBrowser.contentWindow.history.state), JSON.stringify({obj1:1}),
          "first popstate object.");
 
       // Add a node with id "new-elem" to the document.
       let doc = contentWindow.document;
       ok(!doc.getElementById("new-elem"),
          "doc shouldn't contain new-elem before we add it.");
       let elem = doc.createElement("div");
       elem.id = "new-elem";
@@ -73,20 +73,24 @@ function checkState(tab) {
       let doc = contentWindow.document;
       let newElem = doc.getElementById("new-elem");
       ok(newElem, "doc should contain new-elem.");
       newElem.parentNode.removeChild(newElem);
       ok(!doc.getElementById("new-elem"), "new-elem should be removed.");
 
       // Clean up after ourselves and finish the test.
       tab.linkedBrowser.removeEventListener("popstate", arguments.callee, false);
+      tab.linkedBrowser.removeEventListener("load", arguments.callee, false);
       gBrowser.removeTab(tab);
       finish();
     }
-  }, true);
+  };
+
+  tab.linkedBrowser.addEventListener("load", handler, true);
+  tab.linkedBrowser.addEventListener("popstate", handler, true);
 
   tab.linkedBrowser.contentWindow.history.back();
 }
 
 function test() {
   // Tests session restore functionality of history.pushState and
   // history.replaceState().  (Bug 500328)
 
@@ -110,17 +114,17 @@ function test() {
       // After these push/replaceState calls, the window should have three
       // history entries:
       //   testURL (state object: null)      <-- oldest
       //   testURL (state object: {obj1:1})
       //   page2   (state object: {obj3:3})  <-- newest
       let contentWindow = tab.linkedBrowser.contentWindow;
       let history = contentWindow.history;
       history.pushState({obj1:1}, "title-obj1");
-      history.pushState({obj2:2}, "title-obj2", "page2");
+      history.pushState({obj2:2}, "title-obj2", "?foo");
       history.replaceState({obj3:3}, "title-obj3");
 
       let state = ss.getTabState(tab);
 
       // In order to make sure that setWindowState actually modifies the
       // window's state, we modify the state here.  checkState will fail if
       // this change isn't overwritten by setWindowState.
       history.replaceState({should_be_overwritten:true}, "title-overwritten");
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5777,16 +5777,19 @@ nsDocShell::Embed(nsIContentViewer * aCo
             SetBaseUrlForWyciwyg(aContentViewer);
     }
     // XXX What if SetupNewViewer fails?
     if (mLSHE) {
         // Restore the editing state, if it's stored in session history.
         if (mLSHE->HasDetachedEditor()) {
             ReattachEditorToWindow(mLSHE);
         }
+        // Set history.state
+        SetDocCurrentStateObj(mLSHE);
+
         SetHistoryEntry(&mOSHE, mLSHE);
     }
 
     PRBool updateHistory = PR_TRUE;
 
     // Determine if this type of load should update history
     switch (mLoadType) {
     case LOAD_NORMAL_REPLACE:
@@ -6067,20 +6070,16 @@ nsDocShell::EndPageLoad(nsIWebProgress *
     // someone is so very very rude as to bring this window down
     // during this load handler.
     //
     nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
     // Notify the ContentViewer that the Document has finished loading.  This
     // will cause any OnLoad(...) and PopState(...) handlers to fire.
     if (!mEODForCurrentDocument && mContentViewer) {
-        // Set the pending state object which will be returned to the page in
-        // the popstate event.
-        SetDocCurrentStateObj(mLSHE);
-
         mIsExecutingOnLoadHandler = PR_TRUE;
         mContentViewer->LoadComplete(aStatus);
         mIsExecutingOnLoadHandler = PR_FALSE;
 
         mEODForCurrentDocument = PR_TRUE;
 
         // If all documents have completed their loading
         // favor native event dispatch priorities
@@ -8424,17 +8423,22 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 doc->SetDocumentURI(newURI);
             }
 
             SetDocCurrentStateObj(mOSHE);
 
             // Dispatch the popstate and hashchange events, as appropriate.
             nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
             if (window) {
-                window->DispatchSyncPopState();
+                // Need the doHashchange check here since sameDocIdent is
+                // false if we're navigating to a new shentry (i.e. a aSHEntry
+                // is null), such as when clicking a <a href="#foo">.
+                if (sameDocIdent || doHashchange) {
+                  window->DispatchSyncPopState();
+                }
 
                 if (doHashchange)
                   window->DispatchAsyncHashchange();
             }
 
             return NS_OK;
         }
     }
@@ -9823,16 +9827,17 @@ nsDocShell::AddState(nsIVariant *aData, 
         SetCurrentURI(newURI, nsnull, PR_TRUE);
         document->SetDocumentURI(newURI);
 
         AddURIVisit(newURI, oldURI, oldURI, 0);
     }
     else {
         FireDummyOnLocationChange();
     }
+    document->SetCurrentStateObject(dataStr);
 
     return NS_OK;
 }
 
 PRBool
 nsDocShell::ShouldAddToSessionHistory(nsIURI * aURI)
 {
     // I believe none of the about: urls should go in the history. But then
--- a/docshell/test/file_bug590573_1.html
+++ b/docshell/test/file_bug590573_1.html
@@ -1,7 +1,8 @@
 <html>
-<body onpopstate='opener.page1Popstate();'>
+<body onpopstate='opener.page1Popstate();' onload='opener.page1Load();'
+      onpageshow='opener.page1PageShow();'>
 
 <div style='height:300%' id='div1'>This is a very tall div.</div>
 
 </body>
 </html>
--- a/docshell/test/file_bug590573_2.html
+++ b/docshell/test/file_bug590573_2.html
@@ -1,7 +1,8 @@
 <html>
-<body onpopstate='opener.page2Popstate()'>
+<body onpopstate='opener.page2Popstate();' onload='opener.page2Load();'
+      onpageshow='opener.page2PageShow();'>
 
 <div style='height:300%' id='div2'>The second page also has a big div.</div>
 
 </body>
 </html>
--- a/docshell/test/test_bug590573.html
+++ b/docshell/test/test_bug590573.html
@@ -12,42 +12,94 @@ https://bugzilla.mozilla.org/show_bug.cg
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590573">Mozilla Bug 590573</a>
 
 <script type='application/javascript;version=1.7'>
 SimpleTest.waitForExplicitFinish();
 
 // Listen to the first callback, since this indicates that the page loaded.
-var page1PopstateCallbackEnabled = true;
+var page1LoadCallbackEnabled = true;
+function page1Load()
+{
+  if (page1LoadCallbackEnabled) {
+    page1LoadCallbackEnabled = false;
+    dump('Got page1 load.\n');
+    pageLoad();
+  }
+  else {
+    dump('Ignoring page1 load.\n');
+  }
+}
+
+var page1PopstateCallbackEnabled = false;
 function page1Popstate()
 {
   if (page1PopstateCallbackEnabled) {
     page1PopstateCallbackEnabled = false;
     dump('Got page1 popstate.\n');
     pageLoad();
   }
   else {
     dump('Ignoring page1 popstate.\n');
   }
 }
 
+var page1PageShowCallbackEnabled = false;
+function page1PageShow()
+{
+  if (page1PageShowCallbackEnabled) {
+    page1PageShowCallbackEnabled = false;
+    dump('Got page1 pageshow.\n');
+    pageLoad();
+  }
+  else {
+    dump('Ignoring page1 pageshow.\n');
+  }
+}
+
+var page2LoadCallbackEnabled = false;
+function page2Load()
+{
+  if (page2LoadCallbackEnabled) {
+    page2LoadCallbackEnabled = false;
+    dump('Got page2 popstate.\n');
+    pageLoad();
+  }
+  else {
+    dump('Ignoring page2 popstate.\n');
+  }
+}
+
 var page2PopstateCallbackEnabled = false;
 function page2Popstate()
 {
   if (page2PopstateCallbackEnabled) {
     page2PopstateCallbackEnabled = false;
     dump('Got page2 popstate.\n');
     pageLoad();
   }
   else {
     dump('Ignoring page2 popstate.\n');
   }
 }
 
+var page2PageShowCallbackEnabled = false;
+function page2PageShow()
+{
+  if (page2PageShowCallbackEnabled) {
+    page2PageShowCallbackEnabled = false;
+    dump('Got page2 pageshow.\n');
+    pageLoad();
+  }
+  else {
+    dump('Ignoring page2 pageshow.\n');
+  }
+}
+
 function dumpSHistory(theWindow)
 {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   const Ci = Components.interfaces;
   let sh = theWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .sessionHistory;
@@ -111,33 +163,33 @@ function pageLoad()
 
     // Now test that the scroll position is persisted when we have real
     // navigations involved.  First, we need to spin the event loop so that the
     // navigation doesn't replace our current history entry.
 
     setTimeout(pageLoad, 0);
   }
   else if (loads == 2) {
-    page2PopstateCallbackEnabled = true;
+    page2LoadCallbackEnabled = true;
     popup.location = 'file_bug590573_2.html';
   }
   else if (loads == 3) {
     ok(popup.location.href.match('file_bug590573_2.html$'),
        "Location was " + popup.location +
        " but should end with file_bug590573_2.html");
 
     is(popup.scrollY, 0, "test 7");
     popup.scroll(0, 300);
 
     // We need to spin the event loop again before we go back, otherwise the
     // scroll positions don't get updated properly.
     setTimeout(pageLoad, 0);
   }
   else if (loads == 4) {
-    page1PopstateCallbackEnabled = true;
+    page1PageShowCallbackEnabled = true;
     popup.history.back();
   }
   else if (loads == 5) {
     // Spin the event loop again so that we get the right scroll positions.
     setTimeout(pageLoad, 0);
   }
   else if (loads == 6) {
     is(popup.location.search, "?pushed");
@@ -148,17 +200,17 @@ function pageLoad()
     is(popup.scrollY, 150, "test 9");
     popup.history.forward();
     is(popup.scrollY, 200, "test 10");
 
     // Spin one last time...
     setTimeout(pageLoad, 0);
   }
   else if (loads == 7) {
-    page2PopstateCallbackEnabled = true;
+    page2PageShowCallbackEnabled = true;
     popup.history.forward();
   }
   else if (loads == 8) {
     is(popup.scrollY, 300, "test 11");
     popup.close();
     SimpleTest.finish();
   }
   else {
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -2187,18 +2187,17 @@ nsDOMClassInfo::WrapNativeParent(JSConte
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDocumentRange)                              \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDocumentTraversal)                          \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDocumentXBL)                                \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)                              \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)                                \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOM3Document)                                  \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOM3Node)                                      \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMXPathEvaluator)                             \
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNodeSelector)                               \
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSDocument_MOZILLA_2_0_BRANCH)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMNodeSelector)
 
 
 #define DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES                                \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSHTMLElement)                              \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMElementCSSInlineStyle)                      \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSEventTarget)                              \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)                                \
     DOM_CLASSINFO_MAP_ENTRY(nsIDOM3Node)                                      \
@@ -2323,16 +2322,17 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(BarProp, nsIDOMBarProp)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBarProp)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(History, nsIDOMHistory)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHistory)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMHistory_MOZILLA_2_0_BRANCH)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(Screen, nsIDOMScreen)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMScreen)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(DOMPrototype, nsIDOMDOMConstructor)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMConstructor)
@@ -10034,17 +10034,17 @@ nsStringArraySH::GetProperty(nsIXPConnec
 
 
 // History helper
 
 NS_IMETHODIMP
 nsHistorySH::PreCreate(nsISupports *nativeObj, JSContext *cx,
                        JSObject *globalObj, JSObject **parentObj)
 {
-  nsHistory *history = (nsHistory *)nativeObj;
+  nsHistory *history = (nsHistory *)((nsIDOMHistory*)nativeObj);
   nsCOMPtr<nsPIDOMWindow> innerWindow;
   history->GetWindow(getter_AddRefs(innerWindow));
   if (!innerWindow) {
     NS_WARNING("refusing to create history object in the wrong scope");
     return NS_ERROR_FAILURE;
   }
 
   *parentObj = static_cast<nsGlobalWindow *>(innerWindow.get())->FastGetGlobalJSObject();
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -80,16 +80,17 @@ nsHistory::~nsHistory()
 
 
 DOMCI_DATA(History, nsHistory)
 
 // QueryInterface implementation for nsHistory
 NS_INTERFACE_MAP_BEGIN(nsHistory)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMHistory)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHistory)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMHistory_MOZILLA_2_0_BRANCH)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(History)
 NS_INTERFACE_MAP_END
 
 
 NS_IMPL_ADDREF(nsHistory)
 NS_IMPL_RELEASE(nsHistory)
 
 
@@ -333,16 +334,36 @@ nsHistory::ReplaceState(nsIVariant *aDat
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
 
   // PR_TRUE tells the docshell to modify the current SHEntry, rather than
   // create a new one.
   return docShell->AddState(aData, aTitle, aURL, PR_TRUE);
 }
 
 NS_IMETHODIMP
+nsHistory::GetState(nsIVariant **aState)
+{
+  *aState = nsnull;
+
+  nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
+  if (!win)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  if (!nsContentUtils::CanCallerAccess(win->GetOuterWindow()))
+    return NS_ERROR_DOM_SECURITY_ERR;
+
+  nsCOMPtr<nsIDOMNSDocument_MOZILLA_2_0_BRANCH> doc =
+    do_QueryInterface(win->GetExtantDocument());
+  if (!doc)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  return doc->GetMozCurrentStateObject(aState);
+}
+
+NS_IMETHODIMP
 nsHistory::Item(PRUint32 aIndex, nsAString& aReturn)
 {
   aReturn.Truncate();
   if (!nsContentUtils::IsCallerTrustedForRead()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsresult rv = NS_OK;
--- a/dom/base/nsHistory.h
+++ b/dom/base/nsHistory.h
@@ -44,27 +44,29 @@
 #include "nsIScriptContext.h"
 #include "nsISHistory.h"
 #include "nsIWeakReference.h"
 #include "nsPIDOMWindow.h"
 
 class nsIDocShell;
 
 // Script "History" object
-class nsHistory : public nsIDOMHistory
+class nsHistory : public nsIDOMHistory,
+                  public nsIDOMHistory_MOZILLA_2_0_BRANCH
 {
 public:
   nsHistory(nsPIDOMWindow* aInnerWindow);
   virtual ~nsHistory();
 
   // nsISupports
   NS_DECL_ISUPPORTS
 
   // nsIDOMHistory
   NS_DECL_NSIDOMHISTORY
+  NS_DECL_NSIDOMHISTORY_MOZILLA_2_0_BRANCH
 
   nsIDocShell *GetDocShell() {
     nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
     if (!win)
       return nsnull;
     return win->GetDocShell();
   }
 
--- a/dom/interfaces/base/nsIDOMHistory.idl
+++ b/dom/interfaces/base/nsIDOMHistory.idl
@@ -56,8 +56,14 @@ interface nsIDOMHistory : nsISupports
   DOMString                  item(in unsigned long index);
   void                       pushState(in nsIVariant aData,
                                        in DOMString aTitle,
                                        [optional] in DOMString aURL);
   void                       replaceState(in nsIVariant aData,
                                           in DOMString aTitle,
                                           [optional] in DOMString aURL);
 };
+
+[scriptable, uuid(949fcdc1-664b-4a4b-939a-7144c94b48ac)]
+interface nsIDOMHistory_MOZILLA_2_0_BRANCH : nsISupports
+{
+  readonly attribute nsIVariant state;
+};
--- a/dom/interfaces/core/nsIDOMNSDocument.idl
+++ b/dom/interfaces/core/nsIDOMNSDocument.idl
@@ -126,13 +126,14 @@ interface nsIDOMNSDocument : nsISupports
    * @param aImageElement a DOM element to be used as the source image of
    * |-moz-element(#aImageElementId)|. If this is null, the function will
    * unregister the image element ID |aImageElementId|.
    */
   void mozSetImageElement(in DOMString aImageElementId,
                           in nsIDOMElement aImageElement);
 };
 
+// WARNING! This interface is currently not exposed to script in classinfo
 [scriptable, uuid(fee67aed-3b94-4136-ad7d-fb88ef23f45f)]
 interface nsIDOMNSDocument_MOZILLA_2_0_BRANCH : nsISupports
 {
-  readonly attribute nsIVariant mozCurrentStateObject;
+  [noscript] readonly attribute nsIVariant mozCurrentStateObject;
 };
--- a/dom/tests/mochitest/whatwg/file_bug500328_1.html
+++ b/dom/tests/mochitest/whatwg/file_bug500328_1.html
@@ -6,33 +6,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
 <title>test 1</title>
   <style>
     a           { fill:blue; }
     a:visited   { fill:purple; }
   </style>
 </head>
-<body onload="load();" onpopstate="popstate(event);">
+<body onload="load(event);" onpopstate="popstate(event);" onpageshow="pageshow(event);">
 <script type="application/javascript">
-  function load() {
+  if (parent && parent.onChildScript)
+    parent.onChildScript(history.state);
+  if (opener && opener.onChildScript)
+    opener.onChildScript(history.state);
+
+  function load(e) {
     if(parent && parent.onChildLoad)
-      parent.onChildLoad();
+      parent.onChildLoad(e);
     if(opener && opener.onChildLoad)
-      opener.onChildLoad();
+      opener.onChildLoad(e);
+  }
+
+  function pageshow(e) {
+    if(parent && parent.onChildPageShow)
+      parent.onChildPageShow(e);
+    if(opener && opener.onChildPageShow)
+      opener.onChildPageShow(e);
   }
 
   function popstate(e) {
     if(parent && parent.onChildLoad)
       parent.onChildPopState(e);
     if(opener && opener.onChildLoad)
       opener.onChildPopState(e);
   }
 
   function navigateTo(loc) {
     location = loc;
   }
+
 </script>
 
 <a id="link-anchor1", href="#1">Link Anchor1</a>
 <a id="link-self" href="file_bug500328_1.html">Self</a>
 </body>
 </html>
--- a/dom/tests/mochitest/whatwg/test_bug500328.html
+++ b/dom/tests/mochitest/whatwg/test_bug500328.html
@@ -31,19 +31,21 @@ var iframeCw = iframe.contentWindow;
 
 var iframe2 = document.getElementById("iframe2");
 var iframe2Cw = iframe2.contentWindow;
 
 const unvisitedColor = "rgb(0, 0, 238)";
 const visitedColor = "rgb(85, 26, 139)";
 
 var gCallbackOnIframeLoad = false;
+var gCallbackOnIframePageShow = false;
 var gCallbackOnPopState = false;
 var gNumPopStates = 0;
 var gLastPopStateEvent;
+var gLastScriptHistoryState;
 
 var gGen;
 
 function statusMsg(msg) {
   var msgElem = document.createElement("p");
   msgElem.appendChild(document.createTextNode(msg));
 
   document.getElementById("status").appendChild(msgElem);
@@ -65,31 +67,69 @@ function onChildPopState(e) {
     gCallbackOnPopState = false;
     gGen.next();
   }
   else {
     statusMsg("Popstate(" + JSON.stringify(e.state) + ").  NOT calling gGen.next().");
   }
 }
 
-function onChildLoad() {
+function onChildScript(state) {
+  gLastScriptHistoryState = state;
+}
+
+function getURLFromEvent(e) {
+  try {
+    var target = e.target;
+    if ("contentWindow" in target) {
+      return target.contentWindow.location.toString();
+    }
+    if ("ownerDocument" in target && target.ownerDocument) {
+      return target.ownerDocument.location.toString();
+    }
+    if ("location" in target) {
+      return target.location.toString();
+    }
+    return target.toString();
+  }
+  catch(ex) {
+    return "<cross-site object>";
+  }
+}
+
+function onChildLoad(e) {
   if(gCallbackOnIframeLoad) {
-    statusMsg("Got load.  About to call gGen.next().");
+    statusMsg("Got load for " + getURLFromEvent(e) + ".  About to call gGen.next().");
     gCallbackOnIframeLoad = false;
     gGen.next();
   }
   else {
-    statusMsg("Got load, but not calling gGen.next() because gCallbackOnIframeLoad was false.");
+    statusMsg("Got load for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframeLoad was false.");
+  }
+}
+
+function onChildPageShow(e) {
+  if(gCallbackOnIframePageShow) {
+    statusMsg("Got pageshow for " + getURLFromEvent(e) + ".  About to call gGen.next().");
+    gCallbackOnIframePageShow = false;
+    gGen.next();
+  }
+  else {
+    statusMsg("Got pageshow for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframePageShow was false.");
   }
 }
 
 function enableChildLoadCallback() {
   gCallbackOnIframeLoad = true;
 }
 
+function enableChildPageShowCallback() {
+  gCallbackOnIframePageShow = true;
+}
+
 function enableChildPopStateCallback() {
   gCallbackOnPopState = true;
 }
 
 function clearPopStateCounter() {
   gNumPopStates = 0;
 }
 
@@ -177,34 +217,41 @@ function runTest() {
 
   /**
    * TEST 1 tests basic pushState functionality
    */
   enableChildLoadCallback();
   iframeCw.location = "file_bug500328_1.html";
   yield;
   innerLoc = iframeCw.location.toString();
-  // Load should be fired before popstate.
-  enableChildPopStateCallback();
+  // No popstate during initial load.
+  shortWait();
   yield;
-  popstateExpected("Expected initial popstate.");
-  statusMsg("Awake after first popstate.");
+  noPopStateExpected("No initial popstate.");
+  is(JSON.stringify(gLastScriptHistoryState), "null", "null initial state.");
+  statusMsg("Awake after first load.");
 
-  var testObj1 = 42;
-  var testObj2 = 4.2;
-  iframeCw.history.pushState(testObj1, "test 1");
-  is(iframeCw.location.search, "",
-     "First pushstate should leave us where we were.");
-
-  // Make sure that this pushstate doesn't trigger a hashchange.
+  // Make sure that the pushstate below doesn't trigger a hashchange.
   iframeCw.onhashchange = function() {
     ok(false, "Pushstate shouldn't trigger a hashchange.");
   };
 
+  var testObj1 = 42;
+  var testObj2 = { x: 4.2 };
+  iframeCw.history.pushState(testObj1, "test 1");
+  is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj1),
+     "correct state after pushState");
+  is(iframeCw.location.search, "",
+     "First pushstate should leave us where we were.");
+
   iframeCw.history.pushState(testObj2, "test 1#foo", "?test1#foo");
+  is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2),
+     "correct state after pushState");
+  isnot(iframeCw.history.state, testObj2,
+     "correct state object identity after pushState");
   is(iframeCw.location.search, "?test1",
      "Second pushstate should push us to '?test1'.");
   is(iframeCw.location.hash, "#foo",
      "Second pushstate should push us to '#foo'");
   shortWait();
   yield;
 
   // Let the hashchange event fire, if it's going to.
@@ -217,77 +264,90 @@ function runTest() {
   // are completely synchronous.  In fact, if we did yield, JS would throw an
   // error because we'd be calling gGen.next from within gGen.next.
   iframeCw.history.back();
 
   statusMsg("Awake after going back to page 1.");
   popstateExpected("Going back to page 1 should trigger a popstate.");
   is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1),
      "Wrong state object popped after going back to page 1.");
-  ok(gLastPopStateEvent.state === iframeCw.document.mozCurrentStateObject,
+  ok(gLastPopStateEvent.state === iframeCw.history.state,
      "Wrong state object in document after going back to page 1.");
   ok(iframeCw.location.toString().match(/file_bug500328_1.html$/),
       "Going back to page 1 hould take us to original page.");
 
   iframeCw.history.back();
   popstateExpected("Going back to page 0 should trigger a popstate.");
   is(gLastPopStateEvent.state, null,
       "Going back to page 0 should pop a null state.");
-  is(iframeCw.document.mozCurrentStateObject, null,
+  is(iframeCw.history.state, null,
       "Going back to page 0 should pop a null state.");
   is(iframeCw.location.search, "",
       "Going back to page 0 should clear the querystring.");
 
   iframeCw.history.forward();
   popstateExpected("Going forward to page 1 should trigger a popstate.");
   is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1),
       "Wrong state object popped after going forward to page 1.");
-  ok(gLastPopStateEvent.state === iframeCw.document.mozCurrentStateObject,
+  ok(gLastPopStateEvent.state === iframeCw.history.state,
       "Wrong state object in document after going forward to page 1.");
   ok(iframeCw.location.toString().match(/file_bug500328_1.html$/),
       "Going forward to page 1 should leave us at original page.");
 
-
   statusMsg("About to go forward to page 2.");
   iframeCw.history.forward();
   statusMsg("Awake after going forward to page 2.");
   popstateExpected("Going forward to page 2 should trigger a popstate.");
   is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj2),
      "Wrong state object popped after going forward to page 2.");
-  ok(iframeCw.document.mozCurrentStateObject === gLastPopStateEvent.state,
+  ok(iframeCw.history.state === gLastPopStateEvent.state,
      "Wrong state object in document after going forward to page 2.");
   ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/),
      "Going forward to page 2 took us to " + iframeCw.location.toString());
 
+  statusMsg("About to reload page 2.");
+  iframeCw.location.reload();
+  enableChildLoadCallback();
+  yield;
+  statusMsg("Awake after reloading page 2.");
+  noPopStateExpected("Reloading page 2 should not trigger popstate.");
+  is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2),
+     "Wrong state object after reloading page 2.");
+  is(JSON.stringify(gLastScriptHistoryState), JSON.stringify(testObj2),
+     "Wrong state object while reloading page 2.");
+  ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/),
+     "Reloading page 2 took us to " + iframeCw.location.toString());
+
   // The iframe's current location is file_bug500328_1.html?test1#foo.
   // Clicking link1 should take us to file_bug500328_1.html?test1#1.
 
   enableChildPopStateCallback();
   sendMouseEvent({type:'click'}, 'link-anchor1', iframeCw);
   yield;
   popstateExpected("Clicking on link-anchor1 should trigger a popstate.");
   is(iframeCw.location.search, "?test1",
       "search should be ?test1 after clicking link.");
   is(iframeCw.location.hash, "#1",
       "hash should be #1 after clicking link.");
+  ok(iframeCw.history.state === null,
+     "Wrong state object in document after clicking link to hash '#1'.");
 
   /*
    * Reload file_bug500328_1.html; we're now going to test that link hrefs
    * and colors are updated correctly on push/popstates.
    */
 
   iframe.onload = onChildLoad;
   enableChildLoadCallback();
   iframeCw.location = "about:blank";
   yield;
-  iframe.onload = null;
+  enableChildLoadCallback();
   iframeCw.location = "file_bug500328_1.html";
-  enableChildPopStateCallback();
   yield;
-  popstateExpected("No popstate after re-loading file_bug500328_1.html");
+  noPopStateExpected("No popstate after re-loading file_bug500328_1.html");
   statusMsg("Done loading file_bug500328_1.html for the second time.");
 
   var ifLink = iframeCw.document.getElementById("link-anchor1");
   var rand = Date.now() + "-" + Math.random();
   ifLink.href = rand;
 
   // Poll the document until the link has the correct color, or this test times
   // out.  Unfortunately I can't come up with a more elegant way to do this.
@@ -387,21 +447,22 @@ function runTest() {
    * We have to run this test in a popup rather than an iframe because only the
    * root docshell has a session history object.
    */
   statusMsg("About to open popup.");
   var popup = window.open("file_bug500328_1.html", "popup0",
                           "height=200,width=200,location=yes," +
                           "menubar=yes,status=yes,toolbar=yes,dependent=yes");
 
+  enableChildLoadCallback();
   var shistory = getSHistory(popup);
-
-  enableChildPopStateCallback();
   yield;
-  popstateExpected("Didn't get popstate after opening window.");
+  shortWait();
+  yield;
+  noPopStateExpected("Shouldn't get popstate after opening window.");
 
   popup.history.pushState(null, "title 0");
   ok(SpecialPowers.isBackButtonEnabled(popup),
      "Back button was not enabled after initial pushstate.");
 
   popup.document.title = "title 1";
 
   // Yield to the event loop so listeners will be notified of the title change
@@ -462,59 +523,53 @@ function runTest() {
   // If we fired the popstate event asynchronously, we'd expect this assert to
   // fire.
   popup.onpopstate = function() {
     ok(false, "Initial load of popup shouldn't give us a popstate.");
   };
 
   shistory = getSHistory(popup);
 
-  enableChildPopStateCallback();
+  enableChildLoadCallback();
   yield;
   statusMsg("Awake after loading content into popup.");
 
   popup.history.replaceState({n:1, ok:true}, "state 1", "good1.html");
   locationEndsWith(popup, "good1.html");
 
   // Even though we replaceState with title "state 1", the title should remain
   // "test 1" because we ignore the title argument in push/replaceState.  
   // See bug 544535.
   is(getSHTitle(shistory), "test 1", "SHEntry title 'state 1'");
 
   // Flush the event loop so our next load creates a new session history entry.
   shortWait();
   yield;
 
-  enableChildPopStateCallback();
+  enableChildLoadCallback();
   popup.location = "file_bug500328_1.html";
   yield;
 
   // Flush the event loop so nsDocShell::OnNewURI runs and our load is recorded
-  // properly.  OnNewURI is called after PopState fires, because OnNewURI
-  // results from an async event, while PopState is sync.  We'd have to do the
-  // same thing if we were listening to onload here, so this isn't
-  // unreasonable.
+  // properly.
   shortWait();
   yield;
 
   // Now go back and make sure everything is as it should be.
-  enableChildPopStateCallback();
+  enableChildPageShowCallback();
   popup.history.back();
   yield;
-
-  // Flush the event loop so the document's location is updated.
+  // Flush the event loop so the document's location is updated and any
+  // popstates fire.
   shortWait();
   yield;
-
-  // We had some popstates above without corresponding popstateExpected()
-  // calls, so we need to clear the counter.
-  clearPopStateCounter();
+  noPopStateExpected("no popstate during initial load");
 
   locationEndsWith(popup, "good1.html");
-  is(JSON.stringify(gLastPopStateEvent.state), '{"n":1,"ok":true}',
+  is(JSON.stringify(popup.history.state), '{"n":1,"ok":true}',
      "Wrong state popped after going back to initial state.");
 
   // We're back at state 0, which was replaceState-ed to state1.html.  Let's do
   // some push/pop/replaces to make sure everything works OK when we involve
   // large numbers of SHEntries.
   for(var i = 2; i <= 30; i++) {
     if (i % 3 == 0) {
       popup.history.pushState({n:i, ok:true}, "state " + i, "good" + i + ".html");
@@ -596,44 +651,41 @@ function runTest() {
    * Then at the end, page A should be displayed, not the 404 page.
    */
   enableChildLoadCallback();
   iframe.onload = onChildLoad;
   iframeCw.location = "about:blank";
   yield;
   iframe.onload = null;
 
-  enableChildPopStateCallback();
-  // navigate to http://localhost:8888/[...]/file_bug500328_1.html
+  enableChildLoadCallback();
+  // navigate to http://mochi.test:8888/[...]/file_bug500328_1.html
   iframeCw.location = innerLoc;
   yield;
 
-  // Let the PopState handler finish.  If we call refresh (below) from within
-  // the handler, we get slightly confused and can't tell that we're at a 404
-  // after the refresh.
-  shortWait();
+  // Let the load handler finish.
+  longWait();
   yield;
 
   // PushState to a URL which doesn't exist
   iframeCw.history.pushState({}, "", rand);
 
   // Refresh.  We'll end up a 404 page.
   iframe.onload = onChildLoad;
   enableChildLoadCallback();
   iframeCw.location.reload(true);
   yield;
+  iframe.onload = null;
 
   // Since the last page was a 404, going back should actually show the
   // contents of the old page, instead of persisting the contents of the 404
   // page.
-  clearPopStateCounter();
-  enableChildPopStateCallback();
+  enableChildPageShowCallback();
   iframeCw.history.back();
   yield;
-  popstateExpected("Didn't get popstate after going back.");
 
   // Make sure that we're actually showing the contents of
   // file_bug500328_1.html, as opposed to the 404 page.
   var identifierElem = iframeCw.document.getElementById("link-anchor1");
   ok(identifierElem != undefined && identifierElem != null,
      "iframe didn't contain file_bug500328_1.html's contents.");
 
   /**
@@ -654,40 +706,39 @@ function runTest() {
   yield;
 
   // Run within the onload handler.  This should work without issue.
   iframeCw.history.pushState(null, "", "newpage1.html");
 
   // iframeCw.navigateTo() causes the iframe to set its location on our
   // behalf.  We can't just set its location ourselves, because then *we*
   // become the referrer.
-  enableChildPopStateCallback();
+  enableChildLoadCallback();
   iframeCw.navigateTo("file_bug500328_1.html");
   yield;
 
   ok(iframeCw.document.referrer.toString().match(/newpage1.html$/),
-     "Wrong referrer after replaceState.  Expected newpage1.html, but was " +
+     "Wrong referrer after pushState.  Expected newpage1.html, but was " +
      iframeCw.document.referrer);
 
   /*
    * We're back at file_bug500328_1.html.  Now do the following:
    *   * replaceState to newpage2.html#foo
    *   * Click a link back to file_bug500328_1.html
    * The referrer should be newpage2.html, without the hash.
    */
   iframeCw.history.replaceState(null, null, "newpage2.html#foo");
-  enableChildPopStateCallback();
+  enableChildLoadCallback();
   sendMouseEvent({type:'click'}, 'link-self', iframeCw);
   yield;
 
   ok(iframeCw.document.referrer.toString().match(/newpage2.html$/),
      "Wrong referrer after replaceState.  Expected newpage2.html, but was " +
      iframeCw.document.referrer);
 
-
   /*
    * Set up a cycle with the popstate event to make sure it's properly
    * collected.
    */
   var evt = document.createEvent("popstateevent");
   evt.initEvent("foo", false, false, evt);
 
   /* */
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1109,20 +1109,16 @@ DocumentViewerImpl::LoadComplete(nsresul
     mPrintIsPending        = PR_FALSE;
     mPrintDocIsFullyLoaded = PR_TRUE;
     Print(mCachedPrintSettings, mCachedPrintWebProgressListner);
     mCachedPrintSettings           = nsnull;
     mCachedPrintWebProgressListner = nsnull;
   }
 #endif
 
-  if (!mStopped && window) {
-    window->DispatchSyncPopState();
-  }
-
   return rv;
 }
 
 NS_IMETHODIMP
 DocumentViewerImpl::PermitUnload(PRBool aCallerClosesWindow, PRBool *aPermitUnload)
 {
   *aPermitUnload = PR_TRUE;