Bug 1184995 - StructuredCloneHelper for BroadcastChannel and DataStore, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 28 Jul 2015 08:38:16 +0100
changeset 286571 2329a3c7a96471337541b719a323b4a994af5e6b
parent 286570 03bb7cc89e2c0b8b7df44df3b8a691a2a30f1353
child 286572 cab9b6be7188601a7258f32315361c4494b6a22e
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1184995
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1184995 - StructuredCloneHelper for BroadcastChannel and DataStore, r=smaug
dom/base/FileList.cpp
dom/base/FileList.h
dom/base/PostMessageEvent.cpp
dom/base/StructuredCloneHelper.cpp
dom/base/StructuredCloneHelper.h
dom/base/test/test_postMessages.html
dom/broadcastchannel/BroadcastChannel.cpp
dom/broadcastchannel/BroadcastChannelChild.cpp
dom/workers/DataStore.cpp
--- a/dom/base/FileList.cpp
+++ b/dom/base/FileList.cpp
@@ -16,40 +16,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFileList)
   NS_INTERFACE_MAP_ENTRY(nsIDOMFileList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(FileList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(FileList)
 
-/* static */ already_AddRefed<FileList>
-FileList::Create(nsISupports* aParent, FileListClonedData* aData)
-{
-  MOZ_ASSERT(aData);
-
-  nsRefPtr<FileList> fileList = new FileList(aParent);
-
-  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = aData->BlobImpls();
-  for (uint32_t i = 0; i < blobImpls.Length(); ++i) {
-    const nsRefPtr<BlobImpl>& blobImpl = blobImpls[i];
-    MOZ_ASSERT(blobImpl);
-    MOZ_ASSERT(blobImpl->IsFile());
-
-    nsRefPtr<File> file = File::Create(aParent, blobImpl);
-    MOZ_ASSERT(file);
-
-    if (NS_WARN_IF(!fileList->Append(file))) {
-      return nullptr;
-    }
-  }
-
-  return fileList.forget();
-}
-
 JSObject*
 FileList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return mozilla::dom::FileListBinding::Wrap(aCx, this, aGivenProto);
 }
 
 NS_IMETHODIMP
 FileList::GetLength(uint32_t* aLength)
@@ -62,24 +38,10 @@ FileList::GetLength(uint32_t* aLength)
 NS_IMETHODIMP
 FileList::Item(uint32_t aIndex, nsISupports** aFile)
 {
   nsCOMPtr<nsIDOMBlob> file = Item(aIndex);
   file.forget(aFile);
   return NS_OK;
 }
 
-already_AddRefed<FileListClonedData>
-FileList::CreateClonedData() const
-{
-  nsTArray<nsRefPtr<BlobImpl>> blobImpls;
-  for (uint32_t i = 0; i < mFiles.Length(); ++i) {
-    blobImpls.AppendElement(mFiles[i]->Impl());
-  }
-
-  nsRefPtr<FileListClonedData> data = new FileListClonedData(blobImpls);
-  return data.forget();
-}
-
-NS_IMPL_ISUPPORTS0(FileListClonedData)
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/FileList.h
+++ b/dom/base/FileList.h
@@ -2,62 +2,40 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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_FileList_h
 #define mozilla_dom_FileList_h
 
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCycleCollectionParticipant.h"
 #include "nsIDOMFileList.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpls;
 class File;
 
-class FileListClonedData final : public nsISupports
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  explicit FileListClonedData(const nsTArray<nsRefPtr<BlobImpl>>& aBlobImpls)
-    : mBlobImpls(aBlobImpls)
-  {}
-
-  const nsTArray<nsRefPtr<BlobImpl>>& BlobImpls() const
-  {
-    return mBlobImpls;
-  }
-
-private:
-  ~FileListClonedData()
-  {}
-
-  const nsTArray<nsRefPtr<BlobImpl>> mBlobImpls;
-};
-
 class FileList final : public nsIDOMFileList,
                        public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(FileList)
 
   NS_DECL_NSIDOMFILELIST
 
   explicit FileList(nsISupports* aParent)
     : mParent(aParent)
   {}
 
