Bug 1185569 - Use StructuredCloneHelper in MessagePort, r=smaug
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 28 Jul 2015 11:47:36 +0100
changeset 286588 6d1971130ee1728580ae7ae681ed262accbe2d42
parent 286587 c70a55f8feb8e9e839ffbdb1792ec9ae67c5da07
child 286589 a0cb66ac149fd5f77e89a6fe40b8f38feffa34ad
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
bugs1185569
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 1185569 - Use StructuredCloneHelper in MessagePort, r=smaug
dom/base/StructuredCloneHelper.cpp
dom/base/StructuredCloneHelper.h
dom/base/test/test_postMessages.html
dom/broadcastchannel/BroadcastChannel.cpp
dom/broadcastchannel/BroadcastChannelChild.cpp
dom/messagechannel/MessagePort.cpp
dom/messagechannel/MessagePortList.h
dom/messagechannel/MessagePortUtils.cpp
dom/messagechannel/MessagePortUtils.h
dom/messagechannel/SharedMessagePortMessage.cpp
dom/messagechannel/SharedMessagePortMessage.h
dom/messagechannel/moz.build
--- a/dom/base/StructuredCloneHelper.cpp
+++ b/dom/base/StructuredCloneHelper.cpp
@@ -241,36 +241,58 @@ StructuredCloneHelper::Read(nsISupports*
   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);
+  return JS_ReadStructuredClone(aCx, aBuffer, aBufferLength,
+                                JS_STRUCTURED_CLONE_VERSION, aValue,
+                                &gCallbacks, this);
+}
+
+void
+StructuredCloneHelper::MoveBufferDataToArray(FallibleTArray<uint8_t>& aArray,
+                                             ErrorResult& aRv)
+{
+  MOZ_ASSERT(mBuffer, "MoveBuffer() cannot be called without a Write().");
+
+  if (NS_WARN_IF(!aArray.SetLength(BufferSize(), mozilla::fallible))) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
 
-  bool ok = JS_ReadStructuredClone(aCx, aBuffer, aBufferLength,
-                                   JS_STRUCTURED_CLONE_VERSION, aValue,
-                                   &gCallbacks, this);
+  uint64_t* buffer;
+  size_t size;
+  mBuffer->steal(&buffer, &size);
+  mBuffer = nullptr;
+
+  memcpy(aArray.Elements(), buffer, size);
+  js_free(buffer);
+}
 
-  mBlobImplArray.Clear();
-  return ok;
+void
+StructuredCloneHelper::FreeBuffer(uint64_t* aBuffer,
+                                  size_t aBufferLength)
+{
+  MOZ_ASSERT(!mBuffer, "FreeBuffer() must be called without a Write().");
+  MOZ_ASSERT(aBuffer);
+  MOZ_ASSERT(aBufferLength);
+
+  JS_ClearStructuredClone(aBuffer, aBufferLength, &gCallbacks, this, false);
 }
 
 JSObject*
 StructuredCloneHelper::ReadCallback(JSContext* aCx,
                                     JSStructuredCloneReader* aReader,
                                     uint32_t aTag,
                                     uint32_t aIndex)
 {
--- a/dom/base/StructuredCloneHelper.h
+++ b/dom/base/StructuredCloneHelper.h
@@ -118,46 +118,66 @@ public:
   // 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();
 
+  // Normally you should just use Write() and Read().
+
   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);
 
+  // Sometimes, when IPC is involved, you must send a buffer after a Write().
+  // This method 'steals' the internal data from this helper class.
+  // You should free this buffer with FreeBuffer().
+  void MoveBufferDataToArray(FallibleTArray<uint8_t>& aArray,
+                             ErrorResult& aRv);
+
+  // If you receive a buffer from IPC, you can use this method to retrieve a
+  // JS::Value. It can happen that you want to pre-populate the array of Blobs
+  // and/or the PortIdentifiers.
   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
+  // Use this method to free a buffer generated by MoveToBuffer().
+  void FreeBuffer(uint64_t* aBuffer,
+                  size_t aBufferLength);
+
+  nsTArray<nsRefPtr<BlobImpl>>& BlobImpls()
   {
-    MOZ_ASSERT(mBuffer, "Write() has never been called.");
+    MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported.");
     return mBlobImplArray;
   }
 
