Bug 720083 - Workers: add support for transferable objects from HTML5 spec. r=sphink, r=bent
authorAndrea Marchesini <amarchesini@mozilla.com>
Wed, 03 Oct 2012 18:19:22 -0400
changeset 109242 db347c212f8063033e2b43bcb386dcf762916455
parent 109241 057d9fe28e35610095694520e31fdf2dc80c3728
child 109243 439eafb9c71e5b11dce5ab60afe6fecd1e59e058
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewerssphink, bent
bugs720083
milestone18.0a1
Bug 720083 - Workers: add support for transferable objects from HTML5 spec. r=sphink, r=bent
content/base/test/test_XHR_timeout.js
dom/base/nsStructuredCloneContainer.cpp
dom/ipc/StructuredCloneUtils.cpp
dom/ipc/StructuredCloneUtils.h
dom/workers/Worker.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/WorkerScope.cpp
dom/workers/test/Makefile.in
dom/workers/test/test_transferable.html
dom/workers/test/transferable_worker.js
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsclone.cpp
js/src/jsclone.h
js/src/shell/js.cpp
--- a/content/base/test/test_XHR_timeout.js
+++ b/content/base/test/test_XHR_timeout.js
@@ -7,33 +7,40 @@
 
 var inWorker = false;
 try {
   inWorker = !(self instanceof Window);
 } catch (e) {
   inWorker = true;
 }
 