-  static already_AddRefed<FileList>
-  Create(nsISupports* aParent, FileListClonedData* aData);
-
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   nsISupports* GetParentObject()
   {
     return mParent;
   }
 
@@ -109,19 +87,16 @@ public:
     return aFound ? mFiles.ElementAt(aIndex) : nullptr;
   }
 
   uint32_t Length()
   {
     return mFiles.Length();
   }
 
-  // Useful for cloning
-  already_AddRefed<FileListClonedData> CreateClonedData() const;
-
 private:
   ~FileList() {}
 
   nsTArray<nsRefPtr<File>> mFiles;
   nsCOMPtr<nsISupports> mParent;
 };
 
 } // namespace dom
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -23,17 +23,18 @@
 namespace mozilla {
 namespace dom {
 
 PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource,
                                    const nsAString& aCallerOrigin,
                                    nsGlobalWindow* aTargetWindow,
                                    nsIPrincipal* aProvidedPrincipal,
                                    bool aTrustedCaller)
-: mSource(aSource),
+: StructuredCloneHelper(CloningSupported, TransferringSupported),
+  mSource(aSource),
   mCallerOrigin(aCallerOrigin),
   mTargetWindow(aTargetWindow),
   mProvidedPrincipal(aProvidedPrincipal),
   mTrustedCaller(aTrustedCaller)
 {
   MOZ_COUNT_CTOR(PostMessageEvent);
 }
 
--- a/dom/base/StructuredCloneHelper.cpp
+++ b/dom/base/StructuredCloneHelper.cpp
@@ -194,149 +194,208 @@ StructuredCloneHelperInternal::FreeTrans
                                                     void* aContent,
                                                     uint64_t aExtraData)
 {
   MOZ_CRASH("Nothing to free.");
 }
 
 // StructuredCloneHelper class
 
