Bug 551225 - Make pushState use structured clone. r=zpao, sicking
authorJustin Lebar <justin.lebar@gmail.com>
Sun, 24 Apr 2011 22:30:54 -0400
changeset 69448 0221eb8660f4d958dbb0f92cdfd18aeb9daa54ad
parent 69447 b486f0b4a30de3c476968beb1739feb9b2620160
child 69449 299c005302cd86c06fc6eae9b48693210696fa06
push idunknown
push userunknown
push dateunknown
reviewerszpao, sicking
bugs551225
milestone6.0a1
Bug 551225 - Make pushState use structured clone. r=zpao, sicking
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/browser_500328.js
content/base/public/nsContentUtils.h
content/base/public/nsIDocument.h
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
docshell/base/Makefile.in
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/build/nsDocShellModule.cpp
docshell/shistory/public/nsISHEntry.idl
docshell/shistory/src/nsSHEntry.cpp
docshell/shistory/src/nsSHEntry.h
docshell/test/Makefile.in
docshell/test/test_bug551225.html
dom/base/Makefile.in
dom/base/nsGlobalWindow.cpp
dom/base/nsHistory.cpp
dom/base/nsStructuredCloneContainer.cpp
dom/base/nsStructuredCloneContainer.h
dom/interfaces/base/Makefile.in
dom/interfaces/base/nsIStructuredCloneContainer.idl
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -1776,18 +1776,19 @@ SessionStoreService.prototype = {
       }
       catch (ex) { debug(ex); }
     }
 
     if (aEntry.docIdentifier) {
       entry.docIdentifier = aEntry.docIdentifier;
     }
 
-    if (aEntry.stateData) {
-      entry.stateData = aEntry.stateData;
+    if (aEntry.stateData != null) {
+      entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
+      entry.structuredCloneVersion = aEntry.stateData.formatVersion;
     }
 
     if (!(aEntry instanceof Ci.nsISHContainer)) {
       return entry;
     }
     
     if (aEntry.childCount > 0) {
       entry.children = [];
@@ -2993,18 +2994,23 @@ SessionStoreService.prototype = {
         aIdMap.used[id] = true;
       }
       shEntry.ID = id;
     }
 
     if (aEntry.docshellID)
       shEntry.docshellID = aEntry.docshellID;
 
-    if (aEntry.stateData) {
-      shEntry.stateData = aEntry.stateData;
+    if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
+      shEntry.stateData =
+        Cc["@mozilla.org/docshell/structured-clone-container;1"].
+        createInstance(Ci.nsIStructuredCloneContainer);
+
+      shEntry.stateData.initFromBase64(aEntry.structuredCloneState,
+                                       aEntry.structuredCloneVersion);
     }
 
     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]);
     }
 