-  nsTArray<nsRefPtr<MessagePortBase>>& GetTransferredPorts()
+  const nsTArray<nsRefPtr<MessagePortBase>>& GetTransferredPorts() const
   {
     MOZ_ASSERT(mSupportsTransferring);
     return mTransferredPorts;
   }
 
+  nsTArray<MessagePortIdentifier>& PortIdentifiers()
+  {
+    MOZ_ASSERT(mSupportsTransferring);
+    return mPortIdentifiers;
+  }
+
   // Custom Callbacks
 
   virtual JSObject* ReadCallback(JSContext* aCx,
                                  JSStructuredCloneReader* aReader,
                                  uint32_t aTag,
                                  uint32_t aIndex) override;
 
   virtual bool WriteCallback(JSContext* aCx,
--- a/dom/base/test/test_postMessages.html
+++ b/dom/base/test/test_postMessages.html
@@ -226,16 +226,17 @@ function test_windowToIframeURL(url) {
         onmessage = null;
         next();
       }
     });
   }
   document.body.appendChild(ifr);
 }
 
+// PostMessage for BroadcastChannel
 function test_broadcastChannel() {
   info("Testing broadcastChannel");
 
   var bc1 = new BroadcastChannel('postMessagesTest');
   var bc2 = new BroadcastChannel('postMessagesTest');
 
   var resolve;
 
@@ -263,24 +264,63 @@ function test_broadcastChannel() {
 
     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) {
+    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;
+        mc.port1.postMessage(what, ports);
+      });
+    },
+
+    finished: function() {
+      onmessage = null;
+      next();
+    }
+  });
+}
+
 var tests = [
   create_fileList,
 
   test_windowToWindow,
   test_windowToIframe,
   test_windowToCrossOriginIframe,
 
   test_broadcastChannel,
+  // TODO BroadcastChannel in worker
+
+  test_messagePort,
+  // TODO MessagePort in worker
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/broadcastchannel/BroadcastChannel.cpp
+++ b/dom/broadcastchannel/BroadcastChannel.cpp
@@ -166,17 +166,17 @@ public:
 
     SerializedStructuredCloneBuffer& buffer = message.data();
     buffer.data = mData->BufferData();
     buffer.dataLength = mData->BufferSize();
 
     PBackgroundChild* backgroundManager = mActor->Manager();
     MOZ_ASSERT(backgroundManager);
 
-    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = mData->ClonedBlobImpls();
+    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = mData->BlobImpls();
 
     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]);
@@ -454,17 +454,17 @@ BroadcastChannel::PostMessageInternal(JS
 {
   nsRefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
 
   if (!data->Write(aCx, aMessage)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
-  const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->ClonedBlobImpls();
+  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);
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -85,21 +85,22 @@ BroadcastChannelChild::RecvNotify(const 
     return true;
   }
 
   JSContext* cx = jsapi.cx();
   const SerializedStructuredCloneBuffer& buffer = aData.data();
   StructuredCloneHelper cloneHelper(StructuredCloneHelper::CloningSupported,
                                     StructuredCloneHelper::TransferringNotSupported);
 
+  cloneHelper.BlobImpls().AppendElements(blobs);
+
   JS::Rooted<JS::Value> value(cx, JS::NullValue());
   if (buffer.dataLength &&
       !cloneHelper.ReadFromBuffer(mBC->GetParentObject(), cx,
-                                  buffer.data, buffer.dataLength, blobs,
-                                  &value)) {
+                                  buffer.data, buffer.dataLength, &value)) {
     JS_ClearPendingException(cx);
     return false;
   }
 
   RootedDictionary<MessageEventInit> init(cx);
   init.mBubbles = false;
   init.mCancelable = false;
   init.mOrigin.Construct(mOrigin);
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -111,56 +111,43 @@ public:
     AutoJSAPI jsapi;
     if (!globalObject || !jsapi.Init(globalObject)) {
       NS_WARNING("Failed to initialize AutoJSAPI object.");
       return NS_ERROR_FAILURE;
     }
 
     JSContext* cx = jsapi.cx();
 
-    nsTArray<nsRefPtr<MessagePort>> ports;
     nsCOMPtr<nsPIDOMWindow> window =
       do_QueryInterface(mPort->GetParentObject());
 
+    ErrorResult rv;
     JS::Rooted<JS::Value> value(cx);
-    if (!mData->mData.IsEmpty()) {
-      bool ok = ReadStructuredCloneWithTransfer(cx, mData->mData,
-                                                mData->mClosure,
-                                                &value, window, ports);
-      FreeStructuredClone(mData->mData, mData->mClosure);
 
-      if (!ok) {
-        return NS_ERROR_FAILURE;
-      }
+    mData->Read(window, cx, &value, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return rv.StealNSResult();
     }
 
-    // The data should be already be cleaned.
-    MOZ_ASSERT(!mData->mData.Length());
-
     // Create the event
     nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
       do_QueryInterface(mPort->GetOwner());
     nsRefPtr<MessageEvent> event =
       new MessageEvent(eventTarget, nullptr, nullptr);
 
     event->InitMessageEvent(NS_LITERAL_STRING("message"),
                             false /* non-bubbling */,
                             false /* cancelable */, value, EmptyString(),
                             EmptyString(), nullptr);
     event->SetTrusted(true);
     event->SetSource(mPort);
 
-    nsTArray<nsRefPtr<MessagePortBase>> array;
-    array.SetCapacity(ports.Length());
-    for (uint32_t i = 0; i < ports.Length(); ++i) {
-      array.AppendElement(ports[i]);
-    }
-
     nsRefPtr<MessagePortList> portList =
-      new MessagePortList(static_cast<dom::Event*>(event.get()), array);
+      new MessagePortList(static_cast<dom::Event*>(event.get()),
+                          mData->GetTransferredPorts());
     event->SetPorts(portList);
 
     bool dummy;
     mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &dummy);
     return NS_OK;
   }
 
   NS_IMETHOD
