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 id76
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:00:57 +0000
treeherdermozilla-beta@d3a2732c35f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszpao, sicking
bugs551225
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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;
+};