-StructuredCloneHelper::StructuredCloneHelper(uint32_t aFlags)
-  : mFlags(aFlags)
+StructuredCloneHelper::StructuredCloneHelper(CloningSupport aSupportsCloning,
+                                             TransferringSupport aSupportsTransferring)
+  : mSupportsCloning(aSupportsCloning == CloningSupported)
+  , mSupportsTransferring(aSupportsTransferring == TransferringSupported)
   , mParent(nullptr)
 {}
 
 StructuredCloneHelper::~StructuredCloneHelper()
 {
   Shutdown();
 }
 
 bool
 StructuredCloneHelper::Write(JSContext* aCx,
+                             JS::Handle<JS::Value> aValue)
+{
+  return Write(aCx, aValue, JS::UndefinedHandleValue);
+}
+
+bool
+StructuredCloneHelper::Write(JSContext* aCx,
                              JS::Handle<JS::Value> aValue,
                              JS::Handle<JS::Value> aTransfer)
 {
   bool ok = StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer);
   mTransferringPort.Clear();
   return ok;
 }
 
 bool
 StructuredCloneHelper::Read(nsISupports* aParent,
                             JSContext* aCx,
                             JS::MutableHandle<JS::Value> aValue)
 {
   mozilla::AutoRestore<nsISupports*> guard(mParent);
   mParent = aParent;
 
-  return StructuredCloneHelperInternal::Read(aCx, aValue);
+  bool ok = StructuredCloneHelperInternal::Read(aCx, aValue);
+  mBlobImplArray.Clear();
+  return ok;
+}
+
+bool
+StructuredCloneHelper::ReadFromBuffer(nsISupports* aParent,
+                                      JSContext* aCx,
+                                      uint64_t* aBuffer,
+                                      size_t aBufferLength,
+                                      nsTArray<nsRefPtr<BlobImpl>>& aBlobImpls,
+                                      JS::MutableHandle<JS::Value> aValue)
+{
+  MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write().");
+  MOZ_ASSERT(mBlobImplArray.IsEmpty());
+
+  MOZ_ASSERT(aBuffer);
+  MOZ_ASSERT_IF(!mSupportsCloning, aBlobImpls.IsEmpty());
+
+  mozilla::AutoRestore<nsISupports*> guard(mParent);
+  mParent = aParent;
+
+  mBlobImplArray.AppendElements(aBlobImpls);
+
+  bool ok = JS_ReadStructuredClone(aCx, aBuffer, aBufferLength,
+                                   JS_STRUCTURED_CLONE_VERSION, aValue,
+                                   &gCallbacks, this);
+
+  mBlobImplArray.Clear();
+  return ok;
 }
 
 JSObject*
 StructuredCloneHelper::ReadCallback(JSContext* aCx,
                                     JSStructuredCloneReader* aReader,
                                     uint32_t aTag,
                                     uint32_t aIndex)
 {
+  MOZ_ASSERT(mSupportsCloning);
+
   if (aTag == SCTAG_DOM_BLOB) {
-    MOZ_ASSERT(!(mFlags & eBlobNotSupported));
+    MOZ_ASSERT(aIndex < mBlobImplArray.Length());
+    nsRefPtr<BlobImpl> blobImpl =  mBlobImplArray[aIndex];
 
-    BlobImpl* blobImpl;
-    if (JS_ReadBytes(aReader, &blobImpl, sizeof(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(mParent, blobImpl);
+      if (!ToJSValue(aCx, blob, &val)) {
+        return nullptr;
+      }
+    }
+
+    return &val.toObject();
+  }
 
-      // 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)) {
+  if (aTag == SCTAG_DOM_FILELIST) {
+    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());
+
+        nsRefPtr<BlobImpl> blobImpl = mBlobImplArray[index];
+        MOZ_ASSERT(blobImpl->IsFile());
+
+        nsRefPtr<File> file = File::Create(mParent, blobImpl);
+        if (!fileList->Append(file)) {
           return nullptr;
         }
       }
 
-      return &val.toObject();
+      if (!ToJSValue(aCx, fileList, &val)) {
+        return nullptr;
+      }
     }
-  }
-
-  if (aTag == SCTAG_DOM_FILELIST) {
-    MOZ_ASSERT(!(mFlags & eFileListNotSupported));
-
-    FileListClonedData* fileListClonedData;
-    if (JS_ReadBytes(aReader, &fileListClonedData,
-                     sizeof(fileListClonedData))) {
-      MOZ_ASSERT(fileListClonedData);
 
-      // nsRefPtr<FileList> 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<FileList> fileList =
-          FileList::Create(mParent, fileListClonedData);
-        if (!fileList || !ToJSValue(aCx, fileList, &val)) {
-          return nullptr;
-        }
-      }
-
-      return &val.toObject();
-    }
+    return &val.toObject();
   }
 
   return NS_DOMReadStructuredClone(aCx, aReader, aTag, aIndex, nullptr);
 }
 
 bool
 StructuredCloneHelper::WriteCallback(JSContext* aCx,
                                      JSStructuredCloneWriter* aWriter,
                                      JS::Handle<JSObject*> aObj)
 {
+  if (!mSupportsCloning) {
+    return false;
+  }
+
   // See if this is a File/Blob object.
-  if (!(mFlags & eBlobNotSupported)) {
+  {
     Blob* blob = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
       BlobImpl* blobImpl = blob->Impl();
-      return JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, 0) &&
-             JS_WriteBytes(aWriter, &blobImpl, sizeof(blobImpl)) &&
-             StoreISupports(blobImpl);
+      if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
+                             mBlobImplArray.Length())) {
+        mBlobImplArray.AppendElement(blobImpl);
+        return true;
+      }
+
+      return false;
     }
   }
 
-  if (!(mFlags & eFileListNotSupported)) {
+  {
     FileList* fileList = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
-      nsRefPtr<FileListClonedData> fileListClonedData =
-        fileList->CreateClonedData();
-      MOZ_ASSERT(fileListClonedData);
-      FileListClonedData* ptr = fileListClonedData.get();
-      return JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, 0) &&
-             JS_WriteBytes(aWriter, &ptr, sizeof(ptr)) &&
-             StoreISupports(fileListClonedData);
+      // 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;
+      }
+
+      for (uint32_t i = 0; i < fileList->Length(); ++i) {
+        mBlobImplArray.AppendElement(fileList->Item(i)->Impl());
+      }
+
+      return true;
     }
   }
 
   return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr);
 }
 
 bool
 StructuredCloneHelper::ReadTransferCallback(JSContext* aCx,
                                             JSStructuredCloneReader* aReader,
                                             uint32_t aTag,
                                             void* aContent,
                                             uint64_t aExtraData,
                                             JS::MutableHandleObject aReturnObject)
 {
+  MOZ_ASSERT(mSupportsTransferring);
+
   if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
-    MOZ_ASSERT(!(mFlags & eMessagePortNotSupported));
-
     // This can be null.
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mParent);
 
     MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
     const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData];
 
     // aExtraData is the index of this port identifier.
     ErrorResult rv;
