Bug 500328 - Implement History.pushState(), History.replaceState() methods. r=smaug, r=zpao, sr=sicking
authorJustin Lebar <jlebar@mozilla.com>
Tue, 01 Sep 2009 09:45:05 -0700
changeset 37802 a2f4681188684698c5f7fb70e06b03f449dfc942
parent 37801 afff5c13d2960bbebe4afe015e267326626c740b
child 37803 0fec65bf4b56cd90e27b4d44c3d6ea8a5d112541
push id11443
push usersicking@mozilla.com
push dateMon, 01 Feb 2010 22:56:30 +0000
treeherdermozilla-central@a2f468118868 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, zpao, sicking
bugs500328
milestone1.9.3a1pre
Bug 500328 - Implement History.pushState(), History.replaceState() methods. r=smaug, r=zpao, sr=sicking
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/Makefile.in
browser/components/sessionstore/test/browser/browser_500328.js
content/base/public/nsIDocument.h
content/base/src/nsContentUtils.cpp
content/base/src/nsGkAtomList.h
content/events/src/Makefile.in
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsDOMPopStateEvent.cpp
content/events/src/nsDOMPopStateEvent.h
content/events/src/nsEventDispatcher.cpp
docshell/base/crashtests/500328-1.html
docshell/base/crashtests/crashtests.list
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsDocShellLoadTypes.h
docshell/base/nsIDocShell.idl
docshell/base/nsIDocShellHistory.idl
docshell/shistory/public/nsISHEntry.idl
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/shistory/src/nsSHistory.cpp
docshell/shistory/src/nsSHistory.h
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfo.h
dom/base/nsDOMClassInfoID.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsHistory.cpp
dom/base/nsLocation.cpp
dom/base/nsPIDOMWindow.h
dom/interfaces/base/nsIDOMHistory.idl
dom/interfaces/events/Makefile.in
dom/interfaces/events/nsIDOMPopStateEvent.idl
dom/interfaces/json/nsIJSON.idl
dom/src/json/Makefile.in
dom/src/json/nsJSON.cpp
dom/tests/mochitest/whatwg/Makefile.in
dom/tests/mochitest/whatwg/file_bug500328_1.html
dom/tests/mochitest/whatwg/file_bug500328_2.html
dom/tests/mochitest/whatwg/test_bug500328.html
js/src/xpconnect/idl/nsIDispatchSupport.idl
js/src/xpconnect/idl/nsIXPConnect.idl
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcprivate.h
js/src/xpconnect/src/xpcvariant.cpp
layout/base/nsDocumentViewer.cpp
modules/libpref/src/init/all.js
storage/src/Variant_inl.h
widget/public/nsGUIEvent.h
widget/src/xpwidgets/nsBaseWidget.cpp
xpcom/ds/nsIVariant.idl
xpcom/ds/nsVariant.cpp
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1329,17 +1329,25 @@ SessionStoreService.prototype = {
           scriptableStream.readByteArray(scriptableStream.available());
         // We can stop doing base64 encoding once our serialization into JSON
         // is guaranteed to handle all chars in strings, including embedded
         // nulls.
         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
       }
       catch (ex) { debug(ex); }
     }
-    
+
+    if (aEntry.docIdentifier) {
+      entry.docIdentifier = aEntry.docIdentifier;
+    }
+
+    if (aEntry.stateData) {
+      entry.stateData = aEntry.stateData;
+    }
+
     if (!(aEntry instanceof Ci.nsISHContainer)) {
       return entry;
     }
     
     if (aEntry.childCount > 0) {
       entry.children = [];
       for (var i = 0; i < aEntry.childCount; i++) {
         var child = aEntry.GetChildAt(i);
@@ -2033,33 +2041,36 @@ SessionStoreService.prototype = {
 
     if (!this._isWindowLoaded(aWindow)) {
       // from now on, the data will come from the actual window
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
       delete this._windows[aWindow.__SSi]._restoring;
     }
     
-    // helper hash for ensuring unique frame IDs
+    // helper hashes for ensuring unique frame IDs and unique document
+    // identifiers.
     var idMap = { used: {} };
-    this.restoreHistory(aWindow, aTabs, aTabData, idMap);
+    var docIdentMap = {};
+    this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap);
   },
 
   /**
    * Restory history for a window
    * @param aWindow
    *        Window reference
    * @param aTabs
    *        Array of tab references
    * @param aTabData
    *        Array of tab data
    * @param aIdMap
    *        Hash for ensuring unique frame IDs
    */
-  restoreHistory: function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap) {
+  restoreHistory:
+    function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap) {
     var _this = this;
     while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
       aTabs.shift(); // this tab got removed before being completely restored
       aTabData.shift();
     }
     if (aTabs.length == 0) {
       return; // no more tabs to restore
     }
@@ -2085,17 +2096,18 @@ SessionStoreService.prototype = {
     }
     else
       delete tab.__SS_extdata;
     
     for (var i = 0; i < tabData.entries.length; i++) {
       //XXXzpao Wallpaper patch for bug 514751
       if (!tabData.entries[i].url)
         continue;
-      history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
+      history.addEntry(this._deserializeHistoryEntry(tabData.entries[i],
+                                                     aIdMap, aDocIdentMap), true);
     }
     
     // make sure to reset the capabilities and attributes, in case this tab gets reused
     var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
     CAPABILITIES.forEach(function(aCapability) {
       browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
     });
     Array.filter(tab.attributes, function(aAttr) {
@@ -2147,28 +2159,32 @@ SessionStoreService.prototype = {
     // Handle userTypedValue. Setting userTypedValue seems to update gURLbar
     // as needed. Calling loadURI will cancel form filling in restoreDocument_proxy
     if (tabData.userTypedValue) {
       browser.userTypedValue = tabData.userTypedValue;
       if (tabData.userTypedClear)
         browser.loadURI(tabData.userTypedValue, null, null, true);
     }
 
-    aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap); }, 0);
+    aWindow.setTimeout(function(){
+      _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap);
+    }, 0);
   },
 
   /**
    * expands serialized history data into a session-history-entry instance
    * @param aEntry
    *        Object containing serialized history data for a URL
    * @param aIdMap
    *        Hash for ensuring unique frame IDs
    * @returns nsISHEntry
    */
-  _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
+  _deserializeHistoryEntry:
+    function sss_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
+
     var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
                   createInstance(Ci.nsISHEntry);
 
     shEntry.setURI(IOSvc.newURI(aEntry.url, null, null));
     shEntry.setTitle(aEntry.title || aEntry.url);
     if (aEntry.subframe)
       shEntry.setIsSubFrame(aEntry.subframe || false);
     shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
@@ -2190,17 +2206,21 @@ SessionStoreService.prototype = {
       var id = aIdMap[aEntry.ID] || 0;
       if (!id) {
         for (id = Date.now(); id in aIdMap.used; id++);
         aIdMap[aEntry.ID] = id;
         aIdMap.used[id] = true;
       }
       shEntry.ID = id;
     }
-    
+
+    if (aEntry.stateData) {
+      shEntry.stateData = aEntry.stateData;
+    }
+
     if (aEntry.scroll) {
       var scrollPos = (aEntry.scroll || "0,0").split(",");
       scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
       shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
     }
 
     var postdata;
     if (aEntry.postdata_b64) {  // Firefox 3
@@ -2211,16 +2231,38 @@ SessionStoreService.prototype = {
 
     if (postdata) {
       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
                    createInstance(Ci.nsIStringInputStream);
       stream.setData(postdata, postdata.length);
       shEntry.postData = stream;
     }
 
+    if (aEntry.docIdentifier) {
+      // Get a new document identifier for this entry to ensure that history
+      // entries after a session restore are considered to have different
+      // documents from the history entries before the session restore.
+      // Document identifiers are 64-bit ints, so JS will loose precision and
+      // start assigning all entries the same doc identifier if these ever get
+      // large enough.
+      //
+      // It's a potential security issue if document identifiers aren't
+      // globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
+      // that we won't re-use a doc identifier within a given instance of the
+      // application.
+      let ident = aDocIdentMap[aEntry.docIdentifier];
+      if (!ident) {
+        shEntry.setUniqueDocIdentifier();
+        aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
+      }
+      else {
+        shEntry.docIdentifier = ident;
+      }
+    }
+
     if (aEntry.owner_b64) {  // Firefox 3
       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
                        createInstance(Ci.nsIStringInputStream);
       var binaryData = atob(aEntry.owner_b64);
       ownerInput.setData(binaryData, binaryData.length);
       var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
                          createInstance(Ci.nsIObjectInputStream);
       binaryStream.setInputStream(ownerInput);
@@ -2232,17 +2274,18 @@ SessionStoreService.prototype = {
       shEntry.owner = SecuritySvc.getCodebasePrincipal(uriObj);
     }
     
     if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
       for (var i = 0; i < aEntry.children.length; i++) {
         //XXXzpao Wallpaper patch for bug 514751
         if (!aEntry.children[i].url)
           continue;
-        shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
+        shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap,
+                                                       aDocIdentMap), i);
       }
     }
     
     return shEntry;
   },
 
   /**
    * restores all sessionStorage "super cookies"
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -103,16 +103,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_485482.js \
 	browser_485482_sample.html \
 	browser_485563.js \
 	browser_490040.js \
 	browser_491168.js \
 	browser_491577.js \
 	browser_493467.js \
 	browser_495495.js \
+	browser_500328.js \
 	browser_506482.js \
 	browser_514751.js \
 	browser_522545.js \
 	browser_524745.js \
 	browser_528776.js \
 	$(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -0,0 +1,135 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is sessionstore test code.
+ *
+ * The Initial Developer of the Original Code is
+ *  Justin Lebar <justin.lebar@gmail.com>
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function checkState(tab) {
+  // Go back and then forward, and make sure that the state objects received
+  // 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 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}),
+         "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";
+      doc.body.appendChild(elem);
+
+      contentWindow.history.forward();
+    }
+    else if (popStateCount == 1) {
+      popStateCount++;
+      is(JSON.stringify(aEvent.state), JSON.stringify({obj3:3}),
+         "second popstate object.");
+
+      // Make sure that the new-elem node is present in the document.  If it's
+      // not, then this history entry has a different doc identifier than the
+      // previous entry, which is bad.
+      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);
+      gBrowser.removeTab(tab);
+      finish();
+    }
+  }, true);
+
+  tab.linkedBrowser.contentWindow.history.back();
+}
+
+function test() {
+  // Tests session restore functionality of history.pushState and
+  // history.replaceState().  (Bug 500328)
+
+  let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+  waitForExplicitFinish();
+
+  // We open a new blank window, let it load, and then load in
+  // http://example.com.  We need to load the blank window first, otherwise the
+  // docshell gets confused and doesn't have a current history entry.
+  let tab = gBrowser.addTab("about:blank");
+  let tabBrowser = tab.linkedBrowser;
+
+  tabBrowser.addEventListener("load", function(aEvent) {
+    tabBrowser.removeEventListener("load", arguments.callee, true);
+
+    tabBrowser.loadURI("http://example.com", null, null);
+
+    tabBrowser.addEventListener("load", function(aEvent) {
+      tabBrowser.removeEventListener("load", arguments.callee, true);
+
+      // 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.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");
+
+      // Restore the state and make sure it looks right, after giving the event
+      // loop a chance to flush.
+      ss.setTabState(tab, state, true);
+      executeSoon(function() { checkState(tab); });
+
+    }, true);
+  }, true);
+}
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -101,18 +101,18 @@ class nsBindingManager;
 class nsIDOMNodeList;
 class mozAutoSubtreeModified;
 struct JSObject;
 class nsFrameLoader;
 class nsIBoxObject;
 
 // IID for the nsIDocument interface
 #define NS_IDOCUMENT_IID      \
-  { 0xb04d9176, 0xf087, 0x4d3c, \
-    { 0x87, 0x11, 0x13, 0x9d, 0x19, 0x95, 0x43, 0x55 } }
+{ 0x6b2f1996, 0x95d4, 0x48db, \
+  {0xaf, 0xd1, 0xfd, 0xaa, 0x75, 0x4c, 0x79, 0x92 } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 //----------------------------------------------------------------------
 
 // Document interface.  This is implemented by all document objects in
 // Gecko.
@@ -1221,16 +1221,38 @@ public:
     Doc_Theme_Uninitialized, // not determined yet
     Doc_Theme_None,
     Doc_Theme_Neutral,
     Doc_Theme_Dark,
     Doc_Theme_Bright
   };
 
   /**
+   * Returns the document's pending state object (serialized to JSON), or the
+   * empty string if one doesn't exist.
+   *
+   * This field serves as a waiting place for the history entry's state object:
+   * We set the field's value to the history entry's state object early on in
+   * the load, then after we fire onload we deserialize the field's value and
+   * fire a popstate event containing the resulting object.
+   */
+  nsAString& GetPendingStateObject()
+  {
+    return mPendingStateObject;
+  }
+
+  /**
+   * Set the document's pending state object (as serialized to JSON).
+   */
+  void SetPendingStateObject(nsAString &obj)
+  {
+    mPendingStateObject.Assign(obj);
+  }
+
+  /**
    * Returns Doc_Theme_None if there is no lightweight theme specified,
    * Doc_Theme_Dark for a dark theme, Doc_Theme_Bright for a light theme, and
    * Doc_Theme_Neutral for any other theme. This is used to determine the state
    * of the pseudoclasses :-moz-lwtheme and :-moz-lwtheme-text.
    */
   virtual int GetDocumentLWTheme() { return Doc_Theme_None; }
 
   /**
@@ -1383,16 +1405,18 @@ protected:
   PRUint32            mSubtreeModifiedDepth;
 
   // If we're an external resource document, this will be non-null and will
   // point to our "display document": the one that all resource lookups should
   // go to.
   nsCOMPtr<nsIDocument> mDisplayDocument;
 
   PRUint32 mEventsSuppressed;
+
+  nsString mPendingStateObject;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)
 
 /**
  * mozAutoSubtreeModified batches DOM mutations so that a DOMSubtreeModified
  * event is dispatched, if necessary, when the outermost mozAutoSubtreeModified
  * object is deleted.
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -402,16 +402,17 @@ nsContentUtils::InitializeEventTable() {
     { &nsGkAtoms::onblur,                        { NS_BLUR_CONTENT, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onoffline,                     { NS_OFFLINE, EventNameType_HTMLXUL }},
     { &nsGkAtoms::ononline,                      { NS_ONLINE, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onsubmit,                      { NS_FORM_SUBMIT, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onreset,                       { NS_FORM_RESET, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onchange,                      { NS_FORM_CHANGE, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onselect,                      { NS_FORM_SELECTED, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onload,                        { NS_LOAD, EventNameType_All }},
+    { &nsGkAtoms::onpopstate,                    { NS_POPSTATE, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onunload,                      { NS_PAGE_UNLOAD,
                                                  (EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
     { &nsGkAtoms::onhashchange,                  { NS_HASHCHANGE, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onbeforeunload,                { NS_BEFORE_PAGE_UNLOAD, EventNameType_HTMLXUL }},
     { &nsGkAtoms::onabort,                       { NS_IMAGE_ABORT,
                                                  (EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
     { &nsGkAtoms::onerror,                       { NS_LOAD_ERROR,
                                                  (EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -645,16 +645,17 @@ GK_ATOM(onfocus, "onfocus")
 GK_ATOM(onget, "onget")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(oninput, "oninput")
 GK_ATOM(onkeydown, "onkeydown")
 GK_ATOM(onkeypress, "onkeypress")
 GK_ATOM(onkeyup, "onkeyup")
 GK_ATOM(onLoad, "onLoad")
 GK_ATOM(onload, "onload")
+GK_ATOM(onpopstate, "onpopstate")
 GK_ATOM(only, "only")               // this one is not an event
 GK_ATOM(onmousedown, "onmousedown")
 GK_ATOM(onmousemove, "onmousemove")
 GK_ATOM(onmouseout, "onmouseout")
 GK_ATOM(onmouseover, "onmouseover")
 GK_ATOM(onmouseup, "onmouseup")
 GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
--- a/content/events/src/Makefile.in
+++ b/content/events/src/Makefile.in
@@ -77,16 +77,17 @@ CPPSRCS		= \
 		nsEventListenerService.cpp \
 		nsDOMProgressEvent.cpp \
 		nsDOMDataTransfer.cpp \
 		nsDOMNotifyPaintEvent.cpp \
 		nsDOMSimpleGestureEvent.cpp \
 		nsDOMEventTargetHelper.cpp \
 		nsDOMScrollAreaEvent.cpp \
 		nsDOMTransitionEvent.cpp \
+		nsDOMPopStateEvent.cpp \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES	= \
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -50,22 +50,23 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "prmem.h"
 #include "nsGkAtoms.h"
 #include "nsMutationEvent.h"
 #include "nsContentUtils.h"
 #include "nsIURI.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIScriptError.h"
+#include "nsDOMPopStateEvent.h"
 
 static const char* const sEventNames[] = {
   "mousedown", "mouseup", "click", "dblclick", "mouseover",
   "mouseout", "mousemove", "contextmenu", "keydown", "keyup", "keypress",
-  "focus", "blur", "load", "beforeunload", "unload", "hashchange", "abort", "error",
-  "submit", "reset", "change", "select", "input" ,"text",
+  "focus", "blur", "load", "popstate", "beforeunload", "unload", "hashchange",
+  "abort", "error", "submit", "reset", "change", "select", "input", "text",
   "compositionstart", "compositionend", "popupshowing", "popupshown",
   "popuphiding", "popuphidden", "close", "command", "broadcast", "commandupdate",
   "dragenter", "dragover", "dragexit", "dragdrop", "draggesture",
   "drag", "dragend", "dragstart", "dragleave", "drop", "resize",
   "scroll", "overflow", "underflow", "overflowchanged",
   "DOMSubtreeModified", "DOMNodeInserted", "DOMNodeRemoved", 
   "DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument",
   "DOMAttrModified", "DOMCharacterDataModified",
@@ -1319,16 +1320,18 @@ const char* nsDOMEvent::GetEventName(PRU
   case NS_FOCUS_CONTENT:
     return sEventNames[eDOMEvents_focus];
   case NS_BLUR_CONTENT:
     return sEventNames[eDOMEvents_blur];
   case NS_XUL_CLOSE:
     return sEventNames[eDOMEvents_close];
   case NS_LOAD:
     return sEventNames[eDOMEvents_load];
+  case NS_POPSTATE:
+    return sEventNames[eDOMEvents_popstate];
   case NS_BEFORE_PAGE_UNLOAD:
     return sEventNames[eDOMEvents_beforeunload];
   case NS_PAGE_UNLOAD:
     return sEventNames[eDOMEvents_unload];
   case NS_HASHCHANGE:
     return sEventNames[eDOMEvents_hashchange];
   case NS_IMAGE_ABORT:
     return sEventNames[eDOMEvents_abort];
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -53,32 +53,33 @@
 class nsIContent;
  
 class nsDOMEvent : public nsIDOMEvent,
                    public nsIDOMNSEvent,
                    public nsIPrivateDOMEvent
 {
 public:
 
-  // Note: this enum must be kept in sync with mEventNames in nsDOMEvent.cpp
+  // Note: this enum must be kept in sync with sEventNames in nsDOMEvent.cpp
   enum nsDOMEvents {
     eDOMEvents_mousedown=0,
     eDOMEvents_mouseup,
     eDOMEvents_click,
     eDOMEvents_dblclick,
     eDOMEvents_mouseover,
     eDOMEvents_mouseout,
     eDOMEvents_mousemove,
     eDOMEvents_contextmenu,
     eDOMEvents_keydown,
     eDOMEvents_keyup,
     eDOMEvents_keypress,
     eDOMEvents_focus,
     eDOMEvents_blur,
     eDOMEvents_load,
+    eDOMEvents_popstate,
     eDOMEvents_beforeunload,
     eDOMEvents_unload,
     eDOMEvents_hashchange,
     eDOMEvents_abort,
     eDOMEvents_error,
     eDOMEvents_submit,
     eDOMEvents_reset,
     eDOMEvents_change,
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMPopStateEvent.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMPopStateEvent.h"
+#include "nsCycleCollectionParticipant.h"
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMPopStateEvent)
+
+NS_IMPL_ADDREF_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
+NS_IMPL_RELEASE_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mState)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mState)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMPopStateEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMPopStateEvent)
+  NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(PopStateEvent)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
+
+nsDOMPopStateEvent::~nsDOMPopStateEvent()
+{
+}
+
+NS_IMETHODIMP
+nsDOMPopStateEvent::GetState(nsIVariant **aState)
+{
+  NS_PRECONDITION(aState, "null state arg");
+  NS_IF_ADDREF(*aState = mState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMPopStateEvent::InitPopStateEvent(const nsAString &aTypeArg,
+                                      PRBool aCanBubbleArg,
+                                      PRBool aCancelableArg,
+                                      nsIVariant *aStateArg)
+{
+  nsresult rv = nsDOMEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mState = aStateArg;
+  return NS_OK;
+}
+
+nsresult NS_NewDOMPopStateEvent(nsIDOMEvent** aInstancePtrResult,
+                                nsPresContext* aPresContext,
+                                nsEvent* aEvent)
+{
+  nsDOMPopStateEvent* event =
+    new nsDOMPopStateEvent(aPresContext, aEvent);
+
+  if (!event) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  return CallQueryInterface(event, aInstancePtrResult);
+}
new file mode 100644
--- /dev/null
+++ b/content/events/src/nsDOMPopStateEvent.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsDOMPopStateEvent_h__
+#define nsDOMPopStateEvent_h__
+
+class nsEvent;
+
+#include "nsIDOMPopStateEvent.h"
+#include "nsDOMEvent.h"
+#include "nsIVariant.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsDOMPopStateEvent : public nsDOMEvent,
+                           public nsIDOMPopStateEvent
+{
+public:
+  nsDOMPopStateEvent(nsPresContext* aPresContext, nsEvent* aEvent)
+    : nsDOMEvent(aPresContext, aEvent)  // state
+  {
+  }
+
+  virtual ~nsDOMPopStateEvent();
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
+
+  NS_DECL_NSIDOMPOPSTATEEVENT
+
+  NS_FORWARD_TO_NSDOMEVENT
+
+protected:
+  nsCOMPtr<nsIVariant> mState;
+};
+
+nsresult NS_NewDOMPopStateEvent(nsIDOMEvent** aInstancePtrResult,
+                                nsPresContext* aPresContext,
+                                nsEvent* aEvent);
+
+#endif // nsDOMPopStateEvent_h__
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -43,16 +43,17 @@
 #include "nsEventListenerManager.h"
 #include "nsContentUtils.h"
 #include "nsDOMError.h"
 #include "nsMutationEvent.h"
 #include NEW_H
 #include "nsFixedSizeAllocator.h"
 #include "nsINode.h"
 #include "nsPIDOMWindow.h"
+#include "nsDOMPopStateEvent.h"
 
 #define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH  (1 << 0)
 #define NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT (1 << 1)
 #define NS_TARGET_CHAIN_MAY_HAVE_MANAGER        (1 << 2)
 
 // nsEventTargetChainItem represents a single item in the event target chain.
 class nsEventTargetChainItem
 {
@@ -791,11 +792,13 @@ nsEventDispatcher::CreateEvent(nsPresCon
   if (aEventType.LowerCaseEqualsLiteral("pagetransition"))
     return NS_NewDOMPageTransitionEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("scrollareaevent"))
     return NS_NewDOMScrollAreaEvent(aDOMEvent, aPresContext, nsnull);
   // FIXME: Should get spec to say what the right string is here!  This
   // is probably wrong!
   if (aEventType.LowerCaseEqualsLiteral("transitionevent"))
     return NS_NewDOMTransitionEvent(aDOMEvent, aPresContext, nsnull);
+  if (aEventType.LowerCaseEqualsLiteral("popstateevent"))
+    return NS_NewDOMPopStateEvent(aDOMEvent, aPresContext, nsnull);
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
new file mode 100644
--- /dev/null
+++ b/docshell/base/crashtests/500328-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="test();">
+<script>
+  function test() {
+    // Test that calling pushState() with a state object which calls
+    // history.back() doesn't crash. We need to make sure that there's at least
+    // one entry in the history before we do anything else.
+    history.pushState(null, "");
+
+    x = {};
+    x.toJSON = { history.back(); return "{a:1}"; };
+    history.pushState(x, "");
+  }
+</script>
+</body>
+</html>
--- a/docshell/base/crashtests/crashtests.list
+++ b/docshell/base/crashtests/crashtests.list
@@ -1,8 +1,9 @@
 load 40929-1.html
 load 369126-1.html
 load 403574-1.xhtml
 load 430124-1.html
 load 430628-1.html
 load 432114-1.html
 load 432114-2.html
 load 436900-1.html
+load 500328-1.html
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -104,16 +104,17 @@
 #include "nsDOMJSUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIView.h"
 #include "nsIViewManager.h"
 #include "nsIScriptChannel.h"
 #include "nsIURIClassifier.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsCPrefetchService.h"
+#include "nsJSON.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"  
 
 
 // Local Includes
@@ -650,16 +651,18 @@ DispatchPings(nsIContent *content, nsIUR
     return;
 
   info.numPings = 0;
   info.referrer = referrer;
 
   ForEachPing(content, SendPing, &info);
 }
 
+static nsISHEntry* GetRootSHEntry(nsISHEntry *entry);
+
 //*****************************************************************************
 //***    nsDocShell: Object Management
 //*****************************************************************************
 
 // Note: operator new zeros our memory
 nsDocShell::nsDocShell():
     nsDocLoader(),
     mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto),
@@ -858,16 +861,24 @@ NS_IMETHODIMP nsDocShell::GetInterface(c
              NS_SUCCEEDED(EnsureScriptEnvironment())) {
         return mScriptGlobal->QueryInterface(aIID, aSink);
     }
     else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) &&
              NS_SUCCEEDED(EnsureContentViewer())) {
         mContentViewer->GetDOMDocument((nsIDOMDocument **) aSink);
         return *aSink ? NS_OK : NS_NOINTERFACE;
     }
+    else if (aIID.Equals(NS_GET_IID(nsIDocument)) &&
+             NS_SUCCEEDED(EnsureContentViewer())) {
+        nsCOMPtr<nsIDOMDocument> domDoc;
+        mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+        if (!domDoc)
+            return NS_NOINTERFACE;
+        return domDoc->QueryInterface(aIID, aSink);
+    }
     else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) {
         *aSink = nsnull;
 
         // Return application cache associated with this docshell, if any
 
         nsCOMPtr<nsIContentViewer> contentViewer;
         GetContentViewer(getter_AddRefs(contentViewer));
         if (!contentViewer)
@@ -1191,17 +1202,17 @@ nsDocShell::LoadURI(nsIURI * aURI,
                     // This is a newly created frame. Check for exception cases first. 
                     // By default the subframe will inherit the parent's loadType.
                     if (shEntry && (parentLoadType == LOAD_NORMAL ||
                                     parentLoadType == LOAD_LINK   ||
                                     parentLoadType == LOAD_NORMAL_EXTERNAL)) {
                         // The parent was loaded normally. In this case, this *brand new* child really shouldn't
                         // have a SHEntry. If it does, it could be because the parent is replacing an
                         // existing frame with a new frame, in the onLoadHandler. We don't want this
-                        // url to get into session history. Clear off shEntry, and set laod type to
+                        // url to get into session history. Clear off shEntry, and set load type to
                         // LOAD_BYPASS_HISTORY. 
                         PRBool inOnLoadHandler=PR_FALSE;
                         parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
                         if (inOnLoadHandler) {
                             loadType = LOAD_NORMAL_REPLACE;
                             shEntry = nsnull;
                         }
                     }   
@@ -3221,21 +3232,21 @@ nsDocShell::GetChildSHEntry(PRInt32 aChi
                 (*aResult)->SetLoadType(loadType);            
         }
     }
     return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::AddChildSHEntry(nsISHEntry * aCloneRef, nsISHEntry * aNewEntry,
-                            PRInt32 aChildOffset)
+                            PRInt32 aChildOffset, PRUint32 loadType)
 {
     nsresult rv;
 
-    if (mLSHE) {
+    if (mLSHE && loadType != LOAD_PUSHSTATE) {
         /* You get here if you are currently building a 
          * hierarchy ie.,you just visited a frameset page
          */
         nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE, &rv));
         if (container) {
             rv = container->AddChild(aNewEntry, aChildOffset);
         }
     }
