Backed out a19daae11647 (Bug 1186307) for W8 and W4 bustage on CLOSED TREE
authorNigel Babu <nigelbabu@gmail.com>
Thu, 27 Aug 2015 15:04:25 +0530
changeset 293876 f83d676130fcd277e308e821b05a901f7839c641
parent 293875 f4f537fe2647ccbe117abb3a21c92da4c0342ac7
child 293877 04acf83210811464d0837b9ab9d129198db09980
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1186307
milestone43.0a1
backs outa19daae1164762c39879c2454b23fe2d6281fd1d
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
Backed out a19daae11647 (Bug 1186307) for W8 and W4 bustage on CLOSED TREE
dom/base/PostMessageEvent.cpp
dom/base/StructuredCloneHelper.cpp
dom/base/StructuredCloneHelper.h
dom/base/StructuredCloneTags.h
dom/base/nsGlobalWindow.cpp
dom/base/nsStructuredCloneContainer.cpp
dom/base/test/mochitest.ini
dom/base/test/test_postMessages.html
dom/base/test/worker_postMessages.js
dom/broadcastchannel/BroadcastChannel.cpp
dom/canvas/ImageBitmap.cpp
dom/messagechannel/MessagePort.cpp
dom/messagechannel/SharedMessagePortMessage.cpp
dom/workers/DataStore.cpp
dom/workers/ServiceWorkerClient.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
dom/workers/WorkerStructuredClone.h
dom/workers/XMLHttpRequest.cpp
dom/workers/XMLHttpRequest.h
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -108,21 +108,18 @@ PostMessageEvent::Run()
     do_QueryInterface(static_cast<nsPIDOMWindow*>(targetWindow.get()));
   nsRefPtr<MessageEvent> event =
     new MessageEvent(eventTarget, nullptr, nullptr);
 
   event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */,
                           false /*cancelable */, messageData, mCallerOrigin,
                           EmptyString(), mSource);
 
-  nsTArray<nsRefPtr<MessagePortBase>> ports;
-  TakeTransferredPorts(ports);
-
   event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()),
-                                      ports));
+                                      GetTransferredPorts()));
 
   // We can't simply call dispatchEvent on the window because doing so ends
   // up flipping the trusted bit on the event, and we don't want that to
   // happen because then untrusted content can call postMessage on a chrome
   // window if it can get a reference to it.
 
   nsIPresShell *shell = targetWindow->GetExtantDoc()->GetShell();
   nsRefPtr<nsPresContext> presContext;
--- a/dom/base/StructuredCloneHelper.cpp
+++ b/dom/base/StructuredCloneHelper.cpp
@@ -9,34 +9,21 @@
 #include "ImageContainer.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/FileListBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
-#include "mozilla/dom/ImageData.h"
-#include "mozilla/dom/ImageDataBinding.h"
-#include "mozilla/dom/ipc/BlobChild.h"
-#include "mozilla/dom/StructuredClone.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/PMessagePort.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/ToJSValue.h"
-#include "mozilla/dom/WebCryptoCommon.h"
-#include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/BackgroundUtils.h"
-#include "MultipartBlobImpl.h"
-#include "nsFormData.h"
-#include "nsIRemoteBlob.h"
-#include "nsQueryObject.h"
-
-using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 JSObject*
 StructuredCloneCallbacksRead(JSContext* aCx,
@@ -150,17 +137,21 @@ StructuredCloneHelperInternal::Shutdown(
 
   mBuffer = nullptr;
 }
 
 bool
 StructuredCloneHelperInternal::Write(JSContext* aCx,
                                      JS::Handle<JS::Value> aValue)
 {
-  return Write(aCx, aValue, JS::UndefinedHandleValue);
+  MOZ_ASSERT(!mBuffer, "Double Write is not allowed");
+  MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown.");
+
+  mBuffer = new JSAutoStructuredCloneBuffer(&gCallbacks, this);
+  return mBuffer->write(aCx, aValue, &gCallbacks, this);
 }
 
 bool
 StructuredCloneHelperInternal::Write(JSContext* aCx,
                                      JS::Handle<JS::Value> aValue,
                                      JS::Handle<JS::Value> aTransfer)
 {
   MOZ_ASSERT(!mBuffer, "Double Write is not allowed");
@@ -221,47 +212,34 @@ StructuredCloneHelper::StructuredCloneHe
   : mSupportsCloning(aSupportsCloning == CloningSupported)
   , mSupportsTransferring(aSupportsTransferring == TransferringSupported)
   , mParent(nullptr)
 {}
 
 StructuredCloneHelper::~StructuredCloneHelper()
 {
   Shutdown();
-  MOZ_ASSERT(mTransferredPorts.IsEmpty());
 }
 
 void
 StructuredCloneHelper::Write(JSContext* aCx,
                              JS::Handle<JS::Value> aValue,
-                             bool aMaybeToDifferentThread,
                              ErrorResult& aRv)
 {
-  Write(aCx, aValue, JS::UndefinedHandleValue, aMaybeToDifferentThread, aRv);
+  Write(aCx, aValue, JS::UndefinedHandleValue, aRv);
 }
 
 void
 StructuredCloneHelper::Write(JSContext* aCx,
                              JS::Handle<JS::Value> aValue,
                              JS::Handle<JS::Value> aTransfer,
-                             bool aMaybeToDifferentThread,
                              ErrorResult& aRv)
 {
   if (!StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-    return;
-  }
-
-  if (aMaybeToDifferentThread) {
-    for (uint32_t i = 0, len = mBlobImplArray.Length(); i < len; ++i) {
-      if (!mBlobImplArray[i]->MayBeClonedToOtherThreads()) {
-        aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
-        return;
-      }
-    }
   }
 }
 
 void
 StructuredCloneHelper::Read(nsISupports* aParent,
                             JSContext* aCx,
                             JS::MutableHandle<JS::Value> aValue,
                             ErrorResult& aRv)
@@ -341,378 +319,75 @@ StructuredCloneHelper::FreeBuffer(uint64
 {
   MOZ_ASSERT(!mBuffer, "FreeBuffer() must be called without a Write().");
   MOZ_ASSERT(aBuffer);
   MOZ_ASSERT(aBufferLength);
 
   JS_ClearStructuredClone(aBuffer, aBufferLength, &gCallbacks, this, false);
 }
 
-namespace {
-
-// Recursive!
-already_AddRefed<BlobImpl>
-EnsureBlobForBackgroundManager(BlobImpl* aBlobImpl,
-                               PBackgroundChild* aManager = nullptr)
-{
-  MOZ_ASSERT(aBlobImpl);
-
-  if (!aManager) {
-    aManager = BackgroundChild::GetForCurrentThread();
-    MOZ_ASSERT(aManager);
-  }
-
-  nsRefPtr<BlobImpl> blobImpl = aBlobImpl;
-
-  const nsTArray<nsRefPtr<BlobImpl>>* subBlobImpls =
-    aBlobImpl->GetSubBlobImpls();
-
-  if (!subBlobImpls || !subBlobImpls->Length()) {
-    if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl)) {
-      // Always make sure we have a blob from an actor we can use on this
-      // thread.
-      BlobChild* blobChild = BlobChild::GetOrCreate(aManager, blobImpl);
-      MOZ_ASSERT(blobChild);
-
-      blobImpl = blobChild->GetBlobImpl();
-      MOZ_ASSERT(blobImpl);
-
-      DebugOnly<bool> isMutable;
-      MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable)));
-      MOZ_ASSERT(!isMutable);
-    } else {
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(blobImpl->SetMutable(false)));
-    }
-
-    return blobImpl.forget();
-  }
-
-  const uint32_t subBlobCount = subBlobImpls->Length();
-  MOZ_ASSERT(subBlobCount);
-
-  nsTArray<nsRefPtr<BlobImpl>> newSubBlobImpls;
-  newSubBlobImpls.SetLength(subBlobCount);
-
-  bool newBlobImplNeeded = false;
-
-  for (uint32_t index = 0; index < subBlobCount; index++) {
-    const nsRefPtr<BlobImpl>& subBlobImpl = subBlobImpls->ElementAt(index);
-    MOZ_ASSERT(subBlobImpl);
-
-    nsRefPtr<BlobImpl>& newSubBlobImpl = newSubBlobImpls[index];
-
-    newSubBlobImpl = EnsureBlobForBackgroundManager(subBlobImpl, aManager);
-    MOZ_ASSERT(newSubBlobImpl);
-
-    if (subBlobImpl != newSubBlobImpl) {
-      newBlobImplNeeded = true;
-    }
-  }
-
-  if (newBlobImplNeeded) {
-    nsString contentType;
-    blobImpl->GetType(contentType);
-
-    if (blobImpl->IsFile()) {
-      nsString name;
-      blobImpl->GetName(name);
-
-      blobImpl = new MultipartBlobImpl(newSubBlobImpls, name, contentType);
-    } else {
-      blobImpl = new MultipartBlobImpl(newSubBlobImpls, contentType);
-    }
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(blobImpl->SetMutable(false)));
-  }
-
-  return blobImpl.forget();
-}
-
-JSObject*
-ReadBlob(JSContext* aCx,
-         uint32_t aIndex,
-         StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aHelper);
-  MOZ_ASSERT(aIndex < aHelper->BlobImpls().Length());
-  nsRefPtr<BlobImpl> blobImpl =  aHelper->BlobImpls()[aIndex];
-
-  blobImpl = EnsureBlobForBackgroundManager(blobImpl);
-  MOZ_ASSERT(blobImpl);
-
-  // nsRefPtr<File> needs to go out of scope before toObjectOrNull() is
-  // called because the static analysis thinks dereferencing XPCOM objects
-  // can GC (because in some cases it can!), and a return statement with a
-  // JSObject* type means that JSObject* is on the stack as a raw pointer
-  // while destructors are running.
-  JS::Rooted<JS::Value> val(aCx);
-  {
-    nsRefPtr<Blob> blob = Blob::Create(aHelper->ParentDuringRead(), blobImpl);
-    if (!ToJSValue(aCx, blob, &val)) {
-      return nullptr;
-    }
-  }
-
-  return &val.toObject();
-}
-
-bool
-WriteBlob(JSStructuredCloneWriter* aWriter,
-          Blob* aBlob,
-          StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aWriter);
-  MOZ_ASSERT(aBlob);
-  MOZ_ASSERT(aHelper);
-
-  nsRefPtr<BlobImpl> blobImpl = EnsureBlobForBackgroundManager(aBlob->Impl());
-  MOZ_ASSERT(blobImpl);
-
-  // We store the position of the blobImpl in the array as index.
-  if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
-                         aHelper->BlobImpls().Length())) {
-    aHelper->BlobImpls().AppendElement(blobImpl);
-    return true;
-  }
-
-  return false;
-}
-
-// Read the WriteFileList for the format.
-JSObject*
-ReadFileList(JSContext* aCx,
-             JSStructuredCloneReader* aReader,
-             uint32_t aCount,
-             StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aCx);
-  MOZ_ASSERT(aReader);
-
-  JS::Rooted<JS::Value> val(aCx);
-  {
-    nsRefPtr<FileList> fileList = new FileList(aHelper->ParentDuringRead());
-
-    uint32_t tag, offset;
-    // Offset is the index of the blobImpl from which we can find the blobImpl
-    // for this FileList.
-    if (!JS_ReadUint32Pair(aReader, &tag, &offset)) {
-      return nullptr;
-    }
-
-    MOZ_ASSERT(tag == 0);
-
-    // |aCount| is the number of BlobImpls to use from the |offset|.
-    for (uint32_t i = 0; i < aCount; ++i) {
-      uint32_t index = offset + i;
-      MOZ_ASSERT(index < aHelper->BlobImpls().Length());
-
-      nsRefPtr<BlobImpl> blobImpl = aHelper->BlobImpls()[index];
-      MOZ_ASSERT(blobImpl->IsFile());
-
-      nsRefPtr<File> file = File::Create(aHelper->ParentDuringRead(), blobImpl);
-      if (!fileList->Append(file)) {
-        return nullptr;
-      }
-    }
-
-    if (!ToJSValue(aCx, fileList, &val)) {
-      return nullptr;
-    }
-  }
-
-  return &val.toObject();
-}
-
-// The format of the FileList serialization is:
-// - pair of ints: SCTAG_DOM_FILELIST, Length of the FileList
-// - pair of ints: 0, The offset of the BlobImpl array
-bool
-WriteFileList(JSStructuredCloneWriter* aWriter,
-              FileList* aFileList,
-              StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aWriter);
-  MOZ_ASSERT(aFileList);
-  MOZ_ASSERT(aHelper);
-
-  // A FileList is serialized writing the X number of elements and the offset
-  // from mBlobImplArray. The Read will take X elements from mBlobImplArray
-  // starting from the offset.
-  if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST,
-                          aFileList->Length()) ||
-      !JS_WriteUint32Pair(aWriter, 0,
-                          aHelper->BlobImpls().Length())) {
-    return false;
-  }
-
-  for (uint32_t i = 0; i < aFileList->Length(); ++i) {
-    aHelper->BlobImpls().AppendElement(aFileList->Item(i)->Impl());
-  }
-
-  return true;
-}
-
-// Read the WriteFormData for the format.
-JSObject*
-ReadFormData(JSContext* aCx,
-             JSStructuredCloneReader* aReader,
-             uint32_t aCount,
-             StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aCx);
-  MOZ_ASSERT(aReader);
-  MOZ_ASSERT(aHelper);
-
-  // See the serialization of the FormData for the format.
-  JS::Rooted<JS::Value> val(aCx);
-  {
-    nsRefPtr<nsFormData> formData =
-      new nsFormData(aHelper->ParentDuringRead());
-
-    Optional<nsAString> thirdArg;
-    for (uint32_t i = 0; i < aCount; ++i) {
-      nsAutoString name;
-      if (!ReadString(aReader, name)) {
-        return nullptr;
-      }
-
-      uint32_t tag, indexOrLengthOfString;
-      if (!JS_ReadUint32Pair(aReader, &tag, &indexOrLengthOfString)) {
-        return nullptr;
-      }
-
-      if (tag == SCTAG_DOM_BLOB) {
-        MOZ_ASSERT(indexOrLengthOfString < aHelper->BlobImpls().Length());
-
-        nsRefPtr<BlobImpl> blobImpl =
-          aHelper->BlobImpls()[indexOrLengthOfString];
-        MOZ_ASSERT(blobImpl->IsFile());
-
-        nsRefPtr<File> file =
-          File::Create(aHelper->ParentDuringRead(), blobImpl);
-        MOZ_ASSERT(file);
-
-        formData->Append(name, *file, thirdArg);
-      } else {
-        MOZ_ASSERT(tag == 0);
-
-        nsAutoString value;
-        value.SetLength(indexOrLengthOfString);
-        size_t charSize = sizeof(nsString::char_type);
-        if (!JS_ReadBytes(aReader, (void*) value.BeginWriting(),
-                          indexOrLengthOfString * charSize)) {
-          return nullptr;
-        }
-
-        formData->Append(name, value);
-      }
-    }
-
-    if (!ToJSValue(aCx, formData, &val)) {
-      return nullptr;
-    }
-  }
-
-  return &val.toObject();
-}
-
-// The format of the FormData serialization is:
-// - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements
-// - for each Element element:
-//   - name string
-//   - if it's a blob:
-//     - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array
-//       mBlobImplArray.
-//   - else:
-//     - pair of ints: 0, string length
-//     - value string
-bool
-WriteFormData(JSStructuredCloneWriter* aWriter,
-              nsFormData* aFormData,
-              StructuredCloneHelper* aHelper)
-{
-  MOZ_ASSERT(aWriter);
-  MOZ_ASSERT(aFormData);
-  MOZ_ASSERT(aHelper);
-
-  if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FORMDATA,
-                          aFormData->Length())) {
-    return false;
-  }
-
-  class MOZ_STACK_CLASS Closure final
-  {
-    JSStructuredCloneWriter* mWriter;
-    StructuredCloneHelper* mHelper;
-
-  public:
-    Closure(JSStructuredCloneWriter* aWriter,
-            StructuredCloneHelper* aHelper)
-      : mWriter(aWriter),
-        mHelper(aHelper)
-    { }
-
-    static bool
-    Write(const nsString& aName, bool isFile, const nsString& aValue,
-          File* aFile, void* aClosure)
-    {
-      Closure* closure = static_cast<Closure*>(aClosure);
-      if (!WriteString(closure->mWriter, aName)) {
-        return false;
-      }
-
-      if (isFile) {
-        BlobImpl* blobImpl = aFile->Impl();
-        if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB,
-                                closure->mHelper->BlobImpls().Length())) {
-          return false;
-        }
-
-        closure->mHelper->BlobImpls().AppendElement(blobImpl);
-        return true;
-      }
-
-      size_t charSize = sizeof(nsString::char_type);
-      if (!JS_WriteUint32Pair(closure->mWriter, 0, aValue.Length()) ||
-          !JS_WriteBytes(closure->mWriter, aValue.get(),
-                         aValue.Length() * charSize)) {
-        return false;
-      }
-
-      return true;
-    }
-  };
-  Closure closure(aWriter, aHelper);
-  return aFormData->ForEach(Closure::Write, &closure);
-}
-
-} // anonymous namespace
-
 JSObject*
 StructuredCloneHelper::ReadCallback(JSContext* aCx,
                                     JSStructuredCloneReader* aReader,
                                     uint32_t aTag,
                                     uint32_t aIndex)
 {
   MOZ_ASSERT(mSupportsCloning);
 
   if (aTag == SCTAG_DOM_BLOB) {
-    return ReadBlob(aCx, aIndex, this);
+    MOZ_ASSERT(aIndex < mBlobImplArray.Length());
+    nsRefPtr<BlobImpl> blobImpl =  mBlobImplArray[aIndex];
+
+    // nsRefPtr<File> needs to go out of scope before toObjectOrNull() is
+    // called because the static analysis thinks dereferencing XPCOM objects
+    // can GC (because in some cases it can!), and a return statement with a
+    // JSObject* type means that JSObject* is on the stack as a raw pointer
+    // while destructors are running.
+    JS::Rooted<JS::Value> val(aCx);
+    {
+      nsRefPtr<Blob> blob = Blob::Create(mParent, blobImpl);
+      if (!ToJSValue(aCx, blob, &val)) {
+        return nullptr;
+      }
+    }
+
+    return &val.toObject();
   }
 
   if (aTag == SCTAG_DOM_FILELIST) {
-    return ReadFileList(aCx, aReader, aIndex, this);
-  }
+    JS::Rooted<JS::Value> val(aCx);
+    {
+      nsRefPtr<FileList> fileList = new FileList(mParent);
+
+      // |aIndex| is the number of BlobImpls to use from |offset|.
+      uint32_t tag, offset;
+      if (!JS_ReadUint32Pair(aReader, &tag, &offset)) {
+        return nullptr;
+      }
+      MOZ_ASSERT(tag == 0);
+
+      for (uint32_t i = 0; i < aIndex; ++i) {
+        uint32_t index = offset + i;
+        MOZ_ASSERT(index < mBlobImplArray.Length());
 
-  if (aTag == SCTAG_DOM_IMAGEDATA) {
-    return ReadStructuredCloneImageData(aCx, aReader);
-  }
+        nsRefPtr<BlobImpl> blobImpl = mBlobImplArray[index];
+        MOZ_ASSERT(blobImpl->IsFile());
 
-  if (aTag == SCTAG_DOM_FORMDATA) {
-    return ReadFormData(aCx, aReader, aIndex, this);
+        nsRefPtr<File> file = File::Create(mParent, blobImpl);
+        if (!fileList->Append(file)) {
+          return nullptr;
+        }
+      }
+
+      if (!ToJSValue(aCx, fileList, &val)) {
+        return nullptr;
+      }
+    }
+
+    return &val.toObject();
   }
 
   if (aTag == SCTAG_DOM_IMAGEBITMAP) {
      // Get the current global object.
      // This can be null.
      nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(mParent);
      // aIndex is the index of the cloned image.
      return ImageBitmap::ReadStructuredClone(aCx, aReader,
@@ -730,41 +405,45 @@ StructuredCloneHelper::WriteCallback(JSC
   if (!mSupportsCloning) {
     return false;
   }
 
   // See if this is a File/Blob object.
   {
     Blob* blob = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
-      return WriteBlob(aWriter, blob, this);
-    }
-  }
+      BlobImpl* blobImpl = blob->Impl();
+      if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
+                             mBlobImplArray.Length())) {
+        mBlobImplArray.AppendElement(blobImpl);
+        return true;
+      }
 
-  // See if this is a FileList object.
-  {
-    FileList* fileList = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
-      return WriteFileList(aWriter, fileList, this);
+      return false;
     }
   }
 