--- a/browser/components/sessionstore/test/browser/browser_500328.js
+++ b/browser/components/sessionstore/test/browser/browser_500328.js
@@ -63,18 +63,17 @@ function checkState(tab) {
       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.");
+      is(aEvent.state.obj3.toString(), '/^a$/', "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);
@@ -115,22 +114,22 @@ function test() {
 
     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
+      //   page2   (state object: {obj3:/^a$/})  <-- 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");
+      history.replaceState({obj3:/^a$/}, "title-obj3");
 
       let state = ss.getTabState(tab);
       gBrowser.removeTab(tab);
 
       // Restore the state into a new tab.  Things don't work well when we
       // restore into the old tab, but that's not a real use case anyway.
       let tab2 = gBrowser.addTab("about:blank");
       ss.setTabState(tab2, state, true);
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -185,16 +185,21 @@ struct nsShortcutCandidate {
 class nsContentUtils
 {
   typedef mozilla::dom::Element Element;
 
 public:
   static nsresult Init();
 
   /**
+   * Get a JSContext from the document's scope object.
+   */
+  static JSContext* GetContextFromDocument(nsIDocument *aDocument);
+
+  /**
    * Get a scope from aNewDocument. Also get a context through the scope of one
    * of the documents, from the stack or the safe context.
    *
    * @param aOldDocument The document to try to get a context from. May be null.
    * @param aNewDocument The document to get aNewScope from.
    * @param aCx [out] Context gotten through one of the scopes, from the stack
    *                  or the safe context.
    * @param aNewScope [out] Scope gotten from aNewDocument.
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -62,16 +62,17 @@
 #include "nsPIDOMWindow.h"
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 #include "nsIScriptGlobalObject.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIAnimationFrameListener.h"
 #include "nsEventStates.h"
+#include "nsIStructuredCloneContainer.h"
 
 class nsIContent;
 class nsPresContext;
 class nsIPresShell;
 class nsIDocShell;
 class nsStyleSet;
 class nsIStyleSheet;
 class nsIStyleRule;
@@ -118,18 +119,18 @@ class Loader;
 namespace dom {
 class Link;
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 
 #define NS_IDOCUMENT_IID      \
-{ 0x2c6ad63f, 0xb7b9, 0x42f8, \
- { 0xbd, 0xde, 0x76, 0x0a, 0x83, 0xe3, 0xb0, 0x49 } }
+{ 0x26ef6218, 0xcd5e, 0x4953,  \
+ { 0xbb, 0x57, 0xb8, 0x50, 0x29, 0xa1, 0xae, 0x40 } }
 
 // Flag for AddStyleSheet().
 #define NS_STYLESHEET_FROM_CATALOG                (1 << 0)
 
 // Document states
 
 // RTL locale: specific to the XUL localedir attribute
 #define NS_DOCUMENT_STATE_RTL_LOCALE              NS_DEFINE_EVENT_STATE_MACRO(0)
@@ -1417,22 +1418,23 @@ public:
     Doc_Theme_Uninitialized, // not determined yet
     Doc_Theme_None,
     Doc_Theme_Neutral,
     Doc_Theme_Dark,
     Doc_Theme_Bright
   };
 
   /**
-   * Set the document's pending state object (as serialized to JSON).
+   * Set the document's pending state object (as serialized using structured
+   * clone).
    */
-  void SetCurrentStateObject(nsAString &obj)
+  void SetStateObject(nsIStructuredCloneContainer *scContainer)
   {
-    mCurrentStateObject.Assign(obj);
-    mCurrentStateObjectCached = nsnull;
+    mStateObjectContainer = scContainer;
+    mStateObjectCached = nsnull;
   }
 
   /**
    * 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.
    */
@@ -1510,17 +1512,17 @@ public:
   // Add/Remove images from the document image tracker
   virtual nsresult AddImage(imgIRequest* aImage) = 0;
   virtual nsresult RemoveImage(imgIRequest* aImage) = 0;
 
   // Makes the images on this document locked/unlocked. By default, the locking
   // state is unlocked/false.
   virtual nsresult SetImageLockingState(PRBool aLocked) = 0;
 
-  virtual nsresult GetMozCurrentStateObject(nsIVariant** aResult) = 0;
+  virtual nsresult GetStateObject(nsIVariant** aResult) = 0;
 
 protected:
   ~nsIDocument()
   {
     // XXX The cleanup of mNodeInfoManager (calling DropDocumentReference and
     //     releasing it) happens in the nsDocument destructor. We'd prefer to
     //     do it here but nsNodeInfoManager is a concrete class that we don't
     //     want to expose to users of the nsIDocument API outside of Gecko.
@@ -1719,34 +1721,33 @@ protected:
   PRUint32 mEventsSuppressed;
 
   /**
    * The number number of external scripts (ones with the src attribute) that
    * have this document as their owner and that are being evaluated right now.
    */
   PRUint32 mExternalScriptsBeingEvaluated;
 
-  nsString mCurrentStateObject;
-
   // Weak reference to mScriptGlobalObject QI:d to nsPIDOMWindow,
   // updated on every set of mSecriptGlobalObject.
   nsPIDOMWindow *mWindow;
 
   nsCOMPtr<nsIDocumentEncoder> mCachedEncoder;
 
   AnimationListenerList mAnimationFrameListeners;
 
   // The session history entry in which we're currently bf-cached. Non-null
   // if and only if we're currently in the bfcache.
   nsISHEntry* mSHEntry;
 
   // Our base target.
   nsString mBaseTarget;
 
-  nsCOMPtr<nsIVariant> mCurrentStateObjectCached;
+  nsCOMPtr<nsIStructuredCloneContainer> mStateObjectContainer;
+  nsCOMPtr<nsIVariant> mStateObjectCached;
 };
 
 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
@@ -1386,18 +1386,18 @@ nsContentUtils::InProlog(nsINode *aNode)
   }
 
   nsIDocument* doc = static_cast<nsIDocument*>(parent);
   nsIContent* root = doc->GetRootElement();
 
   return !root || doc->IndexOf(aNode) < doc->IndexOf(root);
 }
 
-static JSContext *
-GetContextFromDocument(nsIDocument *aDocument)
+JSContext *
+nsContentUtils::GetContextFromDocument(nsIDocument *aDocument)
 {
   nsIScriptGlobalObject *sgo = aDocument->GetScopeObject();
   if (!sgo) {
     // No script global, no context.
     return nsnull;
   }
 
   nsIScriptContext *scx = sgo->GetContext();
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -1876,17 +1876,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptEventManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mXPathEvaluatorTearoff)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLayoutHistoryState)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnloadBlocker)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFirstBaseNodeWithHref)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDOMImplementation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOriginalDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCachedEncoder)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCurrentStateObjectCached)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStateObjectCached)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mStyleSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mCatalogSheets)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mPreloadingImages)
 
   for (PRUint32 i = 0; i < tmp->mAnimationFrameListeners.Length(); ++i) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAnimationFrameListeners[i]");
@@ -8148,57 +8148,32 @@ nsIDocument::ScheduleBeforePaintEvent(ns
       !mPresShell ||
       mPresShell->GetPresContext()->RefreshDriver()->
         ScheduleBeforePaintEvent(this);
   }
 
 }
 
 nsresult