@@ -3280,17 +3291,18 @@ nsDocShell::AddChildSHEntry(nsISHEntry *
             }
         }
     }
     else {
         /* Just pass this along */
         nsCOMPtr<nsIDocShellHistory> parent =
             do_QueryInterface(GetAsSupports(mParent), &rv);
         if (parent) {
-            rv = parent->AddChildSHEntry(aCloneRef, aNewEntry, aChildOffset);
+            rv = parent->AddChildSHEntry(aCloneRef, aNewEntry, aChildOffset,
+                                         loadType);
         }          
     }
     return rv;
 }
 
 nsresult
 nsDocShell::DoAddChildSHEntry(nsISHEntry* aNewEntry, PRInt32 aChildOffset)
 {
@@ -3308,17 +3320,17 @@ nsDocShell::DoAddChildSHEntry(nsISHEntry
     if (rootSH) {
         rootSH->GetIndex(&mPreviousTransIndex);
     }
 
     nsresult rv;
     nsCOMPtr<nsIDocShellHistory> parent =
         do_QueryInterface(GetAsSupports(mParent), &rv);
     if (parent) {
-        rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset);
+        rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType);
     }
 
 
     if (rootSH) {
         rootSH->GetIndex(&mLoadedTransIndex);
 #ifdef DEBUG_PAGE_CACHE
         printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex,
                mLoadedTransIndex);
@@ -3452,17 +3464,16 @@ NS_IMETHODIMP nsDocShell::GotoIndex(PRIn
     rv = GetRootSessionHistory(getter_AddRefs(rootSH));
     nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH));
     NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
     rv = webnav->GotoIndex(aIndex);
     return rv;
 
 }
 
-
 NS_IMETHODIMP
 nsDocShell::LoadURI(const PRUnichar * aURI,
                     PRUint32 aLoadFlags,
                     nsIURI * aReferringURI,
                     nsIInputStream * aPostStream,
                     nsIInputStream * aHeaderStream)
 {
     NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags");
@@ -3841,16 +3852,23 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, 
                ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this,
                 spec.get(), NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
     }
 #endif
     mFailedChannel = aFailedChannel;
     mFailedURI = aURI;
     mFailedLoadType = mLoadType;
 