@@ -449,19 +436,18 @@ MessagePort::PostMessage(JSContext* aCx,
       return;
     }
 
     transferable.setObject(*array);
   }
 
   nsRefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage();
 
-  if (!WriteStructuredCloneWithTransfer(aCx, aMessage, transferable,
-                                        data->mData, data->mClosure)) {
-    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+  data->Write(aCx, aMessage, transferable, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   // This message has to be ignored.
   if (mState > eStateEntangled) {
     return;
   }
 
--- a/dom/messagechannel/MessagePortList.h
+++ b/dom/messagechannel/MessagePortList.h
@@ -23,17 +23,18 @@ class MessagePortList final : public nsI
 {
   ~MessagePortList() {}
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MessagePortList)
 
 public:
-  MessagePortList(nsISupports* aOwner, nsTArray<nsRefPtr<MessagePortBase>>& aPorts)
+  MessagePortList(nsISupports* aOwner,
+                  const nsTArray<nsRefPtr<MessagePortBase>>& aPorts)
     : mOwner(aOwner)
     , mPorts(aPorts)
   {
   }
 
   nsISupports*
   GetParentObject() const
   {
deleted file mode 100644
--- a/dom/messagechannel/MessagePortUtils.cpp
+++ /dev/null
@@ -1,291 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "MessagePortUtils.h"
-#include "MessagePort.h"
-#include "mozilla/dom/BlobBinding.h"
-#include "mozilla/dom/File.h"
-#include "mozilla/dom/MessagePortBinding.h"
-#include "mozilla/dom/StructuredCloneTags.h"
-
-namespace mozilla {
-namespace dom {
-namespace messageport {
-
-namespace {
-
-struct MOZ_STACK_CLASS StructuredCloneClosureInternal
-{
-  StructuredCloneClosureInternal(
-    StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow)
-    : mClosure(aClosure)
-    , mWindow(aWindow)
-  { }
-
-  StructuredCloneClosure& mClosure;
-  nsPIDOMWindow* mWindow;
-  nsTArray<nsRefPtr<MessagePort>> mMessagePorts;
-  nsTArray<nsRefPtr<MessagePortBase>> mTransferredPorts;
-};
-
-struct MOZ_STACK_CLASS StructuredCloneClosureInternalReadOnly
-{
-  StructuredCloneClosureInternalReadOnly(
-    const StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow)
-    : mClosure(aClosure)
-    , mWindow(aWindow)
-  { }
-
-  const StructuredCloneClosure& mClosure;
-  nsPIDOMWindow* mWindow;
-  nsTArray<nsRefPtr<MessagePort>> mMessagePorts;
-  nsTArray<nsRefPtr<MessagePortBase>> mTransferredPorts;
-};
-
-void
-Error(JSContext* aCx, uint32_t aErrorId)
-{
-  if (NS_IsMainThread()) {
-    NS_DOMStructuredCloneError(aCx, aErrorId);
-  } else {
-    Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
-  }
-}
-
-JSObject*
-Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
-     uint32_t aData, void* aClosure)
-{
-  MOZ_ASSERT(aClosure);
-
-  auto* closure = static_cast<StructuredCloneClosureInternalReadOnly*>(aClosure);
-
-  if (aTag == SCTAG_DOM_BLOB) {
-    // 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);
-    {
-      MOZ_ASSERT(aData < closure->mClosure.mBlobImpls.Length());
-      nsRefPtr<BlobImpl> blobImpl = closure->mClosure.mBlobImpls[aData];
-
-#ifdef DEBUG
-      {
-        // Blob should not be mutable.
-        bool isMutable;
-        MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable)));
-        MOZ_ASSERT(!isMutable);
-      }
-#endif
-
-      // Let's create a new blob with the correct parent.
-      nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
-      MOZ_ASSERT(global);
-
-      nsRefPtr<Blob> newBlob = Blob::Create(global, blobImpl);
-      if (!ToJSValue(aCx, newBlob, &val)) {
-        return nullptr;
-      }
-    }
-
-    return &val.toObject();
-  }
-
-  return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr);
-}
-
-bool
-Write(JSContext* aCx, JSStructuredCloneWriter* aWriter,
-      JS::Handle<JSObject*> aObj, void* aClosure)
-{
-  MOZ_ASSERT(aClosure);
-
-  auto* closure = static_cast<StructuredCloneClosureInternal*>(aClosure);
-
-  // See if the wrapped native is a File/Blob.
-  {
-    Blob* blob = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
-        NS_SUCCEEDED(blob->SetMutable(false)) &&
-        JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB,
-                           closure->mClosure.mBlobImpls.Length())) {
-      closure->mClosure.mBlobImpls.AppendElement(blob->Impl());
-      return true;
-    }
-  }
-
-  return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr);
-}
-
-bool
-ReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader,
-             uint32_t aTag, void* aContent, uint64_t aExtraData,
-             void* aClosure, JS::MutableHandle<JSObject*> aReturnObject)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aClosure);
-
-  auto* closure = static_cast<StructuredCloneClosureInternalReadOnly*>(aClosure);
-
-  if (aTag != SCTAG_DOM_MAP_MESSAGEPORT) {
-    return false;
-  }
-
-  MOZ_ASSERT(aContent == 0);
-  MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length());
-
-  ErrorResult rv;
-  nsRefPtr<MessagePort> port =
-    MessagePort::Create(closure->mWindow,
-                        closure->mClosure.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;
-}
-
-bool
-WriteTransfer(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<StructuredCloneClosureInternal*>(aClosure);
-
-  MessagePortBase* port = nullptr;
-  nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
-  if (NS_FAILED(rv)) {
-    return false;
-  }
-
-  if (closure->mTransferredPorts.Contains(port)) {
-    // No duplicates.
-    return false;
-  }
-
-  MessagePortIdentifier identifier;
-  if (!port->CloneAndDisentangle(identifier)) {
-    return false;
-  }
-
-  closure->mClosure.mMessagePortIdentifiers.AppendElement(identifier);
-  closure->mTransferredPorts.AppendElement(port);
-
-  *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
-  *aOwnership = JS::SCTAG_TMO_CUSTOM;
-  *aContent = nullptr;
-  *aExtraData = closure->mClosure.mMessagePortIdentifiers.Length() - 1;
-
-  return true;
-}
-
-void
-FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership,
-             void* aContent, uint64_t aExtraData, void* aClosure)
-{
-  MOZ_ASSERT(aClosure);
-  auto* closure = static_cast<StructuredCloneClosureInternal*>(aClosure);
-
-  if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) {
-    MOZ_ASSERT(!aContent);
-    MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length());
-    MessagePort::ForceClose(closure->mClosure.mMessagePortIdentifiers[(uint32_t)aExtraData]);
-  }
-}
-
-const JSStructuredCloneCallbacks gCallbacks = {
-  Read,
-  Write,
-  Error,
-  ReadTransfer,
-  WriteTransfer,
-  FreeTransfer,
-};
-
-} // namespace
-
-bool
-ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray<uint8_t>& aData,
-                                const StructuredCloneClosure& aClosure,
-                                JS::MutableHandle<JS::Value> aClone,
-                                nsPIDOMWindow* aParentWindow,
-                                nsTArray<nsRefPtr<MessagePort>>& aMessagePorts)
-{
-  auto* data = reinterpret_cast<uint64_t*>(aData.Elements());
-  size_t dataLen = aData.Length();
-  MOZ_ASSERT(!(dataLen % sizeof(*data)));
-
-  StructuredCloneClosureInternalReadOnly internalClosure(aClosure,
-                                                         aParentWindow);
-
-  bool rv = JS_ReadStructuredClone(aCx, data, dataLen,
-                                   JS_STRUCTURED_CLONE_VERSION, aClone,
-                                   &gCallbacks, &internalClosure);
-  if (rv) {
-    aMessagePorts.SwapElements(internalClosure.mMessagePorts);
-  }
-
-  return rv;
-}
-
-bool
-WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle<JS::Value> aSource,
-                                 JS::Handle<JS::Value> aTransferable,
-                                 nsTArray<uint8_t>& aData,
-                                 StructuredCloneClosure& aClosure)
-{
-  StructuredCloneClosureInternal internalClosure(aClosure, nullptr);
-  JSAutoStructuredCloneBuffer buffer(&gCallbacks, &internalClosure);
-
-  if (!buffer.write(aCx, aSource, aTransferable, &gCallbacks,
-                    &internalClosure)) {
-    return false;
-  }
-
-  FallibleTArray<uint8_t> cloneData;
-  if (NS_WARN_IF(!cloneData.SetLength(buffer.nbytes(), mozilla::fallible))) {
-    return false;
-  }
-
-  uint64_t* data;
-  size_t size;
-  buffer.steal(&data, &size);
-
-  memcpy(cloneData.Elements(), data, size);
-  js_free(data);
-
-  MOZ_ASSERT(aData.IsEmpty());
-  aData.SwapElements(cloneData);
-  return true;
-}
-
-void
-FreeStructuredClone(nsTArray<uint8_t>& aData, StructuredCloneClosure& aClosure)
-{
-  auto* data = reinterpret_cast<uint64_t*>(aData.Elements());
-  size_t dataLen = aData.Length();
-  MOZ_ASSERT(!(dataLen % sizeof(*data)));
-
-  JS_ClearStructuredClone(data, dataLen, &gCallbacks, &aClosure, false);
-  aData.Clear();
-}
-
-} // namespace messageport
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/messagechannel/MessagePortUtils.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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_MessagePortUtils_h
-#define mozilla_dom_MessagePortUtils_h
-
-#include "MessagePort.h"
-#include "mozilla/dom/File.h"
-#include "mozilla/dom/PMessagePort.h"
-
-class nsPIDOMWindow;
-
-namespace mozilla {
-namespace dom {
-namespace messageport {
-
-struct
-StructuredCloneClosure
-{
-  nsTArray<nsRefPtr<BlobImpl>> mBlobImpls;
-  nsTArray<MessagePortIdentifier> mMessagePortIdentifiers;
-};
-
-struct
-StructuredCloneData
-{
-  StructuredCloneData() : mData(nullptr), mDataLength(0) {}
-  uint64_t* mData;
-  size_t mDataLength;
-  StructuredCloneClosure mClosure;
-};
-
-bool
-ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray<uint8_t>& aData,
-                                const StructuredCloneClosure& aClosure,
-                                JS::MutableHandle<JS::Value> aClone,
-                                nsPIDOMWindow* aParentWindow,
-                                nsTArray<nsRefPtr<MessagePort>>& aMessagePorts);
-
-bool
-WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle<JS::Value> aSource,
-                                 JS::Handle<JS::Value> aTransferable,
-                                 nsTArray<uint8_t>& aData,
-                                 StructuredCloneClosure& aClosure);
-
-void
-FreeStructuredClone(nsTArray<uint8_t>& aData,
-                    StructuredCloneClosure& aClosure);
-
-} // namespace messageport
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_MessagePortUtils_h
--- a/dom/messagechannel/SharedMessagePortMessage.cpp
+++ b/dom/messagechannel/SharedMessagePortMessage.cpp
@@ -15,23 +15,87 @@
 #include "mozilla/ipc/BackgroundParent.h"
 
 namespace mozilla {
 
 using namespace ipc;
 
 namespace dom {
 
-SharedMessagePortMessage::~SharedMessagePortMessage()
+void
+SharedMessagePortMessage::Read(nsISupports* aParent,
+                               JSContext* aCx,
+                               JS::MutableHandle<JS::Value> aValue,
+                               ErrorResult& aRv)
+{
+  if (mData.IsEmpty()) {
+    return;
+  }
+
+  auto* data = reinterpret_cast<uint64_t*>(mData.Elements());
+  size_t dataLen = mData.Length();
+  MOZ_ASSERT(!(dataLen % sizeof(*data)));
+
+  bool ok = ReadFromBuffer(aParent, aCx, data, dataLen, aValue);
+  Free();
+
+  if (NS_WARN_IF(!ok)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+}
+
+void
+SharedMessagePortMessage::Write(JSContext* aCx,
+                                JS::Handle<JS::Value> aValue,
+                                JS::Handle<JS::Value> aTransfer,
+                                ErrorResult& aRv)
+{
+  if (NS_WARN_IF(!StructuredCloneHelper::Write(aCx, aValue, aTransfer))) {
+    aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+    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());
+  mData.SwapElements(cloneData);
+}
+
+void
+SharedMessagePortMessage::Free()
 {
   if (!mData.IsEmpty()) {
-    FreeStructuredClone(mData, mClosure);
+    auto* data = reinterpret_cast<uint64_t*>(mData.Elements());
+    size_t dataLen = mData.Length();
+    MOZ_ASSERT(!(dataLen % sizeof(*data)));
+
+    FreeBuffer(data, dataLen);
+    mData.Clear();
   }
 }
 
+SharedMessagePortMessage::~SharedMessagePortMessage()
+{
+  Free();
+}
+
 /* static */ void
 SharedMessagePortMessage::FromSharedToMessagesChild(
                       MessagePortChild* aActor,
                       const nsTArray<nsRefPtr<SharedMessagePortMessage>>& aData,
                       nsTArray<MessagePortMessage>& aArray)
 {
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(aArray.IsEmpty());
@@ -39,31 +103,29 @@ SharedMessagePortMessage::FromSharedToMe
 
   PBackgroundChild* backgroundManager = aActor->Manager();
   MOZ_ASSERT(backgroundManager);
 
   for (auto& data : aData) {
     MessagePortMessage* message = aArray.AppendElement();
     message->data().SwapElements(data->mData);
 
-    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls =
-      data->mClosure.mBlobImpls;
+    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->BlobImpls();
     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]);
         message->blobsChild().AppendElement(blobChild);
       }
     }
 
-    message->transferredPorts().AppendElements(
-      data->mClosure.mMessagePortIdentifiers);
+    message->transferredPorts().AppendElements(data->PortIdentifiers());
   }
 }
 
 /* static */ bool
 SharedMessagePortMessage::FromMessagesToSharedChild(
                       nsTArray<MessagePortMessage>& aArray,
                       FallibleTArray<nsRefPtr<SharedMessagePortMessage>>& aData)
 {
@@ -75,27 +137,26 @@ SharedMessagePortMessage::FromMessagesTo
 
   for (auto& message : aArray) {
     nsRefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage();
 
     data->mData.SwapElements(message.data());
 
     const nsTArray<PBlobChild*>& blobs = message.blobsChild();
     if (!blobs.IsEmpty()) {
-      data->mClosure.mBlobImpls.SetCapacity(blobs.Length());
+      data->BlobImpls().SetCapacity(blobs.Length());
 
       for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
         nsRefPtr<BlobImpl> impl =
           static_cast<BlobChild*>(blobs[i])->GetBlobImpl();
-        data->mClosure.mBlobImpls.AppendElement(impl);
+        data->BlobImpls().AppendElement(impl);
       }
     }
 
-    data->mClosure.mMessagePortIdentifiers.AppendElements(
-      message.transferredPorts());
+    data->PortIdentifiers().AppendElements(message.transferredPorts());
 
     if (!aData.AppendElement(data, mozilla::fallible)) {
       return false;
     }
   }
 
   return true;
 }