+function message(data) {
+  if (inWorker)
+    self.postMessage(data);
+  else
+    self.postMessage(data, "*");
+}
+
 function is(got, expected, msg) {
   var obj = {};
   obj.type = "is";
   obj.got = got;
   obj.expected = expected;
   obj.msg = msg;
 
-  self.postMessage(obj, "*");
+  message(obj);
 }
 
 function ok(bool, msg) {
   var obj = {};
   obj.type = "ok";
   obj.bool = bool;
   obj.msg = msg;
 
-  self.postMessage(obj, "*");
+  message(obj);
 }
 
 /**
  * Generate and track results from a XMLHttpRequest with regards to timeouts.
  *
  * @param {String} id         The test description.
  * @param {Number} timeLimit  The initial setting for the request timeout.
  * @param {Number} resetAfter (Optional) The time after sending the request, to
@@ -318,17 +325,17 @@ var TestCounter = {
 
   next: function() {
     var test = TestRequests.shift();
 
     if (test) {
       test.startXHR();
     }
     else {
-      self.postMessage("done", "*");
+      message("done");
     }
   }
 };
 
 self.addEventListener("message", function (event) {
   if (event.data == "start") {
     TestCounter.next();
   }
--- a/dom/base/nsStructuredCloneContainer.cpp
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -46,47 +46,44 @@ nsStructuredCloneContainer::InitFromVari
   // 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);
- JSAutoCompartment ac(aCx, JS_GetGlobalObject(aCx));
+  JSAutoCompartment ac(aCx, JS_GetGlobalObject(aCx));
   JS_WrapValue(aCx, &jsData);
 
   nsCxPusher cxPusher;
   cxPusher.Push(aCx);
 
   uint64_t* jsBytes = nullptr;
   bool success = JS_WriteStructuredClone(aCx, jsData, &jsBytes, &mSize,
-                                           nullptr, nullptr);
+                                           nullptr, nullptr, JSVAL_VOID);
   NS_ENSURE_STATE(success);
   NS_ENSURE_STATE(jsBytes);
 
   // Copy jsBytes into our own buffer.
   mData = (uint64_t*) malloc(mSize);
   if (!mData) {
     mSize = 0;
     mVersion = 0;
 
-    // FIXME This should really be js::Foreground::Free, but that's not public.
-    JS_free(aCx, jsBytes);
-
+    JS_ClearStructuredClone(jsBytes, mSize);
     return NS_ERROR_FAILURE;
   }
   else {
     mVersion = JS_STRUCTURED_CLONE_VERSION;
   }
 
   memcpy(mData, jsBytes, mSize);
 
-  // FIXME Similarly, this should be js::Foreground::free.
-  JS_free(aCx, jsBytes);
+  JS_ClearStructuredClone(jsBytes, mSize);
   return NS_OK;
 }
 
 nsresult
 nsStructuredCloneContainer::InitFromBase64(const nsAString &aData,
                                            uint32_t aFormatVersion,
                                            JSContext *aCx)
 {
@@ -114,19 +111,24 @@ nsStructuredCloneContainer::DeserializeT
                                                  nsIVariant **aData)
 {
   NS_ENSURE_STATE(mData);
   NS_ENSURE_ARG_POINTER(aData);
   *aData = nullptr;
 
   // Deserialize to a jsval.
   jsval jsStateObj;
+  JSBool hasTransferable;
   bool success = JS_ReadStructuredClone(aCx, mData, mSize, mVersion,
-                                          &jsStateObj, nullptr, nullptr);
-  NS_ENSURE_STATE(success);
+                                          &jsStateObj, nullptr, nullptr) &&
+                 JS_StructuredCloneHasTransferables(mData, mSize,
+                                                    &hasTransferable);
+  // We want to be sure that mData doesn't contain transferable objects
+  MOZ_ASSERT(!hasTransferable);
+  NS_ENSURE_STATE(success && !hasTransferable);
 
   // 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);
 
--- a/dom/ipc/StructuredCloneUtils.cpp
+++ b/dom/ipc/StructuredCloneUtils.cpp
@@ -161,17 +161,17 @@ JSStructuredCloneCallbacks gCallbacks = 
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 
 bool
-ReadStructuredClone(JSContext* aCx, const uint64_t* aData, size_t aDataLength,
+ReadStructuredClone(JSContext* aCx, uint64_t* aData, size_t aDataLength,
                     const StructuredCloneClosure& aClosure, JS::Value* aClone)
 {
   void* closure = &const_cast<StructuredCloneClosure&>(aClosure);
   return !!JS_ReadStructuredClone(aCx, aData, aDataLength,
                                   JS_STRUCTURED_CLONE_VERSION, aClone,
                                   &gCallbacks, closure);
 }
 
--- a/dom/ipc/StructuredCloneUtils.h
+++ b/dom/ipc/StructuredCloneUtils.h
@@ -29,17 +29,17 @@ StructuredCloneData
 {
   StructuredCloneData() : mData(nullptr), mDataLength(0) {}
   uint64_t* mData;
   size_t mDataLength;
   StructuredCloneClosure mClosure;
 };
 
 bool
-ReadStructuredClone(JSContext* aCx, const uint64_t* aData, size_t aDataLength,
+ReadStructuredClone(JSContext* aCx, uint64_t* aData, size_t aDataLength,
                     const StructuredCloneClosure& aClosure, JS::Value* aClone);
 
 inline bool
 ReadStructuredClone(JSContext* aCx, const StructuredCloneData& aData,
                     JS::Value* aClone)
 {
   return ReadStructuredClone(aCx, aData.mData, aData.mDataLength,
                              aData.mClosure, aClone);
--- a/dom/workers/Worker.cpp
+++ b/dom/workers/Worker.cpp
@@ -281,21 +281,23 @@ private:
 
     const char*& name = sFunctions[1].name;
     WorkerPrivate* worker = GetInstancePrivate(aCx, obj, name);
     if (!worker) {
       return !JS_IsExceptionPending(aCx);
     }
 
     jsval message;
-    if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &message)) {
+    jsval transferable = JSVAL_VOID;
+    if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v/v",
+                             &message, &transferable)) {
       return false;
     }
 
-    return worker->PostMessage(aCx, message);
+    return worker->PostMessage(aCx, message, transferable);
   }
 };
 
 MOZ_STATIC_ASSERT(prototypes::MaxProtoChainLength == 3,
                   "The MaxProtoChainLength must match our manual DOMJSClasses");
 
 // When this DOMJSClass is removed and it's the last consumer of
 // sNativePropertyHooks then sNativePropertyHooks should be removed too.
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -2226,17 +2226,18 @@ WorkerPrivateParent<Derived>::ForgetMain
   SwapToISupportsArray(mPrincipal, aDoomed);
   SwapToISupportsArray(mCSP, aDoomed);
 
   mMainThreadObjectsForgotten = true;
 }
 
 template <class Derived>
 bool
-WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage)
+WorkerPrivateParent<Derived>::PostMessage(JSContext* aCx, jsval aMessage,
+                                          jsval aTransferable)
 {
   AssertIsOnParentThread();
 
   {
     MutexAutoLock lock(mMutex);
     if (mParentStatus != Running) {
       return true;
     }
@@ -2260,17 +2261,17 @@ WorkerPrivateParent<Derived>::PostMessag
     else {
       callbacks = &gMainThreadWorkerStructuredCloneCallbacks;
     }
   }
 
   nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
 
   JSAutoStructuredCloneBuffer buffer;
-  if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
+  if (!buffer.write(aCx, aMessage, aTransferable, callbacks, &clonedObjects)) {
     return false;
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(ParentAsWorkerPrivate(),
                              WorkerRunnable::WorkerThread, buffer,
                              clonedObjects);
   return runnable->Dispatch(aCx);
@@ -3417,29 +3418,30 @@ WorkerPrivate::StopSyncLoop(uint32_t aSy
 
   NS_ASSERTION(!syncQueue->mComplete, "Already called StopSyncLoop?!");
 
   syncQueue->mResult = aSyncResult;
   syncQueue->mComplete = true;
 }
 
 bool
-WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage)
+WorkerPrivate::PostMessageToParent(JSContext* aCx, jsval aMessage,
+                                   jsval aTransferable)
 {
   AssertIsOnWorkerThread();
 
   JSStructuredCloneCallbacks* callbacks =
     IsChromeWorker() ?
     &gChromeWorkerStructuredCloneCallbacks :
     &gWorkerStructuredCloneCallbacks;
 
   nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
 
   JSAutoStructuredCloneBuffer buffer;
-  if (!buffer.write(aCx, aMessage, callbacks, &clonedObjects)) {
+  if (!buffer.write(aCx, aMessage, aTransferable, callbacks, &clonedObjects)) {
     return false;
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(this, WorkerRunnable::ParentThread, buffer,
                              clonedObjects);
   return runnable->Dispatch(aCx);
 }
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -289,17 +289,17 @@ public:
 
   bool
   RootJSObject(JSContext* aCx, bool aRoot);
 
   void
   ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed);
 
   bool
-  PostMessage(JSContext* aCx, jsval aMessage);
+  PostMessage(JSContext* aCx, jsval aMessage, jsval aTransferable);
 
   uint64_t
   GetInnerWindowId();
 
   void
   UpdateJSContextOptions(JSContext* aCx, uint32_t aOptions);
 
   void
@@ -657,17 +657,18 @@ public:
 
   bool
   RunSyncLoop(JSContext* aCx, uint32_t aSyncLoopKey);
 
   void
   StopSyncLoop(uint32_t aSyncLoopKey, bool aSyncResult);
 
   bool
-  PostMessageToParent(JSContext* aCx, jsval aMessage);
+  PostMessageToParent(JSContext* aCx, jsval aMessage,
+                      jsval transferable);
 
   bool
   NotifyInternal(JSContext* aCx, Status aStatus);
 
   void
   ReportError(JSContext* aCx, const char* aMessage, JSErrorReport* aReport);
 
   bool
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -834,21 +834,23 @@ private:
 
     const char*& name = sFunctions[0].name;
     DedicatedWorkerGlobalScope* scope = GetInstancePrivate(aCx, obj, name);
     if (!scope) {
       return false;
     }
 
     jsval message;
-    if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v", &message)) {
+    jsval transferable = JSVAL_VOID;
+    if (!JS_ConvertArguments(aCx, aArgc, JS_ARGV(aCx, aVp), "v/v",
+                             &message, &transferable)) {
       return false;
     }
 
-    return scope->mWorker->PostMessageToParent(aCx, message);
+    return scope->mWorker->PostMessageToParent(aCx, message, transferable);
   }
 };
 
 MOZ_STATIC_ASSERT(prototypes::MaxProtoChainLength == 3,
                   "The MaxProtoChainLength must match our manual DOMJSClasses");
 
 // When this DOMJSClass is removed and it's the last consumer of
 // sNativePropertyHooks then sNativePropertyHooks should be removed too.
--- a/dom/workers/test/Makefile.in
+++ b/dom/workers/test/Makefile.in
@@ -95,16 +95,18 @@ MOCHITEST_FILES = \
   test_xhr_parameters.js \
   test_xhr_system.html \
   test_xhr_system.js \
   test_blobConstructor.html \
   test_csp.html \
   test_csp.js \
   test_csp.html^headers^ \
   csp_worker.js \
+  test_transferable.html \
+  transferable_worker.js \
   $(NULL)
 
 _SUBDIRMOCHITEST_FILES = \
   relativeLoad_sub_worker.js \
   relativeLoad_sub_worker2.js \
   relativeLoad_sub_import.js \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_transferable.html
@@ -0,0 +1,75 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker transferable objects
+-->
+<head>
+  <title>Test for DOM Worker transferable objects</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" language="javascript">
+
+  function test1(sizes) {
+    if (!sizes.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    var size = sizes.pop();
+
+    var worker = new Worker("transferable_worker.js");
+    worker.onmessage = function(event) {
+      ok(event.data.status, event.data.event);
+      if (!event.data.status) {
+        SimpleTest.finish();
+        return;
+      }
+
+      if (!event.data.last)
+        return;
+
+      test1(sizes);
+    }
+    worker.onerror = function(event) {
+      ok(false, "No errors!");
+    }
+
+    try {
+      worker.postMessage(42, true);
+      ok(false, "P: PostMessage - Exception for wrong type");
+    } catch(e) {
+      ok(true, "P: PostMessage - Exception for wrong type");
+    }
+
+    try {
+      ab = new ArrayBuffer(size);
+      worker.postMessage(42,[ab, ab]);
+      ok(false, "P: PostMessage - Exception for duplicate");
+    } catch(e) {
+      ok(true, "P: PostMessage - Exception for duplicate");
+    }
+
+    ab = new ArrayBuffer(size);
+    ok(ab.byteLength == size, "P: The size is: " + size + " == " + ab.byteLength);
+    worker.postMessage({ data: 0, timeout: 0, ab: ab, cb: ab, size: size }, [ab]);
+    ok(ab.byteLength == 0, "P: PostMessage - The size is: 0 == " + ab.byteLength)
+  }
+
+  test1([1024 * 1024 * 32, 128, 4]);
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/transferable_worker.js
@@ -0,0 +1,33 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+  if (event.data.data == 0) {
+    ab = new ArrayBuffer(event.data.size);
+    postMessage({ event: "W: The size is: " + event.data.size + " == " + ab.byteLength,
+                  status: ab.byteLength == event.data.size, last: false });
+
+    postMessage({ event: "W: postMessage with arrayBuffer", status: true,
+                  ab: ab, bc: [ ab, ab, { dd: ab } ] }, [ab]);
+
+    postMessage({ event: "W: The size is: 0 == " + ab.byteLength,
+                  status: ab.byteLength == 0, last: false });
+
+    postMessage({ event: "last one!", status: true, last: true });
+
+  } else {
+    var worker = new Worker('sync_worker.js');
+    worker.onmessage = function(event) {
+      postMessage(event.data);
+    }
+    worker.onsyncmessage = function(event) {
+      var v = postSyncMessage(event.data, null, 500);
+      event.reply(v);
+    }
+
+    --event.data.data;
+    worker.postMessage(event.data);
+  }
+}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6404,17 +6404,17 @@ JS_ParseJSONWithReviver(JSContext *cx, c
     if (!ParseJSONWithReviver(cx, chars, len, reviver, &value))
         return false;
 
     *vp = value;
     return true;
 }
 
 JS_PUBLIC_API(JSBool)
-JS_ReadStructuredClone(JSContext *cx, const uint64_t *buf, size_t nbytes,
+JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes,
                        uint32_t version, jsval *vp,
                        const JSStructuredCloneCallbacks *optionalCallbacks,
                        void *closure)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     if (version > JS_STRUCTURED_CLONE_VERSION) {
@@ -6426,28 +6426,47 @@ JS_ReadStructuredClone(JSContext *cx, co
         optionalCallbacks :
         cx->runtime->structuredCloneCallbacks;
     return ReadStructuredClone(cx, buf, nbytes, vp, callbacks, closure);
 }
 
 JS_PUBLIC_API(JSBool)
 JS_WriteStructuredClone(JSContext *cx, jsval valueArg, uint64_t **bufp, size_t *nbytesp,
                         const JSStructuredCloneCallbacks *optionalCallbacks,
-                        void *closure)
+                        void *closure, jsval transferable)
 {
     RootedValue value(cx, valueArg);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, value);
 
     const JSStructuredCloneCallbacks *callbacks =
         optionalCallbacks ?
         optionalCallbacks :
         cx->runtime->structuredCloneCallbacks;
-    return WriteStructuredClone(cx, value, (uint64_t **) bufp, nbytesp, callbacks, closure);
+    return WriteStructuredClone(cx, valueArg, (uint64_t **) bufp, nbytesp,
+                                callbacks, closure, transferable);
+}
+
+JS_PUBLIC_API(JSBool)
+JS_ClearStructuredClone(const uint64_t *data, size_t nbytes)
+{
+    return ClearStructuredClone(data, nbytes);
+}
+
+JS_PUBLIC_API(JSBool)
+JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes,
+                                   JSBool *hasTransferable)
+{
+    bool transferable;
+    if (!StructuredCloneHasTransferObjects(data, nbytes, &transferable))
+        return false;
+
+    *hasTransferable = transferable;
+    return true;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_StructuredClone(JSContext *cx, jsval valueArg, jsval *vp,
                    const JSStructuredCloneCallbacks *optionalCallbacks,
                    void *closure)
 {
     RootedValue value(cx, valueArg);
@@ -6463,17 +6482,17 @@ JS_StructuredClone(JSContext *cx, jsval 
     return buf.write(cx, value, callbacks, closure) &&
            buf.read(cx, vp, callbacks, closure);
 }
 
 void
 JSAutoStructuredCloneBuffer::clear()
 {
     if (data_) {
-        js_free(data_);
+        ClearStructuredClone(data_, nbytes_);
         data_ = NULL;
         nbytes_ = 0;
         version_ = 0;
     }
 }
 
 void
 JSAutoStructuredCloneBuffer::adopt(uint64_t *data, size_t nbytes, uint32_t version)
@@ -6482,16 +6501,22 @@ JSAutoStructuredCloneBuffer::adopt(uint6
     data_ = data;
     nbytes_ = nbytes;
     version_ = version;
 }
 
 bool
 JSAutoStructuredCloneBuffer::copy(const uint64_t *srcData, size_t nbytes, uint32_t version)
 {
+    // transferable objects cannot be copied
+    bool hasTransferable;
+    if (!StructuredCloneHasTransferObjects(data_, nbytes_, &hasTransferable) ||
+        hasTransferable)
+        return false;
+
     uint64_t *newData = static_cast<uint64_t *>(js_malloc(nbytes));
     if (!newData)
         return false;
 
     js_memcpy(newData, srcData, nbytes);
 
     clear();
     data_ = newData;
@@ -6510,33 +6535,44 @@ JSAutoStructuredCloneBuffer::steal(uint6
     data_ = NULL;
     nbytes_ = 0;
     version_ = 0;
 }
 
 bool
 JSAutoStructuredCloneBuffer::read(JSContext *cx, jsval *vp,
                                   const JSStructuredCloneCallbacks *optionalCallbacks,
-                                  void *closure) const
+                                  void *closure)
 {
     JS_ASSERT(cx);
     JS_ASSERT(data_);
     return !!JS_ReadStructuredClone(cx, data_, nbytes_, version_, vp,
                                     optionalCallbacks, closure);
 }
 
 bool
 JSAutoStructuredCloneBuffer::write(JSContext *cx, jsval valueArg,
                                    const JSStructuredCloneCallbacks *optionalCallbacks,
                                    void *closure)
 {
+    jsval transferable = JSVAL_VOID;
+    return write(cx, valueArg, transferable, optionalCallbacks, closure);
+}
+
+bool
+JSAutoStructuredCloneBuffer::write(JSContext *cx, jsval valueArg,
+                                   jsval transferable,
+                                   const JSStructuredCloneCallbacks *optionalCallbacks,
+                                   void *closure)
+{
     RootedValue value(cx, valueArg);
     clear();
     bool ok = !!JS_WriteStructuredClone(cx, value, &data_, &nbytes_,
-                                        optionalCallbacks, closure);
+                                        optionalCallbacks, closure,
+                                        transferable);
     if (!ok) {
         data_ = NULL;
         nbytes_ = 0;
         version_ = JS_STRUCTURED_CLONE_VERSION;
     }
     return ok;
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2038,19 +2038,19 @@ typedef JSObject *(*ReadStructuredCloneO
  * writer w. closure is any value passed to the JS_WriteStructuredCLone function.
  *
  * Return true on success, false on error/exception.
  */
 typedef JSBool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w,
                                          JSObject *obj, void *closure);
 
 /*
- * This is called when JS_WriteStructuredClone finds that the object to be
- * written is recursive. To follow HTML5, the application must throw a
- * DATA_CLONE_ERR DOMException. errorid is always JS_SCERR_RECURSION.
+ * This is called when JS_WriteStructuredClone is given an invalid transferable.
+ * To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException
+ * with error set to one of the JS_SCERR_* values.
  */
 typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32_t errorid);
 
 /************************************************************************/
 
 JS_BEGIN_EXTERN_C
 
 /*
@@ -5700,27 +5700,37 @@ JS_ParseJSONWithReviver(JSContext *cx, c
 #define JS_STRUCTURED_CLONE_VERSION 1
 
 struct JSStructuredCloneCallbacks {
     ReadStructuredCloneOp read;
     WriteStructuredCloneOp write;
     StructuredCloneErrorOp reportError;
 };
 
+/* Note: if the *data contains transferable objects, it can be read
+ * only once */
 JS_PUBLIC_API(JSBool)