+    if (mLSHE) {
+        // If we don't give mLSHE a new doc identifier here, when we go back or
+        // forward to another SHEntry with the same doc identifier, the error
+        // page will persist.
+        mLSHE->SetUniqueDocIdentifier();
+    }
+
     nsCAutoString url;
     nsCAutoString charset;
     if (aURI)
     {
         nsresult rv = aURI->GetSpec(url);
         rv |= aURI->GetOriginCharset(charset);
         NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -5709,23 +5727,23 @@ nsDocShell::EndPageLoad(nsIWebProgress *
     // someone is so very very rude as to bring this window down
     // during this load handler.
     //
     nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
     // We're done with the URI classifier for this channel
     mClassifier = nsnull;
 
-    //
-    // Notify the ContentViewer that the Document has finished loading...
-    //
-    // This will cause any OnLoad(...) handlers to fire, if it is a HTML
-    // document...
-    //
+    // 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.
+        SetDocPendingStateObj(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
@@ -7363,16 +7381,36 @@ nsDocShell::SetupNewViewer(nsIContentVie
 
     // We don't show the mContentViewer yet, since we want to draw the old page
     // until we have enough of the new page to show.  Just return with the new
     // viewer still set to hidden.
 
     return NS_OK;
 }
 
+nsresult
+nsDocShell::SetDocPendingStateObj(nsISHEntry *shEntry)
+{
+    nsresult rv;
+
+    nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
+    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+    nsAutoString stateData;
+    if (shEntry) {
+        rv = shEntry->GetStateData(stateData);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // if shEntry is null, we just set the pending state object to the
+        // empty string.
+    }
+
+    document->SetPendingStateObject(stateData);
+    return NS_OK;
+}
 
 nsresult
 nsDocShell::CheckLoadingPermissions()
 {
     // This method checks whether the caller may load content into
     // this docshell. Even though we've done our best to hide windows
     // from code that doesn't have the right to access them, it's
     // still possible for an evil site to open a window and access
@@ -7839,86 +7877,121 @@ nsDocShell::InternalLoad(nsIURI * aURI,
         if (allowScroll) {
             nsCOMPtr<nsIInputStream> currentPostData;
             mOSHE->GetPostData(getter_AddRefs(currentPostData));
             NS_ASSERTION(currentPostData == aPostData,
                          "Different POST data for entries for the same page?");
         }
 #endif
     }
-    
-    if ((aLoadType == LOAD_NORMAL ||
-         aLoadType == LOAD_STOP_CONTENT ||
-         LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) ||
-         aLoadType == LOAD_HISTORY ||
-         aLoadType == LOAD_LINK) && allowScroll) {
+
+    if (aLoadType == LOAD_NORMAL ||
+        aLoadType == LOAD_STOP_CONTENT ||
+        LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) ||
+        aLoadType == LOAD_HISTORY ||
+        aLoadType == LOAD_LINK) {
+
         PRBool wasAnchor = PR_FALSE;
         PRBool doHashchange = PR_FALSE;
         nscoord cx, cy;
-        NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx, &cy,
-                                         &doHashchange),
-                          NS_ERROR_FAILURE);
-
-        if (wasAnchor) {
+
+        if (allowScroll) {
+            NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx,
+                                             &cy, &doHashchange),
+                              NS_ERROR_FAILURE);
+        }
+
+        // If this is a history load, aSHEntry will have document identifier X
+        // if it was created as a result of a History.pushState() from a
+        // SHEntry with doc ident X, or if it was created by changing the hash
+        // of the URI corresponding to a SHEntry with doc ident X.
+        PRBool sameDocIdent = PR_FALSE;
+        if (mOSHE && aSHEntry) {
+          PRUint64 ourDocIdent, otherDocIdent;
+          mOSHE->GetDocIdentifier(&ourDocIdent);
+          aSHEntry->GetDocIdentifier(&otherDocIdent);
+          sameDocIdent = (ourDocIdent == otherDocIdent);
+        }
+
+        // Do a short-circuited load if the new URI differs from the current
+        // URI only in the hash, or if the two entries belong to the same
+        // document and don't point to the same object.
+        //
+        // (If we didn't check that the SHEntries are different objects,
+        // history.go(0) would short-circuit instead of triggering a true
+        // load, and we wouldn't dispatch an onload event to the page.)
+        if (wasAnchor || (sameDocIdent && (mOSHE != aSHEntry))) {
             mLoadType = aLoadType;
             mURIResultedInDocument = PR_TRUE;
 
-            /* we need to assign mLSHE to aSHEntry right here, so that on History loads, 
-             * SetCurrentURI() called from OnNewURI() will send proper 
+            /* we need to assign mLSHE to aSHEntry right here, so that on History loads,
+             * SetCurrentURI() called from OnNewURI() will send proper
              * onLocationChange() notifications to the browser to update
-             * back/forward buttons. 
+             * back/forward buttons.
              */
             SetHistoryEntry(&mLSHE, aSHEntry);
 
             /* This is a anchor traversal with in the same page.
              * call OnNewURI() so that, this traversal will be 
              * recorded in session and global history.
              */
             nsCOMPtr<nsISupports> owner;
             if (mOSHE) {
                 mOSHE->GetOwner(getter_AddRefs(owner));
             }
             OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE);
+
             nsCOMPtr<nsIInputStream> postData;
             PRUint32 pageIdent = PR_UINT32_MAX;
             nsCOMPtr<nsISupports> cacheKey;
-            
+
             if (mOSHE) {
                 /* save current position of scroller(s) (bug 59774) */
                 mOSHE->SetScrollPosition(cx, cy);
                 // Get the postdata and page ident from the current page, if
                 // the new load is being done via normal means.  Note that
                 // "normal means" can be checked for just by checking for
                 // LOAD_CMD_NORMAL, given the loadType and allowScroll check
                 // above -- it filters out some LOAD_CMD_NORMAL cases that we
                 // wouldn't want here.
                 if (aLoadType & LOAD_CMD_NORMAL) {
                     mOSHE->GetPostData(getter_AddRefs(postData));
                     mOSHE->GetPageIdentifier(&pageIdent);
                     mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
                 }
+
+                if (mLSHE && wasAnchor) {
+                    // If it's an anchor load, set mLSHE's doc identifier to
+                    // mOSHE's doc identifier -- These are the same documents,
+                    // as far as HTML5 is concerned.
+                    PRUint64 docIdent;
+                    rv = mOSHE->GetDocIdentifier(&docIdent);
+                    if (NS_SUCCEEDED(rv)) {
+                        mLSHE->SetDocIdentifier(docIdent);
+                    }
+                }
             }
-            
+
             /* Assign mOSHE to mLSHE. This will either be a new entry created
              * by OnNewURI() for normal loads or aSHEntry for history loads.
              */
             if (mLSHE) {
                 SetHistoryEntry(&mOSHE, mLSHE);
                 // Save the postData obtained from the previous page
                 // in to the session history entry created for the 
                 // anchor page, so that any history load of the anchor
                 // page will restore the appropriate postData.
                 if (postData)
                     mOSHE->SetPostData(postData);
 
                 // Make sure we won't just repost without hitting the
                 // cache first
                 if (cacheKey)
                     mOSHE->SetCacheKey(cacheKey);
-                
+
                 // Propagate our page ident to the new mOSHE so that
                 // we'll know it just differed by a scroll on the page.
                 if (pageIdent != PR_UINT32_MAX)
                     mOSHE->SetPageIdentifier(pageIdent);
             }
 
             /* restore previous position of scroller(s), if we're moving
              * back in history (bug 59774)
@@ -7944,22 +8017,37 @@ nsDocShell::InternalLoad(nsIURI * aURI,
                 mSessionHistory->GetEntryAtIndex(index, PR_FALSE,
                                                  getter_AddRefs(hEntry));
                 NS_ENSURE_TRUE(hEntry, NS_ERROR_FAILURE);
                 nsCOMPtr<nsISHEntry> shEntry(do_QueryInterface(hEntry));
                 if (shEntry)
                     shEntry->SetTitle(mTitle);
             }
 
-            if (doHashchange) {
-                nsCOMPtr<nsPIDOMWindow> window =
-                    do_QueryInterface(mScriptGlobal);
-
-                if (window)
-                    window->DispatchSyncHashchange();
+            if (sameDocIdent) {
+                // Set the doc's URI according to the new history entry's URI
+                nsCOMPtr<nsIURI> newURI;
+                mOSHE->GetURI(getter_AddRefs(newURI));
+                NS_ENSURE_TRUE(newURI, NS_ERROR_FAILURE);
+                nsCOMPtr<nsIDocument> doc =
+                  do_GetInterface(GetAsSupports(this));
+                NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+                doc->SetDocumentURI(newURI);
+            }
+
+            SetDocPendingStateObj(mOSHE);
+
+            // Dispatch the popstate and hashchange events, as appropriate
+            nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
+            if (window) {
+                window->DispatchSyncPopState();
+
+                if (doHashchange)
+                  window->DispatchSyncHashchange();
             }
 
             return NS_OK;
         }
     }
     
     // mContentViewer->PermitUnload can destroy |this| docShell, which
     // causes the next call of CanSavePresentation to crash. 
@@ -8832,16 +8920,24 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
             GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
         }
 
         if (httpChannel) {
             nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
             if (uploadChannel) {
                 uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
             }
+
+            // If the response status indicates an error, unlink this session
+            // history entry from any entries sharing its doc ident.
+            PRUint32 responseStatus;
+            nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
+            if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
+                mLSHE->SetUniqueDocIdentifier();
+            }
         }
     }
     /* Create SH Entry (mLSHE) only if there is a  SessionHistory object (mSessionHistory) in
      * the current frame or in the root docshell
      */
     nsCOMPtr<nsISHistory> rootSH = mSessionHistory;
     if (!rootSH) {
         // Get the handle to SH from the root docshell          
@@ -8853,17 +8949,17 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
 
     // Determine if this type of load should update history.
     if (aLoadType == LOAD_BYPASS_HISTORY ||
         aLoadType == LOAD_ERROR_PAGE ||
         aLoadType & LOAD_CMD_HISTORY ||
         aLoadType & LOAD_CMD_RELOAD)
         updateHistory = PR_FALSE;
 
-    // Check if the url to be loaded is the same as the one already loaded. 
+    // Check if the url to be loaded is the same as the one already loaded.
     if (mCurrentURI)
         aURI->Equals(mCurrentURI, &equalUri);
 
 #ifdef DEBUG
     PR_LOG(gDocShellLog, PR_LOG_DEBUG,
            ("  shAvailable=%i updateHistory=%i equalURI=%i\n",
             shAvailable, updateHistory, equalUri));
 #endif
@@ -8890,17 +8986,16 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIC
     }
 
     // If this is a refresh to the currently loaded url, we don't
     // have to update session or global history.
     if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
         SetHistoryEntry(&mLSHE, mOSHE);
     }
 
-
     /* If the user pressed shift-reload, cache will create a new cache key
      * for the page. Save the new cacheKey in Session History. 
      * see bug 90098
      */
     if (aChannel &&
         (aLoadType == LOAD_RELOAD_BYPASS_CACHE ||
          aLoadType == LOAD_RELOAD_BYPASS_PROXY ||
          aLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
@@ -8980,17 +9075,293 @@ nsDocShell::OnLoadingSite(nsIChannel * a
 void
 nsDocShell::SetReferrerURI(nsIURI * aURI)
 {
     mReferrerURI = aURI;        // This assigment addrefs
 }
 
 //*****************************************************************************
 // nsDocShell: Session History
-//*****************************************************************************   
+//*****************************************************************************
+
+nsresult
+nsDocShell::StringifyJSValVariant(nsIVariant *aData, nsAString &aResult)
+{
+    nsresult rv;
+    aResult.Truncate();
+
+    // First, try to extract a jsval from the variant |aData|.  This works only
+    // if the variant implements GetAsJSVal.
+    jsval jsData;
+    rv = aData->GetAsJSVal(&jsData);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+    // Now get the JSContext associated with the current document.
+    // First get the current document.
+    nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
+    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+    // Get the JSContext from the document, like we do in
+    // nsContentUtils::GetContextFromDocument().
+    nsIScriptGlobalObject *sgo = document->GetScopeObject();
+    NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
+
+    nsIScriptContext *scx = sgo->GetContext();
+    NS_ENSURE_TRUE(scx, NS_ERROR_FAILURE);
+
+    JSContext *cx = (JSContext *)scx->GetNativeContext();
+
+    // If our json call triggers a JS-to-C++ call, we want that call to use cx
+    // as the context.  So we push cx onto the context stack.
+    nsCOMPtr<nsIJSContextStack> contextStack =
+        do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    contextStack->Push(cx);
+
+    nsCOMPtr<nsIJSON> json = do_GetService("@mozilla.org/dom/json;1");
+    if(json) {
+        // Do the encoding
+        rv = json->EncodeFromJSVal(&jsData, cx, aResult);
+    }
+    else {
+        rv = NS_ERROR_FAILURE;
+    }
+
+    // Always pop the stack!
+    contextStack->Pop(&cx);
+
+    return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
+                     const nsAString& aURL, PRBool aReplace)
+{
+    // Implements History.pushState and History.replaceState
+
+    // Here's what we do, roughly in the order specified by HTML5:
+    // 1. Serialize aData to JSON.
+    // 2. If the third argument is present,
+    //     a. Resolve the url, relative to the first script's base URL
+    //     b. If (a) fails, raise a SECURITY_ERR
+    //     c. Compare the resulting absolute URL to the document's address.  If
+    //        any part of the URLs difer other than the <path>, <query>, and
+    //        <fragment> components, raise a SECURITY_ERR and abort.
+    // 3. If !aReplace:
+    //     Remove from the session history all entries after the current entry,
+    //     as we would after a regular navigation.
+    // 4. As apropriate, either add a state object entry to the session history
+    //    after the current entry with the following properties, or modify the
+    //    current session history entry to set
+    //      a. cloned data as the state object,
+    //      b. the given title as the title, and,
+    //      c. if the third argument was present, the absolute URL found in
+    //         step 2
+    // 5. If aReplace is false (i.e. we're doing a pushState instead of a
+    //    replaceState), notify bfcache that we've navigated to a new page.
+    // 6. If the third argument is present, set the document's current address
+    //    to the absolute URL found in step 2.
+    //
+    // It's important that this function not run arbitrary scripts after step 1
+    // and before completing step 5.  For example, if a script called
+    // history.back() before we completed step 5, bfcache might destroy an
+    // active content viewer.  Since EvictContentViewers at the end of step 5
+    // might run script, we can't just put a script blocker around the critical
+    // section.
+
+    nsresult rv;
+
+    nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
+    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+    mLoadType = LOAD_PUSHSTATE;
+
+    // Step 1: Clone aData by getting its JSON representation
+    nsString dataStr;
+    rv = StringifyJSValVariant(aData, dataStr);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Check that the state object isn't too long.
+    // Default max length: 640k chars.
+    PRInt32 maxStateObjSize = 0xA0000;
+    if (mPrefs) {
+      mPrefs->GetIntPref("browser.history.maxStateObjectSize",
+                         &maxStateObjSize);
+    }
+    if (maxStateObjSize < 0)
+      maxStateObjSize = 0;
+    NS_ENSURE_TRUE(dataStr.Length() <= (PRUint32)maxStateObjSize,
+                   NS_ERROR_ILLEGAL_VALUE);
+
+    // Step 2: Resolve aURL
+    PRBool equalURIs = PR_TRUE;
+    nsCOMPtr<nsIURI> oldURI = mCurrentURI;
+    nsCOMPtr<nsIURI> newURI;
+    if (aURL.Length() == 0) {
+      newURI = mCurrentURI;
+    }
+    else {
+        // 2a: Resolve aURL relative to mURI
+
+        nsIURI* docBaseURI = document->GetBaseURI();
+        if (!docBaseURI)
+            return NS_ERROR_FAILURE;
+
+        nsCAutoString spec;
+        docBaseURI->GetSpec(spec);
+
+        nsCAutoString charset;
+        rv = docBaseURI->GetOriginCharset(charset);
+        NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+        rv = NS_NewURI(getter_AddRefs(newURI), aURL,
+                       charset.get(), docBaseURI);
+
+        // 2b: If 2a fails, raise a SECURITY_ERR
+        if (NS_FAILED(rv)) {
+            return NS_ERROR_DOM_SECURITY_ERR;
+        }
+
+        // 2c: Same-origin check.
+        if (!URIIsLocalFile(newURI)) {
+            // In addition to checking that the security manager says that
+            // the new URI has the same origin as our current URI, we also
+            // check that the two URIs have the same userpass. (The
+            // security manager says that |http://foo.com| and
+            // |http://me@foo.com| have the same origin.)  mCurrentURI
+            // won't contain the password part of the userpass, so this
+            // means that it's never valid to specify a password in a
+            // pushState or replaceState URI.
+
+            nsCOMPtr<nsIScriptSecurityManager> secMan =
+                do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+            NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+            // It's very important that we check that newURI is of the same
+            // origin as mCurrentURI, not docBaseURI, because a page can
+            // set docBaseURI arbitrarily to any domain.
+            nsCAutoString currentUserPass, newUserPass;
+            NS_ENSURE_SUCCESS(mCurrentURI->GetUserPass(currentUserPass),
+                              NS_ERROR_FAILURE);
+            NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass),
+                              NS_ERROR_FAILURE);
+            if (NS_FAILED(secMan->CheckSameOriginURI(mCurrentURI,
+                                                     newURI, PR_TRUE)) ||
+                !currentUserPass.Equals(newUserPass)) {
+
+                return NS_ERROR_DOM_SECURITY_ERR;
+            }
+        }
+        else {
+            // It's a file:// URI
+            nsCOMPtr<nsIScriptObjectPrincipal> docScriptObj =
+                do_QueryInterface(document);
+
+            if (!docScriptObj) {
+                return NS_ERROR_DOM_SECURITY_ERR;
+            }
+
+            nsCOMPtr<nsIPrincipal> principal = docScriptObj->GetPrincipal();
+
+            if (!principal ||
+                NS_FAILED(principal->CheckMayLoad(newURI, PR_TRUE))) {
+
+                return NS_ERROR_DOM_SECURITY_ERR;
+            }
+        }
+
+        mCurrentURI->Equals(newURI, &equalURIs);
+
+    } // end of same-origin check
+
+    nsCOMPtr<nsISHistory> sessionHistory = mSessionHistory;
+    if (!sessionHistory) {
+        // Get the handle to SH from the root docshell
+        GetRootSessionHistory(getter_AddRefs(sessionHistory));
+    }
+    NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
+
+    nsCOMPtr<nsISHistoryInternal> shInternal =
+      do_QueryInterface(sessionHistory, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Step 3: Create a new entry in the session history; this will erase
+    // all SHEntries after the new entry and make this entry the current
+    // one.  This operation may modify mOSHE, which we need later, so we
+    // keep a reference here.
+    NS_ENSURE_TRUE(mOSHE, NS_ERROR_FAILURE);
+    nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
+
+    nsCOMPtr<nsISHEntry> newSHEntry;
+    if (!aReplace) {
+        rv = AddToSessionHistory(newURI, nsnull, nsnull,
+                                 getter_AddRefs(newSHEntry));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
+
+        // Set the new SHEntry's document identifier, if we can.
+        PRUint64 ourDocIdent;
+        NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent),
+                          NS_ERROR_FAILURE);
+        NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
+                          NS_ERROR_FAILURE);
+
+        // AddToSessionHistory may not modify mOSHE.  In case it doesn't,
+        // we'll just set mOSHE here.
+        mOSHE = newSHEntry;
+
+    } else {
+        newSHEntry = mOSHE;
+        newSHEntry->SetURI(newURI);
+    }
+
+    // Step 4: Modify new/original session history entry
+    newSHEntry->SetStateData(dataStr);
+
+    // 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.
+    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);
+
+        PRInt32 curIndex = -1;
+        rv = rootSH->GetIndex(&curIndex);
+        if (NS_SUCCEEDED(rv) && curIndex > -1) {
+            internalSH->EvictContentViewers(curIndex - 1, curIndex);
+        }
+    }
+
+    // Step 6: If the document's URI changed, update document's URI and update
+    // global history
+    if (!equalURIs) {
+        SetCurrentURI(newURI, nsnull, PR_TRUE);
+        document->SetDocumentURI(newURI);
+
+        AddToGlobalHistory(newURI, PR_FALSE, oldURI);
+    }
+
+    // Try to set the title of the current history element
+    if (mOSHE)
+      mOSHE->SetTitle(aTitle);
+
+    // We need this to ensure that the back button is enabled after a
+    // pushState, if it wasn't already enabled.
+    FireOnLocationChange(this, nsnull, mCurrentURI);
+
+    return NS_OK;
+}
+
 PRBool
 nsDocShell::ShouldAddToSessionHistory(nsIURI * aURI)
 {
     // I believe none of the about: urls should go in the history. But then
     // that could just be me... If the intent is only deny about:blank then we
     // should just do a spec compare, rather than two gets of the scheme and
     // then the path.  -Gagan
     nsresult rv;
@@ -9423,17 +9794,16 @@ nsDocShell::CloneAndReplace(nsISHEntry *
 
     CloneAndReplaceData data(aCloneID, aReplaceEntry, nsnull);
     nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data);
 
     data.resultEntry.swap(*aResultEntry);
     return rv;
 }
 
-
 void
 nsDocShell::SwapHistoryEntries(nsISHEntry *aOldEntry, nsISHEntry *aNewEntry)
 {
     if (aOldEntry == mOSHE)
         mOSHE = aNewEntry;
 
     if (aOldEntry == mLSHE)
         mLSHE = aNewEntry;
@@ -9674,51 +10044,59 @@ NS_IMETHODIMP nsDocShell::MakeEditable(P
 
   return mEditorData->MakeEditable(inWaitForUriLoad);
 }
 
 nsresult
 nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
                                nsIChannel * aChannel)
 {
-    if (mItemType != typeContent || !mGlobalHistory)
-        return NS_OK;
-
     // If this is a POST request, we do not want to include this in global
     // history, so return early.
     nsCOMPtr<nsIHttpChannel> hchan(do_QueryInterface(aChannel));
     if (hchan) {
         nsCAutoString type;
         nsresult rv = hchan->GetRequestMethod(type);
         if (NS_SUCCEEDED(rv) && type.EqualsLiteral("POST"))
             return NS_OK;
     }
 
+    nsCOMPtr<nsIURI> referrer;
+    if (aChannel)
+        NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
+
+    return AddToGlobalHistory(aURI, aRedirect, referrer);
+}
+
+nsresult
+nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
+                               nsIURI * aReferrer)
+{
+    if (mItemType != typeContent || !mGlobalHistory)
+        return NS_OK;
+
     PRBool visited;
     nsresult rv = mGlobalHistory->IsVisited(aURI, &visited);
     if (NS_FAILED(rv))
         return rv;
 
-    nsCOMPtr<nsIURI> referrer;
-    if (aChannel)
-        NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
-
-    rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), referrer);
+    rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), aReferrer);
     if (NS_FAILED(rv))
         return rv;
 
     if (!visited) {
         nsCOMPtr<nsIObserverService> obsService =
             do_GetService("@mozilla.org/observer-service;1");
         if (obsService) {
             obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
         }
     }
 
     return NS_OK;