-  // See if this is a ImageData object.
   {
-    ImageData* imageData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, aObj, imageData))) {
-      return WriteStructuredCloneImageData(aCx, aWriter, imageData);
-    }
-  }
+    FileList* fileList = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
+      // A FileList is serialized writing the X number of elements and the offset
+      // from mBlobImplArray. The Read will take X elements from mBlobImplArray
+      // starting from the offset.
+      if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST,
+                              fileList->Length()) ||
+          !JS_WriteUint32Pair(aWriter, 0,
+                              mBlobImplArray.Length())) {
+        return false;
+      }
 
-  // See if this is a FormData object.
-  {
-    nsFormData* formData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) {
-      return WriteFormData(aWriter, formData, this);
+      for (uint32_t i = 0; i < fileList->Length(); ++i) {
+        mBlobImplArray.AppendElement(fileList->Item(i)->Impl());
+      }
+
+      return true;
     }
   }
 
   // See if this is an ImageBitmap object.
   {
     ImageBitmap* imageBitmap = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
       return ImageBitmap::WriteStructuredClone(aWriter,
--- a/dom/base/StructuredCloneHelper.h
+++ b/dom/base/StructuredCloneHelper.h
@@ -76,21 +76,16 @@ public:
 
   bool Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfer);
 
   bool Read(JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue);
 
-  bool HasBeenWritten() const
-  {
-    return !!mBuffer;
-  }
-
   uint64_t* BufferData() const
   {
     MOZ_ASSERT(mBuffer, "Write() has never been called.");
     return mBuffer->data();
   }
 
   size_t BufferSize() const
   {
@@ -132,23 +127,21 @@ public:
   explicit StructuredCloneHelper(CloningSupport aSupportsCloning,
                                  TransferringSupport aSupportsTransferring);
   virtual ~StructuredCloneHelper();
 
   // Normally you should just use Write() and Read().
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
-             bool aMaybeToDifferentThread,
              ErrorResult &aRv);
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfer,
-             bool aMaybeToDifferentThread,
              ErrorResult &aRv);
 
   void Read(nsISupports* aParent,
             JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue,
             ErrorResult &aRv);
 
   // Sometimes, when IPC is involved, you must send a buffer after a Write().
@@ -186,29 +179,20 @@ public:
   }
 
   nsTArray<nsRefPtr<BlobImpl>>& BlobImpls()
   {
     MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
     return mBlobImplArray;
   }
 
-  nsISupports* ParentDuringRead() const
-  {
-    return mParent;
-  }
-
-  // This must be called if the transferring has ports generated by Read().
-  // MessagePorts are not thread-safe and they must be retrieved in the thread
-  // where they are created.
-  void TakeTransferredPorts(nsTArray<nsRefPtr<MessagePortBase>>& aPorts)
+  const nsTArray<nsRefPtr<MessagePortBase>>& GetTransferredPorts() const
   {
     MOZ_ASSERT(mSupportsTransferring);
-    MOZ_ASSERT(aPorts.IsEmpty());
-    aPorts.SwapElements(mTransferredPorts);
+    return mTransferredPorts;
   }
 
   nsTArray<MessagePortIdentifier>& PortIdentifiers()
   {
     MOZ_ASSERT(mSupportsTransferring);
     return mPortIdentifiers;
   }
 
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -41,17 +41,15 @@ enum StructuredCloneTags {
   SCTAG_DOM_SYSTEM_PRINCIPAL,
   SCTAG_DOM_CONTENT_PRINCIPAL,
 
   SCTAG_DOM_NFC_NDEF,
   SCTAG_DOM_IMAGEBITMAP,
 
   SCTAG_DOM_RTC_CERTIFICATE,
 
-  SCTAG_DOM_FORMDATA,
-
   SCTAG_DOM_MAX
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // StructuredCloneTags_h__
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8614,17 +8614,17 @@ nsGlobalWindow::PostMessageMozOuter(JSCo
                          origin,
                          this,
                          providedPrincipal,
                          nsContentUtils::IsCallerChrome());
 
   JS::Rooted<JS::Value> message(aCx, aMessage);
   JS::Rooted<JS::Value> transfer(aCx, aTransfer);
 
-  event->Write(aCx, message, transfer, false, aError);
+  event->Write(aCx, message, transfer, aError);
   if (NS_WARN_IF(aError.Failed())) {
     return;
   }
 
   aError = NS_DispatchToCurrentThread(event);
 }
 
 void