-JS_ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes,
+JS_ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes,
                        uint32_t version, jsval *vp,
                        const JSStructuredCloneCallbacks *optionalCallbacks,
                        void *closure);
 
-/* Note: On success, the caller is responsible for calling js::Foreground::free(*datap). */
+/* Note: On success, the caller is responsible for calling
+ * JS_ClearStructuredClone(*datap, nbytesp). */
 JS_PUBLIC_API(JSBool)
 JS_WriteStructuredClone(JSContext *cx, jsval v, uint64_t **datap, size_t *nbytesp,
                         const JSStructuredCloneCallbacks *optionalCallbacks,
-                        void *closure);
+                        void *closure, jsval transferable);
+
+JS_PUBLIC_API(JSBool)
+JS_ClearStructuredClone(const uint64_t *data, size_t nbytes);
+
+JS_PUBLIC_API(JSBool)
+JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes,
+                                   JSBool *hasTransferable);
 
 JS_PUBLIC_API(JSBool)
 JS_StructuredClone(JSContext *cx, jsval v, jsval *vp,
                    const JSStructuredCloneCallbacks *optionalCallbacks,
                    void *closure);
 
 #ifdef __cplusplus
 JS_END_EXTERN_C
@@ -5756,22 +5766,27 @@ class JS_PUBLIC_API(JSAutoStructuredClon
      * Remove the buffer so that it will not be automatically freed.
      * After this, the caller is responsible for feeding the memory back to
      * JSAutoStructuredCloneBuffer::adopt.
      */
     void steal(uint64_t **datap, size_t *nbytesp, uint32_t *versionp=NULL);
 
     bool read(JSContext *cx, jsval *vp,
               const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
-              void *closure=NULL) const;
+              void *closure=NULL);
 
     bool write(JSContext *cx, jsval v,
                const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
                void *closure=NULL);
 
