Bug 1371246: Handle serializing Blobs in StructuredCloneHolder instances. r?billm draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 12 Jun 2017 14:42:49 -0700
changeset 592903 b4a6eda36c97c7521ae1db94a37721f03d63cdee
parent 592866 da431ada55fda32be5335a18df5d925e2a84ebbb
child 632959 98a353333f88ca861374aec214153b151ab74e2f
push id63533
push usermaglione.k@gmail.com
push dateMon, 12 Jun 2017 21:43:25 +0000
reviewersbillm
bugs1371246
milestone56.0a1
Bug 1371246: Handle serializing Blobs in StructuredCloneHolder instances. r?billm MozReview-Commit-ID: 2n15NCnLC48
dom/base/StructuredCloneBlob.cpp
dom/base/StructuredCloneBlob.h
dom/base/StructuredCloneHolder.cpp
toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
--- a/dom/base/StructuredCloneBlob.cpp
+++ b/dom/base/StructuredCloneBlob.cpp
@@ -94,61 +94,76 @@ StructuredCloneBlob::Deserialize(JSConte
   if (!JS_WrapValue(aCx, aResult)) {
     aResult.set(JS::UndefinedValue());
     aRv.NoteJSContextException(aCx);
   }
 }
 
 
 /* static */ JSObject*
-StructuredCloneBlob::ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader)
+StructuredCloneBlob::ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader,
+                                         StructuredCloneHolder* aHolder)
 {
   JS::RootedObject obj(aCx);
   {
     RefPtr<StructuredCloneBlob> holder = new StructuredCloneBlob();
 
-    if (!holder->ReadStructuredCloneInternal(aCx, aReader) ||
+    if (!holder->ReadStructuredCloneInternal(aCx, aReader, aHolder) ||
         !holder->WrapObject(aCx, nullptr, &obj)) {
       return nullptr;
     }
   }
   return obj.get();
 }
 
 bool
-StructuredCloneBlob::ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader)
+StructuredCloneBlob::ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader,
+                                                 StructuredCloneHolder* aHolder)
 {
   uint32_t length;
   uint32_t version;
   if (!JS_ReadUint32Pair(aReader, &length, &version)) {
     return false;
   }
 
+  uint32_t blobOffset;
+  uint32_t blobCount;
+  if (!JS_ReadUint32Pair(aReader, &blobOffset, &blobCount)) {
+    return false;
+  }
+  if (blobCount) {
+    BlobImpls().AppendElements(&aHolder->BlobImpls()[blobOffset], blobCount);
+  }
+
   JSStructuredCloneData data(length, length, 4096);
   if (!JS_ReadBytes(aReader, data.Start(), length)) {
     return false;
   }
 
   mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(mStructuredCloneScope,
                                                     &StructuredCloneHolder::sCallbacks,
                                                     this);
   mBuffer->adopt(Move(data), version, &StructuredCloneHolder::sCallbacks);
 
   return true;
 }
 
 bool
-StructuredCloneBlob::WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter)
+StructuredCloneBlob::WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+                                          StructuredCloneHolder* aHolder)
 {
   auto& data = mBuffer->data();
   if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_STRUCTURED_CLONE_HOLDER, 0) ||
-      !JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION)) {
+      !JS_WriteUint32Pair(aWriter, data.Size(), JS_STRUCTURED_CLONE_VERSION) ||
+      !JS_WriteUint32Pair(aWriter, aHolder->BlobImpls().Length(), BlobImpls().Length())) {
     return false;
   }
 
+  aHolder->BlobImpls().AppendElements(BlobImpls());
+
   auto iter = data.Iter();
   while (!iter.Done()) {
     if (!JS_WriteBytes(aWriter, iter.Data(), iter.RemainingInSegment())) {
       return false;
     }
     iter.Advance(data, iter.RemainingInSegment());
   }
 
--- a/dom/base/StructuredCloneBlob.h
+++ b/dom/base/StructuredCloneBlob.h
@@ -21,18 +21,20 @@ namespace dom {
 class StructuredCloneBlob : public StructuredCloneHolder
                           , public RefCounted<StructuredCloneBlob>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(StructuredCloneBlob)
 
   explicit StructuredCloneBlob();
 
-  static JSObject* ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
-  bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter);
+  static JSObject* ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader,
+                                       StructuredCloneHolder* aHolder);
+  bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+                            StructuredCloneHolder* aHolder);
 
   static already_AddRefed<StructuredCloneBlob>
   Constructor(GlobalObject& aGlobal, JS::HandleValue aValue, JS::HandleObject aTargetGlobal, ErrorResult& aRv);
 
   void Deserialize(JSContext* aCx, JS::HandleObject aTargetScope,
                    JS::MutableHandleValue aResult, ErrorResult& aRv);
 
   nsISupports* GetParentObject() const { return nullptr; }