@@ -365,17 +424,21 @@ StructuredCloneHelper::ReadTransferCallb
 bool
 StructuredCloneHelper::WriteTransferCallback(JSContext* aCx,
                                              JS::Handle<JSObject*> aObj,
                                              uint32_t* aTag,
                                              JS::TransferableOwnership* aOwnership,
                                              void** aContent,
                                              uint64_t* aExtraData)
 {
-  if (!(mFlags & eMessagePortNotSupported)) {
+  if (!mSupportsTransferring) {
+    return false;
+  }
+
+  {
     MessagePortBase* port = nullptr;
     nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
     if (NS_SUCCEEDED(rv)) {
       if (mTransferringPort.Contains(port)) {
         // No duplicates.
         return false;
       }
 
@@ -401,18 +464,19 @@ StructuredCloneHelper::WriteTransferCall
 }
 
 void
 StructuredCloneHelper::FreeTransferCallback(uint32_t aTag,
                                             JS::TransferableOwnership aOwnership,
                                             void* aContent,
                                             uint64_t aExtraData)
 {
+  MOZ_ASSERT(mSupportsTransferring);
+
   if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
-    MOZ_ASSERT(!(mFlags & eMessagePortNotSupported));
     MOZ_ASSERT(!aContent);
     MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
     MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
   }
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/base/StructuredCloneHelper.h
+++ b/dom/base/StructuredCloneHelper.h
@@ -71,61 +71,90 @@ public:
 
   bool Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfer);
 
   bool Read(JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue);
 
+  uint64_t* BufferData() const
+  {
+    MOZ_ASSERT(mBuffer, "Write() has never been called.");
+    return mBuffer->data();
+  }
+
+  size_t BufferSize() const
+  {
+    MOZ_ASSERT(mBuffer, "Write() has never been called.");
+    return mBuffer->nbytes();
+  }
+
 protected:
   nsAutoPtr<JSAutoStructuredCloneBuffer> mBuffer;
 
 #ifdef DEBUG
   bool mShutdownCalled;
 #endif
 };
 
+class BlobImpl;
 class MessagePortBase;
 class MessagePortIdentifier;
 
 class StructuredCloneHelper : public StructuredCloneHelperInternal
 {
 public:
-  enum StructuredCloneHelperFlags {
-    eAll = 0,
-
-    // Disable the cloning of blobs. If a blob is part of the cloning value,
-    // an exception will be thrown.
-    eBlobNotSupported = 1 << 0,
-
-    // Disable the cloning of FileLists. If a FileList is part of the cloning
-    // value, an exception will be thrown.
-    eFileListNotSupported = 1 << 1,
-
-    // MessagePort can just be transfered. Using this flag we do not support
-    // the transfering.
-    eMessagePortNotSupported = 1 << 2,
+  enum CloningSupport
+  {
+    CloningSupported,
+    CloningNotSupported
   };
 
-  // aFlags is a bitmap of StructuredCloneHelperFlags.
-  explicit StructuredCloneHelper(uint32_t aFlags = eAll);
+  enum TransferringSupport
+  {
+    TransferringSupported,
+    TransferringNotSupported
+  };
+
+  // If cloning is supported, this object will clone objects such as Blobs,
+  // FileList, ImageData, etc.
+  // If transferring is supported, we will transfer MessagePorts and in the
+  // future other transferrable objects.
+  explicit StructuredCloneHelper(CloningSupport aSupportsCloning,
+                                 TransferringSupport aSupportsTransferring);
   virtual ~StructuredCloneHelper();
 
   bool Write(JSContext* aCx,
+             JS::Handle<JS::Value> aValue);
+
+  bool Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              JS::Handle<JS::Value> aTransfer);
 
   bool Read(nsISupports* aParent,
             JSContext* aCx,
             JS::MutableHandle<JS::Value> aValue);
 
+  bool ReadFromBuffer(nsISupports* aParent,
+                      JSContext* aCx,
+                      uint64_t* aBuffer,
+                      size_t aBufferLength,
+                      nsTArray<nsRefPtr<BlobImpl>>& aBlobImpls,
+                      JS::MutableHandle<JS::Value> aValue);
+
+  const nsTArray<nsRefPtr<BlobImpl>>& ClonedBlobImpls() const
+  {
+    MOZ_ASSERT(mBuffer, "Write() has never been called.");
+    return mBlobImplArray;
+  }
+
   nsTArray<nsRefPtr<MessagePortBase>>& GetTransferredPorts()
   {
-    MOZ_ASSERT(!(mFlags & eMessagePortNotSupported));
+    MOZ_ASSERT(mSupportsTransferring);
     return mTransferredPorts;
   }
 
   // Custom Callbacks
 
   virtual JSObject* ReadCallback(JSContext* aCx,
                                  JSStructuredCloneReader* aReader,
                                  uint32_t aTag,
@@ -149,29 +178,22 @@ public:
                                      void** aContent,
                                      uint64_t* aExtraData) override;
 
   virtual void FreeTransferCallback(uint32_t aTag,
                                     JS::TransferableOwnership aOwnership,
                                     void* aContent,
                                     uint64_t aExtraData) override;
 private:
-  bool StoreISupports(nsISupports* aSupports)
-  {
-    MOZ_ASSERT(aSupports);
-    mSupportsArray.AppendElement(aSupports);
-    return true;
-  }
-
-  // This is our bitmap.
-  uint32_t mFlags;
+  bool mSupportsCloning;
+  bool mSupportsTransferring;
 
   // Useful for the structured clone algorithm:
 
-  nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
+  nsTArray<nsRefPtr<BlobImpl>> mBlobImplArray;
 
   // This raw pointer is set and unset into the ::Read(). It's always null
   // outside that method. For this reason it's a raw pointer.
   nsISupports* MOZ_NON_OWNING_REF mParent;
 
   // This hashtable contains the ports while doing write (transferring and
   // mapping transferred objects to the objects in the clone). It's an empty
   // array outside the 'Write()' method.
--- a/dom/base/test/test_postMessages.html
+++ b/dom/base/test/test_postMessages.html
@@ -226,22 +226,61 @@ function test_windowToIframeURL(url) {
         onmessage = null;
         next();
       }
     });
   }
   document.body.appendChild(ifr);
 }
 