--- a/dom/base/nsStructuredCloneContainer.cpp
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -47,17 +47,17 @@ NS_IMETHODIMP
 nsStructuredCloneContainer::InitFromJSVal(JS::Handle<JS::Value> aData,
                                           JSContext* aCx)
 {
   if (mState != eNotInitialized) {
     return NS_ERROR_FAILURE;
   }
 
   ErrorResult rv;
-  Write(aCx, aData, true, rv);
+  Write(aCx, aData, rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   mState = eInitializedFromJSVal;
   return NS_OK;
 }
 
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -831,10 +831,9 @@ skip-if = buildapp == 'mulet' || buildap
 [test_integer_attr_with_leading_zero.html]
 [test_getAttribute_after_createAttribute.html]
 [test_script_loader_crossorigin_data_url.html]
 [test_file_negative_date.html]
 [test_nonascii_blob_url.html]
 [test_window_element_enumeration.html]
 [test_referrer_redirect.html]
 [test_postMessages.html]
-support-files = worker_postMessages.js
 [test_window_proto.html]
--- a/dom/base/test/test_postMessages.html
+++ b/dom/base/test/test_postMessages.html
@@ -273,61 +273,16 @@ function test_windowToIframeURL(url) {
         onmessage = null;
         next();
       }
     });
   }
   document.body.appendChild(ifr);
 }
 
-// PostMessage for Workers
-function test_workers() {
-  info("Testing Workers");
-
-  var resolve;
-
-  var w = new Worker('worker_postMessages.js');
-  w.postMessage('workers');
-  w.onmessage = function(e) {
-    is(e.data, 'ok', "Worker ready!");
-
-    w.onmessage = function(e) {
-      if (!resolve) {
-        ok(false, "Unexpected message!");
-        return;
-      }
-
-      let tmp = resolve;
-      resolve = null;
-      tmp({ data: e.data, ports: e.ports });
-    }
-
-    runTests({
-      clonableObjects: true,
-      transferableObjects: true,
-      send: function(what, ports) {
-        return new Promise(function(r, rr) {
-          resolve = r;
-          try {
-            w.postMessage(what, ports);
-          } catch(e) {
-            resolve = null;
-            rr();
-          }
-        });
-      },
-
-      finished: function() {
-        onmessage = null;
-        next();
-      }
-    });
-  }
-}
-
 // PostMessage for BroadcastChannel
 function test_broadcastChannel() {
   info("Testing broadcastChannel");
 
   var bc1 = new BroadcastChannel('postMessagesTest');
   var bc2 = new BroadcastChannel('postMessagesTest');
 
   var resolve;
@@ -360,62 +315,16 @@ function test_broadcastChannel() {
 
     finished: function() {
       onmessage = null;
       next();
     }
   });
 }
 
-// PostMessage for BroadcastChannel in workers
-function test_broadcastChannel_inWorkers() {
-  info("Testing broadcastChannel in Workers");
-
-  var bc = new BroadcastChannel('postMessagesTest_inWorkers');
-  var resolve;
-
-  var w = new Worker('worker_postMessages.js');
-  w.postMessage('broadcastChannel');
-  w.onmessage = function(e) {
-    is(e.data, 'ok', "Worker ready!");
-
-    w.onmessage = function(e) {
-      if (!resolve) {
-        ok(false, "Unexpected message!");
-        return;
-      }
-
-      let tmp = resolve;
-      resolve = null;
-      tmp({ data: e.data, ports: e.ports });
-    }
-
-    runTests({
-      clonableObjects: true,
-      transferableObjects: false,
-      send: function(what, ports) {
-        return new Promise(function(r, rr) {
-          if (ports.length) {
-            rr();
-            return;
-          }
-
-          resolve = r;
-          bc.postMessage(what);
-        });
-      },
-
-      finished: function() {
-        onmessage = null;
-        next();
-      }
-    });
-  }
-}
-
 // PostMessage for MessagePort
 function test_messagePort() {
   info("Testing messagePort");
 
   var mc = new MessageChannel();
   var resolve;
 
   mc.port2.onmessage = function(e) {
@@ -446,76 +355,28 @@ function test_messagePort() {
 
     finished: function() {
       onmessage = null;
       next();
     }
   });
 }
 