+    bool write(JSContext *cx, jsval v,
+               jsval transferable,
+               const JSStructuredCloneCallbacks *optionalCallbacks=NULL,
+               void *closure=NULL);
+
     /**
      * Swap ownership with another JSAutoStructuredCloneBuffer.
      */
     void swap(JSAutoStructuredCloneBuffer &other);
 
   private:
     /* Copy and assignment are not supported. */
     JSAutoStructuredCloneBuffer(const JSAutoStructuredCloneBuffer &other);
@@ -5783,16 +5798,17 @@ JS_BEGIN_EXTERN_C
 
 /* API for implementing custom serialization behavior (for ImageData, File, etc.) */
 
 /* The range of tag values the application may use for its own custom object types. */
 #define JS_SCTAG_USER_MIN  ((uint32_t) 0xFFFF8000)
 #define JS_SCTAG_USER_MAX  ((uint32_t) 0xFFFFFFFF)
 
 #define JS_SCERR_RECURSION 0
+#define JS_SCERR_TRANSFERABLE 1
 
 JS_PUBLIC_API(void)
 JS_SetStructuredCloneCallbacks(JSRuntime *rt, const JSStructuredCloneCallbacks *callbacks);
 
 JS_PUBLIC_API(JSBool)
 JS_ReadUint32Pair(JSStructuredCloneReader *r, uint32_t *p1, uint32_t *p2);
 
 JS_PUBLIC_API(JSBool)
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -13,49 +13,16 @@
 
 #include "vm/BooleanObject-inl.h"
 #include "vm/NumberObject-inl.h"
 #include "vm/RegExpObject-inl.h"
 #include "vm/StringObject-inl.h"
 
 using namespace js;
 