+function test_broadcastChannel() {
+  info("Testing broadcastChannel");
+
+  var bc1 = new BroadcastChannel('postMessagesTest');
+  var bc2 = new BroadcastChannel('postMessagesTest');
+
+  var resolve;
+
+  bc2.onmessage = function(e) {
+    if (!resolve) {
+      ok(false, "Unexpected message!");
+      return;
+    }
+
+    let tmp = resolve;
+    resolve = null;
+    tmp({ data: e.data, ports: [] });
+  }
+
+  runTests({
+    clonableObjects: true,
+    transferableObjects: false,
+    send: function(what, ports) {
+      is(ports.length, 0, "No ports for this test!");
+      return new Promise(function(r, rr) {
+        resolve = r;
+        bc1.postMessage(what);
+      });
+    },
+
+    finished: function() {
+      onmessage = null;
+      next();
+    }
+  });
+}
+
 var tests = [
   create_fileList,
 
   test_windowToWindow,
   test_windowToIframe,
   test_windowToCrossOriginIframe,
+
+  test_broadcastChannel,
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -3,17 +3,18 @@
 /* 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 "BroadcastChannel.h"
 #include "BroadcastChannelChild.h"
 #include "mozilla/dom/BroadcastChannelBinding.h"
 #include "mozilla/dom/Navigator.h"
-#include "mozilla/dom/StructuredCloneUtils.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/StructuredCloneHelper.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
@@ -25,30 +26,28 @@
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
 using namespace workers;
 
-class BroadcastChannelMessage final
+class BroadcastChannelMessage final : public StructuredCloneHelper
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage)
 
-  JSAutoStructuredCloneBuffer mBuffer;
-  StructuredCloneClosure mClosure;
-
   BroadcastChannelMessage()
-  { }
+    : StructuredCloneHelper(CloningSupported, TransferringNotSupported)
+  {}
 
 private:
   ~BroadcastChannelMessage()
-  { }
+  {}
 };
 
 namespace {
 
 nsIPrincipal*
 GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate)
 {
   nsIPrincipal* principal = aWorkerPrivate->GetPrincipal();
@@ -161,23 +160,23 @@ public:
     MOZ_ASSERT(mActor);
     if (mActor->IsActorDestroyed()) {
       return NS_OK;
     }
 
     ClonedMessageData message;
 
     SerializedStructuredCloneBuffer& buffer = message.data();
-    buffer.data = mData->mBuffer.data();
-    buffer.dataLength = mData->mBuffer.nbytes();
+    buffer.data = mData->BufferData();
+    buffer.dataLength = mData->BufferSize();
 
     PBackgroundChild* backgroundManager = mActor->Manager();
     MOZ_ASSERT(backgroundManager);
 
-    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = mData->mClosure.mBlobImpls;
+    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = mData->ClonedBlobImpls();
 
     if (!blobImpls.IsEmpty()) {
       message.blobsChild().SetCapacity(blobImpls.Length());
 
       for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
         PBlobChild* blobChild =
           BackgroundChild::GetOrCreateActorForBlobImpl(backgroundManager,
                                                        blobImpls[i]);
@@ -450,22 +449,22 @@ BroadcastChannel::PostMessage(JSContext*
 
 void
 BroadcastChannel::PostMessageInternal(JSContext* aCx,
                                       JS::Handle<JS::Value> aMessage,
                                       ErrorResult& aRv)
 {
   nsRefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
 
-  if (!WriteStructuredClone(aCx, aMessage, data->mBuffer, data->mClosure)) {
+  if (!data->Write(aCx, aMessage)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->mClosure.mBlobImpls;
+  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->ClonedBlobImpls();
   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);
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -6,17 +6,17 @@
 
 #include "BroadcastChannelChild.h"
 #include "BroadcastChannel.h"
 #include "jsapi.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
-#include "mozilla/dom/StructuredCloneUtils.h"
+#include "mozilla/dom/StructuredCloneHelper.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "WorkerPrivate.h"
 
 namespace mozilla {
 
@@ -81,25 +81,25 @@ BroadcastChannelChild::RecvNotify(const 
   }
 
   if (!globalObject || !jsapi.Init(globalObject)) {
     NS_WARNING("Failed to initialize AutoJSAPI object.");
     return true;
   }
 
   JSContext* cx = jsapi.cx();
-
   const SerializedStructuredCloneBuffer& buffer = aData.data();
-  StructuredCloneData cloneData;
-  cloneData.mData = buffer.data;
-  cloneData.mDataLength = buffer.dataLength;
-  cloneData.mClosure.mBlobImpls.SwapElements(blobs);
+  StructuredCloneHelper cloneHelper(StructuredCloneHelper::CloningSupported,
+                                    StructuredCloneHelper::TransferringNotSupported);
 
   JS::Rooted<JS::Value> value(cx, JS::NullValue());
-  if (cloneData.mDataLength && !ReadStructuredClone(cx, cloneData, &value)) {
+  if (buffer.dataLength &&
+      !cloneHelper.ReadFromBuffer(mBC->GetParentObject(), cx,
+                                  buffer.data, buffer.dataLength, blobs,
+                                  &value)) {
     JS_ClearPendingException(cx);
     return false;
   }
 
   RootedDictionary<MessageEventInit> init(cx);
   init.mBubbles = false;
   init.mCancelable = false;
   init.mOrigin.Construct(mOrigin);
--- a/dom/workers/DataStore.cpp
+++ b/dom/workers/DataStore.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/dom/DataStoreCursor.h"
 #include "mozilla/dom/DataStoreChangeEvent.h"
 #include "mozilla/dom/DataStoreBinding.h"
 #include "mozilla/dom/DataStoreImplBinding.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 #include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneHelper.h"
 #include "mozilla/ErrorResult.h"
 
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 BEGIN_WORKERS_NAMESPACE
 
@@ -202,41 +203,42 @@ protected:
     nsRefPtr<Promise> promise = mBackingStore->Get(mId, mRv);
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Put(...) on the main thread.
 class DataStorePutRunnable final : public DataStoreProxyRunnable
+                                 , public StructuredCloneHelper
 {
-  JSAutoStructuredCloneBuffer mObjBuffer;
   const StringOrUnsignedLong& mId;
   const nsString mRevisionId;
   ErrorResult& mRv;
 
 public:
   DataStorePutRunnable(WorkerPrivate* aWorkerPrivate,
                        const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                        Promise* aWorkerPromise,
                        JSContext* aCx,
                        JS::Handle<JS::Value> aObj,
                        const StringOrUnsignedLong& aId,
                        const nsAString& aRevisionId,
                        ErrorResult& aRv)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
+    , StructuredCloneHelper(CloningNotSupported, TransferringNotSupported)
     , 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.
-    if (!mObjBuffer.write(aCx, aObj)) {
+    if (!Write(aCx, aObj)) {
       JS_ClearPendingException(aCx);
       mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     }
   }
 
 protected:
   virtual bool
   MainThreadRun() override
@@ -247,17 +249,17 @@ protected:
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(mBackingStore->GetParentObject()))) {
       mRv.Throw(NS_ERROR_UNEXPECTED);
       return true;
     }
     JSContext* cx = jsapi.cx();
 
     JS::Rooted<JS::Value> value(cx);
-    if (!mObjBuffer.read(cx, &value)) {
+    if (!Read(mBackingStore->GetParentObject(), cx, &value)) {
       JS_ClearPendingException(cx);
       mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
       return true;
     }
 
     nsRefPtr<Promise> promise = mBackingStore->Put(cx,
                                                    value,
                                                    mId,
@@ -265,41 +267,42 @@ protected:
                                                    mRv);
     promise->AppendNativeHandler(mPromiseWorkerProxy);
     return true;
   }
 };
 
 // A DataStoreRunnable to run DataStore::Add(...) on the main thread.
 class DataStoreAddRunnable final : public DataStoreProxyRunnable
+                                 , public StructuredCloneHelper
 {
-  JSAutoStructuredCloneBuffer mObjBuffer;
   const Optional<StringOrUnsignedLong>& mId;
   const nsString mRevisionId;
   ErrorResult& mRv;
 
 public:
   DataStoreAddRunnable(WorkerPrivate* aWorkerPrivate,
                        const nsMainThreadPtrHandle<DataStore>& aBackingStore,
                        Promise* aWorkerPromise,
                        JSContext* aCx,
                        JS::Handle<JS::Value> aObj,
                        const Optional<StringOrUnsignedLong>& aId,
                        const nsAString& aRevisionId,
                        ErrorResult& aRv)
     : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise)
+    , StructuredCloneHelper(CloningNotSupported, TransferringNotSupported)
     , 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.
-    if (!mObjBuffer.write(aCx, aObj)) {
+    if (!Write(aCx, aObj)) {
       JS_ClearPendingException(aCx);
       mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     }
   }
 
 protected:
   virtual bool
   MainThreadRun() override
@@ -310,17 +313,17 @@ protected:
     AutoJSAPI jsapi;
     if (NS_WARN_IF(!jsapi.Init(mBackingStore->GetParentObject()))) {
       mRv.Throw(NS_ERROR_UNEXPECTED);
       return true;
     }
     JSContext* cx = jsapi.cx();
 
     JS::Rooted<JS::Value> value(cx);
-    if (!mObjBuffer.read(cx, &value)) {
+    if (!Read(mBackingStore->GetParentObject(), cx, &value)) {
       JS_ClearPendingException(cx);
       mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
       return true;
     }
 
     nsRefPtr<Promise> promise = mBackingStore->Add(cx,
                                                    value,
                                                    mId,