@@ -114,30 +175,29 @@ SharedMessagePortMessage::FromSharedToMe
 
   PBackgroundParent* backgroundManager = aActor->Manager();
   MOZ_ASSERT(backgroundManager);
 
   for (auto& data : aData) {
     MessagePortMessage* message = aArray.AppendElement(mozilla::fallible);
     message->data().SwapElements(data->mData);
 
-    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->mClosure.mBlobImpls;
+    const nsTArray<nsRefPtr<BlobImpl>>& blobImpls = data->BlobImpls();
     if (!blobImpls.IsEmpty()) {
       message->blobsParent().SetCapacity(blobImpls.Length());
 
       for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) {
         PBlobParent* blobParent =
           BackgroundParent::GetOrCreateActorForBlobImpl(backgroundManager,
                                                         blobImpls[i]);
         message->blobsParent().AppendElement(blobParent);
       }
     }
 
-    message->transferredPorts().AppendElements(
-      data->mClosure.mMessagePortIdentifiers);
+    message->transferredPorts().AppendElements(data->PortIdentifiers());
   }
 
   return true;
 }
 
 /* static */ bool
 SharedMessagePortMessage::FromMessagesToSharedParent(
                       nsTArray<MessagePortMessage>& aArray,
@@ -151,27 +211,26 @@ SharedMessagePortMessage::FromMessagesTo
 
   for (auto& message : aArray) {
     nsRefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage();
 
     data->mData.SwapElements(message.data());
 
     const nsTArray<PBlobParent*>& blobs = message.blobsParent();
     if (!blobs.IsEmpty()) {
-      data->mClosure.mBlobImpls.SetCapacity(blobs.Length());
+      data->BlobImpls().SetCapacity(blobs.Length());
 
       for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
         nsRefPtr<BlobImpl> impl =
           static_cast<BlobParent*>(blobs[i])->GetBlobImpl();
-        data->mClosure.mBlobImpls.AppendElement(impl);
+        data->BlobImpls().AppendElement(impl);
       }
     }
 
-    data->mClosure.mMessagePortIdentifiers.AppendElements(
-      message.transferredPorts());
+    data->PortIdentifiers().AppendElements(message.transferredPorts());
 
     if (!aData.AppendElement(data, mozilla::fallible)) {
       return false;
     }
   }
 
   return true;
 }