-// PostMessage for MessagePort in Workers
-function test_messagePort_inWorkers() {
-  info("Testing messagePort in workers");
-
-  var mc = new MessageChannel();
-  var resolve;
-
-  var w = new Worker('worker_postMessages.js');
-  w.postMessage('messagePort', [ mc.port2 ]);
-  w.onmessage = function(e) {
-    is(e.data, 'ok', "Worker ready!");
-
-    w.onmessage = function(e) {
-      if (!resolve) {
-        ok(false, "Unexpected message!");
-        return;
-      }
-
-      let tmp = resolve;
-      resolve = null;
-      tmp({ data: e.data, ports: e.ports });
-    }
-
-    runTests({
-      clonableObjects: true,
-      transferableObjects: true,
-      send: function(what, ports) {
-        return new Promise(function(r, rr) {
-          resolve = r;
-          try {
-            mc.port1.postMessage(what, ports);
-          } catch(e) {
-            resolve = null;
-            rr();
-          }
-        });
-      },
-
-      finished: function() {
-        onmessage = null;
-        next();
-      }
-    });
-  }
-}
-
 var tests = [
   create_fileList,
 
   test_windowToWindow,
   test_windowToIframe,
   test_windowToCrossOriginIframe,
 
-  test_workers,
-
   test_broadcastChannel,
-  test_broadcastChannel_inWorkers,
+  // TODO BroadcastChannel in worker
 
   test_messagePort,
-  test_messagePort_inWorkers,
+  // TODO MessagePort in worker
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
deleted file mode 100644
--- a/dom/base/test/worker_postMessages.js
+++ /dev/null
@@ -1,33 +0,0 @@
-function test_workers() {
-  onmessage = function(e) {
-    postMessage(e.data, e.ports);
-  }
-}
-
-function test_broadcastChannel() {
-  var bc = new BroadcastChannel('postMessagesTest_inWorkers');
-  bc.onmessage = function(e) {
-    postMessage(e.data);
-  }
-}
-
-function test_messagePort(port) {
-  port.onmessage = function(e) {
-    postMessage(e.data, e.ports);
-  }
-}
-
-onmessage = function(e) {
-  if (e.data == 'workers') {
-    test_workers();
-    postMessage('ok');
-  } else if (e.data == 'broadcastChannel') {
-    test_broadcastChannel();
-    postMessage('ok');
-  } else if (e.data == 'messagePort') {
-    test_messagePort(e.ports[0]);
-    postMessage('ok');
-  } else {
-    postMessage('ko');
-  }
-}
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -449,21 +449,29 @@ BroadcastChannel::PostMessage(JSContext*
 
 void
 BroadcastChannel::PostMessageInternal(JSContext* aCx,
                                       JS::Handle<JS::Value> aMessage,
                                       ErrorResult& aRv)
 {
   nsRefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
 
-  data->Write(aCx, aMessage, true, aRv);
+  data->Write(aCx, aMessage, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
+  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->BlobImpls();
+  for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
+    if (!blobImpls[i]->MayBeClonedToOtherThreads()) {
+      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+      return;
+    }
+  }
+
   PostMessageData(data);
 }
 
 void
 BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
 {
   if (mActor) {
     nsRefPtr<BCPostMessageRunnable> runnable =
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -2,21 +2,21 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ImageBitmap.h"
 #include "mozilla/dom/ImageBitmapBinding.h"
 #include "mozilla/dom/Promise.h"
-#include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/gfx/2D.h"
 #include "imgTools.h"
+#include "js/StructuredClone.h"
 #include "libyuv.h"
 #include "nsLayoutUtils.h"
 
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 namespace mozilla {
 namespace dom {
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -135,22 +135,19 @@ public:
 
     event->InitMessageEvent(NS_LITERAL_STRING("message"),
                             false /* non-bubbling */,
                             false /* cancelable */, value, EmptyString(),
                             EmptyString(), nullptr);
     event->SetTrusted(true);
     event->SetSource(mPort);
 
-    nsTArray<nsRefPtr<MessagePortBase>> ports;
-    mData->TakeTransferredPorts(ports);
-
     nsRefPtr<MessagePortList> portList =
       new MessagePortList(static_cast<dom::Event*>(event.get()),
-                          ports);
+                          mData->GetTransferredPorts());
     event->SetPorts(portList);
 
     bool dummy;
     mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &dummy);
     return NS_OK;
   }
 
   NS_IMETHOD
--- a/dom/messagechannel/SharedMessagePortMessage.cpp
+++ b/dom/messagechannel/SharedMessagePortMessage.cpp
@@ -41,21 +41,29 @@ SharedMessagePortMessage::Read(nsISuppor
 }
 
 void
 SharedMessagePortMessage::Write(JSContext* aCx,
                                 JS::Handle<JS::Value> aValue,
                                 JS::Handle<JS::Value> aTransfer,
                                 ErrorResult& aRv)
 {
-  StructuredCloneHelper::Write(aCx, aValue, aTransfer, true, aRv);
+  StructuredCloneHelper::Write(aCx, aValue, aTransfer, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
+  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = BlobImpls();
+  for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
+    if (!blobImpls[i]->MayBeClonedToOtherThreads()) {
+      aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+      return;
+    }
+  }
+
   FallibleTArray<uint8_t> cloneData;
 
   MoveBufferDataToArray(cloneData, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   MOZ_ASSERT(mData.IsEmpty());
--- a/dom/workers/DataStore.cpp
+++ b/dom/workers/DataStore.cpp
@@ -228,17 +228,17 @@ public:
     , mId(aId)
     , mRevisionId(aRevisionId)
     , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     // This needs to be structured cloned while it's still on the worker thread.
-    Write(aCx, aObj, true, mRv);
+    Write(aCx, aObj, mRv);
     NS_WARN_IF(mRv.Failed());
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
@@ -289,17 +289,17 @@ public:
     , mId(aId)
     , mRevisionId(aRevisionId)
     , mRv(aRv)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     // This needs to be structured cloned while it's still on the worker thread.
-    Write(aCx, aObj, true, mRv);
+    Write(aCx, aObj, mRv);
     NS_WARN_IF(mRv.Failed());
   }
 
 protected:
   virtual bool
   MainThreadRun() override
   {
     AssertIsOnMainThread();
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -8,16 +8,17 @@
 #include "ServiceWorkerClient.h"
 #include "ServiceWorkerContainer.h"
 
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/Navigator.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "WorkerPrivate.h"
+#include "WorkerStructuredClone.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::workers;
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
@@ -67,27 +68,34 @@ ServiceWorkerClientInfo::ServiceWorkerCl
 JSObject*
 ServiceWorkerClient::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ClientBinding::Wrap(aCx, this, aGivenProto);
 }
 
 namespace {
 
-class ServiceWorkerClientPostMessageRunnable final
-  : public nsRunnable
-  , public StructuredCloneHelper
+class ServiceWorkerClientPostMessageRunnable final : public nsRunnable
 {
   uint64_t mWindowId;
+  JSAutoStructuredCloneBuffer mBuffer;
+  WorkerStructuredCloneClosure mClosure;
 
 public:
-  explicit ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId)
-    : StructuredCloneHelper(CloningSupported, TransferringSupported)
-    , mWindowId(aWindowId)
-  {}
+  ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId,
+                                         JSAutoStructuredCloneBuffer&& aData,
+                                         WorkerStructuredCloneClosure& aClosure)
+    : mWindowId(aWindowId),
+      mBuffer(Move(aData))
+  {
+    mClosure.mClonedObjects.SwapElements(aClosure.mClonedObjects);
+    mClosure.mClonedImages.SwapElements(aClosure.mClonedImages);
+    MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty());
+    mClosure.mMessagePortIdentifiers.SwapElements(aClosure.mMessagePortIdentifiers);
+  }
 
   NS_IMETHOD
   Run()
   {
     AssertIsOnMainThread();
     nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId);
     if (!window) {
       return NS_ERROR_FAILURE;
@@ -110,50 +118,50 @@ public:
   }
 
 private:
   NS_IMETHOD
   DispatchDOMEvent(JSContext* aCx, ServiceWorkerContainer* aTargetContainer)
   {
     AssertIsOnMainThread();
 
+    // Release reference to objects that were AddRef'd for
+    // cloning into worker when array goes out of scope.
+    WorkerStructuredCloneClosure closure;
+    closure.mClonedObjects.SwapElements(mClosure.mClonedObjects);
+    closure.mClonedImages.SwapElements(mClosure.mClonedImages);
+    MOZ_ASSERT(mClosure.mMessagePorts.IsEmpty());
+    closure.mMessagePortIdentifiers.SwapElements(mClosure.mMessagePortIdentifiers);
+    closure.mParentWindow = do_QueryInterface(aTargetContainer->GetParentObject());
+
     JS::Rooted<JS::Value> messageData(aCx);
-    ErrorResult rv;
-    Read(aTargetContainer->GetParentObject(), aCx, &messageData, rv);
-    if (NS_WARN_IF(rv.Failed())) {
-      xpc::Throw(aCx, rv.StealNSResult());
+    if (!mBuffer.read(aCx, &messageData,
+                      WorkerStructuredCloneCallbacks(), &closure)) {
+      xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
       return NS_ERROR_FAILURE;
     }
 
-    nsRefPtr<MessageEvent> event = new MessageEvent(aTargetContainer,
-                                                    nullptr, nullptr);
-    rv = event->InitMessageEvent(NS_LITERAL_STRING("message"),
-                                 false /* non-bubbling */,
-                                 false /* not cancelable */,
-                                 messageData,
-                                 EmptyString(),
-                                 EmptyString(),
-                                 nullptr);
-    if (NS_WARN_IF(rv.Failed())) {
-      xpc::Throw(aCx, rv.StealNSResult());
+    nsCOMPtr<nsIDOMMessageEvent> event = new MessageEvent(aTargetContainer,
+                                                          nullptr, nullptr);
+    nsresult rv =
+      event->InitMessageEvent(NS_LITERAL_STRING("message"),
+                              false /* non-bubbling */,
+                              false /* not cancelable */,
+                              messageData,
+                              EmptyString(),
+                              EmptyString(),
+                              nullptr);
+    if (NS_FAILED(rv)) {
+      xpc::Throw(aCx, rv);
       return NS_ERROR_FAILURE;
     }
 
-    nsTArray<nsRefPtr<MessagePortBase>> ports;
-    TakeTransferredPorts(ports);
-
-    nsRefPtr<MessagePortList> portList =
-      new MessagePortList(static_cast<dom::Event*>(event.get()),
-                          ports);
-    event->SetPorts(portList);
-
     event->SetTrusted(true);
     bool status = false;
-    aTargetContainer->DispatchEvent(static_cast<dom::Event*>(event.get()),
-                                    &status);
+    aTargetContainer->DispatchEvent(event, &status);
 
     if (!status) {
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 };
@@ -181,22 +189,27 @@ ServiceWorkerClient::PostMessage(JSConte
     if (!array) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
 
     transferable.setObject(*array);
   }
 
-  nsRefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
-    new ServiceWorkerClientPostMessageRunnable(mWindowId);
+  const JSStructuredCloneCallbacks* callbacks = WorkerStructuredCloneCallbacks();
+
+  WorkerStructuredCloneClosure closure;
 
-  runnable->Write(aCx, aMessage, transferable, true, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  JSAutoStructuredCloneBuffer buffer;
+  if (!buffer.write(aCx, aMessage, transferable, callbacks, &closure)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  aRv = NS_DispatchToMainThread(runnable);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
+  nsRefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
+    new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer),
+                                               closure);
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(NS_ERROR_FAILURE);
   }
 }
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -40,40 +40,53 @@
 #include "js/MemoryMetrics.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/ImageDataBinding.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/MessagePortList.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/dom/StructuredCloneHelper.h"
+#include "mozilla/dom/StructuredClone.h"
 #include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/dom/indexedDB/IDBFactory.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/nsIRemoteBlob.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/Preferences.h"
+#include "MultipartBlobImpl.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
+#include "nsFormData.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsJSEnvironment.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsProxyRelease.h"
 #include "nsQueryObject.h"
 #include "nsSandboxFlags.h"
@@ -95,16 +108,17 @@
 #include "ScriptLoader.h"
 #include "ServiceWorkerManager.h"
 #include "ServiceWorkerWindowClient.h"
 #include "SharedWorker.h"
 #include "WorkerDebuggerManager.h"
 #include "WorkerFeature.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
+#include "WorkerStructuredClone.h"
 #include "WorkerThread.h"
 
 #ifdef XP_WIN
 #undef PostMessage
 #endif
 
 // JS_MaybeGC will run once every second during normal execution.
 #define PERIODIC_GC_TIMER_DELAY_SEC 1
@@ -308,16 +322,491 @@ LogErrorToConsole(const nsAString& aMess
   __android_log_print(ANDROID_LOG_INFO, "Gecko", kErrorString, msg.get(),
                       filename.get(), aLineNumber);
 #endif
 
   fprintf(stderr, kErrorString, msg.get(), filename.get(), aLineNumber);
   fflush(stderr);
 }
 
+// Recursive!
+already_AddRefed<BlobImpl>
+EnsureBlobForBackgroundManager(BlobImpl* aBlobImpl,
+                               PBackgroundChild* aManager = nullptr)
+{
+  MOZ_ASSERT(aBlobImpl);
+
+  if (!aManager) {
+    aManager = BackgroundChild::GetForCurrentThread();
+    MOZ_ASSERT(aManager);
+  }
+
+  nsRefPtr<BlobImpl> blobImpl = aBlobImpl;
+
+  const nsTArray<nsRefPtr<BlobImpl>>* subBlobImpls =
+    aBlobImpl->GetSubBlobImpls();
+
+  if (!subBlobImpls) {
+    if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl)) {
+      // Always make sure we have a blob from an actor we can use on this
+      // thread.
+      BlobChild* blobChild = BlobChild::GetOrCreate(aManager, blobImpl);
+      MOZ_ASSERT(blobChild);
+
+      blobImpl = blobChild->GetBlobImpl();
+      MOZ_ASSERT(blobImpl);
+
+      DebugOnly<bool> isMutable;
+      MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable)));
+      MOZ_ASSERT(!isMutable);
+    } else {
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(blobImpl->SetMutable(false)));
+    }
+
+    return blobImpl.forget();
+  }
+
+  const uint32_t subBlobCount = subBlobImpls->Length();
+  MOZ_ASSERT(subBlobCount);
+
+  nsTArray<nsRefPtr<BlobImpl>> newSubBlobImpls;
+  newSubBlobImpls.SetLength(subBlobCount);
+
+  bool newBlobImplNeeded = false;
+
+  for (uint32_t index = 0; index < subBlobCount; index++) {
+    const nsRefPtr<BlobImpl>& subBlobImpl = subBlobImpls->ElementAt(index);
+    MOZ_ASSERT(subBlobImpl);
+
+    nsRefPtr<BlobImpl>& newSubBlobImpl = newSubBlobImpls[index];
+
+    newSubBlobImpl = EnsureBlobForBackgroundManager(subBlobImpl, aManager);
+    MOZ_ASSERT(newSubBlobImpl);
+
+    if (subBlobImpl != newSubBlobImpl) {
+      newBlobImplNeeded = true;
+    }
+  }
+
+  if (newBlobImplNeeded) {
+    nsString contentType;
+    blobImpl->GetType(contentType);
+
+    if (blobImpl->IsFile()) {
+      nsString name;
+      blobImpl->GetName(name);
+
+      blobImpl = new MultipartBlobImpl(newSubBlobImpls, name, contentType);
+    } else {
+      blobImpl = new MultipartBlobImpl(newSubBlobImpls, contentType);
+    }
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(blobImpl->SetMutable(false)));
+  }
+
+  return blobImpl.forget();
+}
+
+already_AddRefed<Blob>
+ReadBlobOrFileNoWrap(JSContext* aCx,
+                     JSStructuredCloneReader* aReader)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+
+  nsRefPtr<BlobImpl> blobImpl;
+  {
+    BlobImpl* rawBlobImpl;
+    MOZ_ALWAYS_TRUE(JS_ReadBytes(aReader, &rawBlobImpl, sizeof(rawBlobImpl)));
+
+    MOZ_ASSERT(rawBlobImpl);
+
+    blobImpl = rawBlobImpl;
+  }
+
+  blobImpl = EnsureBlobForBackgroundManager(blobImpl);
+  MOZ_ASSERT(blobImpl);
+
+  nsCOMPtr<nsISupports> parent;
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
+      nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
+    parent = do_QueryInterface(scriptGlobal);
+  } else {
+    WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+    MOZ_ASSERT(workerPrivate);
+
+    WorkerGlobalScope* globalScope = workerPrivate->GlobalScope();
+    MOZ_ASSERT(globalScope);
+
+    parent = do_QueryObject(globalScope);
+  }
+
+  nsRefPtr<Blob> blob = Blob::Create(parent, blobImpl);
+  return blob.forget();
+}
+
+void
+ReadBlobOrFile(JSContext* aCx,
+               JSStructuredCloneReader* aReader,
+               JS::MutableHandle<JSObject*> aBlobOrFile)
+{
+  nsRefPtr<Blob> blob = ReadBlobOrFileNoWrap(aCx, aReader);
+  aBlobOrFile.set(blob->WrapObject(aCx, nullptr));
+}
+
+// See WriteFormData for serialization format.
+void
+ReadFormData(JSContext* aCx,
+             JSStructuredCloneReader* aReader,
+             uint32_t aCount,
+             JS::MutableHandle<JSObject*> aFormData)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aReader);
+  MOZ_ASSERT(!aFormData);
+
+  nsCOMPtr<nsISupports> parent;
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
+      nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
+    parent = do_QueryInterface(scriptGlobal);
+  } else {
+    WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+    MOZ_ASSERT(workerPrivate);
+    workerPrivate->AssertIsOnWorkerThread();
+
+    WorkerGlobalScope* globalScope = workerPrivate->GlobalScope();
+    MOZ_ASSERT(globalScope);
+
+    parent = do_QueryObject(globalScope);
+  }
+
+  nsRefPtr<nsFormData> formData = new nsFormData(parent);
+  MOZ_ASSERT(formData);
+
+  Optional<nsAString> thirdArg;
+
+  uint32_t isFile;
+  uint32_t dummy;
+  for (uint32_t i = 0; i < aCount; ++i) {
+    MOZ_ALWAYS_TRUE(JS_ReadUint32Pair(aReader, &isFile, &dummy));
+
+    nsAutoString name;
+    MOZ_ALWAYS_TRUE(ReadString(aReader, name));
+
+    if (isFile) {
+      // Read out the tag since the blob reader isn't expecting it.
+      MOZ_ALWAYS_TRUE(JS_ReadUint32Pair(aReader, &dummy, &dummy));
+      nsRefPtr<Blob> blob = ReadBlobOrFileNoWrap(aCx, aReader);
+      MOZ_ASSERT(blob);
+      formData->Append(name, *blob, thirdArg);
+    } else {
+      nsAutoString value;
+      MOZ_ALWAYS_TRUE(ReadString(aReader, value));
+      formData->Append(name, value);
+    }
+  }
+
+  aFormData.set(formData->WrapObject(aCx, nullptr));
+}
+
+bool
+WriteBlobOrFile(JSStructuredCloneWriter* aWriter,
+                BlobImpl* aBlobImpl,
+                WorkerStructuredCloneClosure& aClosure)
+{
+  MOZ_ASSERT(aWriter);
+  MOZ_ASSERT(aBlobImpl);
+
+  if (!aBlobImpl->MayBeClonedToOtherThreads()) {
+    NS_WARNING("Not all the blob implementations can be sent between threads.");
+    return false;
+  }
+
+  nsRefPtr<BlobImpl> blobImpl = EnsureBlobForBackgroundManager(aBlobImpl);
+  MOZ_ASSERT(blobImpl);
+
+  aBlobImpl = blobImpl;
+
+  if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_BLOB, 0)) ||
+      NS_WARN_IF(!JS_WriteBytes(aWriter, &aBlobImpl, sizeof(aBlobImpl)))) {
+    return false;
+  }
+
+  aClosure.mClonedObjects.AppendElement(aBlobImpl);
+  return true;
+}
+
+// A FormData is serialized as:
+//  - A pair of ints (tag identifying it as a FormData, number of elements in
+//  the FormData)
+//  - for each (key, value) pair:
+//    - pair of ints (is value a file?, 0). If not a file, value is a string.
+//    - string name
+//    - if value is a file:
+//      - write the file/blob
+//    - else:
+//      - string value
+bool
+WriteFormData(JSStructuredCloneWriter* aWriter,
+              nsFormData* aFormData,
+              WorkerStructuredCloneClosure& aClosure)
+{
+  MOZ_ASSERT(aWriter);
+  MOZ_ASSERT(aFormData);
+
+  if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_FORMDATA, aFormData->Length()))) {
+    return false;
+  }
+
+  class MOZ_STACK_CLASS Closure final
+  {
+    JSStructuredCloneWriter* mWriter;
+    WorkerStructuredCloneClosure& mClones;
+
+  public:
+    Closure(JSStructuredCloneWriter* aWriter,
+            WorkerStructuredCloneClosure& aClones)
+      : mWriter(aWriter), mClones(aClones)
+    { }
+
+    static bool
+    Write(const nsString& aName, bool isFile, const nsString& aValue,
+          File* aFile, void* aClosure)
+    {
+      Closure* closure = static_cast<Closure*>(aClosure);
+      if (!JS_WriteUint32Pair(closure->mWriter, /* a file? */ (uint32_t) isFile, 0)) {
+        return false;
+      }
+
+      if (!WriteString(closure->mWriter, aName)) {
+        return false;
+      }
+
+      if (isFile) {
+        if (!WriteBlobOrFile(closure->mWriter, aFile->Impl(),
+                             closure->mClones)) {
+          return false;
+        }
+      } else {
+        if (!WriteString(closure->mWriter, aValue)) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+  };
+
+  Closure closure(aWriter, aClosure);
+  return aFormData->ForEach(Closure::Write, &closure);
+}
+
+struct WorkerStructuredCloneCallbacks
+{
+  static JSObject*
+  Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
+       uint32_t aData, void* aClosure)
+  {
+    // See if object is a nsIDOMBlob pointer.
+    if (aTag == DOMWORKER_SCTAG_BLOB) {
+      MOZ_ASSERT(!aData);
+
+      JS::Rooted<JSObject*> blobOrFile(aCx);
+      ReadBlobOrFile(aCx, aReader, &blobOrFile);
+
+      return blobOrFile;
+    }
+
+    // See if the object is an ImageData.
+    if (aTag == SCTAG_DOM_IMAGEDATA) {
+      MOZ_ASSERT(!aData);
+      return ReadStructuredCloneImageData(aCx, aReader);
+    }
+
+    // See if the object is a FormData.
+    if (aTag == DOMWORKER_SCTAG_FORMDATA) {
+      JS::Rooted<JSObject*> formData(aCx);
+      // aData is the entry count.
+      ReadFormData(aCx, aReader, aData, &formData);
+      return formData;
+    }
+
+    // See if the object is an ImageBitmap.
+    if (aTag == SCTAG_DOM_IMAGEBITMAP) {
+      NS_ASSERTION(aClosure, "Null pointer!");
+
+      // Get the current global object.
+      auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+      nsCOMPtr<nsIGlobalObject> parent = do_QueryInterface(closure->mParentWindow);
+      // aData is the index of the cloned image.
+      return ImageBitmap::ReadStructuredClone(aCx, aReader, parent,
+                                              closure->mClonedImages, aData);
+    }
+
+    return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr);
+  }
+
+  static bool
+  Write(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+        JS::Handle<JSObject*> aObj, void* aClosure)
+  {
+    NS_ASSERTION(aClosure, "Null pointer!");
+
+    auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+
+    // See if this is a Blob/File object.
+    {
+      nsRefPtr<Blob> blob;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+        BlobImpl* blobImpl = blob->Impl();
+        MOZ_ASSERT(blobImpl);
+
+        if (WriteBlobOrFile(aWriter, blobImpl, *closure)) {
+          return true;
+        }
+      }
+    }
+
+    // See if this is an ImageData object.
+    {
+      ImageData* imageData = nullptr;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, aObj, imageData))) {
+        return WriteStructuredCloneImageData(aCx, aWriter, imageData);
+      }
+    }
+
+    // See if this is a FormData object.
+    {
+      nsFormData* formData = nullptr;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) {
+        if (WriteFormData(aWriter, formData, *closure)) {
+          return true;
+        }
+      }
+    }
+
+    // See if this is an ImageBitmap object.
+    {
+      ImageBitmap* imageBitmap = nullptr;
+      if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
+        return ImageBitmap::WriteStructuredClone(aWriter,
+                                                 closure->mClonedImages,
+                                                 imageBitmap);
+      }
+    }
+
+    return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr);
+  }
+
+  static void
+  Error(JSContext* aCx, uint32_t /* aErrorId */)
+  {
+    Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+  }
+
+  static bool
+  ReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader,
+               uint32_t aTag, void* aContent, uint64_t aExtraData,
+               void* aClosure, JS::MutableHandle<JSObject*> aReturnObject)
+  {
+    MOZ_ASSERT(aClosure);
+
+    auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+
+    if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+      MOZ_ASSERT(!aContent);
+      MOZ_ASSERT(aExtraData < closure->mMessagePortIdentifiers.Length());
+
+      ErrorResult rv;
+      nsRefPtr<MessagePortBase> port =
+        dom::MessagePort::Create(closure->mParentWindow,
+                                 closure->mMessagePortIdentifiers[aExtraData],
+                                 rv);
+
+      if (NS_WARN_IF(rv.Failed())) {
+        return false;
+      }
+
+      closure->mMessagePorts.AppendElement(port);
+
+      JS::Rooted<JS::Value> value(aCx);
+      if (!GetOrCreateDOMReflector(aCx, port, &value)) {
+        JS_ClearPendingException(aCx);
+        return false;
+      }
+
+      aReturnObject.set(&value.toObject());
+      return true;
+    }
+
+    return false;
+  }
+
+  static bool
+  Transfer(JSContext* aCx, JS::Handle<JSObject*> aObj, void* aClosure,
+           uint32_t* aTag, JS::TransferableOwnership* aOwnership,
+           void** aContent, uint64_t *aExtraData)
+  {
+    MOZ_ASSERT(aClosure);
+
+    auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+
+    MessagePortBase* port;
+    nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
+    if (NS_SUCCEEDED(rv)) {
+      if (NS_WARN_IF(closure->mTransferredPorts.Contains(port))) {
+        // No duplicates.
+        return false;
+      }
+
+      MessagePortIdentifier identifier;
+      if (!port->CloneAndDisentangle(identifier)) {
+        return false;
+      }
+
+      closure->mMessagePortIdentifiers.AppendElement(identifier);
+      closure->mTransferredPorts.AppendElement(port);
+
+      *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
+      *aOwnership = JS::SCTAG_TMO_CUSTOM;
+      *aContent = nullptr;
+      *aExtraData = closure->mMessagePortIdentifiers.Length() - 1;
+
+      return true;
+    }
+
+    return false;
+  }
+
+  static void
+  FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership,
+               void *aContent, uint64_t aExtraData, void* aClosure)
+  {
+    if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
+      MOZ_ASSERT(aClosure);
+      MOZ_ASSERT(!aContent);
+      auto* closure = static_cast<WorkerStructuredCloneClosure*>(aClosure);
+
+      MOZ_ASSERT(aExtraData < closure->mMessagePortIdentifiers.Length());
+      dom::MessagePort::ForceClose(closure->mMessagePortIdentifiers[aExtraData]);
+    }
+  }
+};
+
+const JSStructuredCloneCallbacks gWorkerStructuredCloneCallbacks = {
+  WorkerStructuredCloneCallbacks::Read,
+  WorkerStructuredCloneCallbacks::Write,
+  WorkerStructuredCloneCallbacks::Error,
+  WorkerStructuredCloneCallbacks::ReadTransfer,
+  WorkerStructuredCloneCallbacks::Transfer,
+  WorkerStructuredCloneCallbacks::FreeTransfer
+};
+
 class MainThreadReleaseRunnable final : public nsRunnable
 {
   nsTArray<nsCOMPtr<nsISupports>> mDoomed;
   nsCOMPtr<nsILoadGroup> mLoadGroupToCancel;
 
 public:
   MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>& aDoomed,
                             nsCOMPtr<nsILoadGroup>& aLoadGroupToCancel)