-JS_FRIEND_API(uint64_t)
-js_GetSCOffset(JSStructuredCloneWriter* writer)
-{
-    JS_ASSERT(writer);
-    return writer->output().count() * sizeof(uint64_t);
-}
-
-namespace js {
-
-bool
-WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
-                     const JSStructuredCloneCallbacks *cb, void *cbClosure)
-{
-    SCOutput out(cx);
-    JSStructuredCloneWriter w(out, cb, cbClosure);
-    return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
-}
-
-bool
-ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
-                    const JSStructuredCloneCallbacks *cb, void *cbClosure)
-{
-    SCInput in(cx, data, nbytes);
-
-    /* XXX disallow callers from using internal pointers to GC things. */
-    SkipRoot skip(cx, &in);
-
-    JSStructuredCloneReader r(in, cb, cbClosure);
-    return r.read(vp);
-}
-
-} /* namespace js */
-
 enum StructuredDataType {
     /* Structured data types provided by the engine */
     SCTAG_FLOAT_MAX = 0xFFF00000,
     SCTAG_NULL = 0xFFFF0000,
     SCTAG_UNDEFINED,
     SCTAG_BOOLEAN,
     SCTAG_INDEX,
     SCTAG_STRING,
@@ -63,30 +30,44 @@ enum StructuredDataType {
     SCTAG_REGEXP_OBJECT,
     SCTAG_ARRAY_OBJECT,
     SCTAG_OBJECT_OBJECT,
     SCTAG_ARRAY_BUFFER_OBJECT,
     SCTAG_BOOLEAN_OBJECT,
     SCTAG_STRING_OBJECT,
     SCTAG_NUMBER_OBJECT,
     SCTAG_BACK_REFERENCE_OBJECT,
+    SCTAG_TRANSFER_MAP_HEADER,
+    SCTAG_TRANSFER_MAP,
     SCTAG_TYPED_ARRAY_MIN = 0xFFFF0100,
     SCTAG_TYPED_ARRAY_INT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT8,
     SCTAG_TYPED_ARRAY_UINT8 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT8,
     SCTAG_TYPED_ARRAY_INT16 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT16,
     SCTAG_TYPED_ARRAY_UINT16 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT16,
     SCTAG_TYPED_ARRAY_INT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_INT32,
     SCTAG_TYPED_ARRAY_UINT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT32,
     SCTAG_TYPED_ARRAY_FLOAT32 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_FLOAT32,
     SCTAG_TYPED_ARRAY_FLOAT64 = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_FLOAT64,
     SCTAG_TYPED_ARRAY_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_UINT8_CLAMPED,
     SCTAG_TYPED_ARRAY_MAX = SCTAG_TYPED_ARRAY_MIN + TypedArray::TYPE_MAX - 1,
     SCTAG_END_OF_BUILTIN_TYPES
 };
 
+enum TransferableMapHeader {
+    SCTAG_TM_NOT_MARKED = 0,
+    SCTAG_TM_MARKED
+};
+
+JS_FRIEND_API(uint64_t)
+js_GetSCOffset(JSStructuredCloneWriter* writer)
+{
+    JS_ASSERT(writer);
+    return writer->output().count() * sizeof(uint64_t);
+}
+
 static StructuredDataType
 ArrayTypeToTag(uint32_t type)
 {
     JS_ASSERT(type < TypedArray::TYPE_MAX);
     return static_cast<StructuredDataType>(uint32_t(SCTAG_TYPED_ARRAY_MIN) + type);
 }
 
 JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN);
@@ -134,24 +115,99 @@ SwapBytes(uint64_t u)
            ((u & 0x0000ff0000000000LLU) >> 24) |
            ((u & 0x00ff000000000000LLU) >> 40) |
            ((u & 0xff00000000000000LLU) >> 56);
 #else
     return u;
 #endif
 }
 