+
 }
 
 //*****************************************************************************
 // nsDocShell: Helper Routines
 //*****************************************************************************
 
 NS_IMETHODIMP
 nsDocShell::SetLoadType(PRUint32 aLoadType)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -359,16 +359,20 @@ protected:
     // exists).  The channel will be suspended until the classification is
     // complete.
     nsresult CheckClassifier(nsIChannel *aChannel);
 
     nsresult ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
                             PRUint32 aLoadType, nscoord *cx, nscoord *cy,
                             PRBool * aDoHashchange);
 
+    // Tries to stringify a given variant by converting it to JSON.  This only
+    // works if the variant is backed by a JSVal.
+    nsresult StringifyJSValVariant(nsIVariant *aData, nsAString &aResult);
+
     // Returns PR_TRUE if would have called FireOnLocationChange,
     // but did not because aFireOnLocationChange was false on entry.
     // In this case it is the caller's responsibility to ensure
     // FireOnLocationChange is called.
     // In all other cases PR_FALSE is returned.
     PRBool OnLoadingSite(nsIChannel * aChannel,
                          PRBool aFireOnLocationChange,
                          PRBool aAddToGlobalHistory = PR_TRUE);
@@ -460,18 +464,21 @@ protected:
     // overridden from nsDocLoader, this provides more information than the
     // normal OnStateChange with flags STATE_REDIRECTING
     virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
                                        nsIChannel* aNewChannel,
                                        PRUint32 aRedirectFlags,
                                        PRUint32 aStateFlags);
 
     // Global History
+
     nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
                                 nsIChannel * aChannel);
+    nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
+                                nsIURI * aReferrer);
 
     // Helper Routines
     nsresult   ConfirmRepost(PRBool * aRepost);
     NS_IMETHOD GetPromptAndStringBundle(nsIPrompt ** aPrompt,
         nsIStringBundle ** aStringBundle);
     NS_IMETHOD GetChildOffset(nsIDOMNode * aChild, nsIDOMNode * aParent,
         PRInt32 * aOffset);
     nsIScrollableFrame* GetRootScrollFrame();
@@ -510,16 +517,21 @@ protected:
     //
     // Helper method that is called when a new document (including any
     // sub-documents - ie. frames) has been completely loaded.
     //
     virtual nsresult EndPageLoad(nsIWebProgress * aProgress,
                                  nsIChannel * aChannel,
                                  nsresult aResult);
 
+    // Sets the current document's pending state object to the given SHEntry's
+    // state object.  The pending state object is eventually given to the page
+    // in the PopState event.
+    nsresult SetDocPendingStateObj(nsISHEntry *shEntry);
+
     nsresult CheckLoadingPermissions();
 
     // Security checks to prevent frameset spoofing.  See comments at
     // implementation sites.
     static PRBool CanAccessItem(nsIDocShellTreeItem* aTargetItem,
                                 nsIDocShellTreeItem* aAccessingItem,
                                 PRBool aConsiderOpener = PR_TRUE);
     static PRBool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem,
--- a/docshell/base/nsDocShellLoadTypes.h
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -87,16 +87,17 @@ enum LoadType {
     LOAD_RELOAD_BYPASS_PROXY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
     LOAD_RELOAD_BYPASS_PROXY_AND_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
     LOAD_LINK = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_IS_LINK),
     LOAD_REFRESH = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH),
     LOAD_RELOAD_CHARSET_CHANGE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE),
     LOAD_BYPASS_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY),
     LOAD_STOP_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT),
     LOAD_STOP_CONTENT_AND_REPLACE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT | nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+    LOAD_PUSHSTATE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_PUSHSTATE, nsIWebNavigation::LOAD_FLAGS_NONE),
     /**
      * Load type for an error page. These loads are never triggered by users of
      * Docshell. Instead, Docshell triggers the load itself when a
      * consumer-triggered load failed.
      */
     LOAD_ERROR_PAGE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, LOAD_FLAGS_ERROR_PAGE)
 
     // NOTE: Adding a new value? Remember to update IsValidLoadType!
@@ -117,16 +118,17 @@ static inline PRBool IsValidLoadType(PRU
     case LOAD_RELOAD_BYPASS_PROXY:
     case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
     case LOAD_LINK:
     case LOAD_REFRESH:
     case LOAD_RELOAD_CHARSET_CHANGE:
     case LOAD_BYPASS_HISTORY:
     case LOAD_STOP_CONTENT:
     case LOAD_STOP_CONTENT_AND_REPLACE:
+    case LOAD_PUSHSTATE:
     case LOAD_ERROR_PAGE:
         return PR_TRUE;
     }
     return PR_FALSE;
 }
 
 #endif // MOZILLA_INTERNAL_API
 #endif 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -64,18 +64,19 @@ interface nsISimpleEnumerator;
 interface nsIInputStream;
 interface nsIRequest;
 interface nsISHEntry;
 interface nsILayoutHistoryState;
 interface nsISecureBrowserUI;
 interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
+interface nsIVariant;
 
-[scriptable, uuid(c95eaff1-14e6-4db1-a806-46be97d5a9b6)]
+[scriptable, uuid(3adde256-05a9-43a7-a190-f8fe75eecfd6)]
 interface nsIDocShell : nsISupports
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -165,16 +166,23 @@ interface nsIDocShell : nsISupports
                               in nsIInputStream aHeadersStream,
                               in unsigned long aLoadFlags,
                               in nsISHEntry aSHEntry,
                               in boolean firstParty,
                               out nsIDocShell aDocShell,
                               out nsIRequest aRequest);
 
   /**
+   * Do either a history.pushState() or history.replaceState() operation,
+   * depending on the value of aReplace.
+   */
+  void addState(in nsIVariant aData, in DOMString aTitle,
+                in DOMString aURL, in boolean aReplace);
+
+  /**
    * Creates a DocShellLoadInfo object that you can manipulate and then pass
    * to loadURI.
    */
   void createLoadInfo(out nsIDocShellLoadInfo loadInfo);
 
   /**
    * Reset state to a new content model within the current document and the document
    * viewer.  Called by the document before initiating an out of band document.write().
@@ -334,19 +342,20 @@ interface nsIDocShell : nsISupports
   const unsigned long BUSY_FLAGS_NONE             = 0;
   const unsigned long BUSY_FLAGS_BUSY             = 1;
   const unsigned long BUSY_FLAGS_BEFORE_PAGE_LOAD = 2;
   const unsigned long BUSY_FLAGS_PAGE_LOADING     = 4;
 
   /**
    * Load commands for the document 
    */
-  const unsigned long LOAD_CMD_NORMAL  = 0x1; // Normal load
-  const unsigned long LOAD_CMD_RELOAD  = 0x2; // Reload
-  const unsigned long LOAD_CMD_HISTORY = 0x4; // Load from history
+  const unsigned long LOAD_CMD_NORMAL  = 0x1;   // Normal load
+  const unsigned long LOAD_CMD_RELOAD  = 0x2;   // Reload
+  const unsigned long LOAD_CMD_HISTORY = 0x4;   // Load from history
+  const unsigned long LOAD_CMD_PUSHSTATE = 0x8; // History.pushState()
 
   readonly attribute unsigned long busyFlags;
 
   /* 
    * attribute to access the loadtype  for the document
    */
   attribute unsigned long  loadType;
 
--- a/docshell/base/nsIDocShellHistory.idl
+++ b/docshell/base/nsIDocShellHistory.idl
@@ -34,29 +34,30 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 interface nsISHEntry;
 
-[scriptable, uuid(89caa9f0-8b1c-47fb-b0d3-f0aef0bff749)]
+[scriptable, uuid(a89b80a8-3c44-4a25-9d2c-2fb42358b46e)]
 interface nsIDocShellHistory : nsISupports
 {
   /**
    * Get the SHEntry associated with a child docshell
    */
   nsISHEntry getChildSHEntry(in long aChildOffset);
 
   /**
-   * Add a Child SHEntry for a frameset page
+   * Add a Child SHEntry for a frameset page, given the child's loadtype.
    */
   void addChildSHEntry(in nsISHEntry aCloneReference,
                        in nsISHEntry aHistoryEntry,
-                       in long aChildOffset);
+                       in long aChildOffset,
+                       in unsigned long aLoadType);
 
   /*
    * Whether this docshell should save entries in global history.
    */
   attribute boolean useGlobalHistory;
 };
 
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -53,17 +53,17 @@ interface nsISupportsArray;
 %{C++
 struct nsIntRect;
 class nsDocShellEditorData;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
 
 
-[scriptable, uuid(09fecea6-5453-43ba-bf91-3ff32618f037)]
+[scriptable, uuid(62b0603f-57ca-439e-a0fb-6f6978500755)]
 interface nsISHEntry : nsIHistoryEntry
 {
     /** URI for the document */
     void setURI(in nsIURI aURI);
 
     /** Referrer URI */
     attribute nsIURI referrerURI;
 
@@ -144,16 +144,31 @@ interface nsISHEntry : nsIHistoryEntry
      * attached to the same docshell only if the two entries are entries for
      * the same page in the sense that one could go from the state represented
      * by one to the state represented by the other simply by scrolling (so the
      * entries are separated by an anchor traversal or a subframe navigation in
      * some other frame).
      */
     attribute unsigned long pageIdentifier;
 
+    /**
+     * docIdentifier is an integer that should be the same for two entries
+     * attached to the same docshell if and only if the two entries are entries
+     * for the same document.  In practice, two entries A and B will have the
+     * same docIdentifier if they have the same pageIdentifier or if B was
+     * created by A calling history.pushState().
+     */
+    attribute unsigned long long docIdentifier;
+
+    /**
+     * Changes this entry's doc identifier to a new value which is unique
+     * among those of all other entries.
+     */
+    void setUniqueDocIdentifier();
+
     /** attribute to set and get the cache key for the entry */
     attribute nsISupports cacheKey;
 
     /** attribute to indicate whether layoutHistoryState should be saved */
     attribute boolean saveLayoutStateFlag;
 
     /** attribute to indicate whether the page is already expired in cache */
     attribute boolean expirationStatus;
@@ -188,16 +203,22 @@ interface nsISHEntry : nsIHistoryEntry
     /**
      * Get the owner, if any, that was associated with the channel
      * that the document that was loaded to create this history entry
      * came from.
      */
     attribute nsISupports owner;
 
     /**
+     * Get/set data associated with this history state via a pushState() call,
+     * encoded as JSON.
+     **/
+    attribute AString stateData;
+
+    /**
      * Gets the owning pointer to the editor data assosicated with
      * this shistory entry. This forgets its pointer, so free it when
      * you're done.
      */
     [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData();
 
     /**
      * Sets the owning pointer to the editor data assosicated with
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -70,16 +70,17 @@ protected:
   virtual void NotifyExpired(nsSHEntry* aObj) {
     RemoveObject(aObj);
     aObj->Expire();
   }
 };
 
 static HistoryTracker *gHistoryTracker = nsnull;
 static PRUint32 gEntryID = 0;
+static PRUint64 gEntryDocIdentifier = 0;
 
 nsresult nsSHEntry::Startup()
 {
   gHistoryTracker = new HistoryTracker();
   return gHistoryTracker ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
 void nsSHEntry::Shutdown()
@@ -99,16 +100,17 @@ static void StopTrackingEntry(nsSHEntry 
 //***    nsSHEntry: Object Management
 //*****************************************************************************
 
 
 nsSHEntry::nsSHEntry() 
   : mLoadType(0)
   , mID(gEntryID++)
   , mPageIdentifier(mID)
+  , mDocIdentifier(gEntryDocIdentifier++)
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mIsFrameNavigation(PR_FALSE)
   , mSaveLayoutState(PR_TRUE)
   , mExpired(PR_FALSE)
   , mSticky(PR_TRUE)
   , mParent(nsnull)
   , mViewerBounds(0, 0, 0, 0)
@@ -120,16 +122,17 @@ nsSHEntry::nsSHEntry(const nsSHEntry &ot
   , mReferrerURI(other.mReferrerURI)
   // XXX why not copy mDocument?
   , mTitle(other.mTitle)
   , mPostData(other.mPostData)
   , mLayoutHistoryState(other.mLayoutHistoryState)
   , mLoadType(0)         // XXX why not copy?
   , mID(other.mID)
   , mPageIdentifier(other.mPageIdentifier)
+  , mDocIdentifier(other.mDocIdentifier)
   , mScrollPositionX(0)  // XXX why not copy?
   , mScrollPositionY(0)  // XXX why not copy?
   , mIsFrameNavigation(other.mIsFrameNavigation)
   , mSaveLayoutState(other.mSaveLayoutState)
   , mExpired(other.mExpired)
   , mSticky(PR_TRUE)
   // XXX why not copy mContentType?
   , mCacheKey(other.mCacheKey)
@@ -388,16 +391,40 @@ NS_IMETHODIMP nsSHEntry::GetPageIdentifi
 }
 
 NS_IMETHODIMP nsSHEntry::SetPageIdentifier(PRUint32 aPageIdentifier)
 {
   mPageIdentifier = aPageIdentifier;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult)
+{
+  *aResult = mDocIdentifier;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
+{
+  // This ensures that after a session restore, gEntryDocIdentifier is greater
+  // than all SHEntries' docIdentifiers, which ensures that we'll never repeat
+  // a doc identifier.
+  if (aDocIdentifier >= gEntryDocIdentifier)
+    gEntryDocIdentifier = aDocIdentifier + 1;
+
+  mDocIdentifier = aDocIdentifier;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsSHEntry::SetUniqueDocIdentifier()
+{
+  mDocIdentifier = gEntryDocIdentifier++;
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsSHEntry::GetIsSubFrame(PRBool * aFlag)
 {
   *aFlag = mIsFrameNavigation;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsSHEntry::SetIsSubFrame(PRBool  aFlag)
 {
@@ -465,17 +492,17 @@ nsSHEntry::Create(nsIURI * aURI, const n
                   nsISupports* aOwner)
 {
   mURI = aURI;
   mTitle = aTitle;
   mPostData = aInputStream;
   mCacheKey = aCacheKey;
   mContentType = aContentType;
   mOwner = aOwner;
-    
+
   // Set the LoadType by default to loadHistory during creation
   mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory;
 
   // By default all entries are set false for subframe flag. 
   // nsDocShell::CloneAndReplace() which creates entries for
   // all subframe navigations, sets the flag to true.
   mIsFrameNavigation = PR_FALSE;
 
@@ -861,8 +888,22 @@ nsSHEntry::SetEditorData(nsDocShellEdito
 }
 
 PRBool
 nsSHEntry::HasDetachedEditor()
 {
   return mEditorData != nsnull;
 }
 
+NS_IMETHODIMP
+nsSHEntry::GetStateData(nsAString &aStateData)
+{
+  aStateData.Assign(mStateData);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetStateData(const nsAString &aDataStr)
+{
+  mStateData.Assign(aDataStr);
+  return NS_OK;
+}
+
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -91,30 +91,32 @@ private:
   nsCOMPtr<nsIURI>                mURI;
   nsCOMPtr<nsIURI>                mReferrerURI;
   nsCOMPtr<nsIContentViewer>      mContentViewer;
   nsCOMPtr<nsIDocument>           mDocument; // document currently being observed
   nsString                        mTitle;
   nsCOMPtr<nsIInputStream>        mPostData;
   nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
   nsCOMArray<nsISHEntry>          mChildren;
-  PRUint32                        mLoadType;  
+  PRUint32                        mLoadType;
   PRUint32                        mID;
   PRUint32                        mPageIdentifier;
+  PRInt64                         mDocIdentifier;
   PRInt32                         mScrollPositionX;
   PRInt32                         mScrollPositionY;
   PRPackedBool                    mIsFrameNavigation;
   PRPackedBool                    mSaveLayoutState;
   PRPackedBool                    mExpired;
   PRPackedBool                    mSticky;
   nsCString                       mContentType;
   nsCOMPtr<nsISupports>           mCacheKey;
   nsISHEntry *                    mParent;  // weak reference
   nsCOMPtr<nsISupports>           mWindowState;
   nsIntRect                       mViewerBounds;
   nsCOMArray<nsIDocShellTreeItem> mChildShells;
   nsCOMPtr<nsISupportsArray>      mRefreshURIList;
   nsCOMPtr<nsISupports>           mOwner;
   nsExpirationState               mExpirationState;
   nsAutoPtr<nsDocShellEditorData> mEditorData;
+  nsString                        mStateData;
 };
 
 #endif /* nsSHEntry_h */
--- a/docshell/shistory/src/nsSHistory.cpp
+++ b/docshell/shistory/src/nsSHistory.cpp
@@ -473,35 +473,36 @@ nsSHistory::PrintHistory()
     nsCOMPtr<nsISHEntry>  entry;
     rv = txn->GetSHEntry(getter_AddRefs(entry));
     if (NS_FAILED(rv) && !entry)
       return NS_ERROR_FAILURE;
 
     nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
     nsCOMPtr<nsIURI>  uri;
     nsXPIDLString title;
-              
+
     entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));
     nsCOMPtr<nsIHistoryEntry> hEntry(do_QueryInterface(entry));
     if (hEntry) {
       hEntry->GetURI(getter_AddRefs(uri));
       hEntry->GetTitle(getter_Copies(title));              
     }
 
 #if 0
     nsCAutoString url;
     if (uri)
      uri->GetSpec(url);
 
     printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get());
-    printf("\t\t URL = %s\n", url);
+    printf("\t\t URL = %s\n", url.get());
+
     printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get());