@@ -587,83 +1076,101 @@ private:
       JS_ReportPendingException(aCx);
     }
 
     aWorkerPrivate->CloseHandlerFinished();
   }
 };
 
 class MessageEventRunnable final : public WorkerRunnable
-                                 , public StructuredCloneHelper
-{
+{
+  JSAutoStructuredCloneBuffer mBuffer;
+  WorkerStructuredCloneClosure mClosure;
   uint64_t mMessagePortSerial;
   bool mToMessagePort;
 
   // This is only used for messages dispatched to a service worker.
   nsAutoPtr<ServiceWorkerClientInfo> mEventSource;
 
 public:
   MessageEventRunnable(WorkerPrivate* aWorkerPrivate,
                        TargetAndBusyBehavior aBehavior,
                        bool aToMessagePort, uint64_t aMessagePortSerial)
   : WorkerRunnable(aWorkerPrivate, aBehavior)
-  , StructuredCloneHelper(CloningSupported, TransferringSupported)
   , mMessagePortSerial(aMessagePortSerial)
   , mToMessagePort(aToMessagePort)
   {
   }
 
+  bool
+  Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
+        JS::Handle<JS::Value> aTransferredValue,
+        const JSStructuredCloneCallbacks *aCallbacks)
+   {
+    bool ok = mBuffer.write(aCx, aValue, aTransferredValue, aCallbacks,
+                            &mClosure);
+    // This hashtable has to be empty because it could contain MessagePort
+    // objects that cannot be freed on a different thread.
+    mClosure.mTransferredPorts.Clear();
+    return ok;
+  }
+
   void
   SetMessageSource(ServiceWorkerClientInfo* aSource)
   {
     mEventSource = aSource;
   }
 
   bool
   DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                    DOMEventTargetHelper* aTarget, bool aIsMainThread)
   {
-    nsCOMPtr<nsPIDOMWindow> parent;
+    // Release reference to objects that were AddRef'd for
+    // cloning into worker when array goes out of scope.
+    WorkerStructuredCloneClosure closure;
+    closure.mClonedObjects.SwapElements(mClosure.mClonedObjects);
+    closure.mClonedImages.SwapElements(mClosure.mClonedImages);
+    MOZ_ASSERT(mClosure.mMessagePorts.IsEmpty());
+    closure.mMessagePortIdentifiers.SwapElements(mClosure.mMessagePortIdentifiers);
+
     if (aIsMainThread) {
-      parent = do_QueryInterface(aTarget->GetParentObject());
+      closure.mParentWindow = do_QueryInterface(aTarget->GetParentObject());
     }
 
     JS::Rooted<JS::Value> messageData(aCx);
-    ErrorResult rv;
-    Read(parent, aCx, &messageData, rv);
-    if (NS_WARN_IF(rv.Failed())) {
-      xpc::Throw(aCx, rv.StealNSResult());
+    if (!mBuffer.read(aCx, &messageData,
+                      workers::WorkerStructuredCloneCallbacks(),
+                      &closure)) {
+      xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
       return false;
     }
 
     nsRefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
-    rv = event->InitMessageEvent(NS_LITERAL_STRING("message"),
-                                 false /* non-bubbling */,
-                                 true /* cancelable */,
-                                 messageData,
-                                 EmptyString(),
-                                 EmptyString(),
-                                 nullptr);
+    nsresult rv =
+      event->InitMessageEvent(NS_LITERAL_STRING("message"),
+                              false /* non-bubbling */,
+                              false /* non-cancelable */,
+                              messageData,
+                              EmptyString(),
+                              EmptyString(),
+                              nullptr);
     if (mEventSource) {
       nsRefPtr<ServiceWorkerClient> client =
         new ServiceWorkerWindowClient(aTarget, *mEventSource);
       event->SetSource(client);
     }
 
-    if (NS_WARN_IF(rv.Failed())) {
-      xpc::Throw(aCx, rv.StealNSResult());
+    if (NS_FAILED(rv)) {
+      xpc::Throw(aCx, rv);
       return false;
     }
 
-    nsTArray<nsRefPtr<MessagePortBase>> ports;
-    TakeTransferredPorts(ports);
-
     event->SetTrusted(true);
     event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()),
-                                        ports));
+                                        closure.mMessagePorts));
     nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event);
 
     nsEventStatus dummy = nsEventStatus_eIgnore;
     aTarget->DispatchDOMEvent(nullptr, domEvent, nullptr, &dummy);
     return true;
   }
 
 private:
@@ -678,17 +1185,18 @@ private:
       if (!aWorkerPrivate->IsAcceptingEvents()) {
         return true;
       }
 
       if (mToMessagePort) {
         return
           aWorkerPrivate->DispatchMessageEventToMessagePort(aCx,
                                                             mMessagePortSerial,
-                                                            *this);
+                                                            Move(mBuffer),
+                                                            mClosure);
       }
 
       if (aWorkerPrivate->IsFrozen()) {
         aWorkerPrivate->QueueRunnable(this);
         return true;
       }
 
       aWorkerPrivate->AssertInnerWindowIsCorrect();
@@ -2794,18 +3302,19 @@ WorkerPrivateParent<Derived>::PostMessag
     transferable.setObject(*array);
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(ParentAsWorkerPrivate(),
                              WorkerRunnable::WorkerThreadModifyBusyCount,
                              aToMessagePort, aMessagePortSerial);
 
-  runnable->Write(aCx, aMessage, transferable, true, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  if (!runnable->Write(aCx, aMessage, transferable,
+                       &gWorkerStructuredCloneCallbacks)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   runnable->SetMessageSource(aClientInfo);
 
   if (!runnable->Dispatch(aCx)) {
     aRv.Throw(NS_ERROR_FAILURE);
   }
@@ -2838,71 +3347,103 @@ WorkerPrivateParent<Derived>::PostMessag
   PostMessageInternal(aCx, aMessage, aTransferable, true, aMessagePortSerial,
                       nullptr, aRv);
 }
 
 template <class Derived>
 bool
 WorkerPrivateParent<Derived>::DispatchMessageEventToMessagePort(
                                 JSContext* aCx, uint64_t aMessagePortSerial,
-                                StructuredCloneHelper& aHelper)
+                                JSAutoStructuredCloneBuffer&& aBuffer,
+                                WorkerStructuredCloneClosure& aClosure)
 {
   AssertIsOnMainThread();
 
+  JSAutoStructuredCloneBuffer buffer(Move(aBuffer));
+
+  class MOZ_STACK_CLASS AutoCloneBufferCleaner final
+  {
+  public:
+    AutoCloneBufferCleaner(JSAutoStructuredCloneBuffer& aBuffer,
+                           const JSStructuredCloneCallbacks* aCallbacks,
+                           WorkerStructuredCloneClosure& aClosure)
+      : mBuffer(aBuffer)
+      , mCallbacks(aCallbacks)
+      , mClosure(aClosure)
+    {}
+
+    ~AutoCloneBufferCleaner()
+    {
+      mBuffer.clear(mCallbacks, &mClosure);
+    }
+
+  private:
+    JSAutoStructuredCloneBuffer& mBuffer;
+    const JSStructuredCloneCallbacks* mCallbacks;
+    WorkerStructuredCloneClosure& mClosure;
+  };
+
+  WorkerStructuredCloneClosure closure;
+  closure.mClonedObjects.SwapElements(aClosure.mClonedObjects);
+  closure.mClonedImages.SwapElements(aClosure.mClonedImages);
+  MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty());
+  closure.mMessagePortIdentifiers.SwapElements(aClosure.mMessagePortIdentifiers);
+
+  AutoCloneBufferCleaner bufferCleaner(buffer,
+                                       &gWorkerStructuredCloneCallbacks,
+                                       closure);
+
   SharedWorker* sharedWorker;
   if (!mSharedWorkers.Get(aMessagePortSerial, &sharedWorker)) {
     // SharedWorker has already been unregistered?
     return true;
   }
 
   nsRefPtr<MessagePort> port = sharedWorker->Port();
   NS_ASSERTION(port, "SharedWorkers always have a port!");
 
   if (port->IsClosed()) {
     return true;
   }
 
-  nsCOMPtr<nsISupports> parent = do_QueryInterface(port->GetParentObject());
+  closure.mParentWindow = do_QueryInterface(port->GetParentObject());
 
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(port->GetParentObject()))) {
     return false;
   }
   JSContext* cx = jsapi.cx();
 
-  ErrorResult rv;
   JS::Rooted<JS::Value> data(cx);
-  aHelper.Read(parent, cx, &data, rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    xpc::Throw(cx, rv.StealNSResult());
+  if (!buffer.read(cx, &data, &gWorkerStructuredCloneCallbacks,
+                   &closure)) {
     return false;
   }
 
   nsRefPtr<MessageEvent> event = new MessageEvent(port, nullptr, nullptr);
-  rv = event->InitMessageEvent(NS_LITERAL_STRING("message"), false, false, data,
-                               EmptyString(), EmptyString(), nullptr);
-  if (NS_WARN_IF(rv.Failed())) {
-    xpc::Throw(cx, rv.StealNSResult());
+  nsresult rv =
+    event->InitMessageEvent(NS_LITERAL_STRING("message"), false, false, data,
+                            EmptyString(), EmptyString(), nullptr);
+  if (NS_FAILED(rv)) {
+    xpc::Throw(cx, rv);
     return false;
   }
 
-  nsTArray<nsRefPtr<MessagePortBase>> ports;
-  aHelper.TakeTransferredPorts(ports);
-
   event->SetTrusted(true);
-  event->SetPorts(new MessagePortList(port, ports));
+
+  event->SetPorts(new MessagePortList(port, closure.mMessagePorts));
 
   nsCOMPtr<nsIDOMEvent> domEvent;
   CallQueryInterface(event.get(), getter_AddRefs(domEvent));
   NS_ASSERTION(domEvent, "This should never fail!");
 
   bool ignored;
   rv = port->DispatchEvent(domEvent, &ignored);
-  if (NS_WARN_IF(rv.Failed())) {
-    xpc::Throw(cx, rv.StealNSResult());
+  if (NS_FAILED(rv)) {
+    xpc::Throw(cx, rv);
     return false;
   }
 
   return true;
 }
 
 template <class Derived>
 void
@@ -5545,18 +6086,19 @@ WorkerPrivate::PostMessageToParentIntern
     transferable.setObject(*array);
   }
 
   nsRefPtr<MessageEventRunnable> runnable =
     new MessageEventRunnable(this,
                              WorkerRunnable::ParentThreadUnchangedBusyCount,
                              aToMessagePort, aMessagePortSerial);
 
-  runnable->Write(aCx, aMessage, transferable, true, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  if (!runnable->Write(aCx, aMessage, transferable,
+                       &gWorkerStructuredCloneCallbacks)) {
+    aRv = NS_ERROR_DOM_DATA_CLONE_ERR;
     return;
   }
 
   if (!runnable->Dispatch(aCx)) {
     aRv = NS_ERROR_FAILURE;
   }
 }
 
@@ -6697,12 +7239,35 @@ GetWorkerCrossThreadDispatcher(JSContext
   }
 
   WorkerPrivate* w = nullptr;
   UNWRAP_OBJECT(Worker, &aWorker.toObject(), w);
   MOZ_ASSERT(w);
   return w->GetCrossThreadDispatcher();
 }
 
+const JSStructuredCloneCallbacks*
+WorkerStructuredCloneCallbacks()
+{
+  return &gWorkerStructuredCloneCallbacks;
+}
+
 // Force instantiation.
 template class WorkerPrivateParent<WorkerPrivate>;
 
+WorkerStructuredCloneClosure::WorkerStructuredCloneClosure()
+{}
+
+WorkerStructuredCloneClosure::~WorkerStructuredCloneClosure()
+{}
+
+void
+WorkerStructuredCloneClosure::Clear()
+{
+  mParentWindow = nullptr;
+  mClonedObjects.Clear();
+  mClonedImages.Clear();
+  mMessagePorts.Clear();
+  mMessagePortIdentifiers.Clear();
+  mTransferredPorts.Clear();
+}
+
 END_WORKERS_NAMESPACE
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -23,20 +23,22 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsTObserverArray.h"
+#include "mozilla/dom/StructuredCloneTags.h"
 
 #include "Queue.h"
 #include "WorkerFeature.h"
 
+class JSAutoStructuredCloneBuffer;
 class nsIChannel;
 class nsIDocument;
 class nsIEventTarget;
 class nsIPrincipal;
 class nsIScriptContext;
 class nsISerializable;
 class nsIThread;
 class nsIThreadInternal;
@@ -45,17 +47,16 @@ class nsIURI;
 
 namespace JS {
 struct RuntimeStats;
 } // namespace JS
 
 namespace mozilla {
 namespace dom {
 class Function;
-class StructuredCloneHelper;
 } // namespace dom
 namespace ipc {
 class PrincipalInfo;
 } // namespace ipc
 } // namespace mozilla
 
 struct PRThread;
 
@@ -68,16 +69,17 @@ class MessagePort;
 class SharedWorker;
 class ServiceWorkerClientInfo;
 class WorkerControlRunnable;
 class WorkerDebugger;
 class WorkerDebuggerGlobalScope;
 class WorkerGlobalScope;
 class WorkerPrivate;
 class WorkerRunnable;
+class WorkerStructuredCloneClosure;
 class WorkerThread;
 
 // SharedMutex is a small wrapper around an (internal) reference-counted Mutex
 // object. It exists to avoid changing a lot of code to use Mutex* instead of
 // Mutex&.
 class SharedMutex
 {
   typedef mozilla::Mutex Mutex;
@@ -343,17 +345,18 @@ public:
                            JS::Handle<JS::Value> aMessage,
                            const Optional<Sequence<JS::Value> >& aTransferable,
                            ErrorResult& aRv);
 
   bool
   DispatchMessageEventToMessagePort(
                                JSContext* aCx,
                                uint64_t aMessagePortSerial,
-                               StructuredCloneHelper& aHelper);
+                               JSAutoStructuredCloneBuffer&& aBuffer,
+                               WorkerStructuredCloneClosure& aClosure);
 
   void
   UpdateRuntimeOptions(JSContext* aCx,
                        const JS::RuntimeOptions& aRuntimeOptions);
 
   void
   UpdateLanguages(JSContext* aCx, const nsTArray<nsString>& aLanguages);
 
@@ -1501,16 +1504,27 @@ WorkerPrivate*
 GetCurrentThreadWorkerPrivate();
 
 bool
 IsCurrentThreadRunningChromeWorker();
 
 JSContext*
 GetCurrentThreadJSContext();
 