-nsDocument::GetMozCurrentStateObject(nsIVariant** aState)
-{
-  // Get the document's current state object. This is the object returned form
-  // both document.mozCurrentStateObject as well as popStateEvent.state
+nsDocument::GetStateObject(nsIVariant** aState)
+{
+  // Get the document's current state object. This is the object backing both
+  // history.state and popStateEvent.state.
+  //
+  // mStateObjectContainer may be null; this just means that there's no
+  // current state object.
 
   nsCOMPtr<nsIVariant> stateObj;
-  // Parse the JSON, if there's any to parse.
-  if (!mCurrentStateObjectCached && !mCurrentStateObject.IsEmpty()) {
-    // Get the JSContext associated with this document. We need this for
-    // deserialization.
-    nsIScriptGlobalObject *sgo = GetScopeObject();
-    NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
-
-    nsIScriptContext *scx = sgo->GetContext();
-    NS_ENSURE_TRUE(scx, NS_ERROR_FAILURE);
-
-    JSContext *cx = (JSContext*) scx->GetNativeContext();
-
-    // Make sure we in the request while we have jsval on the native stack.
-    JSAutoRequest ar(cx);
-
-    // 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;
-
-    // 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);
-    nsresult rv = json->DecodeToJSVal(mCurrentStateObject, 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(mCurrentStateObjectCached));
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  NS_IF_ADDREF(*aState = mCurrentStateObjectCached);
+  if (!mStateObjectCached && mStateObjectContainer) {
+    JSContext *cx = nsContentUtils::GetContextFromDocument(this);
+    mStateObjectContainer->
+      DeserializeToVariant(cx, getter_AddRefs(mStateObjectCached));
+  }
+
+  NS_IF_ADDREF(*aState = mStateObjectCached);
   
   return NS_OK;
 }
 
 nsresult
 nsDocument::AddImage(imgIRequest* aImage)
 {
   NS_ENSURE_ARG_POINTER(aImage);
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -974,17 +974,17 @@ public:
   virtual Element *GetElementById(const nsAString& aElementId);
 
   virtual Element *LookupImageElement(const nsAString& aElementId);
 
   virtual NS_HIDDEN_(nsresult) AddImage(imgIRequest* aImage);
   virtual NS_HIDDEN_(nsresult) RemoveImage(imgIRequest* aImage);
   virtual NS_HIDDEN_(nsresult) SetImageLockingState(PRBool aLocked);
 
-  virtual nsresult GetMozCurrentStateObject(nsIVariant** aResult);
+  virtual nsresult GetStateObject(nsIVariant** aResult);
 
 protected:
   friend class nsNodeUtils;
 
   /**
    * Check that aId is not empty and log a message to the console
    * service if it is.
    * @returns PR_TRUE if aId looks correct, PR_FALSE otherwise.
--- a/docshell/base/Makefile.in
+++ b/docshell/base/Makefile.in
@@ -114,9 +114,10 @@ CPPSRCS = \
 # static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES += \
   -I$(srcdir)/../shistory/src \
   -I$(topsrcdir)/layout/base \
+  -I$(topsrcdir)/js/src/xpconnect/src \
   $(NULL)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -155,16 +155,18 @@
 #include "nsIPermissionManager.h"
 #include "nsStreamUtils.h"
 #include "nsIController.h"
 #include "nsPICommandUpdater.h"
 #include "nsIDOMHTMLAnchorElement.h"
 #include "nsIWebBrowserChrome3.h"
 #include "nsITabChild.h"
 #include "nsIStrictTransportSecurityService.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsIStructuredCloneContainer.h"
 
 // Editor-related
 #include "nsIEditingSession.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsIDOMDocument.h"
 #include "nsICachingChannel.h"
@@ -7736,31 +7738,27 @@ nsDocShell::SetupNewViewer(nsIContentVie
     // viewer still set to hidden.
 
     return NS_OK;
 }
 
 nsresult
 nsDocShell::SetDocCurrentStateObj(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->SetCurrentStateObject(stateData);
+    nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+    nsresult rv = shEntry->GetStateData(getter_AddRefs(scContainer));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // It's OK for scContainer too be null here; that just means there's no
+    // state data associated with this history entry.
+    document->SetStateObject(scContainer);
+
     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
@@ -9408,84 +9406,24 @@ nsDocShell::SetReferrerURI(nsIURI * aURI
 {
     mReferrerURI = aURI;        // This assigment addrefs
 }
 
 //*****************************************************************************
 // nsDocShell: Session History
 //*****************************************************************************
 
-nsresult
-nsDocShell::StringifyJSValVariant(JSContext *aCx, 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);
-
-    nsCOMPtr<nsIJSContextStack> contextStack;
-    JSContext *cx = aCx;
-    if (!cx) {
-        // 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);
-
-        cx = (JSContext *)scx->GetNativeContext();
-
-        // If our json call triggers a JS-to-C++ call, we want that call to use
-        // aCx as the context.  So we push aCx onto the context stack.
-        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;
-    }
-
-    if (contextStack) {
-        if (NS_FAILED(rv)) {
-            JS_ClearPendingException(cx);
-        }
-
-        contextStack->Pop(&cx);
-    }
-
-    return rv;
-}
-
 NS_IMETHODIMP
 nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
                      const nsAString& aURL, PRBool aReplace, JSContext* aCx)
 {
     // Implements History.pushState and History.replaceState
 
     // Here's what we do, roughly in the order specified by HTML5:
-    // 1. Serialize aData to JSON.
+    // 1. Serialize aData using structured clone.
     // 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,
@@ -9509,60 +9447,76 @@ nsDocShell::AddState(nsIVariant *aData, 
     // 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.
     //
     // Note that we completely ignore the aTitle parameter.
 
     nsresult rv;
 
-    // Step 1: Clone aData by getting its JSON representation.
-    //
-    // StringifyJSValVariant might cause arbitrary JS to run, and this code
-    // might navigate the page we're on, potentially to a different origin! (bug
+    nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
+    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+    // Step 1: Serialize aData using structured clone.
+    nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+
+    // scContainer->Init might cause arbitrary JS to run, and this code might
+    // navigate the page we're on, potentially to a different origin! (bug
     // 634834)  To protect against this, we abort if our principal changes due
-    // to the stringify call.
-    nsString dataStr;
+    // to the InitFromVariant() call.
     {
         nsCOMPtr<nsIDocument> origDocument =
             do_GetInterface(GetAsSupports(this));
         if (!origDocument)
             return NS_ERROR_DOM_SECURITY_ERR;
         nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal();
 
-        rv = StringifyJSValVariant(aCx, aData, dataStr);
+        scContainer = new nsStructuredCloneContainer();
+        JSContext *cx = aCx;
+        if (!cx) {
+            cx = nsContentUtils::GetContextFromDocument(document);
+        }
+        rv = scContainer->InitFromVariant(aData, cx);
+
+        // If we're running in the document's context and the structured clone
+        // failed, clear the context's pending exception.  See bug 637116.
+        if (NS_FAILED(rv) && !aCx) {
+            JS_ClearPendingException(aCx);
+        }
         NS_ENSURE_SUCCESS(rv, rv);
 
         nsCOMPtr<nsIDocument> newDocument =
             do_GetInterface(GetAsSupports(this));
         if (!newDocument)
             return NS_ERROR_DOM_SECURITY_ERR;
         nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal();
 
         PRBool principalsEqual = PR_FALSE;
         origPrincipal->Equals(newPrincipal, &principalsEqual);
         NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR);
     }
 
     // Check that the state object isn't too long.
-    // Default max length: 640k chars.
+    // Default max length: 640k bytes.
     PRInt32 maxStateObjSize = 0xA0000;
     if (mPrefs) {
         mPrefs->GetIntPref("browser.history.maxStateObjectSize",
                            &maxStateObjSize);
     }
     if (maxStateObjSize < 0) {
         maxStateObjSize = 0;
     }
-    NS_ENSURE_TRUE(dataStr.Length() <= (PRUint32)maxStateObjSize,
+
+    PRUint64 scSize;
+    rv = scContainer->GetSerializedNBytes(&scSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    NS_ENSURE_TRUE(scSize <= (PRUint32)maxStateObjSize,
                    NS_ERROR_ILLEGAL_VALUE);
 
-    nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
-    NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
-
     // Step 2: Resolve aURL
     PRBool equalURIs = PR_TRUE;
     nsCOMPtr<nsIURI> oldURI = mCurrentURI;
     nsCOMPtr<nsIURI> newURI;
     if (aURL.Length() == 0) {
         newURI = mCurrentURI;
     }
     else {
@@ -9688,17 +9642,17 @@ nsDocShell::AddState(nsIVariant *aData, 
 
     } else {
         newSHEntry = mOSHE;
         newSHEntry->SetURI(newURI);
     }
 
     // Step 4: Modify new/original session history entry and clear its POST
     // data, if there is any.
-    newSHEntry->SetStateData(dataStr);
+    newSHEntry->SetStateData(scContainer);
     newSHEntry->SetPostData(nsnull);
 
     // 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));
@@ -9726,17 +9680,17 @@ nsDocShell::AddState(nsIVariant *aData, 
         SetCurrentURI(newURI, nsnull, PR_TRUE);
         document->SetDocumentURI(newURI);
 
         AddURIVisit(newURI, oldURI, oldURI, 0);
     }
     else {
         FireDummyOnLocationChange();
     }
-    document->SetCurrentStateObject(dataStr);
+    document->SetStateObject(scContainer);
 
     return NS_OK;
 }
 
 PRBool
 nsDocShell::ShouldAddToSessionHistory(nsIURI * aURI)
 {
     // I believe none of the about: urls should go in the history. But then
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -330,19 +330,19 @@ protected:
                                   nsIChannel * aChannel);
     virtual nsresult DoChannelLoad(nsIChannel * aChannel,
                                    nsIURILoader * aURILoader,
                                    PRBool aBypassClassifier);
 
     nsresult ScrollToAnchor(nsACString & curHash, nsACString & newHash,
                             PRUint32 aLoadType);
 
-    // Tries to stringify a given variant by converting it to JSON.  This only
+    // Tries to serialize a given variant using structured clone.  This only
     // works if the variant is backed by a JSVal.
-    nsresult StringifyJSValVariant(JSContext *aCx, nsIVariant *aData,
+    nsresult SerializeJSValVariant(JSContext *aCx, 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,
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -67,16 +67,18 @@
 // session history
 #include "nsSHEntry.h"
 #include "nsSHistory.h"
 #include "nsSHTransaction.h"
 
 // download history
 #include "nsDownloadHistory.h"
 
+#include "nsStructuredCloneContainer.h"
+
 static PRBool gInitialized = PR_FALSE;
 
 // The one time initialization for this module
 static nsresult
 Initialize()
 {
   NS_PRECONDITION(!gInitialized, "docshell module already initialized");
   if (gInitialized) {
@@ -126,16 +128,18 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsExterna
 // session history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory)
 
 // download history
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory)
 
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStructuredCloneContainer)
+
 NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID);
 NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID);
 NS_DEFINE_NAMED_CID(NS_WEBNAVIGATION_INFO_CID);
 NS_DEFINE_NAMED_CID(NS_ABOUT_REDIRECTOR_MODULE_CID);
 NS_DEFINE_NAMED_CID(NS_URI_LOADER_CID);
 NS_DEFINE_NAMED_CID(NS_DOCUMENTLOADER_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALHELPERAPPSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_EXTERNALPROTOCOLHANDLER_CID);
@@ -153,16 +157,17 @@ NS_DEFINE_NAMED_CID(NS_EXTERNALSHARINGAP
 NS_DEFINE_NAMED_CID(NS_EXTERNALURLHANDLERSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SHENTRY_CID);
 NS_DEFINE_NAMED_CID(NS_HISTORYENTRY_CID);
 NS_DEFINE_NAMED_CID(NS_SHTRANSACTION_CID);
 NS_DEFINE_NAMED_CID(NS_SHISTORY_CID);
 NS_DEFINE_NAMED_CID(NS_SHISTORY_INTERNAL_CID);
 NS_DEFINE_NAMED_CID(NS_DOWNLOADHISTORY_CID);
+NS_DEFINE_NAMED_CID(NS_STRUCTUREDCLONECONTAINER_CID);
 
 
 const mozilla::Module::CIDEntry kDocShellCIDs[] = {
   { &kNS_DOCSHELL_CID, false, NULL, nsDocShellConstructor },
   { &kNS_DEFAULTURIFIXUP_CID, false, NULL, nsDefaultURIFixupConstructor },
   { &kNS_WEBNAVIGATION_INFO_CID, false, NULL, nsWebNavigationInfoConstructor },
   { &kNS_ABOUT_REDIRECTOR_MODULE_CID, false, NULL, nsAboutRedirector::Create },
   { &kNS_URI_LOADER_CID, false, NULL, nsURILoaderConstructor },
@@ -183,16 +188,17 @@ const mozilla::Module::CIDEntry kDocShel
   { &kNS_EXTERNALURLHANDLERSERVICE_CID, false, NULL, nsExternalURLHandlerServiceConstructor },
 #endif
   { &kNS_SHENTRY_CID, false, NULL, nsSHEntryConstructor },
   { &kNS_HISTORYENTRY_CID, false, NULL, nsSHEntryConstructor },
   { &kNS_SHTRANSACTION_CID, false, NULL, nsSHTransactionConstructor },
   { &kNS_SHISTORY_CID, false, NULL, nsSHistoryConstructor },
   { &kNS_SHISTORY_INTERNAL_CID, false, NULL, nsSHistoryConstructor },
   { &kNS_DOWNLOADHISTORY_CID, false, NULL, nsDownloadHistoryConstructor },
+  { &kNS_STRUCTUREDCLONECONTAINER_CID, false, NULL, nsStructuredCloneContainerConstructor },
   { NULL }
 };
 
 const mozilla::Module::ContractIDEntry kDocShellContracts[] = {
   { "@mozilla.org/docshell;1", &kNS_DOCSHELL_CID },
   { NS_URIFIXUP_CONTRACTID, &kNS_DEFAULTURIFIXUP_CID },
   { NS_WEBNAVIGATION_INFO_CONTRACTID, &kNS_WEBNAVIGATION_INFO_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
@@ -231,16 +237,17 @@ const mozilla::Module::ContractIDEntry k
   { NS_EXTERNALURLHANDLERSERVICE_CONTRACTID, &kNS_EXTERNALURLHANDLERSERVICE_CID },
 #endif
   { NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID },
   { NS_HISTORYENTRY_CONTRACTID, &kNS_HISTORYENTRY_CID },
   { NS_SHTRANSACTION_CONTRACTID, &kNS_SHTRANSACTION_CID },
   { NS_SHISTORY_CONTRACTID, &kNS_SHISTORY_CID },
   { NS_SHISTORY_INTERNAL_CONTRACTID, &kNS_SHISTORY_INTERNAL_CID },
   { NS_DOWNLOADHISTORY_CONTRACTID, &kNS_DOWNLOADHISTORY_CID },
+  { NS_STRUCTUREDCLONECONTAINER_CONTRACTID, &kNS_STRUCTUREDCLONECONTAINER_CID },
   { NULL }
 };
 
 static const mozilla::Module kDocShellModule = {
   mozilla::Module::kVersion,
   kDocShellCIDs,
   kDocShellContracts,
   NULL,
--- a/docshell/shistory/public/nsISHEntry.idl
+++ b/docshell/shistory/public/nsISHEntry.idl
@@ -45,16 +45,17 @@
 #include "nsIHistoryEntry.idl"
 
 interface nsILayoutHistoryState;
 interface nsIContentViewer;
 interface nsIURI;
 interface nsIInputStream;
 interface nsIDocShellTreeItem;
 interface nsISupportsArray;
+interface nsIStructuredCloneContainer;
 %{C++
 struct nsIntRect;
 class nsDocShellEditorData;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
 
 
@@ -196,19 +197,19 @@ 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.
+     * serialized using structured clone.
      **/
-    attribute AString stateData;
+    attribute nsIStructuredCloneContainer 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();
 
--- a/docshell/shistory/src/nsSHEntry.cpp
+++ b/docshell/shistory/src/nsSHEntry.cpp
@@ -137,18 +137,18 @@ nsSHEntry::nsSHEntry(const nsSHEntry &ot
   , mExpired(other.mExpired)
   , mSticky(PR_TRUE)
   , mDynamicallyCreated(other.mDynamicallyCreated)
   // XXX why not copy mContentType?
   , mCacheKey(other.mCacheKey)
   , mParent(other.mParent)
   , mViewerBounds(0, 0, 0, 0)
   , mOwner(other.mOwner)
+  , mDocShellID(other.mDocShellID)
   , mStateData(other.mStateData)
-  , mDocShellID(other.mDocShellID)
 {
 }
 
 static PRBool
 ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
 {
   if (aEntry) {
     aEntry->SetParent(nsnull);
@@ -987,26 +987,27 @@ nsSHEntry::SetEditorData(nsDocShellEdito
 
 PRBool
 nsSHEntry::HasDetachedEditor()
 {
   return mEditorData != nsnull;
 }
 
 NS_IMETHODIMP
-nsSHEntry::GetStateData(nsAString &aStateData)
+nsSHEntry::GetStateData(nsIStructuredCloneContainer **aContainer)
 {
-  aStateData.Assign(mStateData);
+  NS_ENSURE_ARG_POINTER(aContainer);
+  NS_IF_ADDREF(*aContainer = mStateData);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHEntry::SetStateData(const nsAString &aDataStr)
+nsSHEntry::SetStateData(nsIStructuredCloneContainer *aContainer)
 {
-  mStateData.Assign(aDataStr);
+  mStateData = aContainer;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHEntry::IsDynamicallyAdded(PRBool* aAdded)
 {
   *aAdded = mDynamicallyCreated;
   return NS_OK;
--- a/docshell/shistory/src/nsSHEntry.h
+++ b/docshell/shistory/src/nsSHEntry.h
@@ -112,14 +112,14 @@ private:
   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;
   PRUint64                        mDocShellID;
   PRUint32                        mLastTouched;
+  nsCOMPtr<nsIStructuredCloneContainer> mStateData;
 };
 
 #endif /* nsSHEntry_h */
--- a/docshell/test/Makefile.in
+++ b/docshell/test/Makefile.in
@@ -82,16 +82,17 @@ include $(topsrcdir)/config/rules.mk
 		file_bug385434_3.html \
 		test_bug509055.html \
 		file_bug509055.html \
 		test_bug529119-1.html \
 		test_bug529119-2.html \
 		bug529119-window.html \
 		test_bug540462.html \
 		file_bug540462.html \
+		test_bug551225.html \
 		test_bug580069.html \
 		file_bug580069_1.html \
 		file_bug580069_2.sjs \
 		test_bug590573.html \
 		file_bug590573_1.html \
 		file_bug590573_2.html \
 		test_bug598895.html \
 		test_bug634834.html \
new file mode 100644
--- /dev/null
+++ b/docshell/test/test_bug551225.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551225
+-->
+<head>
+  <title>Test for Bug 551225</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=551225">Mozilla Bug 551225</a>
+
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 551225 **/
+
+obj = {
+  a: new Date('1/1/2000'),
+  b: /^foo$/,
+  c: 'bar'
+};
+
+history.replaceState(obj, '', '');
+is(history.state.a.toString(), new Date('1/1/2000').toString(), 'Date object.');
+is(history.state.b.toString(), '/^foo$/', 'Regex');
+is(history.state.c, 'bar', 'Other state');
+
+</script>
+</body>
+</html>
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -79,16 +79,17 @@ EXPORTS = \
   nsIScriptObjectPrincipal.h \
   nsIScriptRuntime.h \
   nsIScriptTimeoutHandler.h \
   nsPIDOMWindow.h \
   nsPIWindowRoot.h \
   nsFocusManager.h \
   nsWrapperCache.h \
   nsContentPermissionHelper.h \
+  nsStructuredCloneContainer.h \
   $(NULL)
 
 CPPSRCS =			\
 	nsBarProps.cpp          \
 	nsDOMException.cpp 	\
 	nsDOMWindowUtils.cpp 	\
 	nsJSEnvironment.cpp	\
 	nsJSTimeoutHandler.cpp	\
@@ -103,16 +104,17 @@ CPPSRCS =			\
 	nsMimeTypeArray.cpp	\
 	nsPluginArray.cpp	\
 	nsWindowRoot.cpp	\
 	nsDOMClassInfo.cpp	\
 	nsScriptNameSpaceManager.cpp \
 	nsDOMScriptObjectFactory.cpp \
 	nsQueryContentEventResult.cpp \
 	nsContentPermissionHelper.cpp \
+	nsStructuredCloneContainer.cpp \
 	$(NULL)
 
 include $(topsrcdir)/dom/dom-config.mk
 
 ifdef MOZ_JSDEBUGGER
 DEFINES += -DMOZ_JSDEBUGGER
 endif
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7767,20 +7767,20 @@ nsGlobalWindow::DispatchSyncPopState()
   nsresult rv = NS_OK;
 
   // Bail if the window is frozen.
   if (IsFrozen()) {
     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.
+  // going to send along with the popstate event.  The object is serialized
+  // using structured clone.
   nsCOMPtr<nsIVariant> stateObj;
-  rv = mDoc->GetMozCurrentStateObject(getter_AddRefs(stateObj));
+  rv = mDoc->GetStateObject(getter_AddRefs(stateObj));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Obtain a presentation shell for use in creating a popstate event.
   nsIPresShell *shell = mDoc->GetShell();
   nsRefPtr<nsPresContext> presContext;
   if (shell) {
     presContext = shell->GetPresContext();
   }
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -349,17 +349,17 @@ nsHistory::GetState(nsIVariant **aState)
   if (!nsContentUtils::CanCallerAccess(win->GetOuterWindow()))
     return NS_ERROR_DOM_SECURITY_ERR;
 
   nsCOMPtr<nsIDocument> doc =
     do_QueryInterface(win->GetExtantDocument());
   if (!doc)
     return NS_ERROR_NOT_AVAILABLE;
 
-  return doc->GetMozCurrentStateObject(aState);
+  return doc->GetStateObject(aState);
 }
 
 NS_IMETHODIMP
 nsHistory::Item(PRUint32 aIndex, nsAString& aReturn)
 {
   aReturn.Truncate();
   if (!nsContentUtils::IsCallerTrustedForRead()) {
     return NS_ERROR_DOM_SECURITY_ERR;
new file mode 100644
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=80:
+ *
+ * ***** 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 Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * 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 "nsStructuredCloneContainer.h"
+
+#include "nsCOMPtr.h"
+#include "nsIDocument.h"
+#include "nsIJSContextStack.h"
+#include "nsIScriptContext.h"
+#include "nsIVariant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsContentUtils.h"
+#include "xpcprivate.h"
+
+NS_IMPL_ADDREF(nsStructuredCloneContainer)
+NS_IMPL_RELEASE(nsStructuredCloneContainer)
+
+NS_INTERFACE_MAP_BEGIN(nsStructuredCloneContainer)
+  NS_INTERFACE_MAP_ENTRY(nsIStructuredCloneContainer)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsStructuredCloneContainer::nsStructuredCloneContainer()
+  : mData(nsnull), mSize(0), mVersion(0)
+{
+}
+
+nsStructuredCloneContainer::~nsStructuredCloneContainer()
+{
+  free(mData);
+}
+
+nsresult
+nsStructuredCloneContainer::InitFromVariant(nsIVariant *aData, JSContext *aCx)
+{
+  NS_ENSURE_STATE(!mData);
+  NS_ENSURE_ARG_POINTER(aData);
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  // First, try to extract a jsval from the variant |aData|.  This works only
+  // if the variant implements GetAsJSVal.
+  jsval jsData;
+  nsresult rv = aData->GetAsJSVal(&jsData);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+  // Make sure that we serialize in the right context.
+  JSAutoRequest ar(aCx);
+  JSAutoEnterCompartment ac;
+  NS_ENSURE_STATE(ac.enter(aCx, JS_GetGlobalObject(aCx)));
+
+  nsCxPusher cxPusher;
+  cxPusher.Push(aCx);
+
+  PRUint64* jsBytes = nsnull;
+  PRBool success = JS_WriteStructuredClone(aCx, jsData, &jsBytes, &mSize,
+                                           nsnull, nsnull);
+  NS_ENSURE_STATE(success);
+  NS_ENSURE_STATE(jsBytes);
+
+  // Copy jsBytes into our own buffer.
+  mData = (PRUint64*) malloc(mSize);
+  if (!mData) {
+    mSize = 0;
+    mVersion = 0;
+    return NS_ERROR_FAILURE;
+  }
+  else {
+    mVersion = JS_STRUCTURED_CLONE_VERSION;
+  }
+
+  memcpy(mData, jsBytes, mSize);
+  return NS_OK;
+}
+
+nsresult
+nsStructuredCloneContainer::InitFromBase64(const nsAString &aData,
+                                           PRUint32 aFormatVersion,
+                                           JSContext *aCx)
+{
+  NS_ENSURE_STATE(!mData);
+
+  NS_ConvertUTF16toUTF8 data(aData);
+
+  nsCAutoString binaryData;
+  nsresult rv = nsXPConnect::Base64Decode(data, binaryData);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Copy the string's data into our own buffer.
+  mData = (PRUint64*) malloc(binaryData.Length());
+  NS_ENSURE_STATE(mData);
+  memcpy(mData, binaryData.get(), binaryData.Length());
+
+  mSize = binaryData.Length();
+  mVersion = aFormatVersion;
+  return NS_OK;
+}
+
+
+nsresult
+nsStructuredCloneContainer::DeserializeToVariant(JSContext *aCx,
+                                                 nsIVariant **aData)
+{
+  NS_ENSURE_STATE(mData);
+  NS_ENSURE_ARG_POINTER(aData);
+  *aData = nsnull;
+
+  // Deserialize to a jsval.
+  jsval jsStateObj;
+  PRBool success = JS_ReadStructuredClone(aCx, mData, mSize, mVersion,
+                                          &jsStateObj, nsnull, nsnull);
+  NS_ENSURE_STATE(success);
+
+  // Now wrap the jsval as an nsIVariant.
+  nsCOMPtr<nsIVariant> varStateObj;
+  nsCOMPtr<nsIXPConnect> xpconnect = do_GetService(nsIXPConnect::GetCID());
+  NS_ENSURE_STATE(xpconnect);
+  xpconnect->JSValToVariant(aCx, &jsStateObj, getter_AddRefs(varStateObj));
+  NS_ENSURE_STATE(varStateObj);
+
+  NS_IF_ADDREF(*aData = varStateObj);
+  return NS_OK;
+}
+
+nsresult
+nsStructuredCloneContainer::GetDataAsBase64(nsAString &aOut)
+{
+  NS_ENSURE_STATE(mData);
+  aOut.Truncate();
+
+  nsCAutoString binaryData(reinterpret_cast<char*>(mData), mSize);
+  nsCAutoString base64Data;
+  nsresult rv = nsXPConnect::Base64Encode(binaryData, base64Data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aOut.Assign(NS_ConvertASCIItoUTF16(base64Data));
+  return NS_OK;
+}
+
+nsresult
+nsStructuredCloneContainer::GetSerializedNBytes(PRUint64 *aSize)
+{
+  NS_ENSURE_STATE(mData);
+  NS_ENSURE_ARG_POINTER(aSize);
+
+  // mSize is a size_t, while aSize is a PRUint64.  We rely on an implicit cast
+  // here so that we'll get a compile error if a size_t-to-uint64 cast is
+  // narrowing.
+  *aSize = mSize;
+
+  return NS_OK;
+}
+
+nsresult
+nsStructuredCloneContainer::GetFormatVersion(PRUint32 *aFormatVersion)
+{
+  NS_ENSURE_STATE(mData);
+  NS_ENSURE_ARG_POINTER(aFormatVersion);
+  *aFormatVersion = mVersion;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/nsStructuredCloneContainer.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=80:
+ *
+ * ***** 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 Initial Developer of the Original Code is the Mozilla Foundation.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * 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 nsStructuredCloneContainer_h__
+#define nsStructuredCloneContainer_h__
+
+#include "nsIStructuredCloneContainer.h"
+#include "jsapi.h"
+
+#define NS_STRUCTUREDCLONECONTAINER_CLASSNAME "nsStructuredCloneContainer"
+#define NS_STRUCTUREDCLONECONTAINER_CONTRACTID \
+  "@mozilla.org/docshell/structured-clone-container;1"
+#define NS_STRUCTUREDCLONECONTAINER_CID \
+{ /* 38bd0634-0fd4-46f0-b85f-13ced889eeec */       \
+  0x38bd0634,                                      \
+  0x0fd4,                                          \
+  0x46f0,                                          \
+  {0xb8, 0x5f, 0x13, 0xce, 0xd8, 0x89, 0xee, 0xec} \
+}
+
+class nsStructuredCloneContainer : public nsIStructuredCloneContainer
+{
+  public:
+    nsStructuredCloneContainer();
+    ~nsStructuredCloneContainer();
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSISTRUCTUREDCLONECONTAINER
+
+  private:
+    PRUint64* mData;
+
+    // This needs to be size_t rather than a PR-type so it matches the JS API.
+    size_t mSize;
+    PRUint32 mVersion;
+};
+
+#endif
--- a/dom/interfaces/base/Makefile.in
+++ b/dom/interfaces/base/Makefile.in
@@ -82,11 +82,12 @@ XPIDLSRCS =					\
 	nsIDOMNSFeatureFactory.idl		\
         nsIDOMClientRect.idl			\
         nsIDOMClientRectList.idl		\
 	nsIFocusManager.idl			\
 	nsIQueryContentEventResult.idl		\
 	nsITabChild.idl				\
 	nsITabParent.idl			\
 	nsIDOMGlobalPropertyInitializer.idl	\
+	nsIStructuredCloneContainer.idl		\
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIStructuredCloneContainer.idl
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=80:
+ *
+ * ***** 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 Initial Developer of the Original Code is the Mozilla Foundation.
+
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Justin Lebar <justin.lebar@gmail.com>
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIVariant;
+interface nsIDocument;
+
+%{C++
+struct JSContext;
+%}
+
+/**
+ * This interface acts as a container for an object serialized using the
+ * structured clone algorithm.
+ *
+ * You can copy an object into an nsIStructuredCloneContainer using
+ * initFromVariant or initFromBase64.  It's an error to initialize an
+ * nsIStructuredCloneContainer more than once.
+ *
+ * Once you've initialized the container, you can get a copy of the object it
+ * stores by calling deserializeToVariant.  You can also get a base-64-encoded
+ * string containing a copy of the container's serialized data, using
+ * getDataAsBase64.
+ */
+[scriptable, uuid(400a282d-7157-4ed0-85b4-8bdc2fa634cd)]
+interface nsIStructuredCloneContainer : nsISupports
+{
+  /**
+   * Initialize this structured clone container so it contains a clone of the
+   * given variant. aData must be backed by a jsval.
+   */
+  [implicit_jscontext]
+  void initFromVariant(in nsIVariant aData);
+
+  /**
+   * Initialize this structured clone container from a base-64-encoded byte
+   * stream, stored in aData.  aFormatVersion should be the version of the
+   * structured clone algorithm which was used to generate aData.
+   */
+  [implicit_jscontext]
+  void initFromBase64(in AString aData,in unsigned long aFormatVersion);
+
+  /**
+   * Deserialize the object this conatiner holds, returning it wrapped as
+   * an nsIVariant.
+   */
+  [implicit_jscontext]
+  nsIVariant deserializeToVariant();
+
+  /**
+   * Get this structured clone container's data as a base-64-encoded string.
+   */
+  AString getDataAsBase64();
+
+  /**
+   * Get the size in bytes of this container's serialized data.
+   */
+  readonly attribute unsigned long long serializedNBytes;
+
+  /**
+   * Get the version of the structured clone algorithm which was used to
+   * generate this container's serialized buffer.
+   */
+  readonly attribute unsigned long formatVersion;
+};