-    printf("\t\t layout History Data = %x\n", layoutHistoryState);
+    printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
 #endif
-      
+
     nsCOMPtr<nsISHTransaction> next;
     rv = txn->GetNext(getter_AddRefs(next));
     if (NS_SUCCEEDED(rv) && next) {
       txn = next;
       index++;
       continue;
     }
     else
@@ -859,17 +860,17 @@ nsSHistory::EvictContentViewersInRange(P
     trans->GetSHEntry(getter_AddRefs(entry));
     nsCOMPtr<nsIContentViewer> viewer;
     nsCOMPtr<nsISHEntry> ownerEntry;
     entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
                                getter_AddRefs(viewer));
     if (viewer) {
       NS_ASSERTION(ownerEntry,
                    "ContentViewer exists but its SHEntry is null");
-#ifdef DEBUG_PAGE_CACHE 
+#ifdef DEBUG_PAGE_CACHE
       nsCOMPtr<nsIURI> uri;
       ownerEntry->GetURI(getter_AddRefs(uri));
       nsCAutoString spec;
       if (uri)
         uri->GetSpec(spec);
 
       printf("per SHistory limit: evicting content viewer: %s\n", spec.get());
 #endif
@@ -1144,33 +1145,33 @@ NS_IMETHODIMP
 nsSHistory::LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 aHistCmd)
 {
   nsCOMPtr<nsIDocShell> docShell;
   nsCOMPtr<nsISHEntry> shEntry;
   // Keep note of requested history index in mRequestedIndex.
   mRequestedIndex = aIndex;
 
   nsCOMPtr<nsISHEntry> prevEntry;
-  GetEntryAtIndex(mIndex, PR_FALSE, getter_AddRefs(prevEntry));  
-   
-  nsCOMPtr<nsISHEntry> nextEntry;   
+  GetEntryAtIndex(mIndex, PR_FALSE, getter_AddRefs(prevEntry));
+
+  nsCOMPtr<nsISHEntry> nextEntry;
   GetEntryAtIndex(mRequestedIndex, PR_FALSE, getter_AddRefs(nextEntry));
   nsCOMPtr<nsIHistoryEntry> nHEntry(do_QueryInterface(nextEntry));
-  if (!nextEntry || !prevEntry || !nHEntry) {    
+  if (!nextEntry || !prevEntry || !nHEntry) {
     mRequestedIndex = -1;
     return NS_ERROR_FAILURE;
   }
-  
+
   // Send appropriate listener notifications
   PRBool canNavigate = PR_TRUE;
   // Get the uri for the entry we are about to visit
   nsCOMPtr<nsIURI> nextURI;
   nHEntry->GetURI(getter_AddRefs(nextURI));
- 
-  if(mListener) {    
+
+  if(mListener) {
     nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
     if (listener) {
       if (aHistCmd == HIST_CMD_BACK) {
         // We are going back one entry. Send GoBack notifications
         listener->OnHistoryGoBack(nextURI, &canNavigate);
       }
       else if (aHistCmd == HIST_CMD_FORWARD) {
         // We are going forward. Send GoForward notification
@@ -1217,31 +1218,28 @@ nsSHistory::LoadEntry(PRInt32 aIndex, lo
         mRequestedIndex = -1;
         return NS_ERROR_FAILURE; 
       }
       return rv;
     }   // (pCount >0)
     else
       docShell = mRootDocShell;
     }
-  
 
   if (!docShell) {
     // we did not successfully go to the proper index.
     // return error.
     mRequestedIndex = -1;
     return NS_ERROR_FAILURE;
   }
 
   // Start the load on the appropriate docshell
   return InitiateLoad(nextEntry, docShell, aLoadType);
 }
 
-
-
 nsresult
 nsSHistory::CompareFrames(nsISHEntry * aPrevEntry, nsISHEntry * aNextEntry, nsIDocShell * aParent, long aLoadType, PRBool * aIsFrameFound)
 {
   if (!aPrevEntry || !aNextEntry || !aParent)
     return PR_FALSE;
 
   nsresult result = NS_OK;
   PRUint32 prevID, nextID;
--- a/docshell/shistory/src/nsSHistory.h
+++ b/docshell/shistory/src/nsSHistory.h
@@ -61,17 +61,17 @@ class nsIDocShell;
 class nsSHEnumerator;
 class nsSHistoryObserver;
 class nsSHistory: public PRCList,
                   public nsISHistory,
                   public nsISHistoryInternal,
                   public nsIWebNavigation
 {
 public:
-	nsSHistory();
+  nsSHistory();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
   NS_DECL_NSISHISTORYINTERNAL
   NS_DECL_NSIWEBNAVIGATION
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
@@ -89,17 +89,17 @@ protected:
 
    // Could become part of nsIWebNavigation
    NS_IMETHOD GetEntryAtIndex(PRInt32 aIndex, PRBool aModifyIndex, nsISHEntry** aResult);
    NS_IMETHOD GetTransactionAtIndex(PRInt32 aIndex, nsISHTransaction ** aResult);
    nsresult CompareFrames(nsISHEntry * prevEntry, nsISHEntry * nextEntry, nsIDocShell * rootDocShell, long aLoadType, PRBool * aIsFrameFound);
    nsresult InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType);
 
    NS_IMETHOD LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 histCmd);
-	
+
 #ifdef DEBUG
    nsresult PrintHistory();
 #endif
 
   // Evict the viewers at indices between aStartIndex and aEndIndex,
   // including aStartIndex but not aEndIndex.
   void EvictContentViewersInRange(PRInt32 aStartIndex, PRInt32 aEndIndex);
   void EvictWindowContentViewers(PRInt32 aFromIndex, PRInt32 aToIndex);
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -77,16 +77,17 @@
 #include "nsIDOMDocument.h"
 #include "nsIDOM3Document.h"
 #include "nsIDOMXMLDocument.h"
 #include "nsIDOMNSDocument.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNSEvent.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMEventListener.h"
+#include "nsIDOMPopStateEvent.h"
 #include "nsContentUtils.h"
 #include "nsDOMWindowUtils.h"
 
 // Window scriptable helper includes
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIDocShellTreeNode.h"
 #include "nsIScriptExternalNameSet.h"
@@ -1325,23 +1326,25 @@ static nsDOMClassInfoData sClassInfoData
 
   NS_DEFINE_CLASSINFO_DATA(PaintRequest, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(PaintRequestList, nsPaintRequestListSH,
                            ARRAY_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(ScrollAreaEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(PopStateEvent, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(EventListenerInfo, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(TransitionEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 };
 
-// Objects that shuld be constructable through |new Name();|
+// Objects that should be constructable through |new Name();|
 struct nsContractIDMapData
 {
   PRInt32 mDOMClassInfoID;
   const char *mContractID;
 };
 
 #define NS_DEFINE_CONSTRUCTOR_DATA(_class, _contract_id)                      \
   { eDOMClassInfo_##_class##_id, _contract_id },
@@ -1415,16 +1418,17 @@ jsval nsDOMClassInfo::sOnkeypress_id    
 jsval nsDOMClassInfo::sOnmousemove_id     = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnfocus_id         = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnblur_id          = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnsubmit_id        = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnreset_id         = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnchange_id        = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnselect_id        = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnload_id          = JSVAL_VOID;
+jsval nsDOMClassInfo::sOnpopstate_id      = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnbeforeunload_id  = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnunload_id        = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnhashchange_id    = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnpageshow_id      = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnpagehide_id      = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnabort_id         = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnerror_id         = JSVAL_VOID;
 jsval nsDOMClassInfo::sOnpaint_id         = JSVAL_VOID;
@@ -1609,16 +1613,17 @@ nsDOMClassInfo::DefineStaticJSVals(JSCon
   SET_JSVAL_TO_STRING(sOnmousemove_id,     cx, "onmousemove");
   SET_JSVAL_TO_STRING(sOnfocus_id,         cx, "onfocus");
   SET_JSVAL_TO_STRING(sOnblur_id,          cx, "onblur");
   SET_JSVAL_TO_STRING(sOnsubmit_id,        cx, "onsubmit");
   SET_JSVAL_TO_STRING(sOnreset_id,         cx, "onreset");
   SET_JSVAL_TO_STRING(sOnchange_id,        cx, "onchange");
   SET_JSVAL_TO_STRING(sOnselect_id,        cx, "onselect");
   SET_JSVAL_TO_STRING(sOnload_id,          cx, "onload");
+  SET_JSVAL_TO_STRING(sOnpopstate_id,      cx, "onpopstate");
   SET_JSVAL_TO_STRING(sOnbeforeunload_id,  cx, "onbeforeunload");
   SET_JSVAL_TO_STRING(sOnunload_id,        cx, "onunload");
   SET_JSVAL_TO_STRING(sOnhashchange_id,    cx, "onhashchange");
   SET_JSVAL_TO_STRING(sOnpageshow_id,      cx, "onpageshow");
   SET_JSVAL_TO_STRING(sOnpagehide_id,      cx, "onpagehide");
   SET_JSVAL_TO_STRING(sOnabort_id,         cx, "onabort");
   SET_JSVAL_TO_STRING(sOnerror_id,         cx, "onerror");
   SET_JSVAL_TO_STRING(sOnpaint_id,         cx, "onpaint");
@@ -2227,16 +2232,21 @@ nsDOMClassInfo::Init()
 
   DOM_CLASSINFO_MAP_BEGIN(DragEvent, nsIDOMDragEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDragEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMouseEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSMouseEvent)
     DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(PopStateEvent, nsIDOMPopStateEvent)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMPopStateEvent)
+    DOM_CLASSINFO_EVENT_MAP_ENTRIES
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(HTMLDocument, nsIDOMHTMLDocument)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLDocument)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSHTMLDocument)
     DOM_CLASSINFO_DOCUMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(HTMLOptionsCollection, nsIDOMHTMLOptionsCollection)
     // Order is significant.  nsIDOMHTMLOptionsCollection.length shadows
@@ -7322,17 +7332,18 @@ nsEventReceiverSH::ReallyIsEventName(jsv
   case 'h' :
     return id == sOnhashchange_id;
   case 'l' :
     return id == sOnload_id;
   case 'p' :
     return (id == sOnpaint_id        ||
             id == sOnpageshow_id     ||
             id == sOnpagehide_id     ||
-            id == sOnpaste_id);
+            id == sOnpaste_id        ||
+            id == sOnpopstate_id);
   case 'k' :
     return (id == sOnkeydown_id      ||
             id == sOnkeypress_id     ||
             id == sOnkeyup_id);
   case 'u' :
     return id == sOnunload_id;
   case 'm' :
     return (id == sOnmousemove_id    ||
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -322,16 +322,17 @@ protected:
   static jsval sOnmousemove_id;
   static jsval sOnfocus_id;
   static jsval sOnblur_id;
   static jsval sOnsubmit_id;
   static jsval sOnreset_id;
   static jsval sOnchange_id;
   static jsval sOnselect_id;
   static jsval sOnload_id;
+  static jsval sOnpopstate_id;
   static jsval sOnbeforeunload_id;
   static jsval sOnunload_id;
   static jsval sOnhashchange_id;
   static jsval sOnpageshow_id;
   static jsval sOnpagehide_id;
   static jsval sOnabort_id;
   static jsval sOnerror_id;
   static jsval sOnpaint_id;
--- a/dom/base/nsDOMClassInfoID.h
+++ b/dom/base/nsDOMClassInfoID.h
@@ -468,16 +468,17 @@ enum nsDOMClassInfoID {
   eDOMClassInfo_WebGLShader_id,
   eDOMClassInfo_WebGLFramebuffer_id,
   eDOMClassInfo_WebGLRenderbuffer_id,
 
   eDOMClassInfo_PaintRequest_id,
   eDOMClassInfo_PaintRequestList_id,
 
   eDOMClassInfo_ScrollAreaEvent_id,
+  eDOMClassInfo_PopStateEvent_id,
 
   eDOMClassInfo_EventListenerInfo_id,
 
   eDOMClassInfo_TransitionEvent_id,
 
   // This one better be the last one in this list
   eDOMClassInfoIDCount
 };
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -74,16 +74,17 @@
 #include "nsPluginArray.h"
 #include "nsIPluginHost.h"
 #include "nsGeolocation.h"
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
 #include "nsCycleCollector.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsDOMThreadService.h"
+#include "nsAutoJSValHolder.h"
 
 // Interfaces Needed
 #include "nsIFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsIWidget.h"
 #include "nsIBaseWindow.h"
 #include "nsIAccelerometer.h"
 #include "nsWidgetsCID.h"
@@ -107,16 +108,17 @@
 #include "nsIDOMElement.h"
 #include "nsIDOMDocumentEvent.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMMessageEvent.h"
 #include "nsIDOMPopupBlockedEvent.h"
+#include "nsIDOMPopStateEvent.h"
 #include "nsIDOMOfflineResourceList.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsPIDOMStorage.h"
 #include "nsDOMString.h"
 #include "nsIEmbeddingSiteWindow2.h"
 #include "nsThreadUtils.h"
 #include "nsIEventStateManager.h"
 #include "nsIHttpProtocolHandler.h"
@@ -164,16 +166,17 @@
 #include "nsCSSProps.h"
 #include "nsIURIFixup.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsEventDispatcher.h"
 #include "nsIObserverService.h"
 #include "nsIXULAppInfo.h"
 #include "nsNetUtil.h"
 #include "nsFocusManager.h"
+#include "nsIJSON.h"
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsIDOMXULControlElement.h"
 #include "nsIFrame.h"
 #endif
 
 #include "plbase64.h"
 
@@ -356,16 +359,18 @@ static PRBool               gDOMWindowDu
 // CIDs
 static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
 
 static const char sJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
 
 static const char kCryptoContractID[] = NS_CRYPTO_CONTRACTID;
 static const char kPkcs11ContractID[] = NS_PKCS11_CONTRACTID;
 
+static const char sPopStatePrefStr[] = "browser.history.allowPopState";
+
 static PRBool
 IsAboutBlank(nsIURI* aURI)
 {
   NS_PRECONDITION(aURI, "Must have URI");
     
   // GetSpec can be expensive for some URIs, so check the scheme first.
   PRBool isAbout = PR_FALSE;
   if (NS_FAILED(aURI->SchemeIs("about", &isAbout)) || !isAbout) {
@@ -6973,26 +6978,138 @@ nsresult
 nsGlobalWindow::DispatchSyncHashchange()
 {
   FORWARD_TO_INNER(DispatchSyncHashchange, (), NS_OK);
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
                "Must be safe to run script here.");
 
   // Don't do anything if the window is frozen.
   if (IsFrozen())
-      return NS_OK;
+    return NS_OK;
 
   // Dispatch the hashchange event, which doesn't bubble and isn't cancelable,
   // to the outer window.
   return nsContentUtils::DispatchTrustedEvent(mDoc, GetOuterWindow(),
                                               NS_LITERAL_STRING("hashchange"),
                                               PR_FALSE, PR_FALSE);
 }
 
-// Find an nsCanvasFrame under aFrame.  Only search the principal
+nsresult
+nsGlobalWindow::DispatchSyncPopState()
+{
+  FORWARD_TO_INNER(DispatchSyncPopState, (), NS_OK);
+
+  NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+               "Must be safe to run script here.");
+
+  // Check that PopState hasn't been pref'ed off.
+  if (!nsContentUtils::GetBoolPref(sPopStatePrefStr, PR_FALSE))
+    return NS_OK;
+
+  nsresult rv = NS_OK;
+
+  // Bail if the window is frozen.
+  if (IsFrozen()) {
+    return NS_OK;
+  }
+
+  // Bail if there's no document or the document's readystate isn't "complete".
+  if (!mDoc) {
+    return NS_OK;
+  }
+
+  nsIDocument::ReadyState readyState = mDoc->GetReadyStateEnum();
+  if (readyState != nsIDocument::READYSTATE_COMPLETE) {
+    return NS_OK;
+  }
+
+  // Get the document's pending state object -- it contains the data we're
+  // going to send along with the popstate event.  The object is serialized as
+  // JSON.
+  nsAString& stateObjJSON = mDoc->GetPendingStateObject();
+
+  nsCOMPtr<nsIVariant> stateObj;
+  // Parse the JSON, if there's any to parse.
+  if (!stateObjJSON.IsEmpty()) {
+    // Get the JSContext associated with our document. We need this for
+    // deserialization.
+    nsCOMPtr<nsIDocument> document = do_QueryInterface(mDocument);
+    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+    // Get the JSContext from the document, like we do in
+    // nsContentUtils::GetContextFromDocument().
+    nsIScriptGlobalObject *sgo = document->GetScopeObject();
+    NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
+
+    nsIScriptContext *scx = sgo->GetContext();
+    NS_ENSURE_TRUE(scx, NS_ERROR_FAILURE);
+
+    JSContext *cx = (JSContext*) scx->GetNativeContext();
+
+    // If our json call triggers a JS-to-C++ call, we want that call to use cx
+    // as the context.  So we push cx onto the context stack.
+    nsCxPusher cxPusher;
+
+    jsval jsStateObj = JSVAL_NULL;
+    // Root the container which will hold our decoded state object.
+    nsAutoGCRoot(&jsStateObj, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Deserialize the state object into an nsIVariant.
+    nsCOMPtr<nsIJSON> json = do_GetService("@mozilla.org/dom/json;1");
+    NS_ENSURE_TRUE(cxPusher.Push(cx), NS_ERROR_FAILURE);
+    rv = json->DecodeToJSVal(stateObjJSON, cx, &jsStateObj);
+    NS_ENSURE_SUCCESS(rv, rv);
+    cxPusher.Pop();
+
+    nsCOMPtr<nsIXPConnect> xpconnect = do_GetService(nsIXPConnect::GetCID());
+    NS_ENSURE_TRUE(xpconnect, NS_ERROR_FAILURE);
+    rv = xpconnect->JSValToVariant(cx, &jsStateObj, getter_AddRefs(stateObj));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Obtain a presentation shell for use in creating a popstate event.
+  nsIPresShell *shell = mDoc->GetPrimaryShell();
+  nsCOMPtr<nsPresContext> presContext;
+  if (shell) {
+    presContext = shell->GetPresContext();
+  }
+
+  // Create a new popstate event
+  nsCOMPtr<nsIDOMEvent> domEvent;
+  rv = nsEventDispatcher::CreateEvent(presContext, nsnull,
+                                      NS_LITERAL_STRING("popstateevent"),
+                                      getter_AddRefs(domEvent));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(domEvent);
+  NS_ENSURE_TRUE(privateEvent, NS_ERROR_FAILURE);
+
+  // Initialize the popstate event, which does bubble but isn't cancellable.
+  nsCOMPtr<nsIDOMPopStateEvent> popstateEvent = do_QueryInterface(domEvent);
+  rv = popstateEvent->InitPopStateEvent(NS_LITERAL_STRING("popstate"),
+                                        PR_TRUE, PR_FALSE,
+                                        stateObj);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = privateEvent->SetTrusted(PR_TRUE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDOMEventTarget> outerWindow =
+    do_QueryInterface(GetOuterWindow());
+  NS_ENSURE_TRUE(outerWindow, NS_ERROR_UNEXPECTED);
+
+  rv = privateEvent->SetTarget(outerWindow);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool dummy; // default action
+  return DispatchEvent(popstateEvent, &dummy);
+}
+
+// Find an nsICanvasFrame under aFrame.  Only search the principal
 // child lists.  aFrame must be non-null.
 static nsCanvasFrame* FindCanvasFrame(nsIFrame* aFrame)
 {
     nsCanvasFrame* canvasFrame = do_QueryFrame(aFrame);
     if (canvasFrame) {
         return canvasFrame;
     }
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -443,16 +443,18 @@ public:
   virtual NS_HIDDEN_(void)
     CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey,
                              nsScriptObjectHolder& aHandler);
 
   virtual PRBool TakeFocus(PRBool aFocus, PRUint32 aFocusMethod);
   virtual void SetReadyForFocus();
   virtual void PageHidden();
   virtual nsresult DispatchSyncHashchange();
+  virtual nsresult DispatchSyncPopState();
+
   virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin);
 
   static PRBool DOMWindowDumpEnabled();
 
 protected:
   // Object Management
   virtual ~nsGlobalWindow();
   void CleanUp();
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -52,16 +52,23 @@
 #include "nsIHistoryEntry.h"
 #include "nsIURI.h"
 #include "nsIServiceManager.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsContentUtils.h"
+#include "nsIDOMNSDocument.h"
+#include "nsISHistoryInternal.h"
+
+static const char* sAllowPushStatePrefStr  =
+  "browser.history.allowPushState";
+static const char* sAllowReplaceStatePrefStr =
+  "browser.history.allowReplaceState";
 
 //
 //  History class implementation 
 //
 nsHistory::nsHistory(nsIDocShell* aDocShell) : mDocShell(aDocShell)
 {
 }
 
@@ -253,31 +260,72 @@ nsHistory::Go(PRInt32 aDelta)
   NS_ENSURE_TRUE(session_history, NS_ERROR_FAILURE);
 
   // QI SHistory to nsIWebNavigation
   nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(session_history));
   NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE);
 
   PRInt32 curIndex=-1;
   PRInt32 len = 0;
-  nsresult rv = session_history->GetIndex(&curIndex);  
+  nsresult rv = session_history->GetIndex(&curIndex);
   rv = session_history->GetCount(&len);
 
   PRInt32 index = curIndex + aDelta;
   if (index > -1  &&  index < len)
     webnav->GotoIndex(index);
 
   // We always want to return a NS_OK, since returning errors 
   // from GotoIndex() can lead to exceptions and a possible leak
   // of history length
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsHistory::PushState(nsIVariant *aData, const nsAString& aTitle,
+                     const nsAString& aURL)
+{
+  // Check that PushState hasn't been pref'ed off.
+  if (!nsContentUtils::GetBoolPref(sAllowPushStatePrefStr, PR_FALSE))
+    return NS_OK;
+
+  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+  // AddState might run scripts, so we need to hold a strong reference to the
+  // docShell here to keep it from going away.
+  nsCOMPtr<nsIDocShell> docShell = mDocShell;
+
+  // PR_FALSE tells the docshell to add a new history entry instead of
+  // modifying the current one.
+  nsresult rv = mDocShell->AddState(aData, aTitle, aURL, PR_FALSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHistory::ReplaceState(nsIVariant *aData, const nsAString& aTitle,
+                        const nsAString& aURL)
+{
+  // Check that ReplaceState hasn't been pref'ed off
+  if (!nsContentUtils::GetBoolPref(sAllowReplaceStatePrefStr, PR_FALSE))
+    return NS_OK;
+
+  NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+  // As in PushState(), we need to keep a strong reference to the docShell
+  // here.
+  nsCOMPtr<nsIDocShell> docShell = mDocShell;
+
+  // PR_TRUE tells the docshell to modify the current SHEntry, rather than
+  // create a new one.
+  return mDocShell->AddState(aData, aTitle, aURL, PR_TRUE);
+}
+
+NS_IMETHODIMP
 nsHistory::Item(PRUint32 aIndex, nsAString& aReturn)
 {
   aReturn.Truncate();
 
   nsresult rv = NS_OK;
   nsCOMPtr<nsISHistory>  session_history;
 
   GetSessionHistoryFromDocShell(mDocShell, getter_AddRefs(session_history));
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -189,17 +189,16 @@ nsLocation::CheckURL(nsIURI* aURI, nsIDo
     return NS_ERROR_FAILURE;
 
   JSContext *cx;
 
   if (NS_FAILED(GetContextFromStack(stack, &cx)))
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsISupports> owner;
-  nsCOMPtr<nsIURI> sourceURI;
 
   if (cx) {
     // No cx means that there's no JS running, or at least no JS that
     // was run through code that properly pushed a context onto the
     // context stack (as all code that runs JS off of web pages
     // does). We won't bother with security checks in this case, but
     // we need to create the loadinfo etc.
 
@@ -217,30 +216,31 @@ nsLocation::CheckURL(nsIURI* aURI, nsIDo
       return result;
 
     // Now get the principal to use when loading the URI
     nsCOMPtr<nsIPrincipal> principal;
     if (NS_FAILED(secMan->GetSubjectPrincipal(getter_AddRefs(principal))) ||
         !principal)
       return NS_ERROR_FAILURE;
     owner = do_QueryInterface(principal);
-    principal->GetURI(getter_AddRefs(sourceURI));
   }
 
   // Create load info
   nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
   docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
   NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
 
   loadInfo->SetOwner(owner);
 
-  // now set the referrer on the loadinfo
-  if (sourceURI) {
+  // Now set the referrer on the loadinfo.  We need to do this in order to get
+  // the correct referrer URI from a document which was pushStated.
+  nsCOMPtr<nsIURI> sourceURI;
+  result = GetURI(getter_AddRefs(sourceURI));
+  if (NS_SUCCEEDED(result))
     loadInfo->SetReferrer(sourceURI);
-  }
 
   loadInfo.swap(*aLoadInfo);
 
   return NS_OK;
 }
 
 nsresult
 nsLocation::GetURI(nsIURI** aURI, PRBool aGetInnermostURI)
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -456,16 +456,20 @@ public:
    */
   virtual void PageHidden() = 0;
 
   /**
    * Instructs this window to synchronously dispatch a hashchange event.
    */
   virtual nsresult DispatchSyncHashchange() = 0;
 
+  /**
+   * Instructs this window to synchronously dispatch a popState event.
+   */
+  virtual nsresult DispatchSyncPopState() = 0;
 
   /**
    * Tell this window that there is an observer for orientation changes
    */
   virtual void SetHasOrientationEventListener() = 0;
 
   /**
    * Set a arguments for this window. This will be set on the window
--- a/dom/interfaces/base/nsIDOMHistory.idl
+++ b/dom/interfaces/base/nsIDOMHistory.idl
@@ -34,22 +34,30 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
-[scriptable, uuid(896d1d20-b4c4-11d2-bd93-00805f8ae3f4)]
+interface nsIVariant;
+
+[scriptable, uuid(208f2af7-9f2e-497c-8a53-9e7803280898)]
 interface nsIDOMHistory : nsISupports
 {
   readonly attribute long             length;
   readonly attribute DOMString        current;
   readonly attribute DOMString        previous;
   readonly attribute DOMString        next;
 
   void                       back();
   void                       forward();
 
   void                       go([optional] in long aDelta);
   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);
 };
--- a/dom/interfaces/events/Makefile.in
+++ b/dom/interfaces/events/Makefile.in
@@ -78,11 +78,12 @@ XPIDLSRCS =					\
 	nsIDOMNotifyPaintEvent.idl              \
 	nsIDOMPaintRequest.idl			\
 	nsIDOMPaintRequestList.idl		\
 	nsIDOMSimpleGestureEvent.idl		\
 	nsIDOMNSMouseEvent.idl			\
 	nsIDOMOrientationEvent.idl              \
 	nsIDOMScrollAreaEvent.idl		\
 	nsIDOMTransitionEvent.idl		\
+	nsIDOMPopStateEvent.idl			\
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/events/nsIDOMPopStateEvent.idl
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEvent.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(f3834fd5-0ef5-4ccd-a741-0b6685414342)]
+interface nsIDOMPopStateEvent : nsIDOMEvent
+{
+  /**
+   * The state associated with this popstate event
+   */
+  readonly attribute nsIVariant state;
+
+  void initPopStateEvent(in DOMString typeArg,
+                         in boolean canBubbleArg,
+                         in boolean cancelableArg,
+                         in nsIVariant stateArg);
+};
--- a/dom/interfaces/json/nsIJSON.idl
+++ b/dom/interfaces/json/nsIJSON.idl
@@ -37,26 +37,39 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "domstubs.idl"
 
 interface nsIInputStream;
 interface nsIOutputStream;
 interface nsIScriptGlobalObject;
 
+%{ C++
+#include "jspubtd.h"
+%}
+
+      native JSVal(jsval);
+[ptr] native JSValPtr(jsval);
+[ptr] native JSContext(JSContext);
+
 /**
  * Encode and decode JSON text.
  */
-[scriptable, uuid(45464c36-efde-4cb5-8e00-07480533ff35)]
+[scriptable, uuid(6fcf09ee-87d0-42ec-a72a-8d60114e974f)]
 interface nsIJSON : nsISupports
 {
   AString encode(/* in JSObject value */);
 
   void encodeToStream(in nsIOutputStream stream,
                       in string charset,
                       in boolean writeBOM
                       /* in JSObject value */);
 
   void /* JSObject */ decode(in AString str);
 
   void /* JSObject */ decodeFromStream(in nsIInputStream stream,
                                        in long contentLength);
+
+  [noscript] AString  encodeFromJSVal(in JSValPtr value, in JSContext cx);
+
+  // Make sure you GCroot the result of this function before using it.
+  [noscript] JSVal    decodeToJSVal(in AString str, in JSContext cx);
 };
--- a/dom/src/json/Makefile.in
+++ b/dom/src/json/Makefile.in
@@ -46,16 +46,19 @@ MODULE         = dom
 LIBRARY_NAME   = json_s
 LIBXUL_LIBRARY = 1
 
 
 CPPSRCS =                      \
        nsJSON.cpp              \
        $(NULL)
 
+EXPORTS = nsJSON.h \
+          $(NULL)
+
 FORCE_STATIC_LIB = 1
 
 LOCAL_INCLUDES = \
 		-I$(srcdir)/../base \
 		-I$(topsrcdir)/content/events/src
 
 DEFINES += -D_IMPL_NS_LAYOUT
 
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -190,16 +190,37 @@ WriteCallback(const jschar *buf, uint32 
   nsJSONWriter *writer = static_cast<nsJSONWriter*>(data);
   nsresult rv =  writer->Write((const PRUnichar*)buf, (PRUint32)len);
   if (NS_FAILED(rv))
     return JS_FALSE;
 
   return JS_TRUE;
 }
 
+NS_IMETHODIMP
+nsJSON::EncodeFromJSVal(jsval *value, JSContext *cx, nsAString &result)
+{
+  result.Truncate();
+
+  // Begin a new request
+  JSAutoRequest ar(cx);
+
+  nsJSONWriter writer;
+  JSBool ok = JS_Stringify(cx, value, NULL, JSVAL_NULL,
+                           WriteCallback, &writer);
+  if (!ok) {
+    return NS_ERROR_XPC_BAD_CONVERT_JS;
+  }
+
+  NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
+  writer.FlushBuffer();
+  result.Assign(writer.mOutputString);
+  return NS_OK;
+}
+
 nsresult
 nsJSON::EncodeInternal(nsJSONWriter *writer)
 {
   nsresult rv;
   nsIXPConnect *xpc = nsContentUtils::XPConnect();
   if (!xpc)
     return NS_ERROR_FAILURE;
 
@@ -377,16 +398,40 @@ nsJSON::Decode(const nsAString& json)
 }
 
 NS_IMETHODIMP
 nsJSON::DecodeFromStream(nsIInputStream *aStream, PRInt32 aContentLength)
 {
   return DecodeInternal(aStream, aContentLength, PR_TRUE);
 }
 
+NS_IMETHODIMP
+nsJSON::DecodeToJSVal(const nsAString &str, JSContext *cx, jsval *result)
+{
+  JSAutoRequest ar(cx);
+
+  JSONParser *parser = JS_BeginJSONParse(cx, result);
+  NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
+
+  JSBool ok = JS_ConsumeJSONText(cx, parser,
+                                 (jschar*)PromiseFlatString(str).get(),
+                                 (uint32)str.Length());
+
+  // Since we've called JS_BeginJSONParse, we have to call JS_FinishJSONParse,
+  // even if JS_ConsumeJSONText fails.  But if either fails, we'll report an
+  // error.
+  ok = ok && JS_FinishJSONParse(cx, parser, JSVAL_NULL);
+
+  if (!ok) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return NS_OK;
+}
+
 nsresult
 nsJSON::DecodeInternal(nsIInputStream *aStream,
                        PRInt32 aContentLength,
                        PRBool aNeedsConverter)
 {
   nsresult rv;
   nsIXPConnect *xpc = nsContentUtils::XPConnect();
   if (!xpc)
--- a/dom/tests/mochitest/whatwg/Makefile.in
+++ b/dom/tests/mochitest/whatwg/Makefile.in
@@ -72,16 +72,19 @@ include $(topsrcdir)/config/rules.mk
 		test_postMessage_origin.xhtml \
 		postMessage_origin_helper.xhtml \
 		test_postMessage_closed.html \
 		postMessage_closed_helper.html \
 		test_postMessage_jar.html \
 		postMessage.jar \
 		postMessage.jar^headers^ \
 		test_bug477323.html \
+		test_bug500328.html \
+		file_bug500328_1.html \
+		file_bug500328_2.html \
 		$(NULL)
 
 _CHROME_FILES	= \
 		test_postMessage_chrome.html \
 		$(NULL)		
 
 libs:: 	$(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/whatwg/file_bug500328_1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Inner frame for testing bug 500328.
+https://bugzilla.mozilla.org/show_bug.cgi?id=500328
+-->
+<head>
+<title>test 1</title>
+</head>
+<body onload="load();" onpopstate="popstate(event);">
+<script type="application/javascript">
+  function load() {
+    if(parent && parent.onChildLoad)
+      parent.onChildLoad();
+    if(opener && opener.onChildLoad)
+      opener.onChildLoad();
+  }
+
+  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>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/whatwg/file_bug500328_2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Inner frame for testing bug 500328.
+https://bugzilla.mozilla.org/show_bug.cgi?id=500328
+-->
+<head>
+</head>
+<body>
+<!--
+ Yes, this page is basically blank.  But no, about:blank wouldn't do as a
+ replacement, because we use this page to test that pushstate has correct
+ same-origin checks.
+-->
+file_bug500328_2.html
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/whatwg/test_bug500328.html
@@ -0,0 +1,740 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=500328
+-->
+<head>
+  <title>Test for Bug 500328</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.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=500328">Mozilla Bug 500328</a>
+<p id="display"></p>
+<div id="status"></div>
+<div id="content">
+  <iframe id="iframe"></iframe>
+  <iframe id="iframe2"></iframe>
+  <a id="link">link</a>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 500328 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var iframe = document.getElementById("iframe");
+var iframeCw = iframe.contentWindow;
+
+var iframe2 = document.getElementById("iframe2");
+var iframe2Cw = iframe2.contentWindow;
+
+var unvisitedColor;
+var visitedColor;
+
+var gCallbackOnIframeLoad = false;
+var gCallbackOnPopState = false;
+var gNumPopStates = 0;
+var gLastPopStateEvent;
+
+var gGen;
+
+function statusMsg(msg) {
+  var msgElem = document.createElement("p");
+  msgElem.appendChild(document.createTextNode(msg));
+
+  document.getElementById("status").appendChild(msgElem);
+}
+
+function longWait() {
+  setTimeout(function() { gGen.next(); }, 1000);
+}
+
+function shortWait() {
+  setTimeout(function() { gGen.next(); }, 0);
+}
+
+function onChildPopState(e) {
+  gNumPopStates++;
+  gLastPopStateEvent = e;
+  if (gCallbackOnPopState) {
+    statusMsg("Popstate(" + JSON.stringify(e.state) + ").  Calling gGen.next().");
+    gCallbackOnPopState = false;
+    gGen.next();
+  }
+  else {
+    statusMsg("Popstate(" + JSON.stringify(e.state) + ").  NOT calling gGen.next().");
+  }
+}
+
+function onChildLoad() {
+  if(gCallbackOnIframeLoad) {
+    statusMsg("Got load.  About to call gGen.next().");
+    gCallbackOnIframeLoad = false;
+    gGen.next();
+  }
+  else {
+    statusMsg("Got load, but not calling gGen.next() because gCallbackOnIframeLoad was false.");
+  }
+}
+
+function enableChildLoadCallback() {
+  gCallbackOnIframeLoad = true;
+}
+
+function enableChildPopStateCallback() {
+  gCallbackOnPopState = true;
+}
+
+function clearPopStateCounter() {
+  gNumPopStates = 0;
+}
+
+function noPopStateExpected(msg) {
+  is(gNumPopStates, 0, msg);
+
+  // Even if there's an error, set gNumPopStates to 0 so other tests don't
+  // fail.
+  gNumPopStates = 0;
+}
+
+function popstateExpected(msg) {
+  is(gNumPopStates, 1, msg);
+  gNumPopStates = 0;
+}
+
+function getColor(elem) {
+  return document.defaultView.getComputedStyle(elem, "").color;
+}
+
+function getSHistory(theWindow)
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  const Ci = Components.interfaces;
+  var sh = theWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .sessionHistory;
+  if (!sh || sh == null)
+    throw("Couldn't get shistory for window!");
+
+  return sh;
+}
+
+function getChromeWin(theWindow)
+{
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ const Ci = Components.interfaces;
+ return theWindow
+           .QueryInterface(Ci.nsIInterfaceRequestor)
+           .getInterface(Ci.nsIWebNavigation)
+           .QueryInterface(Ci.nsIDocShellTreeItem)
+           .rootTreeItem
+           .QueryInterface(Ci.nsIInterfaceRequestor)
+           .getInterface(Ci.nsIDOMWindow)
+           .QueryInterface(Ci.nsIDOMChromeWindow);
+}
+
+function getSHTitle(sh, offset)
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  if (!offset)
+    offset = 0;
+
+  // False instructs the SHistory not to modify its current index.
+  return sh.getEntryAtIndex(sh.index + offset, false).title;
+}
+
+// Tests that win's location ends with str
+function locationEndsWith(win, str) {
+  var exp = new RegExp(str + "$");
+  ok(win.location.toString().match(exp),
+     "Wrong window location.  Expected it to end with " +
+     str + ", but actuall was " + win.location);
+}
+
+function expectException(func, msg) {
+  var failed = false;
+  try {
+    func();
+  } catch(ex) {
+    failed = true;
+  }
+
+  ok(failed, msg + " succeeded, but should have failed.");
+}
+
+function runTest() {
+  // We can't enable universal XPConnect privleges in this function, because
+  // test 5 needs to be running at normal privleges in order to test the
+  // same-origin policy.
+
+  /**
+   * PRELIMINARY:
+   *  1. Clear the popstate counter
+   *  2. Get the visited and unvisited link colors.
+   */
+
+  clearPopStateCounter();
+
+  // Set the link's href to somewhere we haven't been so we can get the
+  // unvisited link color.
+  var rand = Date.now() + "-" + Math.random();
+  $("link").href = rand;
+  unvisitedColor = getColor($("link"));
+  statusMsg("Unvisited color is " + unvisitedColor);
+
+  // Set the link's href to our current location so we can get the visited link
+  // color.
+  $("link").href = document.location;
+  visitedColor = getColor($("link"));
+  statusMsg("Visited color is " + visitedColor);
+  isnot(visitedColor, unvisitedColor, "visited/unvisited link colors are the same?");
+
+  // The URL of file_bug500328_1.html on http://localhost:8888
+  var innerLoc;
+
+  // Now we can start the tests
+
+  /**
+   * 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();
+  yield;
+  popstateExpected("Expected initial popstate.");
+  statusMsg("Awake after first popstate.");
+
+  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.
+  iframeCw.onhashchange = function() {
+    ok(false, "Pushstate shouldn't trigger a hashchange.");
+  };
+
+  iframeCw.history.pushState(testObj2, "test 1#foo", "?test1#foo");
+  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.
+  longWait();
+  yield;
+  iframeCw.onhashchange = null;
+
+  statusMsg("About to go back to page 1.");
+  // We don't have to yield here because this back() and the resulting popstate
+  // 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(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.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(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.location.toString().match(/file_bug500328_1.html\?test1#foo$/),
+     "Going forward to 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.");
+
+  /*
+   * 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;
+  iframeCw.location = "file_bug500328_1.html";
+  enableChildPopStateCallback();
+  yield;
+  popstateExpected("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");
+  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.
+  // We could listen to MozAfterPaint, but that doesn't guarantee that we'll
+  // observe the new color.
+  while (getColor(ifLink) != unvisitedColor) {
+    // Dump so something shows up in the mochitest logs if we spin here.
+    dump("ifLink has wrong initial color.  Spinning...\n");
+    setTimeout(function() { gGen.next(); }, 10);
+    yield;
+  }
+
+  // Navigate iframe2 to dir/${rand}
+  iframe2.onload = onChildLoad;
+  enableChildLoadCallback();
+  iframe2Cw.location = "mytestdir/" + rand;
+  yield;
+
+  // PushState the iframe into the mytestdir directory.  This should cause
+  // ifLink to turn purple, since we just visited mytestdir/${rand} in iframe2.
+  iframeCw.history.pushState(null, "foo", "mytestdir/foo");
+
+  // Check that the link's color is now visitedColor
+  while (getColor(ifLink) != visitedColor) {
+    dump("ifLink has wrong color after pushstate.  Spinning...\n");
+    setTimeout(function() { gGen.next(); }, 10);
+    yield;
+  }
+
+  ok(ifLink.href.match("mytestdir\\/" + rand + "$"),
+     "inner frame's link should end with 'mytestdir/${rand}'");
+
+  // Navigate out of the mytestdir directory.  This should cause ifLink to turn
+  // blue again.
+  iframeCw.history.pushState(null, "bar", "../file_bug500328_1.html");
+
+  // Check that the link's color is back to the unvisited color.
+  while (getColor(ifLink) != unvisitedColor) {
+    dump("ifLink has wrong color after pushstating out of dir.  Spinning...\n");
+    setTimeout(function() { gGen.next(); }, 10);
+    yield;
+  }
+
+  ok(!ifLink.href.match("mytestdir"),
+     "inner frame's link shouldn't contain 'mytestdir'.");
+
+  /*
+   * TEST 2 tests that pushstate's same-origin checks are correct.
+   */
+  var filename = 'file_bug500328_2.html';
+  // Get the directory we're currently in
+  var dirname = document.location.pathname.replace(/[^\/]*$/, '');
+  statusMsg("Dirname is: " + dirname);
+  var loc = 'http://example.com' + dirname + filename;
+  statusMsg("About to transfer iframe to " + loc);
+  iframeCw.location = loc;
+  // We have to register a listener like this because this file is hosted on a
+  // different domain and can't notify us on load.
+  iframe.onload = onChildLoad;
+  enableChildLoadCallback();
+  yield;
+
+  // This function tries to pushstate and replacestate to the given URL and
+  // fails the test if the calls succeed.
+  var tryBadPushAndReplaceState = function(url) {
+    // XXX ex should be a SECURITY_ERR, not a plain Error.
+
+    var hist = iframeCw.history;
+    var url2 = url + dirname + filename;
+
+    expectException(function() { hist.pushState({}, "foo", url); },
+                    'pushState to ' + url);
+
+    expectException(function() { hist.pushState({}, "foo", url2); },
+                    'pushState to ' + url2);
+
+    expectException(function() { hist.replaceState({}, "foo", url); },
+                    'replaceState to ' + url);
+
+    expectException(function() { hist.replaceState({}, "foo", url2); },
+                    'replaceState to ' + url2);
+  }
+
+  // We're currently at http://example.com/[dirname]/[filename]
+  tryBadPushAndReplaceState("https://example.com");
+  tryBadPushAndReplaceState("http://foo.example.com");
+  tryBadPushAndReplaceState("http://example.com:1234");
+  tryBadPushAndReplaceState("http://example.com.a");
+  tryBadPushAndReplaceState("http://example.con");
+  tryBadPushAndReplaceState("http://eexample.com");
+  tryBadPushAndReplaceState("http://me@example.com");
+
+  /**
+   * TEST 3 tests that the session history entries' titles are properly sync'ed
+   * after push/pop states.
+   *
+   * 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");
+
+  var shistory = getSHistory(popup);
+
+  enableChildPopStateCallback();
+  yield;
+  popstateExpected("Didn't get popstate after opening window.");
+
+  popup.history.pushState(null, "title 0");
+  ok(!getChromeWin(popup).document
+        .getElementById("Browser:Back").hasAttribute("disabled"),
+     "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
+  // and so that the hash change we trigger below generates a new session
+  // history entry.
+  shortWait();
+  yield;
+
+  // Check that the current session history entry's title has been updated to
+  // reflect the new document title.
+  is(getSHTitle(shistory), "title 1", "SHEntry title test 1");
+
+  // Change the page's hash to #1, which will trigger a popstate event.
+  // We don't have to wait, because this happens synchronously.
+  popup.location.hash = "#1";
+  popstateExpected("Didn't get popstate after changing hash.");
+
+  popup.document.title = "title 2";
+
+  // Yield so listeners will be notified of the title change we just performed.
+  shortWait();
+  yield;
+
+  is(getSHTitle(shistory), "title 2", "SHEntry title test 2");
+
+  // Go back.  Happens synchronously.  We should get a popstate.
+  statusMsg("About to go back.");
+  popup.history.go(-1);
+  popstateExpected("Didn't get a popstate after going back.");
+
+  // Even though we went back, we expect the SHEntry title to remain the same
+  // because the document didn't change.
+  is(getSHTitle(shistory), "title 2", "SHEntry title test 3");
+
+  popup.document.title = "Changed 1";
+  shortWait();
+  yield;
+
+  // This check is really a test of bug 509055.
+  is(getSHTitle(shistory), "Changed 1", "SHEntry title test 4");
+
+  popup.close();
+
+  /**
+   * TEST 4 tests replaceState and that we don't get double popstates on
+   * window.open.  It also stress-tests the system and its interaction with
+   * bfcache by making many push/replace state calls.
+   */
+  popup = window.open("file_bug500328_1.html", "popup1",
+                      "height=200,width=200,location=yes," +
+                      "menubar=yes,status=yes,toolbar=yes,dependent=yes");
+
+  // The initial about:blank load into the new window shouldn't result in us
+  // seeing a popstate.  Once file_bug500328_1.html is loaded, it'll overwrite
+  // popup.onpopstate, and this assertion won't fire for that popstate and
+  // others after.
+  //
+  // 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();
+  yield;
+  statusMsg("Awake after loading content into popup.");
+
+  popup.history.replaceState({n:1, ok:true}, "state 1", "good1.html");
+  locationEndsWith(popup, "good1.html");
+  is(getSHTitle(shistory), "state 1", "SHEntry title 'state 1'");
+
+  // Flush the event loop so our next load creates a new session history entry.
+  shortWait();
+  yield;
+
+  enableChildPopStateCallback();
+  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.
+  shortWait();
+  yield;
+
+  // Now go back and make sure everything is as it should be.
+  enableChildPopStateCallback();
+  popup.history.back();
+  yield;
+
+  // Flush the event loop so the document's location is updated.
+  shortWait();
+  yield;
+
+  // We had some popstates above without corresponding popstateExpected()
+  // calls, so we need to clear the counter.
+  clearPopStateCounter();
+
+  locationEndsWith(popup, "good1.html");
+  is(JSON.stringify(gLastPopStateEvent.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");
+    }
+    else {
+      popup.history.pushState({n:i}, "state " + i, "state" + i + ".html");
+      for(var j = 0; j < i % 4; j++) {
+        popup.history.replaceState({n:i, nn:j}, "state " + i + ", " + j);
+      }
+      popup.history.replaceState({n:i, ok:true}, "state " + i, "good" + i + ".html");
+    }
+  }
+
+  for(var i = 29; i >= 1; i--) {
+    popup.history.back();
+    popstateExpected("Didn't get a popstate on iteration " + i);
+    locationEndsWith(popup, "good" + i + ".html");
+    is(gLastPopStateEvent.state.n, i, "Bad counter on last popstate event.");
+    ok(gLastPopStateEvent.state.ok,
+       "Last popstate event should have 'ok' set to true.");
+  }
+
+  popup.close();
+
+  /**
+   * TEST 5 tests misc security features and implementation details of
+   * Push/ReplaceState
+   */
+
+  /*
+   * Test that we can't push/replace an object with a large (over 640k
+   * characters) JSON representation.
+   */
+
+  // (In case you're curious, this loop generates an object which serializes to
+  // 694581 characters.)
+  var bigObject = new Object();
+  for(var i = 0; i < 51200; i++) {
+    bigObject[i] = i;
+  }
+  // statusMsg("Big object has size " + JSON.stringify(bigObject).length);
+
+  // We shouldn't be able to pushstate this large object, due to space
+  // constraints.
+  expectException(
+    function() { iframeCw.history.pushState(bigObject, "foo"); },
+    "pushState-ing large object");
+
+  expectException(
+    function() { iframeCw.history.replaceState(bigObject, "foo"); },
+    "replaceState-ing large object");
+
+  /*
+   * Make sure we can't push/replace state on an iframe of a different origin.
+   * This will work if this function has requested Universal XPConnect
+   * privileges, so any code which needs those privileges can't be in this
+   * function.
+   */
+  enableChildLoadCallback();
+  iframeCw.location = "http://example.com";
+  iframe.onload = onChildLoad;
+  yield;
+  iframe.onload = null;
+
+  expectException(
+    function() { iframeCw.history.pushState({}, "foo"); },
+    "pushState-ing in a different origin");
+
+  expectException(
+    function() { iframeCw.history.replaceState({}, "foo"); },
+    "replaceState-ing in a different origin");
+
+  /*
+   * If we do the following:
+   *   * Start at page A.
+   *   * PushState to page B.
+   *   * Refresh.  The server responds with a 404
+   *   * Go back.
+   * 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
+  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();
+  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;
+
+  // 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();
+  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.");
+
+  /**
+   * TEST 6 tests that the referrer is set properly after push/replace states.
+   */
+
+  /*
+   * First, a simple test:
+   *   * Load file_bug500328_1.html into iframe
+   *   * PushState to newpage1.html#foo
+   *   * Instruct the iframe to load file_bug500328_1.html into itself.
+   * The referer should be newpage1.html, without the hash.
+   *
+   * This also tests that we can call pushState from within the onload handler.
+   */
+  enableChildLoadCallback();
+  iframeCw.location = "file_bug500328_1.html";
+  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();
+  iframeCw.navigateTo("file_bug500328_1.html");
+  yield;
+
+  ok(iframeCw.document.referrer.toString().match(/newpage1.html$/),
+     "Wrong referrer after replaceState.  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();
+  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);
+
+  /* */
+  SimpleTest.finish();
+  statusMsg("********** Finished tests ***********");
+  while(true)
+  {
+    yield;
+
+    // I don't think this will actually make the mochitest fail, but there's
+    // not much we can do about this.  Realistically, though, regressions are
+    // not likely to fire extra events -- this trap is here mostly to catch
+    // errors made while wriring tests.
+    ok(false, "Got extra event!");
+  }
+
+  /*
+  statusMsg("XXXXXXXXXXXXXX");
+  while(true) {
+    yield;
+    statusMsg("Woken up.");
+  }
+  */
+}
+
+// Important: Wait to start the tests until the page has loaded.  Otherwise,
+// the test will occasionally fail when it begins running before the iframes
+// have finished their initial load of about:blank.
+window.addEventListener('load', function() {
+  gGen = runTest();
+  gGen.next();
+}, false);
+
+</script>
+</pre>
+</body>
+</html>
--- a/js/src/xpconnect/idl/nsIDispatchSupport.idl
+++ b/js/src/xpconnect/idl/nsIDispatchSupport.idl
@@ -52,17 +52,16 @@
 // as a class
 #pragma warning(push)
 #pragma warning(disable : 4099)
 #endif
 %}
 
 native COMVARIANT(VARIANT);
 [ptr] native COMVARIANTPtr(VARIANT);
-native JSVal(jsval);
 [ptr] native JSContextPtr(JSContext);
 
 interface IDispatch;
 
 [uuid(38df70e9-12f8-4732-af91-df36c38dc6f6)]
 interface nsIDispatchSupport : nsISupports
 {
     /**
--- a/js/src/xpconnect/idl/nsIXPConnect.idl
+++ b/js/src/xpconnect/idl/nsIXPConnect.idl
@@ -54,21 +54,22 @@
 %{ C++
 #include "jspubtd.h"
 #include "xptinfo.h"
 #include "nsAXPCNativeCallContext.h"
 %}
 
 /***************************************************************************/
 
+// NB: JSVal is declared in nsIVariant.idl
+
 [ptr] native JSContextPtr(JSContext);
 [ptr] native JSClassPtr(JSClass);
 [ptr] native JSObjectPtr(JSObject);
 [ptr] native JSValPtr(jsval);
-      native JSVal(jsval);
       native JSEqualityOp(JSEqualityOp);
       native JSID(jsid);
 [ptr] native voidPtrPtr(void*);
 [ptr] native nsScriptObjectTracerPtr(nsScriptObjectTracer);
 [ref] native nsCCTraversalCallbackRef(nsCycleCollectionTraversalCallback);
 [ptr] native nsAXPCNativeCallContextPtr(nsAXPCNativeCallContext);
 
 /***************************************************************************/
@@ -526,16 +527,22 @@ interface nsIXPConnect : nsISupports
     */
     void
     wrapJS(in JSContextPtr aJSContext,
            in JSObjectPtr  aJSObj,
            in nsIIDRef     aIID,
            [iid_is(aIID),retval] out nsQIResult result);
 
     /**
+     * Wraps the given JSVal in a nsIVariant and returns the new variant.
+     */
+    nsIVariant
+    jSValToVariant(in JSContextPtr cx, in JSValPtr aJSVal);
+
+    /**
     * This only succeeds if the JSObject is a nsIXPConnectWrappedNative.
     * A new wrapper is *never* constructed.
     */
     nsIXPConnectWrappedNative
     getWrappedNativeOfJSObject(in JSContextPtr aJSContext,
                                in JSObjectPtr  aJSObj);
 
     [noscript, notxpcom] nsISupports
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -1365,16 +1365,35 @@ nsXPConnect::WrapJS(JSContext * aJSConte
 
     nsresult rv;
     if(!XPCConvert::JSObject2NativeInterface(ccx, result, aJSObj,
                                              &aIID, nsnull, &rv))
         return rv;
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPConnect::JSValToVariant(JSContext *cx,
+                            jsval *aJSVal,
+                            nsIVariant ** aResult)
+{
+    NS_PRECONDITION(aJSVal, "bad param");
+    NS_PRECONDITION(aResult, "bad param");
+    *aResult = nsnull;
+
+    XPCCallContext ccx(NATIVE_CALLER, cx);
+    if(!ccx.IsValid())
+      return NS_ERROR_FAILURE;
+
+    *aResult = XPCVariant::newVariant(ccx, *aJSVal);
+    NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+
+    return NS_OK;
+}
+
 /* void wrapJSAggregatedToNative (in nsISupports aOuter, in JSContextPtr aJSContext, in JSObjectPtr aJSObj, in nsIIDRef aIID, [iid_is (aIID), retval] out nsQIResult result); */
 NS_IMETHODIMP
 nsXPConnect::WrapJSAggregatedToNative(nsISupports *aOuter,
                                       JSContext * aJSContext,
                                       JSObject * aJSObj,
                                       const nsIID & aIID,
                                       void * *result)
 {
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -4193,16 +4193,20 @@ public:
     // that case.
 
     // We #define and iid so that out module local code can use QI to detect 
     // if a given nsIVariant is in fact an XPCVariant. 
     NS_DECLARE_STATIC_IID_ACCESSOR(XPCVARIANT_IID)
 
     static XPCVariant* newVariant(XPCCallContext& ccx, jsval aJSVal);
 
+    /**
+     * nsIVariant exposes a GetAsJSVal() method, which also returns mJSVal.
+     * But if you can, you should call this one, since it can be inlined.
+     */
     jsval GetJSVal() const {return mJSVal;}
 
     XPCVariant(XPCCallContext& ccx, jsval aJSVal);
 
     /**
      * Convert a variant into a jsval.
      *
      * @param ccx the context for the whole procedure
--- a/js/src/xpconnect/src/xpcvariant.cpp
+++ b/js/src/xpconnect/src/xpcvariant.cpp
@@ -385,62 +385,69 @@ JSBool XPCVariant::InitializeData(XPCCal
     const nsIID& iid = NS_GET_IID(nsISupports);
 
     return nsnull != (xpc = nsXPConnect::GetXPConnect()) &&
            NS_SUCCEEDED(xpc->WrapJS(ccx, jsobj,
                         iid, getter_AddRefs(wrapper))) &&
            NS_SUCCEEDED(nsVariant::SetFromInterface(&mData, iid, wrapper));
 }
 
+NS_IMETHODIMP
+XPCVariant::GetAsJSVal(jsval* result)
+{
+  NS_PRECONDITION(result, "null result arg.");
+  *result = GetJSVal();
+  return NS_OK;
+}
+
 // static 
 JSBool 
 XPCVariant::VariantDataToJS(XPCLazyCallContext& lccx, 
                             nsIVariant* variant,
                             JSObject* scope, nsresult* pErr,
                             jsval* pJSVal)
 {
     // Get the type early because we might need to spoof it below.
     PRUint16 type;
-    if(NS_FAILED(variant->GetDataType(&type)))
+    if (NS_FAILED(variant->GetDataType(&type)))
         return JS_FALSE;
 
+    jsval realVal;
+    nsresult rv = variant->GetAsJSVal(&realVal);
+
+    if(NS_SUCCEEDED(rv) &&
+      (JSVAL_IS_PRIMITIVE(realVal) ||
+       type == nsIDataType::VTYPE_ARRAY ||
+       type == nsIDataType::VTYPE_EMPTY_ARRAY ||
+       type == nsIDataType::VTYPE_ID))
+    {
+        // It's not a JSObject (or it's a JSArray or a JSObject representing an
+        // nsID).  Just pass through the underlying data.
+        *pJSVal = realVal;
+        return JS_TRUE;
+    }
+
     nsCOMPtr<XPCVariant> xpcvariant = do_QueryInterface(variant);
-    if(xpcvariant)
+    if(xpcvariant && xpcvariant->mReturnRawObject)
     {
-        jsval realVal = xpcvariant->GetJSVal();
-        if(JSVAL_IS_PRIMITIVE(realVal) || 
-           type == nsIDataType::VTYPE_ARRAY ||
-           type == nsIDataType::VTYPE_EMPTY_ARRAY ||
-           type == nsIDataType::VTYPE_ID)
-        {
-            // Not a JSObject (or is a JSArray or is a JSObject representing 
-            // an nsID),.
-            // So, just pass through the underlying data.
-            *pJSVal = realVal;
-            return JS_TRUE;
-        }
-
-        if(xpcvariant->mReturnRawObject)
-        {
-            NS_ASSERTION(type == nsIDataType::VTYPE_INTERFACE ||
-                         type == nsIDataType::VTYPE_INTERFACE_IS,
-                         "Weird variant");
-            *pJSVal = realVal;
-            return JS_TRUE;
-        }
+        NS_ASSERTION(type == nsIDataType::VTYPE_INTERFACE ||
+                     type == nsIDataType::VTYPE_INTERFACE_IS,
+                     "Weird variant");
+        *pJSVal = realVal;
+        return JS_TRUE;
 
         // else, it's an object and we really need to double wrap it if we've 
         // already decided that its 'natural' type is as some sort of interface.
-        
+
         // We just fall through to the code below and let it do what it does.
     }
 
     // The nsIVariant is not a XPCVariant (or we act like it isn't).
     // So we extract the data and do the Right Thing.
-    
+
     // We ASSUME that the variant implementation can do these conversions...
 
     nsXPTCVariant xpctvar;
     nsID iid;
     nsAutoString astring;
     nsCAutoString cString;
     nsUTF8String utf8String;
     PRUint32 size;
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -993,17 +993,17 @@ DocumentViewerImpl::LoadComplete(nsresul
   // Flush out layout so it's up-to-date by the time onload is called.
   // Note that this could destroy the window, so do this before
   // checking for our mDocument and its window.
   if (mPresShell && !mStopped) {
     // Hold strong ref because this could conceivably run script
     nsCOMPtr<nsIPresShell> shell = mPresShell;
     shell->FlushPendingNotifications(Flush_Layout);
   }
-  
+
   nsresult rv = NS_OK;
   NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
 
   // First, get the window from the document...
   nsPIDOMWindow *window = mDocument->GetWindow();
 
   mLoaded = PR_TRUE;
 
@@ -1033,18 +1033,16 @@ DocumentViewerImpl::LoadComplete(nsresul
     docShell->GetRestoringDocument(&restoring);
     if (!restoring) {
       nsEventDispatcher::Dispatch(window, mPresContext, &event, nsnull,
                                   &status);
 #ifdef MOZ_TIMELINE
       // if navigator.xul's load is complete, the main nav window is visible
       // mark that point.
 
-      //printf("DEBUG: getting uri from document (%p)\n", mDocument.get());
-
       nsIURI *uri = mDocument ? mDocument->GetDocumentURI() : nsnull;
 
       if (uri) {
         //printf("DEBUG: getting spec for uri (%p)\n", uri.get());
         nsCAutoString spec;
         uri->GetSpec(spec);
         if (spec.EqualsLiteral("chrome://navigator/content/navigator.xul") ||
             spec.EqualsLiteral("chrome://browser/content/browser.xul")) {
@@ -1092,16 +1090,20 @@ DocumentViewerImpl::LoadComplete(nsresul
     mPrintIsPending        = PR_FALSE;
     mPrintDocIsFullyLoaded = PR_TRUE;
     Print(mCachedPrintSettings, mCachedPrintWebProgressListner);
     mCachedPrintSettings           = nsnull;
     mCachedPrintWebProgressListner = nsnull;
   }
 #endif
 
+  if (!mStopped) {
+    window->DispatchSyncPopState();
+  }
+
   return rv;
 }
 
 NS_IMETHODIMP
 DocumentViewerImpl::PermitUnload(PRBool aCallerClosesWindow, PRBool *aPermitUnload)
 {
   *aPermitUnload = PR_TRUE;
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -2833,16 +2833,22 @@ pref("html5.offmainthread", true);
 // first time the flush timer fires in the off-the-main-thread HTML5 parser.
 pref("html5.flushtimer.startdelay", 200);
 // Time in milliseconds between the return to non-speculating more and the 
 // first time the flush timer fires thereafter.
 pref("html5.flushtimer.continuedelay", 150);
 // Time in milliseconds between timer firings once the timer has starting 
 // firing.
 pref("html5.flushtimer.interval", 100);
+
+// Push/Pop/Replace State prefs
+pref("browser.history.allowPushState", true);
+pref("browser.history.allowReplaceState", true);
+pref("browser.history.allowPopState", true);
+pref("browser.history.maxStateObjectSize", 655360);
 // Initial max length for number of tree ops in on flush.
 pref("html5.opqueue.initiallengthlimit", 200);
 // Maximum time in milliseconds to spend flushing the tree op queue when not forced to completion
 pref("html5.opqueue.maxtime", 100);
 // Minimun number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
 pref("html5.opqueue.minlength", 100);
 // Maximum number of tree ops to flush regardless of time (takes precedence over the maxtime pref)
 pref("html5.opqueue.maxlength", 4500); // most top sites stay under this value
--- a/storage/src/Variant_inl.h
+++ b/storage/src/Variant_inl.h
@@ -246,10 +246,17 @@ Variant_base::GetAsStringWithSize(PRUint
 inline
 NS_IMETHODIMP
 Variant_base::GetAsWStringWithSize(PRUint32 *,
                                    PRUnichar **)
 {
   return NS_ERROR_CANNOT_CONVERT_DATA;
 }
 
+inline
+NS_IMETHODIMP
+Variant_base::GetAsJSVal(jsval *)
+{
+  return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
 } // namespace storage
 } // namespace mozilla
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -51,16 +51,17 @@
 #include "nsIAtom.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIDOMDataTransfer.h"
 #include "nsWeakPtr.h"
 #include "nsIWidget.h"
 #include "nsTArray.h"
 #include "nsTraceRefcnt.h"
 #include "nsITransferable.h"
+#include "nsIVariant.h"
 
 class nsIRenderingContext;
 class nsIRegion;
 class nsIMenuItem;
 class nsIAccessible;
 class nsIContent;
 class nsIURI;
 class nsHashKey;
@@ -241,16 +242,17 @@ class nsHashKey;
 #define NS_SCROLLBAR_LINE_PREV          (NS_SCROLLBAR_MESSAGE_START + 4)
 
 #define NS_STREAM_EVENT_START           1100
 #define NS_LOAD                         (NS_STREAM_EVENT_START)
 #define NS_PAGE_UNLOAD                  (NS_STREAM_EVENT_START + 1)
 #define NS_HASHCHANGE                   (NS_STREAM_EVENT_START + 2)
 #define NS_IMAGE_ABORT                  (NS_STREAM_EVENT_START + 3)
 #define NS_LOAD_ERROR                   (NS_STREAM_EVENT_START + 4)
+#define NS_POPSTATE                     (NS_STREAM_EVENT_START + 5)
 #define NS_BEFORE_PAGE_UNLOAD           (NS_STREAM_EVENT_START + 6)
 #define NS_PAGE_RESTORE                 (NS_STREAM_EVENT_START + 7)
  
 #define NS_FORM_EVENT_START             1200
 #define NS_FORM_SUBMIT                  (NS_FORM_EVENT_START)
 #define NS_FORM_RESET                   (NS_FORM_EVENT_START + 1)
 #define NS_FORM_CHANGE                  (NS_FORM_EVENT_START + 2)
 #define NS_FORM_SELECTED                (NS_FORM_EVENT_START + 3)
--- a/widget/src/xpwidgets/nsBaseWidget.cpp
+++ b/widget/src/xpwidgets/nsBaseWidget.cpp
@@ -1093,16 +1093,17 @@ case _value: eventName.AssignWithConvers
     _ASSIGN_eventName(NS_MOUSE_EXIT,"NS_MOUSE_EXIT");
     _ASSIGN_eventName(NS_MOUSE_BUTTON_DOWN,"NS_MOUSE_BUTTON_DOWN");
     _ASSIGN_eventName(NS_MOUSE_BUTTON_UP,"NS_MOUSE_BUTTON_UP");
     _ASSIGN_eventName(NS_MOUSE_CLICK,"NS_MOUSE_CLICK");
     _ASSIGN_eventName(NS_MOUSE_DOUBLECLICK,"NS_MOUSE_DBLCLICK");
     _ASSIGN_eventName(NS_MOUSE_MOVE,"NS_MOUSE_MOVE");
     _ASSIGN_eventName(NS_MOVE,"NS_MOVE");
     _ASSIGN_eventName(NS_LOAD,"NS_LOAD");
+    _ASSIGN_eventName(NS_POPSTATE,"NS_POPSTATE");
     _ASSIGN_eventName(NS_PAGE_UNLOAD,"NS_PAGE_UNLOAD");
     _ASSIGN_eventName(NS_HASHCHANGE,"NS_HASHCHANGE");
     _ASSIGN_eventName(NS_PAINT,"NS_PAINT");
     _ASSIGN_eventName(NS_XUL_BROADCAST, "NS_XUL_BROADCAST");
     _ASSIGN_eventName(NS_XUL_COMMAND_UPDATE, "NS_XUL_COMMAND_UPDATE");
     _ASSIGN_eventName(NS_SCROLLBAR_LINE_NEXT,"NS_SB_LINE_NEXT");
     _ASSIGN_eventName(NS_SCROLLBAR_LINE_PREV,"NS_SB_LINE_PREV");
     _ASSIGN_eventName(NS_SCROLLBAR_PAGE_NEXT,"NS_SB_PAGE_NEXT");
--- a/xpcom/ds/nsIVariant.idl
+++ b/xpcom/ds/nsIVariant.idl
@@ -72,26 +72,31 @@ interface nsIDataType : nsISupports
     const PRUint16 VTYPE_WSTRING_SIZE_IS     = 22; // TD_PWSTRING_SIZE_IS  = 22,
     const PRUint16 VTYPE_UTF8STRING          = 23; // TD_UTF8STRING        = 23,
     const PRUint16 VTYPE_CSTRING             = 24; // TD_CSTRING           = 24,
     const PRUint16 VTYPE_ASTRING             = 25; // TD_ASTRING           = 25,
     const PRUint16 VTYPE_EMPTY_ARRAY         = 254;
     const PRUint16 VTYPE_EMPTY               = 255;
 };
 
+%{ C++
+#include "jspubtd.h"
+%}
+
+native JSVal(jsval);
 
 /**
  * XPConnect has magic to transparently convert between nsIVariant and JS types.
  * We mark the interface [scriptable] so that JS can use methods
  * that refer to this interface. But we mark all the methods and attributes
  * [noscript] since any nsIVariant object will be automatically converted to a
  * JS type anyway.
  */
 
-[scriptable, uuid(6c9eb060-8c6a-11d5-90f3-0010a4e73d9a)]
+[scriptable, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)]
 interface nsIVariant : nsISupports
 {
     [noscript] readonly attribute PRUint16     dataType;
 
     [noscript] PRUint8      getAsInt8();
     [noscript] PRInt16      getAsInt16();
     [noscript] PRInt32      getAsInt32();
     [noscript] PRInt64      getAsInt64();
@@ -107,16 +112,17 @@ interface nsIVariant : nsISupports
     [notxpcom] nsresult     getAsID(out nsID retval);
     [noscript] AString      getAsAString();
     [noscript] DOMString    getAsDOMString();
     [noscript] ACString     getAsACString();
     [noscript] AUTF8String  getAsAUTF8String();
     [noscript] string       getAsString();
     [noscript] wstring      getAsWString();
     [noscript] nsISupports  getAsISupports();
+    [noscript] JSVal        getAsJSVal();
 
     [noscript] void getAsInterface(out nsIIDPtr iid, 
                                    [iid_is(iid), retval] out nsQIResult iface);
 
     [notxpcom] nsresult getAsArray(out PRUint16 type, out nsIID iid,
                                    out PRUint32 count, out voidPtr ptr);
 
     [noscript] void getAsStringWithSize(out PRUint32 size, 
--- a/xpcom/ds/nsVariant.cpp
+++ b/xpcom/ds/nsVariant.cpp
@@ -1881,16 +1881,23 @@ NS_IMETHODIMP nsVariant::GetAsWString(PR
 }
 
 /* nsISupports getAsISupports (); */
 NS_IMETHODIMP nsVariant::GetAsISupports(nsISupports **_retval)
 {
     return nsVariant::ConvertToISupports(mData, _retval);
 }
 
+/* jsval getAsJSVal() */
+NS_IMETHODIMP nsVariant::GetAsJSVal(jsval *_retval)
+{
+    // Can only get the JSVal from an XPCVariant.
+    return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
 /* void getAsInterface (out nsIIDPtr iid, [iid_is (iid), retval] out nsQIResult iface); */
 NS_IMETHODIMP nsVariant::GetAsInterface(nsIID * *iid, void * *iface)
 {
     return nsVariant::ConvertToInterface(mData, iid, iface);
 }
 
 /* [notxpcom] nsresult getAsArray (out PRUint16 type, out nsIID iid, out PRUint32 count, out voidPtr ptr); */
 NS_IMETHODIMP_(nsresult) nsVariant::GetAsArray(PRUint16 *type, nsIID *iid, PRUint32 *count, void * *ptr)