@@ -42,16 +44,17 @@ public:
 
 protected:
   template <typename T, detail::RefCountAtomicity>
   friend class detail::RefCounted;
 
   ~StructuredCloneBlob() = default;
 
 private:
-  bool ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader);
+  bool ReadStructuredCloneInternal(JSContext* aCx, JSStructuredCloneReader* aReader,
+                                   StructuredCloneHolder* aHolder);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_StructuredCloneBlob_h
 
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -353,20 +353,16 @@ StructuredCloneHolder::ReadFromBuffer(ns
 StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
                                                     JSStructuredCloneReader* aReader,
                                                     uint32_t aTag)
 {
   if (aTag == SCTAG_DOM_IMAGEDATA) {
     return ReadStructuredCloneImageData(aCx, aReader);
   }
 
-  if (aTag == SCTAG_DOM_STRUCTURED_CLONE_HOLDER) {
-    return StructuredCloneBlob::ReadStructuredClone(aCx, aReader);
-  }
-
   if (aTag == SCTAG_DOM_WEBCRYPTO_KEY || aTag == SCTAG_DOM_URLSEARCHPARAMS) {
     nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
     if (!global) {
       return nullptr;
     }
 
     // Prevent the return value from being trashed by a GC during ~nsRefPtr.
     JS::Rooted<JSObject*> result(aCx);
@@ -453,24 +449,16 @@ StructuredCloneHolder::WriteFullySeriali
   // See if this is a ImageData object.
   {
     ImageData* imageData = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, aObj, imageData))) {
       return WriteStructuredCloneImageData(aCx, aWriter, imageData);
     }
   }
 
-  // See if this is a StructuredCloneBlob object.
-  {
-    StructuredCloneBlob* holder = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, aObj, holder))) {
-      return holder->WriteStructuredClone(aCx, aWriter);
-    }
-  }
-
   // Handle URLSearchParams cloning
   {
     URLSearchParams* usp = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, aObj, usp))) {
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) &&
              usp->WriteStructuredClone(aWriter);
     }
   }
@@ -994,16 +982,20 @@ StructuredCloneHolder::CustomReadHandler
     // 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,
                                             parent, GetSurfaces(), aIndex);
   }
 
+  if (aTag == SCTAG_DOM_STRUCTURED_CLONE_HOLDER) {
+    return StructuredCloneBlob::ReadStructuredClone(aCx, aReader, this);
+  }
+
   if (aTag == SCTAG_DOM_WASM) {
     return ReadWasmModule(aCx, aIndex, this);
   }
 
   if (aTag == SCTAG_DOM_INPUTSTREAM) {
     return ReadInputStream(aCx, aIndex, this);
   }
 
@@ -1057,16 +1049,24 @@ StructuredCloneHolder::CustomWriteHandle
     ImageBitmap* imageBitmap = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
       return ImageBitmap::WriteStructuredClone(aWriter,
                                                GetSurfaces(),
                                                imageBitmap);
     }
   }
 
+  // See if this is a StructuredCloneBlob object.
+  {
+    StructuredCloneBlob* holder = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, aObj, holder))) {
+      return holder->WriteStructuredClone(aCx, aWriter, this);
+    }
+  }
+
   // See if this is a WasmModule.
   if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
        mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) &&
       JS::IsWasmModuleObject(aObj)) {
     RefPtr<JS::WasmModule> module = JS::GetWasmModule(aObj);
     MOZ_ASSERT(module);
 
     return WriteWasmModule(aWriter, module, this);
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
@@ -72,8 +72,40 @@ add_task(async function tabsSendMessageR
       "extensionpage.html": `<!DOCTYPE html><meta charset="utf-8"><script src="senderScript.js"></script>`,
     },
   });
 
   await extension.startup();
   await extension.awaitFinish("sendMessage");
   await extension.unload();
 });
+
+add_task(async function tabsSendMessageBlob() {
+  function background() {
+    browser.runtime.onMessage.addListener(msg => {
+      browser.test.assertTrue(msg.blob instanceof Blob, "Message is a blob");
+      return Promise.resolve(msg);
+    });
+
+    let childFrame = document.createElement("iframe");
+    childFrame.src = "extensionpage.html";
+    document.body.appendChild(childFrame);
+  }
+
+  function senderScript() {
+    browser.runtime.sendMessage({blob: new Blob(["hello"])}).then(response => {
+      browser.test.assertTrue(response.blob instanceof Blob, "Response is a blob");
+      browser.test.notifyPass("sendBlob");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    files: {
+      "senderScript.js": senderScript,
+      "extensionpage.html": `<!DOCTYPE html><meta charset="utf-8"><script src="senderScript.js"></script>`,
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("sendBlob");
+  await extension.unload();
+});