Bug 1120336 - Fix another hang with blobURL+workers+indexedDB+xhr. r=khuey, a=sledru
authorBen Turner <bent.mozilla@gmail.com>
Tue, 13 Jan 2015 14:15:04 -0800
changeset 232329 eda8c6ec5121b16e33e18d1731675038d30f218f
parent 232328 409521ef3f98acb5b1ad807a99b18b02d032895a
child 232330 3d4bf1b5f0c5e57fdb2380de71071a36c1edaf92
push id36
push userryanvm@gmail.com
push dateWed, 21 Jan 2015 22:10:04 +0000
treeherdermozilla-b2g37_v2_2@4a90da67661e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey, sledru
bugs1120336
milestone37.0a2
Bug 1120336 - Fix another hang with blobURL+workers+indexedDB+xhr. r=khuey, a=sledru
dom/base/MultipartFileImpl.cpp
dom/base/MultipartFileImpl.h
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_blob_worker_xhr_post_multifile.html
dom/indexedDB/test/test_blob_worker_xhr_read_slice.html
dom/ipc/Blob.cpp
dom/workers/URL.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/XMLHttpRequest.cpp
--- a/dom/base/MultipartFileImpl.cpp
+++ b/dom/base/MultipartFileImpl.cpp
@@ -230,16 +230,46 @@ MultipartFileImpl::GetMozFullPathInterna
   if (!blobImpl) {
     FileImplBase::GetMozFullPathInternal(aFilename, aRv);
     return;
   }
 
   blobImpl->GetMozFullPathInternal(aFilename, aRv);
 }
 