+namespace js {
+
+bool
+WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
+                     const JSStructuredCloneCallbacks *cb, void *cbClosure,
+                     jsval transferable)
+{
+    SCOutput out(cx);
+    JSStructuredCloneWriter w(out, cb, cbClosure, transferable);
+    return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp);
+}
+
+bool
+ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp,
+                    const JSStructuredCloneCallbacks *cb, void *cbClosure)
+{
+    SCInput in(cx, data, nbytes);
+
+    /* XXX disallow callers from using internal pointers to GC things. */
+    SkipRoot skip(cx, &in);
+
+    JSStructuredCloneReader r(in, cb, cbClosure);
+    return r.read(vp);
+}
+
+bool
+ClearStructuredClone(const uint64_t *data, size_t nbytes)
+{
+    const uint64_t *point = data;
+    const uint64_t *end = data + nbytes / 8;
+
+    uint64_t u = SwapBytes(*point++);
+    uint32_t tag = uint32_t(u >> 32);
+    if (tag == SCTAG_TRANSFER_MAP_HEADER) {
+        if ((TransferableMapHeader)uint32_t(u) == SCTAG_TM_NOT_MARKED) {
+            while (point != end) {
+                uint64_t u = SwapBytes(*point++);
+                uint32_t tag = uint32_t(u >> 32);
+                if (tag == SCTAG_TRANSFER_MAP) {
+                    u = SwapBytes(*point++);
+                    js_free(reinterpret_cast<void*>(u));
+                }
+            }
+        }
+    }
+
+    js_free((void *)data);
+    return true;
+}
+
+bool
+StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes,
+                                  bool *hasTransferable)
+{
+    *hasTransferable = false;
+
+    if (data) {
+        uint64_t u = SwapBytes(*data);
+        uint32_t tag = uint32_t(u >> 32);
+        if (tag == SCTAG_TRANSFER_MAP_HEADER) {
+            *hasTransferable = true;
+        }
+    }
+
+    return true;
+}
+
+} /* namespace js */
+
+static inline uint64_t
+PairToUInt64(uint32_t tag, uint32_t data)
+{
+    return uint64_t(data) | (uint64_t(tag) << 32);
+}
+
 bool
 SCInput::eof()
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "truncated");
     return false;
 }
 
-SCInput::SCInput(JSContext *cx, const uint64_t *data, size_t nbytes)
+SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes)
     : cx(cx), point(data), end(data + nbytes / 8)
 {
     JS_ASSERT((uintptr_t(data) & 7) == 0);
     JS_ASSERT((nbytes & 7) == 0);
 }
 
 bool
 SCInput::read(uint64_t *p)
@@ -169,16 +225,52 @@ SCInput::readPair(uint32_t *tagp, uint32
     bool ok = read(&u);
     if (ok) {
         *tagp = uint32_t(u >> 32);
         *datap = uint32_t(u);
     }
     return ok;
 }
 
+bool
+SCInput::get(uint64_t *p)
+{
+    if (point == end)
+        return eof();
+    *p = SwapBytes(*point);
+    return true;
+}
+
+bool
+SCInput::getPair(uint32_t *tagp, uint32_t *datap)
+{
+    uint64_t u;
+    if (!get(&u))
+        return false;
+
+    *tagp = uint32_t(u >> 32);
+    *datap = uint32_t(u);
+    return true;
+}
+
+bool
+SCInput::replace(uint64_t u)
+{
+    if (point == end)
+       return eof();
+    *point = SwapBytes(u);
+    return true;
+}
+
+bool
+SCInput::replacePair(uint32_t tag, uint32_t data)
+{
+    return replace(PairToUInt64(tag, data));
+}
+
 /*
  * The purpose of this never-inlined function is to avoid a strange g++ build
  * error on OS X 10.5 (see bug 624080).  :-(
  */
 static JS_NEVER_INLINE double
 CanonicalizeNan(double d)
 {
     return JS_CANONICALIZE_NAN(d);
@@ -231,30 +323,30 @@ SCInput::readBytes(void *p, size_t nbyte
 
 bool
 SCInput::readChars(jschar *p, size_t nchars)
 {
     JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
     return readArray((uint16_t *) p, nchars);
 }
 
+bool
+SCInput::readPtr(void **p)
+{
+    return read((uint64_t *)p);
+}
+
 SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
 
 bool
 SCOutput::write(uint64_t u)
 {
     return buf.append(SwapBytes(u));
 }
 
-static inline uint64_t
-PairToUInt64(uint32_t tag, uint32_t data)
-{
-    return uint64_t(data) | (uint64_t(tag) << 32);
-}
-
 bool
 SCOutput::writePair(uint32_t tag, uint32_t data)
 {
     /*
      * As it happens, the tag word appears after the data word in the output.
      * This is because exponents occupy the last 2 bytes of doubles on the
      * little-endian platforms we care most about.
      *
@@ -340,25 +432,92 @@ SCOutput::writeBytes(const void *p, size
 bool
 SCOutput::writeChars(const jschar *p, size_t nchars)
 {
     JS_ASSERT(sizeof(jschar) == sizeof(uint16_t));
     return writeArray((const uint16_t *) p, nchars);
 }
 
 bool
+SCOutput::writePtr(const void *p)
+{
+    return write(reinterpret_cast<uint64_t>(p));
+}
+
+bool
 SCOutput::extractBuffer(uint64_t **datap, size_t *sizep)
 {
     *sizep = buf.length() * sizeof(uint64_t);
     return (*datap = buf.extractRawBuffer()) != NULL;
 }
 
 JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
 
 bool
+JSStructuredCloneWriter::parseTransferable()
+{
+    transferableObjects.clear();
+
+    if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable))
+        return true;
+
+    if (!transferable.isObject()) {
+        reportErrorTransferable();
+        return false;
+    }
+
+    JSObject* array = &transferable.toObject();
+    if (!JS_IsArrayObject(context(), array)) {
+        reportErrorTransferable();
+        return false;
+    }
+
+    uint32_t length;
+    if (!JS_GetArrayLength(context(), array, &length)) {
+        return false;
+    }
+
+    for (uint32_t i = 0; i < length; ++i) {
+        Value v;
+        if (!JS_GetElement(context(), array, i, &v)) {
+            return false;
+        }
+
+        if (!v.isObject()) {
+            reportErrorTransferable();
+            return false;
+        }
+
+        JSObject* tObj = &v.toObject();
+        if (!tObj->isArrayBuffer()) {
+            reportErrorTransferable();
+            return false;
+        }
+
+        // No duplicate:
+        if (transferableObjects.has(tObj)) {
+            reportErrorTransferable();
+            return false;
+        }
+
+        if (!transferableObjects.putNew(tObj))
+            return false;
+    }
+
+    return true;
+}
+
+void
+JSStructuredCloneWriter::reportErrorTransferable()
+{
+    if (callbacks && callbacks->reportError)
+        return callbacks->reportError(context(), JS_SCERR_TRANSFERABLE);
+}
+
+bool
 JSStructuredCloneWriter::writeString(uint32_t tag, JSString *str)
 {
     size_t length = str->length();
     const jschar *chars = str->getChars(context());
     if (!chars)
         return false;
     return out.writePair(tag, uint32_t(length)) && out.writeChars(chars, length);
 }
@@ -549,16 +708,42 @@ JSStructuredCloneWriter::startWrite(cons
         /* else fall through */
     }
 
     JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_UNSUPPORTED_TYPE);
     return false;
 }
 
 bool