--- a/dom/messagechannel/SharedMessagePortMessage.h
+++ b/dom/messagechannel/SharedMessagePortMessage.h
@@ -1,36 +1,48 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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_SharedMessagePortMessage_h
 #define mozilla_dom_SharedMessagePortMessage_h
 
-#include "MessagePortUtils.h"
+#include "mozilla/dom/StructuredCloneHelper.h"
 
 namespace mozilla {
 namespace dom {
 
 class MessagePortChild;
 class MessagePortMessage;
 class MessagePortParent;
 
-class SharedMessagePortMessage final
+class SharedMessagePortMessage final : public StructuredCloneHelper
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(SharedMessagePortMessage)
 
   nsTArray<uint8_t> mData;
-  messageport::StructuredCloneClosure mClosure;
 
   SharedMessagePortMessage()
+    : StructuredCloneHelper(CloningSupported, TransferringSupported)
   {}
 
+  void Read(nsISupports* aParent,
+            JSContext* aCx,
+            JS::MutableHandle<JS::Value> aValue,
+            ErrorResult& aRv);
+
+  void Write(JSContext* aCx,
+             JS::Handle<JS::Value> aValue,
+             JS::Handle<JS::Value> aTransfer,
+             ErrorResult& aRv);
+
+  void Free();
+
   static void
   FromSharedToMessagesChild(
                       MessagePortChild* aActor,
                       const nsTArray<nsRefPtr<SharedMessagePortMessage>>& aData,
                       nsTArray<MessagePortMessage>& aArray);
 
   static bool
   FromMessagesToSharedChild(
--- a/dom/messagechannel/moz.build
+++ b/dom/messagechannel/moz.build
@@ -16,17 +16,16 @@ EXPORTS.mozilla.dom += [
 
 UNIFIED_SOURCES += [
     'MessageChannel.cpp',
     'MessagePort.cpp',
     'MessagePortChild.cpp',
     'MessagePortList.cpp',
     'MessagePortParent.cpp',
     'MessagePortService.cpp',
-    'MessagePortUtils.cpp',
     'SharedMessagePortMessage.cpp',
 ]
 
 IPDL_SOURCES += [
     'PMessagePort.ipdl',
 ]
 
 LOCAL_INCLUDES += [