+nsresult
+MultipartFileImpl::SetMutable(bool aMutable)
+{
+  nsresult rv;
+
+  // This looks a little sketchy since FileImpl objects are supposed to be
+  // threadsafe. However, we try to enforce that all FileImpl objects must be
+  // set to immutable *before* being passed to another thread, so this should
+  // be safe.
+  if (!aMutable && !mImmutable && !mBlobImpls.IsEmpty()) {
+    for (uint32_t index = 0, count = mBlobImpls.Length();
+         index < count;
+         index++) {
+      rv = mBlobImpls[index]->SetMutable(aMutable);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    }
+  }
+
+  rv = FileImplBase::SetMutable(aMutable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT_IF(!aMutable, mImmutable);
+
+  return NS_OK;
+}
+
 void
 MultipartFileImpl::InitializeChromeFile(File& aBlob,
                                         const ChromeFilePropertyBag& aBag,
                                         ErrorResult& aRv)
 {
   NS_ASSERTION(!mImmutable, "Something went wrong ...");
 
   if (mImmutable) {
--- a/dom/base/MultipartFileImpl.h
+++ b/dom/base/MultipartFileImpl.h
@@ -96,16 +96,19 @@ public:
   virtual const nsTArray<nsRefPtr<FileImpl>>* GetSubBlobImpls() const MOZ_OVERRIDE
   {
     return &mBlobImpls;
   }
 
   virtual void GetMozFullPathInternal(nsAString& aFullPath,
                                       ErrorResult& aRv) MOZ_OVERRIDE;
 
+  virtual nsresult
+  SetMutable(bool aMutable) MOZ_OVERRIDE;
+
   void SetName(const nsAString& aName)
   {
     mName = aName;
   }
 
   void SetFromNsIFile(bool aValue)
   {
     mIsFromNsIFile = aValue;
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -119,18 +119,22 @@ skip-if = (buildapp == 'b2g' && toolkit 
 # This test can only run in the main process.
 skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s
 [test_blob_simple.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_worker_crash.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_blob_worker_xhr_post.html]
 skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
+[test_blob_worker_xhr_post_multifile.html]
+skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
 [test_blob_worker_xhr_read.html]
 skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
+[test_blob_worker_xhr_read_slice.html]
+skip-if = ((buildapp == 'b2g' && toolkit != 'gonk') || (e10s && toolkit == 'windows')) # Bug 931116
 [test_blocked_order.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_bug937006.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_clear.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
 [test_complex_keyPaths.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116
copy from dom/indexedDB/test/test_blob_worker_xhr_post.html
copy to dom/indexedDB/test/test_blob_worker_xhr_post_multifile.html
--- a/dom/indexedDB/test/test_blob_worker_xhr_post.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_post_multifile.html
@@ -5,16 +5,22 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript;version=1.7">
+  /**
+   * Create a composite/multi-file Blob on the worker, then post it as an XHR
+   * payload and ensure that we don't hang/generate an assertion/etc. but
+   * instead generate the expected 404.  This test is basically the same as
+   * test_blob_worker_xhr_post.html except for the composite Blob.
+   */
   function testSteps()
   {
     const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
     const BLOB_TYPE = "text/plain";
     const BLOB_SIZE = BLOB_DATA.join("").length;
 
     info("Setting up");
 
@@ -57,46 +63,40 @@
     event = yield undefined;
 
     blob = event.target.result;
 
     ok(blob instanceof Blob, "Got a blob");
     is(blob.size, BLOB_SIZE, "Correct size");
     is(blob.type, BLOB_TYPE, "Correct type");
 
-    let slice = blob.slice(0, BLOB_DATA[0].length, BLOB_TYPE);
-
-    ok(slice instanceof Blob, "Slice returned a blob");
-    is(slice.size, BLOB_DATA[0].length, "Correct size for slice");
-    is(slice.type, BLOB_TYPE, "Correct type for slice");
-
-    info("Sending slice to a worker");
-
     function workerScript() {
       onmessage = function(event) {
         var blob = event.data;
+        var compositeBlob = new Blob(["preceding string. ", blob],
+                                     { type: "text/plain" });
         var xhr = new XMLHttpRequest();
         // We just want to make sure the error case doesn't fire; it's fine for
         // us to just want a 404.
         xhr.open('POST', 'http://mochi.test:8888/does-not-exist', true);
         xhr.onload = function() {
           postMessage({ status: xhr.status });
         };
         xhr.onerror = function() {
           postMessage({ status: 'error' });
         }
-        xhr.send(blob);
+        xhr.send(compositeBlob);
       }
     }
 
     let workerScriptUrl =
       URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
 
     let xhrWorker = new Worker(workerScriptUrl);
-    xhrWorker.postMessage(slice);
+    xhrWorker.postMessage(blob);
     xhrWorker.onmessage = grabEventAndContinueHandler;
     event = yield undefined;
 
     is(event.data.status, 404, "XHR generated the expected 404");
     xhrWorker.terminate();
 
     URL.revokeObjectURL(workerScriptUrl);
 
copy from dom/indexedDB/test/test_blob_worker_xhr_read.html
copy to dom/indexedDB/test/test_blob_worker_xhr_read_slice.html
--- a/dom/indexedDB/test/test_blob_worker_xhr_read.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_read_slice.html
@@ -7,18 +7,19 @@
   <title>Indexed Database Blob Read From Worker</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript;version=1.7">
   /**
    * Create an IndexedDB-backed Blob, send it to the worker, try and read the
-   * contents of the Blob from the worker using an XHR.  Ideally, we don't
-   * deadlock the main thread.
+   * *SLICED* contents of the Blob from the worker using an XHR.  This is
+   * (as of the time of writing this) basically the same as
+   * test_blob_worker_xhr_read.html but with slicing added.
    */
   function testSteps()
   {
     const BLOB_DATA = ["Green"];
     const BLOB_TYPE = "text/plain";
     const BLOB_SIZE = BLOB_DATA.join("").length;
 
     info("Setting up");
@@ -67,17 +68,18 @@
     is(blob.size, BLOB_SIZE, "Correct size");
     is(blob.type, BLOB_TYPE, "Correct type");
 
     info("Sending blob to a worker");
 
     function workerScript() {
       onmessage = function(event) {
         var blob = event.data;
-        var blobUrl = URL.createObjectURL(blob);
+        var slicedBlob = blob.slice(0, 3, "text/plain");
+        var blobUrl = URL.createObjectURL(slicedBlob);
         var xhr = new XMLHttpRequest();
         xhr.open('GET', blobUrl, true);
         xhr.responseType = 'text';
         xhr.onload = function() {
           postMessage({ data: xhr.response });
           URL.revokeObjectURL(blobUrl);
         };
         xhr.onerror = function() {
@@ -91,17 +93,17 @@
     let workerScriptUrl =
       URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
 
     let xhrWorker = new Worker(workerScriptUrl);
     xhrWorker.postMessage(blob);
     xhrWorker.onmessage = grabEventAndContinueHandler;
     event = yield undefined;
 
-    is(event.data.data, "Green", "XHR returned expected payload.");
+    is(event.data.data, "Gre", "XHR returned expected sliced payload.");
     xhrWorker.terminate();
 
     URL.revokeObjectURL(workerScriptUrl);
 
     finishTest();
     yield undefined;
   }
   </script>
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -1918,17 +1918,21 @@ public:
               ErrorResult& aRv) MOZ_OVERRIDE;
 
   virtual nsresult
   GetInternalStream(nsIInputStream** aStream) MOZ_OVERRIDE;
 
   virtual int64_t
   GetFileId() MOZ_OVERRIDE;
 
-  virtual int64_t GetLastModified(ErrorResult& aRv) MOZ_OVERRIDE;
+  virtual int64_t
+  GetLastModified(ErrorResult& aRv) MOZ_OVERRIDE;
+
+  virtual nsresult
+  SetMutable(bool aMutable) MOZ_OVERRIDE;
 
   virtual BlobChild*
   GetBlobChild() MOZ_OVERRIDE;
 
   virtual BlobParent*
   GetBlobParent() MOZ_OVERRIDE;
 
 protected:
@@ -2000,24 +2004,38 @@ public:
   }
 
   uint64_t
   Start() const
   {
     return mStart;
   }
 
+  void
+  EnsureActorWasCreated()
+  {
+    MOZ_ASSERT_IF(!ActorEventTargetIsOnCurrentThread(),
+                  mActorWasCreated);
+
+    if (!mActorWasCreated) {
+      EnsureActorWasCreatedInternal();
+    }
+  }
+
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual BlobChild*
   GetBlobChild() MOZ_OVERRIDE;
 
 private:
   ~RemoteBlobSliceImpl()
   { }
+
+  void
+  EnsureActorWasCreatedInternal();
 };
 
 /*******************************************************************************
  * BlobParent::RemoteBlobImpl Declaration
  ******************************************************************************/
 
 class BlobParent::RemoteBlobImpl MOZ_FINAL
   : public FileImpl
@@ -2385,16 +2403,36 @@ RemoteBlobImpl::GetLastModified(ErrorRes
 {
   if (IsDateUnknown()) {
     return 0;
   }
 
   return mLastModificationDate;
 }
 
+nsresult
+BlobChild::
+RemoteBlobImpl::SetMutable(bool aMutable)
+{
+  if (!aMutable && IsSlice()) {
+    // Make sure that slices are backed by a real actor now while we are still
+    // on the correct thread.
+    AsSlice()->EnsureActorWasCreated();
+  }
+
+  nsresult rv = FileImplBase::SetMutable(aMutable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT_IF(!aMutable, mImmutable);
+
+  return NS_OK;
+}
+
 BlobChild*
 BlobChild::
 RemoteBlobImpl::GetBlobChild()
 {
   return mActor;
 }
 
 BlobParent*
@@ -2567,31 +2605,22 @@ RemoteBlobSliceImpl::RemoteBlobSliceImpl
     MOZ_ASSERT(parentSize >= aStart + aLength);
   }
 #endif
 
   // Account for the offset of the parent slice, if any.
   mStart = aParent->IsSlice() ? aParent->AsSlice()->mStart + aStart : aStart;
 }
 
-NS_IMPL_ISUPPORTS_INHERITED0(BlobChild::RemoteBlobSliceImpl,
-                             BlobChild::RemoteBlobImpl)
-
-BlobChild*
+void
 BlobChild::
-RemoteBlobSliceImpl::GetBlobChild()
+RemoteBlobSliceImpl::EnsureActorWasCreatedInternal()
 {
-  MOZ_ASSERT_IF(!ActorEventTargetIsOnCurrentThread(),
-                mActorWasCreated);
-
-  if (mActorWasCreated) {
-    return RemoteBlobImpl::GetBlobChild();
-  }
-
   MOZ_ASSERT(ActorEventTargetIsOnCurrentThread());
+  MOZ_ASSERT(!mActorWasCreated);
 
   mActorWasCreated = true;
 
   BlobChild* baseActor = mParent->GetActor();
   MOZ_ASSERT(baseActor);
   MOZ_ASSERT(baseActor->HasManager());
 
   nsID id;
@@ -2606,18 +2635,28 @@ RemoteBlobSliceImpl::GetBlobChild()
                                 mContentType /* contentType */));
 
   if (nsIContentChild* contentManager = baseActor->GetContentManager()) {
     mActor = SendSliceConstructor(contentManager, this, params);
   } else {
     mActor =
       SendSliceConstructor(baseActor->GetBackgroundManager(), this, params);
   }
-
-  return mActor;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(BlobChild::RemoteBlobSliceImpl,
+                             BlobChild::RemoteBlobImpl)
+
+BlobChild*
+BlobChild::
+RemoteBlobSliceImpl::GetBlobChild()
+{
+  EnsureActorWasCreated();
+
+  return RemoteBlobImpl::GetBlobChild();
 }
 
 /*******************************************************************************
  * BlobParent::RemoteBlobImpl
  ******************************************************************************/
 
 BlobParent::
 RemoteBlobImpl::RemoteBlobImpl(BlobParent* aActor, FileImpl* aBlobImpl)
--- a/dom/workers/URL.cpp
+++ b/dom/workers/URL.cpp
@@ -76,16 +76,20 @@ public:
   CreateURLRunnable(WorkerPrivate* aWorkerPrivate, FileImpl* aBlobImpl,
                     const mozilla::dom::objectURLOptions& aOptions,
                     nsString& aURL)
   : WorkerMainThreadRunnable(aWorkerPrivate),
     mBlobImpl(aBlobImpl),
     mURL(aURL)
   {
     MOZ_ASSERT(aBlobImpl);
+
+    DebugOnly<bool> isMutable;
+    MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
+    MOZ_ASSERT(!isMutable);
   }
 
   bool
   MainThreadRun()
   {
     using namespace mozilla::ipc;
 
     AssertIsOnMainThread();
@@ -109,16 +113,20 @@ public:
             MOZ_ASSERT(newBlobImplHolder);
 
             mBlobImpl = newBlobImplHolder;
           }
         }
       }
     }
 
+    DebugOnly<bool> isMutable;
+    MOZ_ASSERT(NS_SUCCEEDED(mBlobImpl->GetMutable(&isMutable)));
+    MOZ_ASSERT(!isMutable);
+
     nsCOMPtr<nsIPrincipal> principal;
     nsIDocument* doc = nullptr;
 
     nsCOMPtr<nsPIDOMWindow> window = mWorkerPrivate->GetWindow();
     if (window) {
       doc = window->GetExtantDoc();
       if (!doc) {
         SetDOMStringToNull(mURL);
@@ -887,18 +895,26 @@ URL::CreateObjectURL(const GlobalObject&
 void
 URL::CreateObjectURL(const GlobalObject& aGlobal, File& aBlob,
                      const mozilla::dom::objectURLOptions& aOptions,
                      nsString& aResult, mozilla::ErrorResult& aRv)
 {
   JSContext* cx = aGlobal.Context();
   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
 
+  nsRefPtr<FileImpl> blobImpl = aBlob.Impl();
+  MOZ_ASSERT(blobImpl);
+
+  aRv = blobImpl->SetMutable(false);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
   nsRefPtr<CreateURLRunnable> runnable =
-    new CreateURLRunnable(workerPrivate, aBlob.Impl(), aOptions, aResult);
+    new CreateURLRunnable(workerPrivate, blobImpl, aOptions, aResult);
 
   if (!runnable->Dispatch(cx)) {
     JS_ReportPendingException(cx);
   }
 }
 
 // static
 void
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/dom/WorkerBinding.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 "MultipartFileImpl.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
 #include "nsHostObjectProtocolHandler.h"
 #include "nsJSEnvironment.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
@@ -302,16 +303,94 @@ 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<FileImpl>
+EnsureBlobForBackgroundManager(FileImpl* aBlobImpl,
+                               PBackgroundChild* aManager = nullptr)
+{
+  MOZ_ASSERT(aBlobImpl);
+
+  if (!aManager) {
+    aManager = BackgroundChild::GetForCurrentThread();
+    MOZ_ASSERT(aManager);
+  }
+
+  nsRefPtr<FileImpl> blobImpl = aBlobImpl;
+
+  const nsTArray<nsRefPtr<FileImpl>>* 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<FileImpl>> newSubBlobImpls;
+  newSubBlobImpls.SetLength(subBlobCount);
+
+  bool newBlobImplNeeded = false;
+
+  for (uint32_t index = 0; index < subBlobCount; index++) {
+    const nsRefPtr<FileImpl>& subBlobImpl = subBlobImpls->ElementAt(index);
+    MOZ_ASSERT(subBlobImpl);
+
+    nsRefPtr<FileImpl>& 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 MultipartFileImpl(newSubBlobImpls, name, contentType);
+    } else {
+      blobImpl = new MultipartFileImpl(newSubBlobImpls, contentType);
+    }
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(blobImpl->SetMutable(false)));
+  }
+
+  return blobImpl.forget();
+}
+
 void
 ReadBlobOrFile(JSContext* aCx,
                JSStructuredCloneReader* aReader,
                bool aIsMainThread,
                JS::MutableHandle<JSObject*> aBlobOrFile)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aReader);
@@ -322,32 +401,18 @@ ReadBlobOrFile(JSContext* aCx,
     FileImpl* rawBlobImpl;
     MOZ_ALWAYS_TRUE(JS_ReadBytes(aReader, &rawBlobImpl, sizeof(rawBlobImpl)));
 
     MOZ_ASSERT(rawBlobImpl);
 
     blobImpl = rawBlobImpl;
   }
 
-  DebugOnly<bool> isMutable;
-  MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable)));
-  MOZ_ASSERT(!isMutable);
-
-  if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(blobImpl)) {
-    PBackgroundChild* backgroundManager =
-      BackgroundChild::GetForCurrentThread();
-    MOZ_ASSERT(backgroundManager);
-
-    // Always make sure we have a blob from an actor we can use on this thread.
-    BlobChild* blobChild = BlobChild::GetOrCreate(backgroundManager, blobImpl);
-    MOZ_ASSERT(blobChild);
-
-    blobImpl = blobChild->GetBlobImpl();
-    MOZ_ASSERT(blobImpl);
-  }
+  blobImpl = EnsureBlobForBackgroundManager(blobImpl);
+  MOZ_ASSERT(blobImpl);
 
   nsCOMPtr<nsISupports> parent;
   if (aIsMainThread) {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
       nsJSUtils::GetStaticScriptGlobal(JS::CurrentGlobalOrNull(aCx));
     parent = do_QueryInterface(scriptGlobal);
@@ -371,34 +436,20 @@ WriteBlobOrFile(JSContext* aCx,
                 JSStructuredCloneWriter* aWriter,
                 FileImpl* aBlobOrFileImpl,
                 nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aWriter);
   MOZ_ASSERT(aBlobOrFileImpl);
 
-  nsRefPtr<FileImpl> newBlobOrFileImpl;
-  if (nsCOMPtr<nsIRemoteBlob> remoteBlob = do_QueryObject(aBlobOrFileImpl)) {
-    PBackgroundChild* backgroundManager =
-      BackgroundChild::GetForCurrentThread();
-    MOZ_ASSERT(backgroundManager);
-
-    // Always make sure we have a blob from an actor we can use on this thread.
-    BlobChild* blobChild =
-      BlobChild::GetOrCreate(backgroundManager, aBlobOrFileImpl);
-    MOZ_ASSERT(blobChild);
-
-    newBlobOrFileImpl = blobChild->GetBlobImpl();
-    MOZ_ASSERT(newBlobOrFileImpl);
-
-    aBlobOrFileImpl = newBlobOrFileImpl;
-  }
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aBlobOrFileImpl->SetMutable(false)));
+  nsRefPtr<FileImpl> blobImpl = EnsureBlobForBackgroundManager(aBlobOrFileImpl);
+  MOZ_ASSERT(blobImpl);
+
+  aBlobOrFileImpl = blobImpl;
 
   if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, DOMWORKER_SCTAG_BLOB, 0)) ||
       NS_WARN_IF(!JS_WriteBytes(aWriter,
                                 &aBlobOrFileImpl,
                                 sizeof(aBlobOrFileImpl)))) {
     return false;
   }
 
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -2173,16 +2173,24 @@ XMLHttpRequest::Send(File& aBody, ErrorR
   }
 
   JS::Rooted<JS::Value> value(cx);
   if (!GetOrCreateDOMReflector(cx, &aBody, &value)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
+  nsRefPtr<FileImpl> blobImpl = aBody.Impl();
+  MOZ_ASSERT(blobImpl);
+
+  aRv = blobImpl->SetMutable(false);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
   const JSStructuredCloneCallbacks* callbacks =
     mWorkerPrivate->IsChromeWorker() ?
     ChromeWorkerStructuredCloneCallbacks(false) :
     WorkerStructuredCloneCallbacks(false);
 
   nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
 
   JSAutoStructuredCloneBuffer buffer;