+JSStructuredCloneWriter::writeTransferMap()
+{
+    if (!transferableObjects.empty()) {
+        if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_NOT_MARKED))
+            return false;
+
+        for (HashSet<JSObject*>::Range r = transferableObjects.all();
+             !r.empty(); r.popFront()) {
+            JSObject *obj = r.front();
+
+            if (!memory.put(obj, memory.count()))
+                return false;
+
+            void *content;
+            if (!JS_StealArrayBufferContents(context(), obj, &content))
+               return false;
+
+            if (!out.writePair(SCTAG_TRANSFER_MAP, 0) || !out.writePtr(content))
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool
 JSStructuredCloneWriter::write(const Value &v)
 {
     if (!startWrite(v))
         return false;
 
     while (!counts.empty()) {
         RootedObject obj(context(), &objs.back().toObject());
         AutoCompartment ac(context(), obj);
@@ -844,16 +1029,30 @@ JSStructuredCloneReader::startRead(Value
                                  JSMSG_SC_BAD_SERIALIZED_DATA,
                                  "invalid back reference in input");
             return false;
         }
         *vp = allObjs[data];
         return true;
       }
 
+      case SCTAG_TRANSFER_MAP_HEADER:
+        // A map header cannot be here but just at the beginning of the buffer.
+        JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
+                             JSMSG_SC_BAD_SERIALIZED_DATA,
+                             "invalid input");
+        return false;
+
+      case SCTAG_TRANSFER_MAP:
+        // A map cannot be here but just at the beginning of the buffer.
+        JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL,
+                             JSMSG_SC_BAD_SERIALIZED_DATA,
+                             "invalid input");
+        return false;
+
       case SCTAG_ARRAY_BUFFER_OBJECT:
         if (!readArrayBuffer(data, vp))
             return false;
         break;
 
       default: {
         if (tag <= SCTAG_FLOAT_MAX) {
             double d = ReinterpretPairAsDouble(tag, data);
@@ -912,18 +1111,58 @@ JSStructuredCloneReader::readId(jsid *id
         *idp = JSID_VOID;
         return true;
     }
     JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "id");
     return false;
 }
 
 bool