+enum WorkerStructuredDataType
+{
+  DOMWORKER_SCTAG_BLOB = SCTAG_DOM_MAX,
+  DOMWORKER_SCTAG_FORMDATA = SCTAG_DOM_MAX + 1,
+
+  DOMWORKER_SCTAG_END
+};
+
+const JSStructuredCloneCallbacks*
+WorkerStructuredCloneCallbacks();
+
 class AutoSyncLoopHolder
 {
   WorkerPrivate* mWorkerPrivate;
   nsCOMPtr<nsIEventTarget> mTarget;
   uint32_t mIndex;
 
 public:
   explicit AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate)
new file mode 100644
--- /dev/null
+++ b/dom/workers/WorkerStructuredClone.h
@@ -0,0 +1,63 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_workers_WorkerStructuredClone_h
+#define mozilla_dom_workers_WorkerStructuredClone_h
+
+#include "Workers.h"
+#include "mozilla/dom/PMessagePort.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace layers {
+class Image;
+}
+
+namespace dom {
+
+class MessagePortBase;
+
+namespace workers {
+
+// This class is implemented in WorkerPrivate.cpp
+class WorkerStructuredCloneClosure final
+{
+private:
+  WorkerStructuredCloneClosure(const WorkerStructuredCloneClosure&) = delete;
+  WorkerStructuredCloneClosure & operator=(const WorkerStructuredCloneClosure&) = delete;
+
+public:
+  WorkerStructuredCloneClosure();
+  ~WorkerStructuredCloneClosure();
+
+  void Clear();
+
+  // This can be null if the MessagePort is created in a worker.
+  nsCOMPtr<nsPIDOMWindow> mParentWindow;
+
+  nsTArray<nsCOMPtr<nsISupports>> mClonedObjects;
+
+  // This is used for sharing the backend of ImageBitmaps.
+  // The layers::Image object must be thread-safely reference-counted.
+  // The layers::Image object will not be written ever via any ImageBitmap
+  // instance, so no race condition will occur.
+  nsTArray<nsRefPtr<layers::Image>> mClonedImages;
+
+  // The transferred ports.
+  nsTArray<nsRefPtr<MessagePortBase>> mMessagePorts;
+
+  // Information for the transferring.
+  nsTArray<MessagePortIdentifier> mMessagePortIdentifiers;
+
+  // To avoid duplicates in the transferred ports.
+  nsTArray<nsRefPtr<MessagePortBase>> mTransferredPorts;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_WorkerStructuredClone_h
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -13,26 +13,26 @@
 #include "nsIXMLHttpRequest.h"
 #include "nsIXPConnect.h"
 
 #include "jsfriendapi.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ProgressEvent.h"
-#include "mozilla/dom/StructuredCloneHelper.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsFormData.h"
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
 
 #include "RuntimeService.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
+#include "WorkerStructuredClone.h"
 #include "XMLHttpRequestUpload.h"
 
 using namespace mozilla;
 
 using namespace mozilla::dom;
 USING_WORKERS_NAMESPACE
 
 /**
@@ -170,126 +170,16 @@ private:
   ~Proxy()
   {
     MOZ_ASSERT(!mXHR);
     MOZ_ASSERT(!mXHRUpload);
     MOZ_ASSERT(!mOutstandingSendCount);
   }
 };
 
-class WorkerThreadProxySyncRunnable : public nsRunnable
-{
-protected:
-  WorkerPrivate* mWorkerPrivate;
-  nsRefPtr<Proxy> mProxy;
-  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
-
-private:
-  class ResponseRunnable final: public MainThreadStopSyncLoopRunnable
-  {
-    nsRefPtr<Proxy> mProxy;
-    nsresult mErrorCode;
-
-  public:
-    ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-                     nsresult aErrorCode)
-    : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(),
-                                     NS_SUCCEEDED(aErrorCode)),
-      mProxy(aProxy), mErrorCode(aErrorCode)
-    {
-      MOZ_ASSERT(aProxy);
-    }
-
-  private:
-    ~ResponseRunnable()
-    { }
-
-    virtual void
-    MaybeSetException(JSContext* aCx) override
-    {
-      MOZ_ASSERT(NS_FAILED(mErrorCode));
-
-      Throw(aCx, mErrorCode);
-    }
-  };
-
-public:
-  WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
-  : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy)
-  {
-    MOZ_ASSERT(aWorkerPrivate);
-    MOZ_ASSERT(aProxy);
-    aWorkerPrivate->AssertIsOnWorkerThread();
-  }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-  bool
-  Dispatch(JSContext* aCx)
-  {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-
-    AutoSyncLoopHolder syncLoop(mWorkerPrivate);
-    mSyncLoopTarget = syncLoop.EventTarget();
-
-    if (NS_FAILED(NS_DispatchToMainThread(this))) {
-      JS_ReportError(aCx, "Failed to dispatch to main thread!");
-      return false;
-    }
-
-    return syncLoop.Run();
-  }
-
-protected:
-  virtual ~WorkerThreadProxySyncRunnable()
-  { }
-
-  virtual nsresult
-  MainThreadRun() = 0;
-
-private:
-  NS_DECL_NSIRUNNABLE
-};
-
-class SendRunnable final
-  : public WorkerThreadProxySyncRunnable
-  , public StructuredCloneHelper
-{
-  nsString mStringBody;
-  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
-  bool mHasUploadListeners;
-
-public:
-  SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
-               const nsAString& aStringBody)
-  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
-  , StructuredCloneHelper(CloningSupported, TransferringNotSupported)
-  , mStringBody(aStringBody)
-  , mHasUploadListeners(false)
-  {
-  }
-
-  void SetHaveUploadListeners(bool aHasUploadListeners)
-  {
-    mHasUploadListeners = aHasUploadListeners;
-  }
-
-  void SetSyncLoopTarget(nsIEventTarget* aSyncLoopTarget)
-  {
-    mSyncLoopTarget = aSyncLoopTarget;
-  }
-
-private:
-  ~SendRunnable()
-  { }
-
-  virtual nsresult
-  MainThreadRun() override;
-};
-
 END_WORKERS_NAMESPACE
 
 namespace {
 
 inline void
 ConvertResponseTypeToString(XMLHttpRequestResponseType aType,
                             nsString& aString)
 {
@@ -515,20 +405,21 @@ public:
 private:
   ~LoadStartDetectionRunnable()
   {
     AssertIsOnMainThread();
     }
 };
 
 class EventRunnable final : public MainThreadProxyRunnable
-                          , public StructuredCloneHelper
 {
   nsString mType;
   nsString mResponseType;
+  JSAutoStructuredCloneBuffer mResponseBuffer;
+  WorkerStructuredCloneClosure mResponseClosure;
   JS::Heap<JS::Value> mResponse;
   nsString mResponseText;
   nsString mResponseURL;
   nsCString mStatusText;
   uint64_t mLoaded;
   uint64_t mTotal;
   uint32_t mEventStreamId;
   uint32_t mStatus;
@@ -560,29 +451,27 @@ public:
     {
       JS_CallValueTracer(aTrc, &mStateData->mResponse,
                          "XMLHttpRequest::StateData::mResponse");
     }
   };
 
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
                 bool aLengthComputable, uint64_t aLoaded, uint64_t aTotal)
-  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
-    StructuredCloneHelper(CloningSupported, TransferringNotSupported),
-    mType(aType), mResponse(JS::UndefinedValue()), mLoaded(aLoaded),
-    mTotal(aTotal), mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0),
-    mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(true),
+  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
+    mResponse(JS::UndefinedValue()), mLoaded(aLoaded), mTotal(aTotal),
+    mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(true),
     mLengthComputable(aLengthComputable), mUseCachedArrayBufferResponse(false),
     mResponseTextResult(NS_OK), mStatusResult(NS_OK), mResponseResult(NS_OK)
   { }
 
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
-  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy),
-    StructuredCloneHelper(CloningSupported, TransferringNotSupported),
-    mType(aType), mResponse(JS::UndefinedValue()), mLoaded(0), mTotal(0),
+  : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
+    mResponse(JS::UndefinedValue()), mLoaded(0), mTotal(0),
     mEventStreamId(aProxy->mInnerEventStreamId), mStatus(0), mReadyState(0),
     mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
     mUseCachedArrayBufferResponse(false), mResponseTextResult(NS_OK),
     mStatusResult(NS_OK), mResponseResult(NS_OK)
   { }
 
 private:
   ~EventRunnable()
@@ -590,16 +479,90 @@ private:
 
   virtual bool
   PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 
   virtual bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
 };
 
+class WorkerThreadProxySyncRunnable : public nsRunnable
+{
+protected:
+  WorkerPrivate* mWorkerPrivate;
+  nsRefPtr<Proxy> mProxy;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+
+private:
+  class ResponseRunnable final: public MainThreadStopSyncLoopRunnable
+  {
+    nsRefPtr<Proxy> mProxy;
+    nsresult mErrorCode;
+
+  public:
+    ResponseRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+                     nsresult aErrorCode)
+    : MainThreadStopSyncLoopRunnable(aWorkerPrivate, aProxy->GetEventTarget(),
+                                     NS_SUCCEEDED(aErrorCode)),
+      mProxy(aProxy), mErrorCode(aErrorCode)
+    {
+      MOZ_ASSERT(aProxy);
+    }
+
+  private:
+    ~ResponseRunnable()
+    { }
+
+    virtual void
+    MaybeSetException(JSContext* aCx) override
+    {
+      MOZ_ASSERT(NS_FAILED(mErrorCode));
+
+      Throw(aCx, mErrorCode);
+    }
+  };
+
+public:
+  WorkerThreadProxySyncRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
+  : mWorkerPrivate(aWorkerPrivate), mProxy(aProxy)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    MOZ_ASSERT(aProxy);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+  bool
+  Dispatch(JSContext* aCx)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    AutoSyncLoopHolder syncLoop(mWorkerPrivate);
+    mSyncLoopTarget = syncLoop.EventTarget();
+
+    if (NS_FAILED(NS_DispatchToMainThread(this))) {
+      JS_ReportError(aCx, "Failed to dispatch to main thread!");
+      return false;
+    }
+
+    return syncLoop.Run();
+  }
+
+protected:
+  virtual ~WorkerThreadProxySyncRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() = 0;
+
+private:
+  NS_DECL_NSIRUNNABLE
+};
+
 class SyncTeardownRunnable final : public WorkerThreadProxySyncRunnable
 {
 public:
   SyncTeardownRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
 private:
@@ -823,16 +786,49 @@ private:
     mProxy->mWorkerPrivate = oldWorker;
     return rv;
   }
 
   nsresult
   MainThreadRunInternal();
 };
 
+class SendRunnable final : public WorkerThreadProxySyncRunnable
+{
+  nsString mStringBody;
+  JSAutoStructuredCloneBuffer mBody;
+  WorkerStructuredCloneClosure mClosure;
+  nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
+  bool mHasUploadListeners;
+
+public:
+  SendRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+               const nsAString& aStringBody, JSAutoStructuredCloneBuffer&& aBody,
+               WorkerStructuredCloneClosure& aClosure,
+               nsIEventTarget* aSyncLoopTarget, bool aHasUploadListeners)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
+  , mStringBody(aStringBody)
+  , mBody(Move(aBody))
+  , mSyncLoopTarget(aSyncLoopTarget)
+  , mHasUploadListeners(aHasUploadListeners)
+  {
+    mClosure.mClonedObjects.SwapElements(aClosure.mClonedObjects);
+    mClosure.mClonedImages.SwapElements(aClosure.mClonedImages);
+    MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty());
+    MOZ_ASSERT(aClosure.mMessagePortIdentifiers.IsEmpty());
+  }
+
+private:
+  ~SendRunnable()
+  { }
+
+  virtual nsresult
+  MainThreadRun() override;
+};
+
 class SetRequestHeaderRunnable final : public WorkerThreadProxySyncRunnable
 {
   nsCString mHeader;
   nsCString mValue;
 
 public:
   SetRequestHeaderRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
                            const nsACString& aHeader, const nsACString& aValue)
@@ -1226,21 +1222,31 @@ EventRunnable::PreDispatch(JSContext* aC
             } else {
               mResponseResult = NS_ERROR_OUT_OF_MEMORY;
               doClone = false;
             }
           }
         }
 
         if (doClone) {
-          ErrorResult rv;
-          Write(aCx, response, transferable, false, rv);
-          if (NS_WARN_IF(rv.Failed())) {
+          // Anything subject to GC must be cloned.
+          const JSStructuredCloneCallbacks* callbacks =
+            workers::WorkerStructuredCloneCallbacks();
+
+          WorkerStructuredCloneClosure closure;
+
+          if (mResponseBuffer.write(aCx, response, transferable, callbacks,
+                                    &closure)) {
+            mResponseClosure.mClonedObjects.SwapElements(closure.mClonedObjects);
+            mResponseClosure.mClonedImages.SwapElements(closure.mClonedImages);
+            MOZ_ASSERT(mResponseClosure.mMessagePorts.IsEmpty());
+            MOZ_ASSERT(mResponseClosure.mMessagePortIdentifiers.IsEmpty());
+          } else {
             NS_WARNING("Failed to clone response!");
-            mResponseResult = rv.StealNSResult();
+            mResponseResult = NS_ERROR_DOM_DATA_CLONE_ERR;
             mProxy->mArrayBufferResponseWasTransferred = false;
           }
         }
       }
     }
   }
 
   mStatusResult = xhr->GetStatus(&mStatus);
@@ -1325,24 +1331,32 @@ EventRunnable::WorkerRun(JSContext* aCx,
     MOZ_ASSERT(mResponse.isUndefined() || mResponse.isNull());
     state->mResponseResult = mResponseTextResult;
     state->mResponse = mResponse;
   }
   else {
     state->mResponseResult = mResponseResult;
 
     if (NS_SUCCEEDED(mResponseResult)) {
-      if (HasBeenWritten()) {
+      if (mResponseBuffer.data()) {
         MOZ_ASSERT(mResponse.isUndefined());
 
-        ErrorResult rv;
+        JSAutoStructuredCloneBuffer responseBuffer(Move(mResponseBuffer));
+
+        const JSStructuredCloneCallbacks* callbacks =
+          workers::WorkerStructuredCloneCallbacks();
+
+        WorkerStructuredCloneClosure closure;
+        closure.mClonedObjects.SwapElements(mResponseClosure.mClonedObjects);
+        closure.mClonedImages.SwapElements(mResponseClosure.mClonedImages);
+        MOZ_ASSERT(mResponseClosure.mMessagePorts.IsEmpty());
+        MOZ_ASSERT(mResponseClosure.mMessagePortIdentifiers.IsEmpty());
+
         JS::Rooted<JS::Value> response(aCx);
-        Read(nullptr, aCx, &response, rv);
-        if (NS_WARN_IF(rv.Failed())) {
-          rv.SuppressException();
+        if (!responseBuffer.read(aCx, &response, callbacks, &closure)) {
           return false;
         }
 
         state->mResponse = response;
       }
       else {
         state->mResponse = mResponse;
       }
@@ -1498,35 +1512,42 @@ OpenRunnable::MainThreadRunInternal()
 }
 
 
 nsresult
 SendRunnable::MainThreadRun()
 {
   nsCOMPtr<nsIVariant> variant;
 
-  if (HasBeenWritten()) {
+  if (mBody.data()) {
     AutoSafeJSContext cx;
     JSAutoRequest ar(cx);
 
     nsIXPConnect* xpc = nsContentUtils::XPConnect();
     MOZ_ASSERT(xpc);
 
-    ErrorResult rv;
+    nsresult rv = NS_OK;
+
+    const JSStructuredCloneCallbacks* callbacks =
+      workers::WorkerStructuredCloneCallbacks();
 
     JS::Rooted<JS::Value> body(cx);
-    Read(nullptr, cx, &body, rv);
-    if (NS_WARN_IF(rv.Failed())) {
-      return rv.StealNSResult();
+    if (mBody.read(cx, &body, callbacks, &mClosure)) {
+      if (NS_FAILED(xpc->JSValToVariant(cx, body, getter_AddRefs(variant)))) {
+        rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+      }
     }
-
-    rv = xpc->JSValToVariant(cx, body, getter_AddRefs(variant));
-    if (NS_WARN_IF(rv.Failed())) {
-      return rv.StealNSResult();
+    else {
+      rv = NS_ERROR_DOM_DATA_CLONE_ERR;
     }
+
+    mBody.clear();
+    mClosure.Clear();
+
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else {
     nsCOMPtr<nsIWritableVariant> wvariant =
       do_CreateInstance(NS_VARIANT_CONTRACTID);
     NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED);
 
     if (NS_FAILED(wvariant->SetAsAString(mStringBody))) {
       MOZ_ASSERT(false, "This should never fail!");
@@ -1819,20 +1840,21 @@ XMLHttpRequest::Unpin()
   mWorkerPrivate->RemoveFeature(cx, this);
 
   mRooted = false;
 
   NS_RELEASE_THIS();
 }
 
 void
-XMLHttpRequest::SendInternal(SendRunnable* aRunnable,
+XMLHttpRequest::SendInternal(const nsAString& aStringBody,
+                             JSAutoStructuredCloneBuffer&& aBody,
+                             WorkerStructuredCloneClosure& aClosure,
                              ErrorResult& aRv)
 {
-  MOZ_ASSERT(aRunnable);
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   // No send() calls when open is running.
   if (mProxy->mOpenCount) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
@@ -1852,20 +1874,20 @@ XMLHttpRequest::SendInternal(SendRunnabl
     autoSyncLoop.emplace(mWorkerPrivate);
     syncLoopTarget = autoSyncLoop->EventTarget();
   }
 
   mProxy->mOuterChannelId++;
 
   JSContext* cx = mWorkerPrivate->GetJSContext();
 
-  aRunnable->SetSyncLoopTarget(syncLoopTarget);
-  aRunnable->SetHaveUploadListeners(hasUploadListeners);
-
-  if (!aRunnable->Dispatch(cx)) {
+  nsRefPtr<SendRunnable> runnable =
+    new SendRunnable(mWorkerPrivate, mProxy, aStringBody, Move(aBody),
+                     aClosure, syncLoopTarget, hasUploadListeners);
+  if (!runnable->Dispatch(cx)) {
     // Dispatch() may have spun the event loop and we may have already unrooted.
     // If so we don't want autoUnpin to try again.
     if (!mRooted) {
       autoUnpin.Clear();
     }
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
@@ -2082,21 +2104,21 @@ XMLHttpRequest::Send(ErrorResult& aRv)
     return;
   }
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  nsRefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, NullString());
-
   // Nothing to clone.
-  SendInternal(sendRunnable, aRv);
+  JSAutoStructuredCloneBuffer buffer;
+  WorkerStructuredCloneClosure closure;
+
+  SendInternal(NullString(), Move(buffer), closure, aRv);
 }
 
 void
 XMLHttpRequest::Send(const nsAString& aBody, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
@@ -2104,21 +2126,21 @@ XMLHttpRequest::Send(const nsAString& aB
     return;
   }
 
   if (!mProxy) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
-  nsRefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, aBody);
-
   // Nothing to clone.
-  SendInternal(sendRunnable, aRv);
+  JSAutoStructuredCloneBuffer buffer;
+  WorkerStructuredCloneClosure closure;
+
+  SendInternal(aBody, Move(buffer), closure, aRv);
 }
 
 void
 XMLHttpRequest::Send(JS::Handle<JSObject*> aBody, ErrorResult& aRv)
 {
   JSContext* cx = mWorkerPrivate->GetJSContext();
 
   MOZ_ASSERT(aBody);
@@ -2144,25 +2166,28 @@ XMLHttpRequest::Send(JS::Handle<JSObject
     JSString* bodyStr = JS::ToString(cx, obj);
     if (!bodyStr) {
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
     valToClone.setString(bodyStr);
   }
 
-  nsRefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
-
-  sendRunnable->Write(cx, valToClone, false, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  const JSStructuredCloneCallbacks* callbacks =
+    WorkerStructuredCloneCallbacks();
+
+  WorkerStructuredCloneClosure closure;
+
+  JSAutoStructuredCloneBuffer buffer;
+  if (!buffer.write(cx, valToClone, callbacks, &closure)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  SendInternal(sendRunnable, aRv);
+  SendInternal(EmptyString(), Move(buffer), closure, aRv);
 }
 
 void
 XMLHttpRequest::Send(Blob& aBody, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   JSContext* cx = mWorkerPrivate->GetJSContext();
 
@@ -2185,25 +2210,28 @@ XMLHttpRequest::Send(Blob& aBody, ErrorR
   nsRefPtr<BlobImpl> blobImpl = aBody.Impl();
   MOZ_ASSERT(blobImpl);
 
   aRv = blobImpl->SetMutable(false);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
-  nsRefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
-
-  sendRunnable->Write(cx, value, false, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  const JSStructuredCloneCallbacks* callbacks =
+    WorkerStructuredCloneCallbacks();
+
+  WorkerStructuredCloneClosure closure;
+
+  JSAutoStructuredCloneBuffer buffer;
+  if (!buffer.write(cx, value, callbacks, &closure)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  SendInternal(sendRunnable, aRv);
+  SendInternal(EmptyString(), Move(buffer), closure, aRv);
 }
 
 void
 XMLHttpRequest::Send(nsFormData& aBody, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   JSContext* cx = mWorkerPrivate->GetJSContext();
 
@@ -2218,25 +2246,27 @@ XMLHttpRequest::Send(nsFormData& aBody, 
   }
 
   JS::Rooted<JS::Value> value(cx);
   if (!GetOrCreateDOMReflector(cx, &aBody, &value)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  nsRefPtr<SendRunnable> sendRunnable =
-    new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
-
-  sendRunnable->Write(cx, value, false, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
+  const JSStructuredCloneCallbacks* callbacks =
+    WorkerStructuredCloneCallbacks();
+
+  JSAutoStructuredCloneBuffer buffer;
+  WorkerStructuredCloneClosure closure;
+  if (!buffer.write(cx, value, callbacks, &closure)) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  SendInternal(sendRunnable, aRv);
+  SendInternal(EmptyString(), Move(buffer), closure, aRv);
 }
 
 void
 XMLHttpRequest::Send(const ArrayBuffer& aBody, ErrorResult& aRv)
 {
   JS::Rooted<JSObject*> obj(mWorkerPrivate->GetJSContext(), aBody.Obj());
   return Send(obj, aRv);
 }
--- a/dom/workers/XMLHttpRequest.h
+++ b/dom/workers/XMLHttpRequest.h
@@ -9,30 +9,31 @@
 
 #include "mozilla/dom/workers/bindings/WorkerFeature.h"
 
 // Need this for XMLHttpRequestResponseType.
 #include "mozilla/dom/XMLHttpRequestBinding.h"
 
 #include "mozilla/dom/TypedArray.h"
 
+#include "js/StructuredClone.h"
 #include "nsXMLHttpRequest.h"
 
 namespace mozilla {
 namespace dom {
 class Blob;
 } // namespace dom
 } // namespace mozilla
 
 BEGIN_WORKERS_NAMESPACE
 
 class Proxy;
-class SendRunnable;
 class XMLHttpRequestUpload;
 class WorkerPrivate;
+class WorkerStructuredCloneClosure;
 
 class XMLHttpRequest final: public nsXHREventTarget,
                             public WorkerFeature
 {
 public:
   struct StateData
   {
     nsString mResponseText;
@@ -285,15 +286,17 @@ private:
   MaybeDispatchPrematureAbortEvents(ErrorResult& aRv);
 
   void
   DispatchPrematureAbortEvent(EventTarget* aTarget,
                               const nsAString& aEventType, bool aUploadTarget,
                               ErrorResult& aRv);
 
   void
-  SendInternal(SendRunnable* aRunnable,
+  SendInternal(const nsAString& aStringBody,
+               JSAutoStructuredCloneBuffer&& aBody,
+               WorkerStructuredCloneClosure& aClosure,
                ErrorResult& aRv);
 };
 
 END_WORKERS_NAMESPACE
 
 #endif // mozilla_dom_workers_xmlhttprequest_h__