+JSStructuredCloneReader::readTransferMap()
+{
+    uint32_t tag, data;
+    if (!in.getPair(&tag, &data))
+        return false;
+
+    if (tag != SCTAG_TRANSFER_MAP_HEADER ||
+        (TransferableMapHeader)data == SCTAG_TM_MARKED)
+        return true;
+
+    if (!in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_MARKED))
+        return false;
+
+    if (!in.readPair(&tag, &data))
+        return false;
+
+    while (1) {
+        if (!in.getPair(&tag, &data))
+            return false;
+
+        if (tag != SCTAG_TRANSFER_MAP)
+            break;
+
+        void *content;
+
+        if (!in.readPair(&tag, &data) || !in.readPtr(&content))
+            return false;
+
+        JSObject *obj = JS_NewArrayBufferWithContents(context(), content);
+        if (!obj || !allObjs.append(ObjectValue(*obj)))
+            return false;
+    }
+
+    return true;
+}
+
+bool
 JSStructuredCloneReader::read(Value *vp)
 {
+    if (!readTransferMap())
+        return false;
+
     if (!startRead(vp))
         return false;
 
     while (objs.length() != 0) {
         RootedObject obj(context(), &objs.back().toObject());
 
         RootedId id(context());
         if (!readId(id.address()))
--- a/js/src/jsclone.h
+++ b/js/src/jsclone.h
@@ -11,73 +11,89 @@
 
 #include "js/HashTable.h"
 #include "js/Vector.h"
 
 namespace js {
 
 bool
 WriteStructuredClone(JSContext *cx, const Value &v, uint64_t **bufp, size_t *nbytesp,
-                     const JSStructuredCloneCallbacks *cb, void *cbClosure);
+                     const JSStructuredCloneCallbacks *cb, void *cbClosure,
+                     jsval transferable);
 
 bool
-ReadStructuredClone(JSContext *cx, const uint64_t *data, size_t nbytes, Value *vp,
+ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp,
                     const JSStructuredCloneCallbacks *cb, void *cbClosure);
 
+bool
+ClearStructuredClone(const uint64_t *data, size_t nbytes);
+
+bool
+StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes,
+                                  bool *hasTransferable);
+
 struct SCOutput {
   public:
     explicit SCOutput(JSContext *cx);
 
     JSContext *context() const { return cx; }
 
     bool write(uint64_t u);
     bool writePair(uint32_t tag, uint32_t data);
     bool writeDouble(double d);
     bool writeBytes(const void *p, size_t nbytes);
     bool writeChars(const jschar *p, size_t nchars);
+    bool writePtr(const void *);
 
     template <class T>
     bool writeArray(const T *p, size_t nbytes);
 
     bool extractBuffer(uint64_t **datap, size_t *sizep);
 
     uint64_t count() { return buf.length(); }
 
   private:
     JSContext *cx;
     js::Vector<uint64_t> buf;
 };
 
 struct SCInput {
   public:
-    SCInput(JSContext *cx, const uint64_t *data, size_t nbytes);
+    SCInput(JSContext *cx, uint64_t *data, size_t nbytes);
 
     JSContext *context() const { return cx; }
 
     bool read(uint64_t *p);
     bool readPair(uint32_t *tagp, uint32_t *datap);
     bool readDouble(double *p);
     bool readBytes(void *p, size_t nbytes);
     bool readChars(jschar *p, size_t nchars);
+    bool readPtr(void **);
+
+    bool get(uint64_t *p);
+    bool getPair(uint32_t *tagp, uint32_t *datap);
+
+    bool replace(uint64_t u);
+    bool replacePair(uint32_t tag, uint32_t data);
 
     template <class T>
     bool readArray(T *p, size_t nelems);
 
   private:
     bool eof();
 
     void staticAssertions() {
         JS_STATIC_ASSERT(sizeof(jschar) == 2);
         JS_STATIC_ASSERT(sizeof(uint32_t) == 4);
         JS_STATIC_ASSERT(sizeof(double) == 8);
     }
 
     JSContext *cx;
-    const uint64_t *point;
-    const uint64_t *end;
+    uint64_t *point;
+    uint64_t *end;
 };
 
 }
 
 struct JSStructuredCloneReader {
   public:
     explicit JSStructuredCloneReader(js::SCInput &in, const JSStructuredCloneCallbacks *cb,
                                      void *cbClosure)
@@ -85,16 +101,18 @@ struct JSStructuredCloneReader {
           callbacks(cb), closure(cbClosure) { }
 
     js::SCInput &input() { return in; }
     bool read(js::Value *vp);
 
   private:
     JSContext *context() { return in.context(); }
 
+    bool readTransferMap();
+
     bool checkDouble(double d);
     JSString *readString(uint32_t nchars);
     bool readTypedArray(uint32_t tag, uint32_t nelems, js::Value *vp);
     bool readArrayBuffer(uint32_t nbytes, js::Value *vp);
     bool readId(jsid *idp);
     bool startRead(js::Value *vp);
 
     js::SCInput &in;
@@ -111,38 +129,48 @@ struct JSStructuredCloneReader {
     // Any value passed to JS_ReadStructuredClone.
     void *closure;
 
     friend JSBool JS_ReadTypedArray(JSStructuredCloneReader *r, jsval *vp);
 };
 
 struct JSStructuredCloneWriter {
   public:
-    explicit JSStructuredCloneWriter(js::SCOutput &out, const JSStructuredCloneCallbacks *cb,
-                                     void *cbClosure)
-        : out(out), objs(out.context()), counts(out.context()), ids(out.context()),
-          memory(out.context()), callbacks(cb), closure(cbClosure) { }
+    explicit JSStructuredCloneWriter(js::SCOutput &out,
+                                     const JSStructuredCloneCallbacks *cb,
+                                     void *cbClosure,
+                                     jsval tVal)
+        : out(out), objs(out.context()),
+          counts(out.context()), ids(out.context()),
+          memory(out.context()), callbacks(cb), closure(cbClosure),
+          transferable(tVal), transferableObjects(out.context()) { }
 
-    bool init() { return memory.init(); }
+    bool init() { return transferableObjects.init() && parseTransferable() &&
+                         memory.init() && writeTransferMap(); }
 
     bool write(const js::Value &v);
 
     js::SCOutput &output() { return out; }
 
   private:
     JSContext *context() { return out.context(); }
 
+    bool writeTransferMap();
+
     bool writeString(uint32_t tag, JSString *str);
     bool writeId(jsid id);
     bool writeArrayBuffer(JSHandleObject obj);
     bool writeTypedArray(JSHandleObject obj);
     bool startObject(JSHandleObject obj, bool *backref);
     bool startWrite(const js::Value &v);
     bool traverseObject(JSHandleObject obj);
 
+    bool parseTransferable();
+    void reportErrorTransferable();
+
     inline void checkStack();
 
     js::SCOutput &out;
 
     // Vector of objects with properties remaining to be written.
     //
     // NB: These can span multiple compartments, so the compartment must be
     // entered before any manipulation is performed.
@@ -162,12 +190,16 @@ struct JSStructuredCloneWriter {
     CloneMemory memory;
 
     // The user defined callbacks that will be used for cloning.
     const JSStructuredCloneCallbacks *callbacks;
 
     // Any value passed to JS_WriteStructuredClone.
     void *closure;
 
+    // List of transferable objects
+    jsval transferable;
+    js::HashSet<JSObject*> transferableObjects;
+
     friend JSBool JS_WriteTypedArray(JSStructuredCloneWriter *w, jsval v);
 };
 
 #endif /* jsclone_h___ */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3285,27 +3285,28 @@ WrapWithProto(JSContext *cx, unsigned ar
 }
 
 static JSBool
 Serialize(JSContext *cx, unsigned argc, jsval *vp)
 {
     jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID;
     uint64_t *datap;
     size_t nbytes;
-    if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL))
+    if (!JS_WriteStructuredClone(cx, v, &datap, &nbytes, NULL, NULL, JSVAL_VOID))
         return false;
 
     JSObject *array = JS_NewUint8Array(cx, nbytes);
     if (!array) {
         JS_free(cx, datap);
         return false;
     }
     JS_ASSERT((uintptr_t(TypedArray::viewData(array)) & 7) == 0);
     js_memcpy(TypedArray::viewData(array), datap, nbytes);
-    JS_free(cx, datap);
+
+    JS_ClearStructuredClone(datap, nbytes);
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(array));
     return true;
 }
 
 static JSBool
 Deserialize(JSContext *cx, unsigned argc, jsval *vp)
 {
     Rooted<jsval> v(cx, argc > 0 ? JS_ARGV(cx, vp)[0] : JSVAL_VOID);