Bug 771288 - Multiprocess FileHandle support (FileHandle on PBackground); r=baku
authorJan Varga <jan.varga@gmail.com>
Wed, 09 Sep 2015 13:15:05 +0200
changeset 295863 ac51f970e85df6b96344c11f3e2f056fb30dfdb9
parent 295862 3ded49bb78d735b442f7965b215de9532f80e787
child 295864 a6a759b2431a1af78df2bc8145e0c2c07791b22a
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs771288
milestone43.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 771288 - Multiprocess FileHandle support (FileHandle on PBackground); r=baku
dom/base/File.h
dom/filehandle/ActorsChild.cpp
dom/filehandle/ActorsChild.h
dom/filehandle/ActorsParent.cpp
dom/filehandle/ActorsParent.h
dom/filehandle/AsyncHelper.cpp
dom/filehandle/AsyncHelper.h
dom/filehandle/FileHandle.cpp
dom/filehandle/FileHandle.h
dom/filehandle/FileHandleBase.cpp
dom/filehandle/FileHandleBase.h
dom/filehandle/FileHandleCommon.cpp
dom/filehandle/FileHandleCommon.h
dom/filehandle/FileHandleStorage.h
dom/filehandle/FileHelper.cpp
dom/filehandle/FileHelper.h
dom/filehandle/FileRequest.cpp
dom/filehandle/FileRequest.h
dom/filehandle/FileRequestBase.h
dom/filehandle/FileService.cpp
dom/filehandle/FileService.h
dom/filehandle/FileStreamWrappers.cpp
dom/filehandle/FileStreamWrappers.h
dom/filehandle/MemoryStreams.cpp
dom/filehandle/MemoryStreams.h
dom/filehandle/MetadataHelper.cpp
dom/filehandle/MetadataHelper.h
dom/filehandle/MutableFile.cpp
dom/filehandle/MutableFile.h
dom/filehandle/MutableFileBase.cpp
dom/filehandle/MutableFileBase.h
dom/filehandle/PBackgroundFileHandle.ipdl
dom/filehandle/PBackgroundFileRequest.ipdl
dom/filehandle/PBackgroundMutableFile.ipdl
dom/filehandle/SerializationHelpers.h
dom/filehandle/moz.build
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/ActorsChild.h
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/ActorsParent.h
dom/indexedDB/FileInfo.cpp
dom/indexedDB/FileSnapshot.cpp
dom/indexedDB/FileSnapshot.h
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBDatabase.h
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBFactory.h
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/IDBFileHandle.h
dom/indexedDB/IDBFileRequest.cpp
dom/indexedDB/IDBFileRequest.h
dom/indexedDB/IDBMutableFile.cpp
dom/indexedDB/IDBMutableFile.h
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/IDBRequest.cpp
dom/indexedDB/IDBRequest.h
dom/indexedDB/IndexedDatabase.h
dom/indexedDB/IndexedDatabaseInlines.h
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
dom/indexedDB/PBackgroundIDBCursor.ipdl
dom/indexedDB/PBackgroundIDBDatabase.ipdl
dom/indexedDB/PBackgroundIDBDatabaseRequest.ipdl
dom/indexedDB/PBackgroundIDBFactory.ipdl
dom/indexedDB/PBackgroundIDBRequest.ipdl
dom/indexedDB/PBackgroundIDBSharedTypes.ipdlh
dom/indexedDB/PBackgroundIDBTransaction.ipdl
dom/indexedDB/PBackgroundIDBVersionChangeTransaction.ipdl
dom/indexedDB/moz.build
dom/indexedDB/test/file.js
dom/indexedDB/test/helpers.js
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_filehandle_append_read_data.html
dom/indexedDB/test/test_filehandle_compat.html
dom/indexedDB/test/test_filehandle_disabled_pref.html
dom/indexedDB/test/test_get_filehandle.html
dom/indexedDB/test/unit/mutableFileUpgrade_profile.zip
dom/indexedDB/test/unit/test_filehandle_append_read_data.js
dom/indexedDB/test/unit/test_mutableFileUpgrade.js
dom/indexedDB/test/unit/xpcshell-head-parent-process.js
dom/indexedDB/test/unit/xpcshell-parent-process.ini
dom/ipc/Blob.cpp
dom/ipc/DOMTypes.ipdlh
dom/webidl/IDBFileHandle.webidl
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
modules/libpref/init/all.js
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -531,18 +531,16 @@ public:
 
   virtual nsresult SetMutable(bool aMutable) override;
 
   virtual void
   SetLazyData(const nsAString& aName, const nsAString& aContentType,
               uint64_t aLength, int64_t aLastModifiedDate,
               BlobDirState aDirState) override
   {
-    NS_ASSERTION(aLength, "must have length");
-
     mName = aName;
     mContentType = aContentType;
     mLength = aLength;
     mLastModificationDate = aLastModifiedDate;
     mIsFile = !aName.IsVoid();
   }
 
   virtual bool IsMemoryFile() const override
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/ActorsChild.cpp
@@ -0,0 +1,743 @@
+/* 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 "ActorsChild.h"
+
+#include "BackgroundChildImpl.h"
+#include "FileHandleBase.h"
+#include "FileRequestBase.h"
+#include "js/Date.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "MutableFileBase.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace dom {
+
+/*******************************************************************************
+ * Helpers
+ ******************************************************************************/
+
+namespace {
+
+class MOZ_STACK_CLASS AutoSetCurrentFileHandle final
+{
+  typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl;
+
+  FileHandleBase* const mFileHandle;
+  FileHandleBase* mPreviousFileHandle;
+  FileHandleBase** mThreadLocalSlot;
+
+public:
+  explicit AutoSetCurrentFileHandle(FileHandleBase* aFileHandle)
+    : mFileHandle(aFileHandle)
+    , mPreviousFileHandle(nullptr)
+    , mThreadLocalSlot(nullptr)
+  {
+    if (aFileHandle) {
+      BackgroundChildImpl::ThreadLocal* threadLocal =
+        BackgroundChildImpl::GetThreadLocalForCurrentThread();
+      MOZ_ASSERT(threadLocal);
+
+      // Hang onto this location for resetting later.
+      mThreadLocalSlot = &threadLocal->mCurrentFileHandle;
+
+      // Save the current value.
+      mPreviousFileHandle = *mThreadLocalSlot;
+
+      // Set the new value.
+      *mThreadLocalSlot = aFileHandle;
+    }
+  }
+
+  ~AutoSetCurrentFileHandle()
+  {
+    MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle);
+    MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle);
+
+    if (mThreadLocalSlot) {
+      // Reset old value.
+      *mThreadLocalSlot = mPreviousFileHandle;
+    }
+  }
+
+  FileHandleBase*
+  FileHandle() const
+  {
+    return mFileHandle;
+  }
+};
+
+class MOZ_STACK_CLASS ResultHelper final
+  : public FileRequestBase::ResultCallback
+{
+  FileRequestBase* mFileRequest;
+  AutoSetCurrentFileHandle mAutoFileHandle;
+
+  union
+  {
+    File* mFile;
+    const nsCString* mString;
+    const FileRequestMetadata* mMetadata;
+    const JS::Handle<JS::Value>* mJSValHandle;
+  } mResult;
+
+  enum
+  {
+    ResultTypeFile,
+    ResultTypeString,
+    ResultTypeMetadata,
+    ResultTypeJSValHandle,
+  } mResultType;
+
+public:
+  ResultHelper(FileRequestBase* aFileRequest,
+               FileHandleBase* aFileHandle,
+               File* aResult)
+    : mFileRequest(aFileRequest)
+    , mAutoFileHandle(aFileHandle)
+    , mResultType(ResultTypeFile)
+  {
+    MOZ_ASSERT(aFileRequest);
+    MOZ_ASSERT(aFileHandle);
+    MOZ_ASSERT(aResult);
+
+    mResult.mFile = aResult;
+  }
+
+  ResultHelper(FileRequestBase* aFileRequest,
+               FileHandleBase* aFileHandle,
+               const nsCString* aResult)
+    : mFileRequest(aFileRequest)
+    , mAutoFileHandle(aFileHandle)
+    , mResultType(ResultTypeString)
+  {
+    MOZ_ASSERT(aFileRequest);
+    MOZ_ASSERT(aFileHandle);
+    MOZ_ASSERT(aResult);
+
+    mResult.mString = aResult;
+  }
+
+  ResultHelper(FileRequestBase* aFileRequest,
+               FileHandleBase* aFileHandle,
+               const FileRequestMetadata* aResult)
+    : mFileRequest(aFileRequest)
+    , mAutoFileHandle(aFileHandle)
+    , mResultType(ResultTypeMetadata)
+  {
+    MOZ_ASSERT(aFileRequest);
+    MOZ_ASSERT(aFileHandle);
+    MOZ_ASSERT(aResult);
+
+    mResult.mMetadata = aResult;
+  }
+
+
+  ResultHelper(FileRequestBase* aFileRequest,
+               FileHandleBase* aFileHandle,
+               const JS::Handle<JS::Value>* aResult)
+    : mFileRequest(aFileRequest)
+    , mAutoFileHandle(aFileHandle)
+    , mResultType(ResultTypeJSValHandle)
+  {
+    MOZ_ASSERT(aFileRequest);
+    MOZ_ASSERT(aFileHandle);
+    MOZ_ASSERT(aResult);
+
+    mResult.mJSValHandle = aResult;
+  }
+
+  FileRequestBase*
+  FileRequest() const
+  {
+    return mFileRequest;
+  }
+
+  FileHandleBase*
+  FileHandle() const
+  {
+    return mAutoFileHandle.FileHandle();
+  }
+
+  virtual nsresult
+  GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) override
+  {
+    MOZ_ASSERT(aCx);
+    MOZ_ASSERT(mFileRequest);
+
+    switch (mResultType) {
+      case ResultTypeFile:
+        return GetResult(aCx, mResult.mFile, aResult);
+
+      case ResultTypeString:
+        return GetResult(aCx, mResult.mString, aResult);
+
+      case ResultTypeMetadata:
+        return GetResult(aCx, mResult.mMetadata, aResult);
+
+      case ResultTypeJSValHandle:
+        aResult.set(*mResult.mJSValHandle);
+        return NS_OK;
+
+      default:
+        MOZ_CRASH("Unknown result type!");
+    }
+
+    MOZ_CRASH("Should never get here!");
+  }
+
+private:
+  nsresult
+  GetResult(JSContext* aCx,
+            File* aFile,
+            JS::MutableHandle<JS::Value> aResult)
+  {
+    bool ok = GetOrCreateDOMReflector(aCx, aFile, aResult);
+    if (NS_WARN_IF(!ok)) {
+      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+
+    return NS_OK;
+  }
+
+  nsresult
+  GetResult(JSContext* aCx,
+            const nsCString* aString,
+            JS::MutableHandle<JS::Value> aResult)
+  {
+    const nsCString& data = *aString;
+
+    nsresult rv;
+
+    if (!mFileRequest->HasEncoding()) {
+      JS::Rooted<JSObject*> arrayBuffer(aCx);
+      rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+      }
+
+      aResult.setObject(*arrayBuffer);
+      return NS_OK;
+    }
+
+    nsAutoCString encoding;
+    // The BOM sniffing is baked into the "decode" part of the Encoding
+    // Standard, which the File API references.
+    if (!nsContentUtils::CheckForBOM(
+          reinterpret_cast<const unsigned char *>(data.get()),
+          data.Length(),
+          encoding)) {
+      // BOM sniffing failed. Try the API argument.
+      if (!EncodingUtils::FindEncodingForLabel(mFileRequest->GetEncoding(),
+                                               encoding)) {
+        // API argument failed. Since we are dealing with a file system file,
+        // we don't have a meaningful type attribute for the blob available,
+        // so proceeding to the next step, which is defaulting to UTF-8.
+        encoding.AssignLiteral("UTF-8");
+      }
+    }
+
+    nsString tmpString;
+    rv = nsContentUtils::ConvertStringFromEncoding(encoding, data, tmpString);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+
+    if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) {
+      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+
+    return NS_OK;
+  }
+
+  nsresult
+  GetResult(JSContext* aCx,
+            const FileRequestMetadata* aMetadata,
+            JS::MutableHandle<JS::Value> aResult)
+  {
+    JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+    if (NS_WARN_IF(!obj)) {
+      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+
+    const FileRequestSize& size = aMetadata->size();
+    if (size.type() != FileRequestSize::Tvoid_t) {
+      MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t);
+
+      JS::Rooted<JS::Value> number(aCx, JS_NumberValue(size.get_uint64_t()));
+
+      if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) {
+        return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+      }
+    }
+
+    const FileRequestLastModified& lastModified = aMetadata->lastModified();
+    if (lastModified.type() != FileRequestLastModified::Tvoid_t) {
+      MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t);
+
+      JS::Rooted<JSObject*> date(aCx,
+        JS::NewDateObject(aCx, JS::TimeClip(lastModified.get_int64_t())));
+      if (NS_WARN_IF(!date)) {
+        return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+      }
+
+      if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) {
+        return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+      }
+    }
+
+    aResult.setObject(*obj);
+    return NS_OK;
+  }
+};
+
+already_AddRefed<File>
+ConvertActorToFile(FileHandleBase* aFileHandle,
+                   const FileRequestGetFileResponse& aResponse)
+{
+  auto* actor = static_cast<BlobChild*>(aResponse.fileChild());
+
+  MutableFileBase* mutableFile = aFileHandle->MutableFile();
+  MOZ_ASSERT(mutableFile);
+
+  const FileRequestMetadata& metadata = aResponse.metadata();
+
+  const FileRequestSize& size = metadata.size();
+  MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t);
+
+  const FileRequestLastModified& lastModified = metadata.lastModified();
+  MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t);
+
+  actor->SetMysteryBlobInfo(mutableFile->Name(),
+                            mutableFile->Type(),
+                            size.get_uint64_t(),
+                            lastModified.get_int64_t(),
+                            BlobDirState::eUnknownIfDir);
+
+  nsRefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
+  MOZ_ASSERT(blobImpl);
+
+  nsRefPtr<File> file = mutableFile->CreateFileFor(blobImpl, aFileHandle);
+  return file.forget();
+}
+
+void
+HandleSuccess(ResultHelper* aResultHelper)
+{
+  MOZ_ASSERT(aResultHelper);
+
+  nsRefPtr<FileRequestBase> fileRequest = aResultHelper->FileRequest();
+  MOZ_ASSERT(fileRequest);
+  fileRequest->AssertIsOnOwningThread();
+
+  nsRefPtr<FileHandleBase> fileHandle = aResultHelper->FileHandle();
+  MOZ_ASSERT(fileHandle);
+
+  if (fileHandle->IsAborted()) {
+    fileRequest->SetError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR);
+    return;
+  }
+
+  MOZ_ASSERT(fileHandle->IsOpen());
+
+  fileRequest->SetResultCallback(aResultHelper);
+
+  MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted());
+}
+
+void
+HandleError(FileRequestBase* aFileRequest,
+            nsresult aErrorCode,
+            FileHandleBase* aFileHandle)
+{
+  MOZ_ASSERT(aFileRequest);
+  aFileRequest->AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(aErrorCode));
+  MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE);
+  MOZ_ASSERT(aFileHandle);
+
+  nsRefPtr<FileRequestBase> fileRequest = aFileRequest;
+  nsRefPtr<FileHandleBase> fileHandle = aFileHandle;
+
+  AutoSetCurrentFileHandle ascfh(aFileHandle);
+
+  fileRequest->SetError(aErrorCode);
+
+  MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted());
+}
+
+} // anonymous namespace
+
+/*******************************************************************************
+ * BackgroundMutableFileChildBase
+ ******************************************************************************/
+
+BackgroundMutableFileChildBase::BackgroundMutableFileChildBase(
+                                             DEBUGONLY(PRThread* aOwningThread))
+  : ThreadObject(DEBUGONLY(aOwningThread))
+  , mMutableFile(nullptr)
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(BackgroundMutableFileChildBase);
+}
+
+BackgroundMutableFileChildBase::~BackgroundMutableFileChildBase()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(BackgroundMutableFileChildBase);
+}
+
+void
+BackgroundMutableFileChildBase::EnsureDOMObject()
+{
+  AssertIsOnOwningThread();
+
+  if (mTemporaryStrongMutableFile) {
+    return;
+  }
+
+  mTemporaryStrongMutableFile = CreateMutableFile();
+
+  MOZ_ASSERT(mTemporaryStrongMutableFile);
+  mTemporaryStrongMutableFile->AssertIsOnOwningThread();
+
+  mMutableFile = mTemporaryStrongMutableFile;
+}
+
+void
+BackgroundMutableFileChildBase::ReleaseDOMObject()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mTemporaryStrongMutableFile);
+  mTemporaryStrongMutableFile->AssertIsOnOwningThread();
+  MOZ_ASSERT(mMutableFile == mTemporaryStrongMutableFile);
+
+  mTemporaryStrongMutableFile = nullptr;
+}
+
+void
+BackgroundMutableFileChildBase::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mTemporaryStrongMutableFile);
+
+  if (mMutableFile) {
+    mMutableFile->ClearBackgroundActor();
+    mMutableFile = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundMutableFileChild::SendDeleteMe());
+  }
+}
+
+void
+BackgroundMutableFileChildBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mMutableFile) {
+    mMutableFile->ClearBackgroundActor();
+    DEBUGONLY(mMutableFile = nullptr;)
+  }
+}
+
+PBackgroundFileHandleChild*
+BackgroundMutableFileChildBase::AllocPBackgroundFileHandleChild(
+                                                          const FileMode& aMode)
+{
+  MOZ_CRASH("PBackgroundFileHandleChild actors should be manually "
+            "constructed!");
+}
+
+bool
+BackgroundMutableFileChildBase::DeallocPBackgroundFileHandleChild(
+                                             PBackgroundFileHandleChild* aActor)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<BackgroundFileHandleChild*>(aActor);
+  return true;
+}
+
+/*******************************************************************************
+ * BackgroundFileHandleChild
+ ******************************************************************************/
+
+BackgroundFileHandleChild::BackgroundFileHandleChild(
+                                             DEBUGONLY(PRThread* aOwningThread,)
+                                             FileHandleBase* aFileHandle)
+  : ThreadObject(DEBUGONLY(aOwningThread))
+  , mTemporaryStrongFileHandle(aFileHandle)
+  , mFileHandle(aFileHandle)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+  aFileHandle->AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(BackgroundFileHandleChild);
+}
+
+BackgroundFileHandleChild::~BackgroundFileHandleChild()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_COUNT_DTOR(BackgroundFileHandleChild);
+}
+
+void
+BackgroundFileHandleChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mFileHandle) {
+    NoteActorDestroyed();
+
+    MOZ_ALWAYS_TRUE(PBackgroundFileHandleChild::SendDeleteMe());
+  }
+}
+
+void
+BackgroundFileHandleChild::NoteActorDestroyed()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT_IF(mTemporaryStrongFileHandle, mFileHandle);
+
+  if (mFileHandle) {
+    mFileHandle->ClearBackgroundActor();
+
+    // Normally this would be DEBUG-only but NoteActorDestroyed is also called
+    // from SendDeleteMeInternal. In that case we're going to receive an actual
+    // ActorDestroy call later and we don't want to touch a dead object.
+    mTemporaryStrongFileHandle = nullptr;
+    mFileHandle = nullptr;
+  }
+}
+
+void
+BackgroundFileHandleChild::NoteComplete()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT_IF(mFileHandle, mTemporaryStrongFileHandle);
+
+  mTemporaryStrongFileHandle = nullptr;
+}
+
+void
+BackgroundFileHandleChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  NoteActorDestroyed();
+}
+
+bool
+BackgroundFileHandleChild::RecvComplete(const bool& aAborted)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileHandle);
+
+  mFileHandle->HandleCompleteOrAbort(aAborted);
+
+  NoteComplete();
+  return true;
+}
+
+PBackgroundFileRequestChild*
+BackgroundFileHandleChild::AllocPBackgroundFileRequestChild(
+                                               const FileRequestParams& aParams)
+{
+  MOZ_CRASH("PBackgroundFileRequestChild actors should be manually "
+            "constructed!");
+}
+
+bool
+BackgroundFileHandleChild::DeallocPBackgroundFileRequestChild(
+                                            PBackgroundFileRequestChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<BackgroundFileRequestChild*>(aActor);
+  return true;
+}
+
+/*******************************************************************************
+ * BackgroundFileRequestChild
+ ******************************************************************************/
+
+BackgroundFileRequestChild::BackgroundFileRequestChild(
+                                             DEBUGONLY(PRThread* aOwningThread,)
+                                             FileRequestBase* aFileRequest)
+  : ThreadObject(DEBUGONLY(aOwningThread))
+  , mFileRequest(aFileRequest)
+  , mFileHandle(aFileRequest->FileHandle())
+  , mActorDestroyed(false)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileRequest);
+  aFileRequest->AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileHandle);
+  mFileHandle->AssertIsOnOwningThread();
+
+  MOZ_COUNT_CTOR(BackgroundFileRequestChild);
+}
+
+BackgroundFileRequestChild::~BackgroundFileRequestChild()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mFileHandle);
+
+  MOZ_COUNT_DTOR(BackgroundFileRequestChild);
+}
+
+void
+BackgroundFileRequestChild::HandleResponse(nsresult aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(aResponse));
+  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_FILEHANDLE);
+  MOZ_ASSERT(mFileHandle);
+
+  HandleError(mFileRequest, aResponse, mFileHandle);
+}
+
+void
+BackgroundFileRequestChild::HandleResponse(
+                                    const FileRequestGetFileResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  nsRefPtr<File> file = ConvertActorToFile(mFileHandle, aResponse);
+
+  ResultHelper helper(mFileRequest, mFileHandle, file);
+
+  HandleSuccess(&helper);
+}
+
+void
+BackgroundFileRequestChild::HandleResponse(const nsCString& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  ResultHelper helper(mFileRequest, mFileHandle, &aResponse);
+
+  HandleSuccess(&helper);
+}
+
+void
+BackgroundFileRequestChild::HandleResponse(const FileRequestMetadata& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  ResultHelper helper(mFileRequest, mFileHandle, &aResponse);
+
+  HandleSuccess(&helper);
+}
+
+void
+BackgroundFileRequestChild::HandleResponse(JS::Handle<JS::Value> aResponse)
+{
+  AssertIsOnOwningThread();
+
+  ResultHelper helper(mFileRequest, mFileHandle, &aResponse);
+
+  HandleSuccess(&helper);
+}
+
+void
+BackgroundFileRequestChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  if (mFileHandle) {
+    mFileHandle->AssertIsOnOwningThread();
+
+    mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */
+                                   aWhy == Deletion);
+
+    DEBUGONLY(mFileHandle = nullptr;)
+  }
+}
+
+bool
+BackgroundFileRequestChild::Recv__delete__(const FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileRequest);
+  MOZ_ASSERT(mFileHandle);
+
+  if (mFileHandle->IsAborted()) {
+    // Always handle an "error" with ABORT_ERR if the file handle was aborted,
+    // even if the request succeeded or failed with another error.
+    HandleResponse(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR);
+  } else {
+    switch (aResponse.type()) {
+      case FileRequestResponse::Tnsresult:
+        HandleResponse(aResponse.get_nsresult());
+        break;
+
+      case FileRequestResponse::TFileRequestGetFileResponse:
+        HandleResponse(aResponse.get_FileRequestGetFileResponse());
+        break;
+
+      case FileRequestResponse::TFileRequestReadResponse:
+        HandleResponse(aResponse.get_FileRequestReadResponse().data());
+        break;
+
+      case FileRequestResponse::TFileRequestWriteResponse:
+        HandleResponse(JS::UndefinedHandleValue);
+        break;
+
+      case FileRequestResponse::TFileRequestTruncateResponse:
+        HandleResponse(JS::UndefinedHandleValue);
+        break;
+
+      case FileRequestResponse::TFileRequestFlushResponse:
+        HandleResponse(JS::UndefinedHandleValue);
+        break;
+
+      case FileRequestResponse::TFileRequestGetMetadataResponse:
+        HandleResponse(aResponse.get_FileRequestGetMetadataResponse()
+                                .metadata());
+        break;
+
+      default:
+        MOZ_CRASH("Unknown response type!");
+    }
+  }
+
+  mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ true);
+
+  // Null this out so that we don't try to call OnRequestFinished() again in
+  // ActorDestroy.
+  mFileHandle = nullptr;
+
+  return true;
+}
+
+bool
+BackgroundFileRequestChild::RecvProgress(const uint64_t& aProgress,
+                                         const uint64_t& aProgressMax)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileRequest);
+
+  mFileRequest->OnProgress(aProgress, aProgressMax);
+
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/ActorsChild.h
@@ -0,0 +1,175 @@
+/* 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_filehandle_ActorsChild_h
+#define mozilla_dom_filehandle_ActorsChild_h
+
+#include "js/RootingAPI.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/FileHandleCommon.h"
+#include "mozilla/dom/PBackgroundFileHandleChild.h"
+#include "mozilla/dom/PBackgroundFileRequestChild.h"
+#include "mozilla/dom/PBackgroundMutableFileChild.h"
+#include "nsAutoPtr.h"
+
+class nsCString;
+
+namespace mozilla {
+namespace dom {
+
+class FileHandleBase;
+class FileRequestBase;
+class MutableFileBase;
+
+class BackgroundMutableFileChildBase
+  : public ThreadObject
+  , public PBackgroundMutableFileChild
+{
+protected:
+  friend class MutableFileBase;
+
+  nsRefPtr<MutableFileBase> mTemporaryStrongMutableFile;
+  MutableFileBase* mMutableFile;
+
+public:
+  void
+  EnsureDOMObject();
+
+  MutableFileBase*
+  GetDOMObject() const
+  {
+    AssertIsOnOwningThread();
+    return mMutableFile;
+  }
+
+  void
+  ReleaseDOMObject();
+
+protected:
+  BackgroundMutableFileChildBase(DEBUGONLY(PRThread* aOwningThread));
+
+  ~BackgroundMutableFileChildBase();
+
+  void
+  SendDeleteMeInternal();
+
+  virtual already_AddRefed<MutableFileBase>
+  CreateMutableFile() = 0;
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual PBackgroundFileHandleChild*
+  AllocPBackgroundFileHandleChild(const FileMode& aMode) override;
+
+  virtual bool
+  DeallocPBackgroundFileHandleChild(PBackgroundFileHandleChild* aActor)
+                                    override;
+
+  bool
+  SendDeleteMe() = delete;
+};
+
+class BackgroundFileHandleChild
+  : public ThreadObject
+  , public PBackgroundFileHandleChild
+{
+  friend class BackgroundMutableFileChildBase;
+  friend class MutableFileBase;
+
+  // mTemporaryStrongFileHandle is strong and is only valid until the end of
+  // NoteComplete() member function or until the NoteActorDestroyed() member
+  // function is called.
+  nsRefPtr<FileHandleBase> mTemporaryStrongFileHandle;
+
+  // mFileHandle is weak and is valid until the NoteActorDestroyed() member
+  // function is called.
+  FileHandleBase* mFileHandle;
+
+public:
+  BackgroundFileHandleChild(DEBUGONLY(PRThread* aOwningThread,)
+                            FileHandleBase* aFileHandle);
+
+  void
+  SendDeleteMeInternal();
+
+private:
+  ~BackgroundFileHandleChild();
+
+  void
+  NoteActorDestroyed();
+
+  void
+  NoteComplete();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  bool
+  RecvComplete(const bool& aAborted) override;
+
+  virtual PBackgroundFileRequestChild*
+  AllocPBackgroundFileRequestChild(const FileRequestParams& aParams)
+                                   override;
+
+  virtual bool
+  DeallocPBackgroundFileRequestChild(PBackgroundFileRequestChild* aActor)
+                                     override;
+
+  bool
+  SendDeleteMe() = delete;
+};
+
+class BackgroundFileRequestChild final
+  : public ThreadObject
+  , public PBackgroundFileRequestChild
+{
+  friend class BackgroundFileHandleChild;
+  friend class FileHandleBase;
+
+  nsRefPtr<FileRequestBase> mFileRequest;
+  nsRefPtr<FileHandleBase> mFileHandle;
+  bool mActorDestroyed;
+
+private:
+  // Only created by FileHandleBase.
+  BackgroundFileRequestChild(DEBUGONLY(PRThread* aOwningThread,)
+                             FileRequestBase* aFileRequest);
+
+  // Only destroyed by BackgroundFileHandleChild.
+  ~BackgroundFileRequestChild();
+
+  void
+  HandleResponse(nsresult aResponse);
+
+  void
+  HandleResponse(const FileRequestGetFileResponse& aResponse);
+
+  void
+  HandleResponse(const nsCString& aResponse);
+
+  void
+  HandleResponse(const FileRequestMetadata& aResponse);
+
+  void
+  HandleResponse(JS::Handle<JS::Value> aResponse);
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  Recv__delete__(const FileRequestResponse& aResponse) override;
+
+  virtual bool
+  RecvProgress(const uint64_t& aProgress,
+               const uint64_t& aProgressMax) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_filehandle_ActorsChild_h
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/ActorsParent.cpp
@@ -0,0 +1,2664 @@
+/* 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 "ActorsParent.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/unused.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileHandleCommon.h"
+#include "mozilla/dom/PBackgroundFileHandleParent.h"
+#include "mozilla/dom/PBackgroundFileRequestParent.h"
+#include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/dom/ipc/BlobParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIEventTarget.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsISeekableStream.h"
+#include "nsIThread.h"
+#include "nsIThreadPool.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+
+#define DISABLE_ASSERTS_FOR_FUZZING 0
+
+#if DISABLE_ASSERTS_FOR_FUZZING
+#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
+#else
+#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
+#endif
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+/******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+const uint32_t kStreamCopyBlockSize = 32768;
+
+} // namespace
+
+class FileHandleThreadPool::FileHandleQueue final
+  : public nsRunnable
+{
+  friend class FileHandleThreadPool;
+
+  nsRefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+  nsRefPtr<FileHandle> mFileHandle;
+  nsTArray<nsRefPtr<FileHandleOp>> mQueue;
+  nsRefPtr<FileHandleOp> mCurrentOp;
+  bool mShouldFinish;
+
+public:
+  explicit
+  FileHandleQueue(FileHandleThreadPool* aFileHandleThreadPool,
+                  FileHandle* aFileHandle);
+
+  void
+  Enqueue(FileHandleOp* aFileHandleOp);
+
+  void
+  Finish();
+
+  void
+  ProcessQueue();
+
+private:
+  ~FileHandleQueue() {}
+
+  NS_DECL_NSIRUNNABLE
+};
+
+struct FileHandleThreadPool::DelayedEnqueueInfo
+{
+  nsRefPtr<FileHandle> mFileHandle;
+  nsRefPtr<FileHandleOp> mFileHandleOp;
+  bool mFinish;
+};
+
+class FileHandleThreadPool::DirectoryInfo
+{
+  friend class FileHandleThreadPool;
+
+  nsRefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+  nsTArray<nsRefPtr<FileHandleQueue>> mFileHandleQueues;
+  nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
+  nsTHashtable<nsStringHashKey> mFilesReading;
+  nsTHashtable<nsStringHashKey> mFilesWriting;
+
+public:
+  FileHandleQueue*
+  CreateFileHandleQueue(FileHandle* aFileHandle);
+
+  FileHandleQueue*
+  GetFileHandleQueue(FileHandle* aFileHandle);
+
+  void
+  RemoveFileHandleQueue(FileHandle* aFileHandle);
+
+  bool
+  HasRunningFileHandles()
+  {
+    return !mFileHandleQueues.IsEmpty();
+  }
+
+  DelayedEnqueueInfo*
+  CreateDelayedEnqueueInfo(FileHandle* aFileHandle,
+                           FileHandleOp* aFileHandleOp,
+                           bool aFinish);
+
+  void
+  LockFileForReading(const nsAString& aFileName)
+  {
+    mFilesReading.PutEntry(aFileName);
+  }
+
+  void
+  LockFileForWriting(const nsAString& aFileName)
+  {
+    mFilesWriting.PutEntry(aFileName);
+  }
+
+  bool
+  IsFileLockedForReading(const nsAString& aFileName)
+  {
+    return mFilesReading.Contains(aFileName);
+  }
+
+  bool
+  IsFileLockedForWriting(const nsAString& aFileName)
+  {
+    return mFilesWriting.Contains(aFileName);
+  }
+
+private:
+  explicit DirectoryInfo(FileHandleThreadPool* aFileHandleThreadPool)
+    : mOwningFileHandleThreadPool(aFileHandleThreadPool)
+  { }
+};
+
+struct FileHandleThreadPool::StoragesCompleteCallback final
+{
+  friend class nsAutoPtr<StoragesCompleteCallback>;
+
+  nsTArray<nsCString> mDirectoryIds;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+  StoragesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
+                           nsIRunnable* aCallback);
+
+private:
+  ~StoragesCompleteCallback();
+};
+
+/******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class FileHandle
+  : public PBackgroundFileHandleParent
+{
+  friend class BackgroundMutableFileParentBase;
+
+  class FinishOp;
+
+  nsRefPtr<BackgroundMutableFileParentBase> mMutableFile;
+  nsCOMPtr<nsISupports> mStream;
+  uint64_t mActiveRequestCount;
+  FileHandleStorage mStorage;
+  Atomic<bool> mInvalidatedOnAnyThread;
+  FileMode mMode;
+  bool mHasBeenActive;
+  bool mActorDestroyed;
+  bool mInvalidated;
+  bool mAborted;
+  bool mFinishOrAbortReceived;
+  bool mFinishedOrAborted;
+  bool mForceAborted;
+
+  DEBUGONLY(nsCOMPtr<nsIEventTarget> mThreadPoolEventTarget;)
+
+public:
+  void
+  AssertIsOnThreadPool() const;
+
+  bool
+  IsActorDestroyed() const
+  {
+    AssertIsOnBackgroundThread();
+
+    return mActorDestroyed;
+  }
+
+  // Must be called on the background thread.
+  bool
+  IsInvalidated() const
+  {
+    MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
+    MOZ_ASSERT_IF(mInvalidated, mAborted);
+
+    return mInvalidated;
+  }
+
+  // May be called on any thread, but is more expensive than IsInvalidated().
+  bool
+  IsInvalidatedOnAnyThread() const
+  {
+    return mInvalidatedOnAnyThread;
+  }
+
+  void
+  SetActive()
+  {
+    AssertIsOnBackgroundThread();
+
+    mHasBeenActive = true;
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::FileHandle)
+
+  nsresult
+  GetOrCreateStream(nsISupports** aStream);
+
+  void
+  Abort(bool aForce);
+
+  FileHandleStorage
+  Storage() const
+  {
+    return mStorage;
+  }
+
+  FileMode
+  Mode() const
+  {
+    return mMode;
+  }
+
+  BackgroundMutableFileParentBase*
+  GetMutableFile() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(mMutableFile);
+
+    return mMutableFile;
+  }
+
+  bool
+  IsAborted() const
+  {
+    AssertIsOnBackgroundThread();
+
+    return mAborted;
+  }
+
+  PBackgroundParent*
+  GetBackgroundParent() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!IsActorDestroyed());
+
+    return GetMutableFile()->GetBackgroundParent();
+  }
+
+  void
+  NoteActiveRequest();
+
+  void
+  NoteFinishedRequest();
+
+  void
+  Invalidate();
+
+private:
+  // This constructor is only called by BackgroundMutableFileParentBase.
+  FileHandle(BackgroundMutableFileParentBase* aMutableFile,
+             FileMode aMode);
+
+  // Reference counted.
+  ~FileHandle();
+
+  void
+  MaybeFinishOrAbort()
+  {
+    AssertIsOnBackgroundThread();
+
+    // If we've already finished or aborted then there's nothing else to do.
+    if (mFinishedOrAborted) {
+      return;
+    }
+
+    // If there are active requests then we have to wait for those requests to
+    // complete (see NoteFinishedRequest).
+    if (mActiveRequestCount) {
+      return;
+    }
+
+    // If we haven't yet received a finish or abort message then there could be
+    // additional requests coming so we should wait unless we're being forced to
+    // abort.
+    if (!mFinishOrAbortReceived && !mForceAborted) {
+      return;
+    }
+
+    FinishOrAbort();
+  }
+
+  void
+  SendCompleteNotification(bool aAborted);
+
+  bool
+  VerifyRequestParams(const FileRequestParams& aParams) const;
+
+  bool
+  VerifyRequestData(const FileRequestData& aData) const;
+
+  void
+  FinishOrAbort();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  RecvDeleteMe() override;
+
+  virtual bool
+  RecvFinish() override;
+
+  virtual bool
+  RecvAbort() override;
+
+  virtual PBackgroundFileRequestParent*
+  AllocPBackgroundFileRequestParent(const FileRequestParams& aParams) override;
+
+  virtual bool
+  RecvPBackgroundFileRequestConstructor(PBackgroundFileRequestParent* aActor,
+                                        const FileRequestParams& aParams)
+                                        override;
+
+  virtual bool
+  DeallocPBackgroundFileRequestParent(PBackgroundFileRequestParent* aActor)
+                                      override;
+};
+
+class FileHandleOp
+{
+protected:
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  nsRefPtr<FileHandle> mFileHandle;
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileHandleOp)
+
+  void
+  AssertIsOnOwningThread() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(mOwningThread);
+    DebugOnly<bool> current;
+    MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+    MOZ_ASSERT(current);
+  }
+
+  nsIEventTarget*
+  OwningThread() const
+  {
+    return mOwningThread;
+  }
+
+  void
+  AssertIsOnThreadPool() const
+  {
+    MOZ_ASSERT(mFileHandle);
+    mFileHandle->AssertIsOnThreadPool();
+  }
+
+  void
+  Enqueue();
+
+  virtual void
+  RunOnThreadPool() = 0;
+
+  virtual void
+  RunOnOwningThread() = 0;
+
+protected:
+  FileHandleOp(FileHandle* aFileHandle)
+    : mOwningThread(NS_GetCurrentThread())
+    , mFileHandle(aFileHandle)
+  {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(aFileHandle);
+  }
+
+  virtual
+  ~FileHandleOp()
+  { }
+};
+
+class FileHandle::FinishOp
+  : public FileHandleOp
+{
+  friend class FileHandle;
+
+  bool mAborted;
+
+private:
+  FinishOp(FileHandle* aFileHandle,
+           bool aAborted)
+    : FileHandleOp(aFileHandle)
+    , mAborted(aAborted)
+  {
+    MOZ_ASSERT(aFileHandle);
+  }
+
+  ~FinishOp()
+  { }
+
+  virtual void
+  RunOnThreadPool() override;
+
+  virtual void
+  RunOnOwningThread() override;
+};
+
+class NormalFileHandleOp
+  : public FileHandleOp
+  , public PBackgroundFileRequestParent
+{
+  nsresult mResultCode;
+  Atomic<bool> mOperationMayProceed;
+  bool mActorDestroyed;
+  const bool mFileHandleIsAborted;
+
+  DEBUGONLY(bool mResponseSent;)
+
+protected:
+  nsCOMPtr<nsISupports> mFileStream;
+
+public:
+  void
+  NoteActorDestroyed()
+  {
+    AssertIsOnOwningThread();
+
+    mActorDestroyed = true;
+    mOperationMayProceed = false;
+  }
+
+  bool
+  IsActorDestroyed() const
+  {
+    AssertIsOnOwningThread();
+
+    return mActorDestroyed;
+  }
+
+  // May be called on any thread, but you should call IsActorDestroyed() if
+  // you know you're on the background thread because it is slightly faster.
+  bool
+  OperationMayProceed() const
+  {
+    return mOperationMayProceed;
+  }
+
+  // May be overridden by subclasses if they need to perform work on the
+  // background thread before being enqueued. Returning false will kill the
+  // child actors and prevent enqueue.
+  virtual bool
+  Init(FileHandle* aFileHandle);
+
+  // This callback will be called on the background thread before releasing the
+  // final reference to this request object. Subclasses may perform any
+  // additional cleanup here but must always call the base class implementation.
+  virtual void
+  Cleanup();
+
+protected:
+  NormalFileHandleOp(FileHandle* aFileHandle)
+    : FileHandleOp(aFileHandle)
+    , mResultCode(NS_OK)
+    , mOperationMayProceed(true)
+    , mActorDestroyed(false)
+    , mFileHandleIsAborted(aFileHandle->IsAborted())
+    DEBUGONLY(, mResponseSent(false))
+  {
+    MOZ_ASSERT(aFileHandle);
+  }
+
+  virtual
+  ~NormalFileHandleOp();
+
+  // Must be overridden in subclasses. Called on the target thread to allow the
+  // subclass to perform necessary file operations. A successful return value
+  // will trigger a SendSuccessResult callback on the background thread while
+  // a failure value will trigger a SendFailureResult callback.
+  virtual nsresult
+  DoFileWork(FileHandle* aFileHandle) = 0;
+
+  // Subclasses use this override to set the IPDL response value.
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) = 0;
+
+private:
+  nsresult
+  SendSuccessResult();
+
+  bool
+  SendFailureResult(nsresult aResultCode);
+
+  virtual void
+  RunOnThreadPool() override;
+
+  virtual void
+  RunOnOwningThread() override;
+
+  // IPDL methods.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class CopyFileHandleOp
+  : public NormalFileHandleOp
+{
+  class ProgressRunnable;
+
+protected:
+  nsCOMPtr<nsISupports> mBufferStream;
+
+  uint64_t mOffset;
+  uint64_t mSize;
+
+  bool mRead;
+
+protected:
+  CopyFileHandleOp(FileHandle* aFileHandle)
+    : NormalFileHandleOp(aFileHandle)
+    , mOffset(0)
+    , mSize(0)
+    , mRead(true)
+  { }
+
+  virtual nsresult
+  DoFileWork(FileHandle* aFileHandle) override;
+
+  virtual void
+  Cleanup() override;
+};
+
+class CopyFileHandleOp::ProgressRunnable final
+  : public nsRunnable
+{
+  nsRefPtr<CopyFileHandleOp> mCopyFileHandleOp;
+  uint64_t mProgress;
+  uint64_t mProgressMax;
+
+public:
+  ProgressRunnable(CopyFileHandleOp* aCopyFileHandleOp,
+                   uint64_t aProgress,
+                   uint64_t aProgressMax)
+    : mCopyFileHandleOp(aCopyFileHandleOp)
+    , mProgress(aProgress)
+    , mProgressMax(aProgressMax)
+  { }
+
+private:
+  ~ProgressRunnable() {}
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class GetMetadataOp
+  : public NormalFileHandleOp
+{
+  friend class FileHandle;
+
+  const FileRequestGetMetadataParams mParams;
+
+protected:
+  FileRequestMetadata mMetadata;
+
+protected:
+  // Only created by FileHandle.
+  GetMetadataOp(FileHandle* aFileHandle,
+                const FileRequestParams& aParams);
+
+  ~GetMetadataOp()
+  { }
+
+  virtual nsresult
+  DoFileWork(FileHandle* aFileHandle) override;
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class ReadOp final
+  : public CopyFileHandleOp
+{
+  friend class FileHandle;
+
+  class MemoryOutputStream;
+
+  const FileRequestReadParams mParams;
+
+private:
+  // Only created by FileHandle.
+  ReadOp(FileHandle* aFileHandle,
+         const FileRequestParams& aParams);
+
+  ~ReadOp()
+  { }
+
+  virtual bool
+  Init(FileHandle* aFileHandle) override;
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class ReadOp::MemoryOutputStream final
+  : public nsIOutputStream
+{
+  nsCString mData;
+  uint64_t mOffset;
+
+public:
+  static already_AddRefed<MemoryOutputStream>
+  Create(uint64_t aSize);
+
+  const nsCString&
+  Data() const
+  {
+    return mData;
+  }
+
+private:
+  MemoryOutputStream()
+  : mOffset(0)
+  { }
+
+  virtual ~MemoryOutputStream()
+  { }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOUTPUTSTREAM
+};
+
+class WriteOp final
+  : public CopyFileHandleOp
+{
+  friend class FileHandle;
+
+  const FileRequestWriteParams mParams;
+
+private:
+  // Only created by FileHandle.
+  WriteOp(FileHandle* aFileHandle,
+          const FileRequestParams& aParams);
+
+  ~WriteOp()
+  { }
+
+  virtual bool
+  Init(FileHandle* aFileHandle) override;
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class TruncateOp final
+  : public NormalFileHandleOp
+{
+  friend class FileHandle;
+
+  const FileRequestTruncateParams mParams;
+
+private:
+  // Only created by FileHandle.
+  TruncateOp(FileHandle* aFileHandle,
+                        const FileRequestParams& aParams);
+
+  ~TruncateOp()
+  { }
+
+  virtual nsresult
+  DoFileWork(FileHandle* aFileHandle) override;
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class FlushOp final
+  : public NormalFileHandleOp
+{
+  friend class FileHandle;
+
+  const FileRequestFlushParams mParams;
+
+private:
+  // Only created by FileHandle.
+  FlushOp(FileHandle* aFileHandle,
+          const FileRequestParams& aParams);
+
+  ~FlushOp()
+  { }
+
+  virtual nsresult
+  DoFileWork(FileHandle* aFileHandle) override;
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class GetFileOp final
+  : public GetMetadataOp
+{
+  friend class FileHandle;
+
+  PBackgroundParent* mBackgroundParent;
+
+private:
+  // Only created by FileHandle.
+  GetFileOp(FileHandle* aFileHandle,
+            const FileRequestParams& aParams);
+
+  ~GetFileOp()
+  { }
+
+  virtual void
+  GetResponse(FileRequestResponse& aResponse) override;
+};
+
+namespace {
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+FileHandleThreadPool*
+GetFileHandleThreadPoolFor(FileHandleStorage aStorage)
+{
+  switch (aStorage) {
+    case FILE_HANDLE_STORAGE_IDB:
+      return mozilla::dom::indexedDB::GetFileHandleThreadPool();
+
+    default:
+      MOZ_CRASH("Bad file handle storage value!");
+  }
+}
+
+} // namespace
+
+/*******************************************************************************
+ * FileHandleThreadPool implementation
+ ******************************************************************************/
+
+FileHandleThreadPool::FileHandleThreadPool()
+  : mOwningThread(NS_GetCurrentThread())
+  , mShutdownRequested(false)
+  , mShutdownComplete(false)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mOwningThread);
+  AssertIsOnOwningThread();
+}
+
+FileHandleThreadPool::~FileHandleThreadPool()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mDirectoryInfos.Count());
+  MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+  MOZ_ASSERT(mShutdownRequested);
+  MOZ_ASSERT(mShutdownComplete);
+}
+
+// static
+already_AddRefed<FileHandleThreadPool>
+FileHandleThreadPool::Create()
+{
+  AssertIsOnBackgroundThread();
+
+  nsRefPtr<FileHandleThreadPool> fileHandleThreadPool =
+    new FileHandleThreadPool();
+  fileHandleThreadPool->AssertIsOnOwningThread();
+
+  if (NS_WARN_IF(NS_FAILED(fileHandleThreadPool->Init()))) {
+    return nullptr;
+  }
+
+  return fileHandleThreadPool.forget();
+}
+
+#ifdef DEBUG
+
+void
+FileHandleThreadPool::AssertIsOnOwningThread() const
+{
+  MOZ_ASSERT(mOwningThread);
+
+  bool current;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+  MOZ_ASSERT(current);
+}
+
+nsIEventTarget*
+FileHandleThreadPool::GetThreadPoolEventTarget() const
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mThreadPool);
+
+  return mThreadPool;
+}
+
+#endif // DEBUG
+
+void
+FileHandleThreadPool::Enqueue(FileHandle* aFileHandle,
+                              FileHandleOp* aFileHandleOp,
+                              bool aFinish)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+  MOZ_ASSERT(!mShutdownRequested);
+
+  BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+
+  const nsACString& directoryId = mutableFile->DirectoryId();
+  const nsAString& fileName = mutableFile->FileName();
+  bool modeIsWrite = aFileHandle->Mode() == FileMode::Readwrite;
+
+  DirectoryInfo* directoryInfo;
+  if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) {
+    nsAutoPtr<DirectoryInfo> newDirectoryInfo(new DirectoryInfo(this));
+
+    mDirectoryInfos.Put(directoryId, newDirectoryInfo);
+
+    directoryInfo = newDirectoryInfo.forget();
+  }
+
+  FileHandleQueue* existingFileHandleQueue =
+    directoryInfo->GetFileHandleQueue(aFileHandle);
+
+  if (existingFileHandleQueue) {
+    existingFileHandleQueue->Enqueue(aFileHandleOp);
+    if (aFinish) {
+      existingFileHandleQueue->Finish();
+    }
+    return;
+  }
+
+  bool lockedForReading = directoryInfo->IsFileLockedForReading(fileName);
+  bool lockedForWriting = directoryInfo->IsFileLockedForWriting(fileName);
+
+  if (modeIsWrite) {
+    if (!lockedForWriting) {
+      directoryInfo->LockFileForWriting(fileName);
+    }
+  }
+  else {
+    if (!lockedForReading) {
+      directoryInfo->LockFileForReading(fileName);
+    }
+  }
+
+  if (lockedForWriting || (lockedForReading && modeIsWrite)) {
+    directoryInfo->CreateDelayedEnqueueInfo(aFileHandle,
+                                            aFileHandleOp,
+                                            aFinish);
+  }
+  else {
+    FileHandleQueue* fileHandleQueue =
+      directoryInfo->CreateFileHandleQueue(aFileHandle);
+
+    if (aFileHandleOp) {
+      fileHandleQueue->Enqueue(aFileHandleOp);
+      if (aFinish) {
+        existingFileHandleQueue->Finish();
+      }
+    }
+  }
+}
+
+void
+FileHandleThreadPool::WaitForDirectoriesToComplete(
+                                             nsTArray<nsCString>&& aDirectoryIds,
+                                             nsIRunnable* aCallback)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!aDirectoryIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
+
+  nsAutoPtr<StoragesCompleteCallback> callback(
+    new StoragesCompleteCallback(Move(aDirectoryIds), aCallback));
+
+  if (!MaybeFireCallback(callback)) {
+    mCompleteCallbacks.AppendElement(callback.forget());
+  }
+}
+
+void
+FileHandleThreadPool::Shutdown()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mShutdownRequested);
+  MOZ_ASSERT(!mShutdownComplete);
+
+  mShutdownRequested = true;
+
+  if (!mThreadPool) {
+    MOZ_ASSERT(!mDirectoryInfos.Count());
+    MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+
+    mShutdownComplete = true;
+    return;
+  }
+
+  if (!mDirectoryInfos.Count()) {
+    Cleanup();
+
+    MOZ_ASSERT(mShutdownComplete);
+    return;
+  }
+
+  nsIThread* currentThread = NS_GetCurrentThread();
+  MOZ_ASSERT(currentThread);
+
+  while (!mShutdownComplete) {
+    MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
+  }
+}
+
+nsresult
+FileHandleThreadPool::Init()
+{
+  AssertIsOnOwningThread();
+
+  mThreadPool = new nsThreadPool();
+
+  nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandles"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetThreadLimit(kThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+FileHandleThreadPool::Cleanup()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mThreadPool);
+  MOZ_ASSERT(mShutdownRequested);
+  MOZ_ASSERT(!mShutdownComplete);
+  MOZ_ASSERT(!mDirectoryInfos.Count());
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mThreadPool->Shutdown()));
+
+  if (!mCompleteCallbacks.IsEmpty()) {
+    // Run all callbacks manually now.
+    for (uint32_t count = mCompleteCallbacks.Length(), index = 0;
+         index < count;
+         index++) {
+      nsAutoPtr<StoragesCompleteCallback> completeCallback(
+        mCompleteCallbacks[index].forget());
+      MOZ_ASSERT(completeCallback);
+      MOZ_ASSERT(completeCallback->mCallback);
+
+      unused << completeCallback->mCallback->Run();
+    }
+
+    mCompleteCallbacks.Clear();
+
+    // And make sure they get processed.
+    nsIThread* currentThread = NS_GetCurrentThread();
+    MOZ_ASSERT(currentThread);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProcessPendingEvents(currentThread)));
+  }
+
+  mShutdownComplete = true;
+}
+
+void
+FileHandleThreadPool::FinishFileHandle(FileHandle* aFileHandle)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+
+  BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+  const nsACString& directoryId = mutableFile->DirectoryId();
+
+  DirectoryInfo* directoryInfo;
+  if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) {
+    NS_ERROR("We don't know anyting about this directory?!");
+    return;
+  }
+
+  directoryInfo->RemoveFileHandleQueue(aFileHandle);
+
+  if (!directoryInfo->HasRunningFileHandles()) {
+    mDirectoryInfos.Remove(directoryId);
+
+    // See if we need to fire any complete callbacks.
+    uint32_t index = 0;
+    while (index < mCompleteCallbacks.Length()) {
+      if (MaybeFireCallback(mCompleteCallbacks[index])) {
+        mCompleteCallbacks.RemoveElementAt(index);
+      }
+      else {
+        index++;
+      }
+    }
+
+    if (mShutdownRequested && !mDirectoryInfos.Count()) {
+      Cleanup();
+    }
+  }
+}
+
+bool
+FileHandleThreadPool::MaybeFireCallback(StoragesCompleteCallback* aCallback)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(!aCallback->mDirectoryIds.IsEmpty());
+  MOZ_ASSERT(aCallback->mCallback);
+
+  for (uint32_t count = aCallback->mDirectoryIds.Length(), index = 0;
+       index < count;
+       index++) {
+    const nsCString& directoryId = aCallback->mDirectoryIds[index];
+    MOZ_ASSERT(!directoryId.IsEmpty());
+
+    if (mDirectoryInfos.Get(directoryId, nullptr)) {
+      return false;
+    }
+  }
+
+  aCallback->mCallback->Run();
+  return true;
+}
+
+FileHandleThreadPool::
+FileHandleQueue::FileHandleQueue(FileHandleThreadPool* aFileHandleThreadPool,
+                                 FileHandle* aFileHandle)
+  : mOwningFileHandleThreadPool(aFileHandleThreadPool)
+  , mFileHandle(aFileHandle)
+  , mShouldFinish(false)
+{
+  MOZ_ASSERT(aFileHandleThreadPool);
+  aFileHandleThreadPool->AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::Enqueue(FileHandleOp* aFileHandleOp)
+{
+  MOZ_ASSERT(!mShouldFinish, "Enqueue called after Finish!");
+
+  mQueue.AppendElement(aFileHandleOp);
+
+  ProcessQueue();
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::Finish()
+{
+  MOZ_ASSERT(!mShouldFinish, "Finish called more than once!");
+
+  mShouldFinish = true;
+}
+
+void
+FileHandleThreadPool::
+FileHandleQueue::ProcessQueue()
+{
+  if (mCurrentOp) {
+    return;
+  }
+
+  if (mQueue.IsEmpty()) {
+    if (mShouldFinish) {
+      mOwningFileHandleThreadPool->FinishFileHandle(mFileHandle);
+
+      // Make sure this is released on this thread.
+      mOwningFileHandleThreadPool = nullptr;
+    }
+
+    return;
+  }
+
+  mCurrentOp = mQueue[0];
+  mQueue.RemoveElementAt(0);
+
+  nsCOMPtr<nsIThreadPool> threadPool = mOwningFileHandleThreadPool->mThreadPool;
+  MOZ_ASSERT(threadPool);
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(threadPool->Dispatch(this, NS_DISPATCH_NORMAL)));
+}
+
+NS_IMETHODIMP
+FileHandleThreadPool::
+FileHandleQueue::Run()
+{
+  MOZ_ASSERT(mCurrentOp);
+
+  if (IsOnBackgroundThread()) {
+    nsRefPtr<FileHandleOp> currentOp;
+
+    mCurrentOp.swap(currentOp);
+    ProcessQueue();
+
+    currentOp->RunOnOwningThread();
+  } else {
+    mCurrentOp->RunOnThreadPool();
+
+    nsCOMPtr<nsIEventTarget> backgroundThread = mCurrentOp->OwningThread();
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      backgroundThread->Dispatch(this, NS_DISPATCH_NORMAL)));
+  }
+
+  return NS_OK;
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::CreateFileHandleQueue(FileHandle* aFileHandle)
+  -> FileHandleQueue*
+{
+  nsRefPtr<FileHandleQueue>* fileHandleQueue =
+    mFileHandleQueues.AppendElement();
+  *fileHandleQueue = new FileHandleQueue(mOwningFileHandleThreadPool,
+                                         aFileHandle);
+  return fileHandleQueue->get();
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::GetFileHandleQueue(FileHandle* aFileHandle) -> FileHandleQueue*
+{
+  uint32_t count = mFileHandleQueues.Length();
+  for (uint32_t index = 0; index < count; index++) {
+    nsRefPtr<FileHandleQueue>& fileHandleQueue = mFileHandleQueues[index];
+    if (fileHandleQueue->mFileHandle == aFileHandle) {
+      return fileHandleQueue;
+    }
+  }
+  return nullptr;
+}
+
+void
+FileHandleThreadPool::
+DirectoryInfo::RemoveFileHandleQueue(FileHandle* aFileHandle)
+{
+  for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
+    if (mDelayedEnqueueInfos[index].mFileHandle == aFileHandle) {
+      MOZ_ASSERT(!mDelayedEnqueueInfos[index].mFileHandleOp, "Should be null!");
+      mDelayedEnqueueInfos.RemoveElementAt(index);
+      return;
+    }
+  }
+
+  uint32_t fileHandleCount = mFileHandleQueues.Length();
+
+  // We can't just remove entries from lock hash tables, we have to rebuild
+  // them instead. Multiple FileHandle objects may lock the same file
+  // (one entry can represent multiple locks).
+
+  mFilesReading.Clear();
+  mFilesWriting.Clear();
+
+  for (uint32_t index = 0, count = fileHandleCount; index < count; index++) {
+    FileHandle* fileHandle = mFileHandleQueues[index]->mFileHandle;
+    if (fileHandle == aFileHandle) {
+      MOZ_ASSERT(count == fileHandleCount, "More than one match?!");
+
+      mFileHandleQueues.RemoveElementAt(index);
+      index--;
+      count--;
+
+      continue;
+    }
+
+    const nsAString& fileName = fileHandle->GetMutableFile()->FileName();
+
+    if (fileHandle->Mode() == FileMode::Readwrite) {
+      if (!IsFileLockedForWriting(fileName)) {
+        LockFileForWriting(fileName);
+      }
+    }
+    else {
+      if (!IsFileLockedForReading(fileName)) {
+        LockFileForReading(fileName);
+      }
+    }
+  }
+
+  MOZ_ASSERT(mFileHandleQueues.Length() == fileHandleCount - 1,
+             "Didn't find the file handle we were looking for!");
+
+  nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos;
+  delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos);
+
+  for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) {
+    DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index];
+    mOwningFileHandleThreadPool->Enqueue(delayedEnqueueInfo.mFileHandle,
+                                         delayedEnqueueInfo.mFileHandleOp,
+                                         delayedEnqueueInfo.mFinish);
+  }
+}
+
+auto
+FileHandleThreadPool::
+DirectoryInfo::CreateDelayedEnqueueInfo(FileHandle* aFileHandle,
+                                        FileHandleOp* aFileHandleOp,
+                                        bool aFinish) -> DelayedEnqueueInfo*
+{
+  DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
+  info->mFileHandle = aFileHandle;
+  info->mFileHandleOp = aFileHandleOp;
+  info->mFinish = aFinish;
+  return info;
+}
+
+FileHandleThreadPool::
+StoragesCompleteCallback::StoragesCompleteCallback(
+                                             nsTArray<nsCString>&& aDirectoryIds,
+                                             nsIRunnable* aCallback)
+  : mDirectoryIds(Move(aDirectoryIds))
+  , mCallback(aCallback)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mDirectoryIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
+
+  MOZ_COUNT_CTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+FileHandleThreadPool::
+StoragesCompleteCallback::~StoragesCompleteCallback()
+{
+  AssertIsOnBackgroundThread();
+
+  MOZ_COUNT_DTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+/*******************************************************************************
+ * BackgroundMutableFileParentBase
+ ******************************************************************************/
+
+BackgroundMutableFileParentBase::BackgroundMutableFileParentBase(
+                                                 FileHandleStorage aStorage,
+                                                 const nsACString& aDirectoryId,
+                                                 const nsAString& aFileName,
+                                                 nsIFile* aFile)
+  : mDirectoryId(aDirectoryId)
+  , mFileName(aFileName)
+  , mStorage(aStorage)
+  , mInvalidated(false)
+  , mActorWasAlive(false)
+  , mActorDestroyed(false)
+  , mFile(aFile)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aStorage != FILE_HANDLE_STORAGE_MAX);
+  MOZ_ASSERT(!aDirectoryId.IsEmpty());
+  MOZ_ASSERT(!aFileName.IsEmpty());
+  MOZ_ASSERT(aFile);
+}
+
+BackgroundMutableFileParentBase::~BackgroundMutableFileParentBase()
+{
+  MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
+}
+
+void
+BackgroundMutableFileParentBase::Invalidate()
+{
+  AssertIsOnBackgroundThread();
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static bool
+    InvalidateFileHandles(nsTHashtable<nsPtrHashKey<FileHandle>>& aTable)
+    {
+      AssertIsOnBackgroundThread();
+
+      const uint32_t count = aTable.Count();
+      if (!count) {
+        return true;
+      }
+
+      FallibleTArray<nsRefPtr<FileHandle>> fileHandles;
+      if (NS_WARN_IF(!fileHandles.SetCapacity(count, fallible))) {
+        return false;
+      }
+
+      for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
+        if (NS_WARN_IF(!fileHandles.AppendElement(iter.Get()->GetKey(),
+                                                  fallible))) {
+          return false;
+        }
+      }
+
+      if (count) {
+        for (uint32_t index = 0; index < count; index++) {
+          nsRefPtr<FileHandle> fileHandle = fileHandles[index].forget();
+          MOZ_ASSERT(fileHandle);
+
+          fileHandle->Invalidate();
+        }
+      }
+
+      return true;
+    }
+  };
+
+  if (mInvalidated) {
+    return;
+  }
+
+  mInvalidated = true;
+
+  if (!Helper::InvalidateFileHandles(mFileHandles)) {
+    NS_WARNING("Failed to abort all file handles!");
+  }
+}
+
+bool
+BackgroundMutableFileParentBase::RegisterFileHandle(FileHandle* aFileHandle)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aFileHandle);
+  MOZ_ASSERT(!mFileHandles.GetEntry(aFileHandle));
+  MOZ_ASSERT(!mInvalidated);
+
+  if (NS_WARN_IF(!mFileHandles.PutEntry(aFileHandle, fallible))) {
+    return false;
+  }
+
+  if (mFileHandles.Count() == 1) {
+    NoteActiveState();
+  }
+
+  return true;
+}
+
+void
+BackgroundMutableFileParentBase::UnregisterFileHandle(FileHandle* aFileHandle)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aFileHandle);
+  MOZ_ASSERT(mFileHandles.GetEntry(aFileHandle));
+
+  mFileHandles.RemoveEntry(aFileHandle);
+
+  if (!mFileHandles.Count()) {
+    NoteInactiveState();
+  }
+}
+
+void
+BackgroundMutableFileParentBase::SetActorAlive()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorWasAlive);
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorWasAlive = true;
+
+  // This reference will be absorbed by IPDL and released when the actor is
+  // destroyed.
+  AddRef();
+}
+
+already_AddRefed<nsISupports>
+BackgroundMutableFileParentBase::CreateStream(bool aReadOnly)
+{
+  AssertIsOnBackgroundThread();
+
+  nsresult rv;
+
+  if (aReadOnly) {
+    nsCOMPtr<nsIInputStream> stream;
+    rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), mFile, -1, -1,
+                                    nsIFileInputStream::DEFER_OPEN);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+    return stream.forget();
+  }
+
+  nsCOMPtr<nsIFileStream> stream;
+  rv = NS_NewLocalFileStream(getter_AddRefs(stream), mFile, -1, -1,
+                             nsIFileStream::DEFER_OPEN);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+  return stream.forget();
+}
+
+void
+BackgroundMutableFileParentBase::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  if (!IsInvalidated()) {
+    Invalidate();
+  }
+}
+
+PBackgroundFileHandleParent*
+BackgroundMutableFileParentBase::AllocPBackgroundFileHandleParent(
+                                                          const FileMode& aMode)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(aMode != FileMode::Readonly &&
+                 aMode != FileMode::Readwrite)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  nsRefPtr<FileHandle> fileHandle = new FileHandle(this, aMode);
+
+  return fileHandle.forget().take();
+}
+
+bool
+BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
+                                            PBackgroundFileHandleParent* aActor,
+                                            const FileMode& aMode)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(aMode == FileMode::Readonly || aMode == FileMode::Readwrite);
+
+  FileHandleThreadPool* fileHandleThreadPool =
+    GetFileHandleThreadPoolFor(mStorage);
+  MOZ_ASSERT(fileHandleThreadPool);
+
+  auto* fileHandle = static_cast<FileHandle*>(aActor);
+
+  // Add a placeholder for this file handle immediately.
+  fileHandleThreadPool->Enqueue(fileHandle, nullptr, false);
+
+  fileHandle->SetActive();
+
+  if (NS_WARN_IF(!RegisterFileHandle(fileHandle))) {
+    fileHandle->Abort(/* aForce */ false);
+    return true;
+  }
+
+  return true;
+}
+
+bool
+BackgroundMutableFileParentBase::DeallocPBackgroundFileHandleParent(
+                                            PBackgroundFileHandleParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  nsRefPtr<FileHandle> fileHandle =
+    dont_AddRef(static_cast<FileHandle*>(aActor));
+  return true;
+}
+
+bool
+BackgroundMutableFileParentBase::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  return PBackgroundMutableFileParent::Send__delete__(this);
+}
+
+bool
+BackgroundMutableFileParentBase::RecvGetFileId(int64_t* aFileId)
+{
+  AssertIsOnBackgroundThread();
+
+  *aFileId = -1;
+  return true;
+}
+
+/*******************************************************************************
+ * FileHandle
+ ******************************************************************************/
+
+FileHandle::FileHandle(BackgroundMutableFileParentBase* aMutableFile,
+                       FileMode aMode)
+  : mMutableFile(aMutableFile)
+  , mActiveRequestCount(0)
+  , mStorage(aMutableFile->Storage())
+  , mInvalidatedOnAnyThread(false)
+  , mMode(aMode)
+  , mHasBeenActive(false)
+  , mActorDestroyed(false)
+  , mInvalidated(false)
+  , mAborted(false)
+  , mFinishOrAbortReceived(false)
+  , mFinishedOrAborted(false)
+  , mForceAborted(false)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aMutableFile);
+
+#ifdef DEBUG
+  FileHandleThreadPool* fileHandleThreadPool =
+    GetFileHandleThreadPoolFor(mStorage);
+  MOZ_ASSERT(fileHandleThreadPool);
+
+  mThreadPoolEventTarget = fileHandleThreadPool->GetThreadPoolEventTarget();
+#endif
+}
+
+FileHandle::~FileHandle()
+{
+  MOZ_ASSERT(!mActiveRequestCount);
+  MOZ_ASSERT(mActorDestroyed);
+  MOZ_ASSERT_IF(mHasBeenActive, mFinishedOrAborted);
+}
+
+void
+FileHandle::AssertIsOnThreadPool() const
+{
+  MOZ_ASSERT(mThreadPoolEventTarget);
+  DebugOnly<bool> current;
+  MOZ_ASSERT(NS_SUCCEEDED(mThreadPoolEventTarget->IsOnCurrentThread(&current)));
+  MOZ_ASSERT(current);
+}
+
+nsresult
+FileHandle::GetOrCreateStream(nsISupports** aStream)
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mStream) {
+    nsCOMPtr<nsISupports> stream =
+      mMutableFile->CreateStream(mMode == FileMode::Readonly);
+    if (NS_WARN_IF(!stream)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    stream.swap(mStream);
+  }
+
+  nsCOMPtr<nsISupports> stream(mStream);
+  stream.forget(aStream);
+
+  return NS_OK;
+}
+
+void
+FileHandle::Abort(bool aForce)
+{
+  AssertIsOnBackgroundThread();
+
+  mAborted = true;
+
+  if (aForce) {
+    mForceAborted = true;
+  }
+
+  MaybeFinishOrAbort();
+}
+
+void
+FileHandle::NoteActiveRequest()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
+
+  mActiveRequestCount++;
+}
+
+void
+FileHandle::NoteFinishedRequest()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mActiveRequestCount);
+
+  mActiveRequestCount--;
+
+  MaybeFinishOrAbort();
+}
+
+void
+FileHandle::Invalidate()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
+
+  if (!mInvalidated) {
+    mInvalidated = true;
+    mInvalidatedOnAnyThread = true;
+
+    Abort(/* aForce */ true);
+  }
+}
+
+void
+FileHandle::SendCompleteNotification(bool aAborted)
+{
+  AssertIsOnBackgroundThread();
+
+  if (!IsActorDestroyed()) {
+    unused << SendComplete(aAborted);
+  }
+}
+
+bool
+FileHandle::VerifyRequestParams(const FileRequestParams& aParams) const
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+  switch (aParams.type()) {
+    case FileRequestParams::TFileRequestGetMetadataParams: {
+      const FileRequestGetMetadataParams& params =
+        aParams.get_FileRequestGetMetadataParams();
+
+      if (NS_WARN_IF(!params.size() && !params.lastModified())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestParams::TFileRequestReadParams: {
+      const FileRequestReadParams& params =
+        aParams.get_FileRequestReadParams();
+
+      if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      if (NS_WARN_IF(!params.size())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestParams::TFileRequestWriteParams: {
+      if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      const FileRequestWriteParams& params =
+        aParams.get_FileRequestWriteParams();
+
+
+      if (NS_WARN_IF(!params.dataLength())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      if (NS_WARN_IF(!VerifyRequestData(params.data()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestParams::TFileRequestTruncateParams: {
+      if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      const FileRequestTruncateParams& params =
+        aParams.get_FileRequestTruncateParams();
+
+      if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestParams::TFileRequestFlushParams: {
+      if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestParams::TFileRequestGetFileParams: {
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
+bool
+FileHandle::VerifyRequestData(const FileRequestData& aData) const
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aData.type() != FileRequestData::T__None);
+
+  switch (aData.type()) {
+    case FileRequestData::TFileRequestStringData: {
+      const FileRequestStringData& data =
+        aData.get_FileRequestStringData();
+
+      if (NS_WARN_IF(data.string().IsEmpty())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case FileRequestData::TFileRequestBlobData: {
+      const FileRequestBlobData& data =
+        aData.get_FileRequestBlobData();
+
+      if (NS_WARN_IF(data.blobChild())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      if (NS_WARN_IF(!data.blobParent())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
+void
+FileHandle::FinishOrAbort()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mFinishedOrAborted);
+
+  mFinishedOrAborted = true;
+
+  if (!mHasBeenActive) {
+    return;
+  }
+
+  nsRefPtr<FinishOp> finishOp = new FinishOp(this, mAborted);
+
+  FileHandleThreadPool* fileHandleThreadPool =
+    GetFileHandleThreadPoolFor(mStorage);
+  MOZ_ASSERT(fileHandleThreadPool);
+
+  fileHandleThreadPool->Enqueue(this, finishOp, true);
+}
+
+void
+FileHandle::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  if (!mFinishedOrAborted) {
+    mAborted = true;
+
+    mForceAborted = true;
+
+    MaybeFinishOrAbort();
+  }
+}
+
+bool
+FileHandle::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!IsActorDestroyed());
+
+  return PBackgroundFileHandleParent::Send__delete__(this);
+}
+
+bool
+FileHandle::RecvFinish()
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(mFinishOrAbortReceived)) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  mFinishOrAbortReceived = true;
+
+  MaybeFinishOrAbort();
+  return true;
+}
+
+bool
+FileHandle::RecvAbort()
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(mFinishOrAbortReceived)) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  mFinishOrAbortReceived = true;
+
+  Abort(/* aForce */ false);
+  return true;
+}
+
+PBackgroundFileRequestParent*
+FileHandle::AllocPBackgroundFileRequestParent(const FileRequestParams& aParams)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  PBackgroundParent* backgroundActor = GetBackgroundParent();
+  MOZ_ASSERT(backgroundActor);
+
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor);
+#endif
+
+  if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(mFinishOrAbortReceived)) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
+  nsRefPtr<NormalFileHandleOp> actor;
+
+  switch (aParams.type()) {
+    case FileRequestParams::TFileRequestGetMetadataParams:
+      actor = new GetMetadataOp(this, aParams);
+      break;
+
+    case FileRequestParams::TFileRequestReadParams:
+      actor = new ReadOp(this, aParams);
+      break;
+
+    case FileRequestParams::TFileRequestWriteParams:
+      actor = new WriteOp(this, aParams);
+      break;
+
+    case FileRequestParams::TFileRequestTruncateParams:
+      actor = new TruncateOp(this, aParams);
+      break;
+
+    case FileRequestParams::TFileRequestFlushParams:
+      actor = new FlushOp(this, aParams);
+      break;
+
+    case FileRequestParams::TFileRequestGetFileParams:
+      actor = new GetFileOp(this, aParams);
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  MOZ_ASSERT(actor);
+
+  // Transfer ownership to IPDL.
+  return actor.forget().take();
+}
+
+bool
+FileHandle::RecvPBackgroundFileRequestConstructor(
+                                           PBackgroundFileRequestParent* aActor,
+                                           const FileRequestParams& aParams)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+  auto* op = static_cast<NormalFileHandleOp*>(aActor);
+
+  if (NS_WARN_IF(!op->Init(this))) {
+    op->Cleanup();
+    return false;
+  }
+
+  op->Enqueue();
+  return true;
+}
+
+bool
+FileHandle::DeallocPBackgroundFileRequestParent(
+                                           PBackgroundFileRequestParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  // Transfer ownership back from IPDL.
+  nsRefPtr<NormalFileHandleOp> actor =
+    dont_AddRef(static_cast<NormalFileHandleOp*>(aActor));
+  return true;
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void
+FileHandleOp::Enqueue()
+{
+  AssertIsOnOwningThread();
+
+  FileHandleThreadPool* fileHandleThreadPool =
+    GetFileHandleThreadPoolFor(mFileHandle->Storage());
+  MOZ_ASSERT(fileHandleThreadPool);
+
+  fileHandleThreadPool->Enqueue(mFileHandle, this, false);
+
+  mFileHandle->NoteActiveRequest();
+}
+
+void
+FileHandle::
+FinishOp::RunOnThreadPool()
+{
+  AssertIsOnThreadPool();
+  MOZ_ASSERT(mFileHandle);
+
+  nsCOMPtr<nsISupports>& stream = mFileHandle->mStream;
+
+  if (!stream) {
+    return;
+  }
+
+  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream);
+  MOZ_ASSERT(inputStream);
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(inputStream->Close()));
+
+  stream = nullptr;
+}
+
+void
+FileHandle::
+FinishOp::RunOnOwningThread()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileHandle);
+
+  mFileHandle->SendCompleteNotification(mAborted);
+
+  mFileHandle->GetMutableFile()->UnregisterFileHandle(mFileHandle);
+
+  mFileHandle = nullptr;
+}
+
+NormalFileHandleOp::~NormalFileHandleOp()
+{
+  MOZ_ASSERT(!mFileHandle,
+             "NormalFileHandleOp::Cleanup() was not called by a subclass!");
+}
+
+bool
+NormalFileHandleOp::Init(FileHandle* aFileHandle)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+
+  nsresult rv = aFileHandle->GetOrCreateStream(getter_AddRefs(mFileStream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+void
+NormalFileHandleOp::Cleanup()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileHandle);
+  MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
+
+  mFileHandle = nullptr;
+}
+
+nsresult
+NormalFileHandleOp::SendSuccessResult()
+{
+  AssertIsOnOwningThread();
+
+  if (!IsActorDestroyed()) {
+    FileRequestResponse response;
+    GetResponse(response);
+
+    MOZ_ASSERT(response.type() != FileRequestResponse::T__None);
+
+    if (response.type() == FileRequestResponse::Tnsresult) {
+      MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
+
+      return response.get_nsresult();
+    }
+
+    if (NS_WARN_IF(!PBackgroundFileRequestParent::Send__delete__(this,
+                                                                 response))) {
+      return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+  }
+
+  DEBUGONLY(mResponseSent = true;)
+
+  return NS_OK;
+}
+
+bool
+NormalFileHandleOp::SendFailureResult(nsresult aResultCode)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(NS_FAILED(aResultCode));
+
+  bool result = false;
+
+  if (!IsActorDestroyed()) {
+    result =
+      PBackgroundFileRequestParent::Send__delete__(this, aResultCode);
+  }
+
+  DEBUGONLY(mResponseSent = true;)
+
+  return result;
+}
+
+void
+NormalFileHandleOp::RunOnThreadPool()
+{
+  AssertIsOnThreadPool();
+  MOZ_ASSERT(mFileHandle);
+  MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+  // There are several cases where we don't actually have to to any work here.
+
+  if (mFileHandleIsAborted) {
+    // This transaction is already set to be aborted.
+    mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+  } else if (mFileHandle->IsInvalidatedOnAnyThread()) {
+    // This file handle is being invalidated.
+    mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+  } else if (!OperationMayProceed()) {
+    // The operation was canceled in some way, likely because the child process
+    // has crashed.
+    mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+  } else {
+    nsresult rv = DoFileWork(mFileHandle);
+    if (NS_FAILED(rv)) {
+      mResultCode = rv;
+    }
+  }
+}
+
+void
+NormalFileHandleOp::RunOnOwningThread()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mFileHandle);
+
+  if (NS_WARN_IF(IsActorDestroyed())) {
+    // Don't send any notifications if the actor was destroyed already.
+    if (NS_SUCCEEDED(mResultCode)) {
+      mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    }
+  } else {
+    if (mFileHandle->IsInvalidated()) {
+      mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    } else if (mFileHandle->IsAborted()) {
+      // Aborted file handles always see their requests fail with ABORT_ERR,
+      // even if the request succeeded or failed with another error.
+      mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+    } else if (NS_SUCCEEDED(mResultCode)) {
+      // This may release the IPDL reference.
+      mResultCode = SendSuccessResult();
+    }
+
+    if (NS_FAILED(mResultCode)) {
+      // This should definitely release the IPDL reference.
+      if (!SendFailureResult(mResultCode)) {
+        // Abort the file handle.
+        mFileHandle->Abort(/* aForce */ false);
+      }
+    }
+  }
+
+  mFileHandle->NoteFinishedRequest();
+
+  Cleanup();
+}
+
+void
+NormalFileHandleOp::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  NoteActorDestroyed();
+}
+
+nsresult
+CopyFileHandleOp::DoFileWork(FileHandle* aFileHandle)
+{
+  AssertIsOnThreadPool();
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  nsCOMPtr<nsIOutputStream> outputStream;
+
+  if (mRead) {
+    inputStream = do_QueryInterface(mFileStream);
+    outputStream = do_QueryInterface(mBufferStream);
+  } else {
+    inputStream = do_QueryInterface(mBufferStream);
+    outputStream = do_QueryInterface(mFileStream);
+  }
+
+  MOZ_ASSERT(inputStream);
+  MOZ_ASSERT(outputStream);
+
+  nsCOMPtr<nsISeekableStream> seekableStream =
+    do_QueryInterface(mFileStream);
+
+  nsresult rv;
+
+  if (seekableStream) {
+    if (mOffset == UINT64_MAX) {
+      rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+    }
+    else {
+      rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mOffset = 0;
+
+  do {
+    char copyBuffer[kStreamCopyBlockSize];
+
+    uint64_t max = mSize - mOffset;
+    if (max == 0) {
+      break;
+    }
+
+    uint32_t count = sizeof(copyBuffer);
+    if (count > max) {
+      count = max;
+    }
+
+    uint32_t numRead;
+    rv = inputStream->Read(copyBuffer, count, &numRead);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (!numRead) {
+      break;
+    }
+
+    uint32_t numWrite;
+    rv = outputStream->Write(copyBuffer, numRead, &numWrite);
+    if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+      rv = NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR;
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(numWrite != numRead)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mOffset += numWrite;
+
+    nsCOMPtr<nsIRunnable> runnable =
+      new ProgressRunnable(this, mOffset, mSize);
+
+    mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+  } while (true);
+
+  MOZ_ASSERT(mOffset == mSize);
+
+  if (mRead) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(outputStream->Close()));
+  } else {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(inputStream->Close()));
+  }
+
+  return NS_OK;
+}
+
+void
+CopyFileHandleOp::Cleanup()
+{
+  AssertIsOnOwningThread();
+
+  mBufferStream = nullptr;
+
+  NormalFileHandleOp::Cleanup();
+}
+
+NS_IMETHODIMP
+CopyFileHandleOp::
+ProgressRunnable::Run()
+{
+  AssertIsOnBackgroundThread();
+
+  unused << mCopyFileHandleOp->SendProgress(mProgress, mProgressMax);
+
+  mCopyFileHandleOp = nullptr;
+
+  return NS_OK;
+}
+
+GetMetadataOp::GetMetadataOp(FileHandle* aFileHandle,
+                             const FileRequestParams& aParams)
+  : NormalFileHandleOp(aFileHandle)
+  , mParams(aParams.get_FileRequestGetMetadataParams())
+{
+  MOZ_ASSERT(aParams.type() ==
+             FileRequestParams::TFileRequestGetMetadataParams);
+}
+
+nsresult
+GetMetadataOp::DoFileWork(FileHandle* aFileHandle)
+{
+  AssertIsOnThreadPool();
+
+  nsresult rv;
+
+  if (mFileHandle->Mode() == FileMode::Readwrite) {
+    // Force a flush (so all pending writes are flushed to the disk and file
+    // metadata is updated too).
+
+    nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+    MOZ_ASSERT(ostream);
+
+    rv = ostream->Flush();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  nsCOMPtr<nsIFileMetadata> metadata = do_QueryInterface(mFileStream);
+  MOZ_ASSERT(metadata);
+
+  if (mParams.size()) {
+    int64_t size;
+    rv = metadata->GetSize(&size);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(size < 0)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mMetadata.size() = uint64_t(size);
+  } else {
+    mMetadata.size() = void_t();
+  }
+
+  if (mParams.lastModified()) {
+    int64_t lastModified;
+    rv = metadata->GetLastModified(&lastModified);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    mMetadata.lastModified() = lastModified;
+  } else {
+    mMetadata.lastModified() = void_t();
+  }
+
+  return NS_OK;
+}
+
+void
+GetMetadataOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  aResponse = FileRequestGetMetadataResponse(mMetadata);
+}
+
+ReadOp::ReadOp(FileHandle* aFileHandle,
+               const FileRequestParams& aParams)
+  : CopyFileHandleOp(aFileHandle)
+  , mParams(aParams.get_FileRequestReadParams())
+{
+  MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestReadParams);
+}
+
+bool
+ReadOp::Init(FileHandle* aFileHandle)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+
+  if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+    return false;
+  }
+
+  mBufferStream = MemoryOutputStream::Create(mParams.size());
+  if (NS_WARN_IF(!mBufferStream)) {
+    return false;
+  }
+
+  mOffset = mParams.offset();
+  mSize = mParams.size();
+  mRead = true;
+
+  return true;
+}
+
+void
+ReadOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  auto* stream = static_cast<MemoryOutputStream*>(mBufferStream.get());
+
+  aResponse = FileRequestReadResponse(stream->Data());
+}
+
+// static
+already_AddRefed<ReadOp::MemoryOutputStream>
+ReadOp::
+MemoryOutputStream::Create(uint64_t aSize)
+{
+  MOZ_ASSERT(aSize, "Passed zero size!");
+
+  if (NS_WARN_IF(aSize > UINT32_MAX)) {
+    return nullptr;
+  }
+
+  nsRefPtr<MemoryOutputStream> stream = new MemoryOutputStream();
+
+  char* dummy;
+  uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible);
+  if (NS_WARN_IF(length != aSize)) {
+    return nullptr;
+  }
+
+  return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS(ReadOp::MemoryOutputStream, nsIOutputStream)
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Close()
+{
+  mData.Truncate(mOffset);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
+{
+  return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::Flush()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+                              uint32_t* _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+                                  uint32_t aCount, uint32_t* _retval)
+{
+  NS_ASSERTION(mData.Length() >= mOffset, "Bad stream state!");
+
+  uint32_t maxCount = mData.Length() - mOffset;
+  if (maxCount == 0) {
+    *_retval = 0;
+    return NS_OK;
+  }
+
+  if (aCount > maxCount) {
+    aCount = maxCount;
+  }
+
+  nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0,
+                        aCount, _retval);
+  if (NS_SUCCEEDED(rv)) {
+    NS_ASSERTION(*_retval <= aCount,
+                 "Reader should not read more than we asked it to read!");
+    mOffset += *_retval;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ReadOp::
+MemoryOutputStream::IsNonBlocking(bool* _retval)
+{
+  *_retval = false;
+  return NS_OK;
+}
+
+WriteOp::WriteOp(FileHandle* aFileHandle,
+                 const FileRequestParams& aParams)
+  : CopyFileHandleOp(aFileHandle)
+  , mParams(aParams.get_FileRequestWriteParams())
+{
+  MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestWriteParams);
+}
+
+bool
+WriteOp::Init(FileHandle* aFileHandle)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileHandle);
+
+  if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIInputStream> inputStream;
+
+  const FileRequestData& data = mParams.data();
+  switch (data.type()) {
+    case FileRequestData::TFileRequestStringData: {
+      const FileRequestStringData& stringData =
+        data.get_FileRequestStringData();
+
+      const nsCString& string = stringData.string();
+
+      nsresult rv =
+        NS_NewCStringInputStream(getter_AddRefs(inputStream), string);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      break;
+    }
+    case FileRequestData::TFileRequestBlobData: {
+      const FileRequestBlobData& blobData =
+        data.get_FileRequestBlobData();
+
+      auto blobActor = static_cast<BlobParent*>(blobData.blobParent());
+
+      nsRefPtr<BlobImpl> blobImpl = blobActor->GetBlobImpl();
+
+      ErrorResult rv;
+      blobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        return false;
+      }
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  mBufferStream = inputStream;
+  mOffset = mParams.offset();
+  mSize = mParams.dataLength();
+  mRead = false;
+
+  return true;
+}
+
+void
+WriteOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  aResponse = FileRequestWriteResponse();
+}
+
+TruncateOp::TruncateOp(FileHandle* aFileHandle,
+                       const FileRequestParams& aParams)
+  : NormalFileHandleOp(aFileHandle)
+  , mParams(aParams.get_FileRequestTruncateParams())
+{
+  MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestTruncateParams);
+}
+
+nsresult
+TruncateOp::DoFileWork(FileHandle* aFileHandle)
+{
+  AssertIsOnThreadPool();
+
+  nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mFileStream);
+  MOZ_ASSERT(sstream);
+
+  nsresult rv = sstream->Seek(nsISeekableStream::NS_SEEK_SET, mParams.offset());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = sstream->SetEOF();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+TruncateOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  aResponse = FileRequestTruncateResponse();
+}
+
+FlushOp::FlushOp(FileHandle* aFileHandle,
+                 const FileRequestParams& aParams)
+  : NormalFileHandleOp(aFileHandle)
+  , mParams(aParams.get_FileRequestFlushParams())
+{
+  MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestFlushParams);
+}
+
+nsresult
+FlushOp::DoFileWork(FileHandle* aFileHandle)
+{
+  AssertIsOnThreadPool();
+
+  nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+  MOZ_ASSERT(ostream);
+
+  nsresult rv = ostream->Flush();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+FlushOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  aResponse = FileRequestFlushResponse();
+}
+
+GetFileOp::GetFileOp(FileHandle* aFileHandle,
+                     const FileRequestParams& aParams)
+  : GetMetadataOp(aFileHandle,
+                  FileRequestGetMetadataParams(true, true))
+  , mBackgroundParent(aFileHandle->GetBackgroundParent())
+{
+  MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestGetFileParams);
+  MOZ_ASSERT(mBackgroundParent);
+}
+
+void
+GetFileOp::GetResponse(FileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  nsRefPtr<BlobImpl> blobImpl = mFileHandle->GetMutableFile()->CreateBlobImpl();
+  MOZ_ASSERT(blobImpl);
+
+  PBlobParent* actor =
+    BackgroundParent::GetOrCreateActorForBlobImpl(mBackgroundParent, blobImpl);
+  if (NS_WARN_IF(!actor)) {
+    // This can only fail if the child has crashed.
+    aResponse = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+    return;
+  }
+
+  FileRequestGetFileResponse response;
+  response.fileParent() = actor;
+  response.metadata() = mMetadata;
+
+  aResponse = response;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/ActorsParent.h
@@ -0,0 +1,221 @@
+/* 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_filehandle_ActorsParent_h
+#define mozilla_dom_filehandle_ActorsParent_h
+
+#include "mozilla/dom/FileHandleStorage.h"
+#include "mozilla/dom/PBackgroundMutableFileParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+
+template <class> struct already_AddRefed;
+class nsIFile;
+class nsIRunnable;
+class nsIThreadPool;
+template <class> class nsTArray;
+
+namespace mozilla {
+
+namespace ipc {
+
+class PBackgroundParent;
+
+} // namespace ipc
+
+namespace dom {
+
+class BlobImpl;
+class FileHandle;
+class FileHandleOp;
+
+class FileHandleThreadPool final
+{
+  class FileHandleQueue;
+  struct DelayedEnqueueInfo;
+  class DirectoryInfo;
+  struct StoragesCompleteCallback;
+
+  nsCOMPtr<nsIThreadPool> mThreadPool;
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+
+  nsClassHashtable<nsCStringHashKey, DirectoryInfo> mDirectoryInfos;
+
+  nsTArray<nsAutoPtr<StoragesCompleteCallback>> mCompleteCallbacks;
+
+  bool mShutdownRequested;
+  bool mShutdownComplete;
+
+public:
+  static already_AddRefed<FileHandleThreadPool>
+  Create();
+
+#ifdef DEBUG
+  void
+  AssertIsOnOwningThread() const;
+
+  nsIEventTarget*
+  GetThreadPoolEventTarget() const;
+#else
+  void
+  AssertIsOnOwningThread() const
+  { }
+#endif
+
+  void
+  Enqueue(FileHandle* aFileHandle,
+          FileHandleOp* aFileHandleOp,
+          bool aFinish);
+
+  NS_INLINE_DECL_REFCOUNTING(FileHandleThreadPool)
+
+  void
+  WaitForDirectoriesToComplete(nsTArray<nsCString>&& aDirectoryIds,
+                               nsIRunnable* aCallback);
+
+  void
+  Shutdown();
+
+private:
+  FileHandleThreadPool();
+
+  // Reference counted.
+  ~FileHandleThreadPool();
+
+  nsresult
+  Init();
+
+  void
+  Cleanup();
+
+  void
+  FinishFileHandle(FileHandle* aFileHandle);
+
+  bool
+  MaybeFireCallback(StoragesCompleteCallback* aCallback);
+};
+
+class BackgroundMutableFileParentBase
+  : public PBackgroundMutableFileParent
+{
+  nsTHashtable<nsPtrHashKey<FileHandle>> mFileHandles;
+  nsCString mDirectoryId;
+  nsString mFileName;
+  FileHandleStorage mStorage;
+  bool mInvalidated;
+  bool mActorWasAlive;
+  bool mActorDestroyed;
+
+protected:
+  nsCOMPtr<nsIFile> mFile;
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundMutableFileParentBase)
+
+  void
+  Invalidate();
+
+  FileHandleStorage
+  Storage() const
+  {
+    return mStorage;
+  }
+
+  const nsCString&
+  DirectoryId() const
+  {
+    return mDirectoryId;
+  }
+
+  const nsString&
+  FileName() const
+  {
+    return mFileName;
+  }
+
+  bool
+  RegisterFileHandle(FileHandle* aFileHandle);
+
+  void
+  UnregisterFileHandle(FileHandle* aFileHandle);
+
+  void
+  SetActorAlive();
+
+  bool
+  IsActorDestroyed() const
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    return mActorWasAlive && mActorDestroyed;
+  }
+
+  bool
+  IsInvalidated() const
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    return mInvalidated;
+  }
+
+  virtual void
+  NoteActiveState()
+  { }
+
+  virtual void
+  NoteInactiveState()
+  { }
+
+  virtual mozilla::ipc::PBackgroundParent*
+  GetBackgroundParent() const = 0;
+
+  virtual already_AddRefed<nsISupports>
+  CreateStream(bool aReadOnly);
+
+  virtual already_AddRefed<BlobImpl>
+  CreateBlobImpl()
+  {
+    return nullptr;
+  }
+
+protected:
+  BackgroundMutableFileParentBase(FileHandleStorage aStorage,
+                                  const nsACString& aDirectoryId,
+                                  const nsAString& aFileName,
+                                  nsIFile* aFile);
+
+  // Reference counted.
+  ~BackgroundMutableFileParentBase();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual PBackgroundFileHandleParent*
+  AllocPBackgroundFileHandleParent(const FileMode& aMode) override;
+
+  virtual bool
+  RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor,
+                                       const FileMode& aMode) override;
+
+  virtual bool
+  DeallocPBackgroundFileHandleParent(PBackgroundFileHandleParent* aActor)
+                                     override;
+
+  virtual bool
+  RecvDeleteMe() override;
+
+  virtual bool
+  RecvGetFileId(int64_t* aFileId) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_filehandle_ActorsParent_h
deleted file mode 100644
--- a/dom/filehandle/AsyncHelper.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "AsyncHelper.h"
-
-#include "FileService.h"
-#include "MainThreadUtils.h"
-#include "nsDebug.h"
-#include "nsIEventTarget.h"
-#include "nsIRequestObserver.h"
-#include "nsNetUtil.h"
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_ISUPPORTS(AsyncHelper, nsIRunnable, nsIRequest)
-
-nsresult
-AsyncHelper::AsyncWork(nsIRequestObserver* aObserver, nsISupports* aCtxt)
-{
-  nsresult rv;
-
-  if (aObserver) {
-    // build proxy for observer events
-    rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), aObserver, aCtxt);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  FileService* service = FileService::GetOrCreate();
-  NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
-
-  nsIEventTarget* target = service->ThreadPoolTarget();
-
-  rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-AsyncHelper::Run()
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  if (mObserver) {
-    mObserver->OnStartRequest(this, nullptr);
-  }
-
-  mStatus = DoStreamWork(mStream);
-
-  if (mObserver) {
-    mObserver->OnStopRequest(this, nullptr, mStatus);
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-AsyncHelper::GetName(nsACString& aName)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::IsPending(bool* _retval)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::GetStatus(nsresult* aStatus)
-{
-  *aStatus = mStatus;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-AsyncHelper::Cancel(nsresult aStatus)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-AsyncHelper::Suspend()
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::Resume()
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::GetLoadGroup(nsILoadGroup** aLoadGroup)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::SetLoadGroup(nsILoadGroup* aLoadGroup)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::GetLoadFlags(nsLoadFlags* aLoadFlags)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-AsyncHelper::SetLoadFlags(nsLoadFlags aLoadFlags)
-{
-  NS_WARNING("Shouldn't be called!");
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/AsyncHelper.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_AsyncHelper_h
-#define mozilla_dom_AsyncHelper_h
-
-#include "nsCOMPtr.h"
-#include "nsIRequest.h"
-#include "nsIRunnable.h"
-
-class nsIRequestObserver;
-
-namespace mozilla {
-namespace dom {
-
-/**
- * Must be subclassed. The subclass must implement DoStreamWork.
- * Async operations that are not supported in necko (truncate, flush, etc.)
- * should use this helper. Call AsyncWork to invoke the operation.
- */
-class AsyncHelper : public nsIRunnable,
-                    public nsIRequest
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-  NS_DECL_NSIREQUEST
-
-  nsresult
-  AsyncWork(nsIRequestObserver* aObserver, nsISupports* aCtxt);
-
-protected:
-  explicit AsyncHelper(nsISupports* aStream)
-  : mStream(aStream),
-    mStatus(NS_OK)
-  { }
-
-  virtual ~AsyncHelper()
-  { }
-
-  virtual nsresult
-  DoStreamWork(nsISupports* aStream) = 0;
-
-private:
-  nsCOMPtr<nsISupports> mStream;
-  nsCOMPtr<nsIRequestObserver> mObserver;
-
-  nsresult mStatus;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_AsyncHelper_h
rename from dom/filehandle/FileHandle.cpp
rename to dom/filehandle/FileHandleBase.cpp
--- a/dom/filehandle/FileHandle.cpp
+++ b/dom/filehandle/FileHandleBase.cpp
@@ -1,360 +1,237 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#include "FileHandle.h"
+#include "FileHandleBase.h"
 
-#include "AsyncHelper.h"
-#include "FileHelper.h"
-#include "FileRequest.h"
-#include "FileService.h"
-#include "FileStreamWrappers.h"
-#include "MemoryStreams.h"
-#include "mozilla/dom/EncodingUtils.h"
+#include "ActorsChild.h"
+#include "BackgroundChildImpl.h"
+#include "FileRequestBase.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
 #include "mozilla/dom/File.h"
-#include "MutableFile.h"
-#include "nsContentUtils.h"
+#include "mozilla/dom/PBackgroundFileHandle.h"
+#include "mozilla/dom/UnionConversions.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "MutableFileBase.h"
+#include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsError.h"
-#include "nsIEventTarget.h"
-#include "nsISeekableStream.h"
-#include "nsNetUtil.h"
-#include "nsIAsyncStreamCopier.h"
 #include "nsString.h"
-#include "nsStringStream.h"
-#include "nsThreadUtils.h"
-#include "xpcpublic.h"
-
-#define STREAM_COPY_BLOCK_SIZE 32768
 
 namespace mozilla {
 namespace dom {
 
-namespace {
-
-class ReadHelper : public FileHelper
-{
-public:
-  ReadHelper(FileHandleBase* aFileHandle,
-             FileRequestBase* aFileRequest,
-             uint64_t aLocation,
-             uint64_t aSize)
-  : FileHelper(aFileHandle, aFileRequest),
-    mLocation(aLocation), mSize(aSize)
-  {
-    MOZ_ASSERT(mSize, "Passed zero size!");
-  }
-
-  nsresult
-  Init();
-
-  nsresult
-  DoAsyncRun(nsISupports* aStream) override;
-
-  nsresult
-  GetSuccessResult(JSContext* aCx,
-                   JS::MutableHandle<JS::Value> aVal) override;
-
-protected:
-  uint64_t mLocation;
-  uint64_t mSize;
-
-  nsRefPtr<MemoryOutputStream> mStream;
-};
-
-class ReadTextHelper : public ReadHelper
-{
-public:
-  ReadTextHelper(FileHandleBase* aFileHandle,
-                 FileRequestBase* aFileRequest,
-                 uint64_t aLocation,
-                 uint64_t aSize,
-                 const nsAString& aEncoding)
-  : ReadHelper(aFileHandle, aFileRequest, aLocation, aSize),
-    mEncoding(aEncoding)
-  { }
-
-  nsresult
-  GetSuccessResult(JSContext* aCx,
-                   JS::MutableHandle<JS::Value> aVal) override;
-
-private:
-  nsString mEncoding;
-};
-
-class WriteHelper : public FileHelper
-{
-public:
-  WriteHelper(FileHandleBase* aFileHandle,
-              FileRequestBase* aFileRequest,
-              uint64_t aLocation,
-              nsIInputStream* aStream,
-              uint64_t aLength)
-  : FileHelper(aFileHandle, aFileRequest),
-    mLocation(aLocation), mStream(aStream), mLength(aLength)
-  {
-    MOZ_ASSERT(mLength, "Passed zero length!");
-  }
-
-  nsresult
-  DoAsyncRun(nsISupports* aStream);
-
-private:
-  uint64_t mLocation;
-  nsCOMPtr<nsIInputStream> mStream;
-  uint64_t mLength;
-};
-
-class TruncateHelper : public FileHelper
-{
-public:
-  TruncateHelper(FileHandleBase* aFileHandle,
-                 FileRequestBase* aFileRequest,
-                 uint64_t aOffset)
-  : FileHelper(aFileHandle, aFileRequest),
-    mOffset(aOffset)
-  { }
+using namespace mozilla::ipc;
 
-  nsresult
-  DoAsyncRun(nsISupports* aStream);
-
-private:
-  class AsyncTruncator : public AsyncHelper
-  {
-  public:
-    AsyncTruncator(nsISupports* aStream, int64_t aOffset)
-    : AsyncHelper(aStream),
-      mOffset(aOffset)
-    { }
-  protected:
-    nsresult
-    DoStreamWork(nsISupports* aStream) override;
-
-    uint64_t mOffset;
-  };
-
-  uint64_t mOffset;
-};
-
-class FlushHelper : public FileHelper
+FileHandleBase::FileHandleBase(DEBUGONLY(PRThread* aOwningThread,)
+                               FileMode aMode)
+  : RefCountedThreadObject(DEBUGONLY(aOwningThread))
+  , mBackgroundActor(nullptr)
+  , mLocation(0)
+  , mPendingRequestCount(0)
+  , mReadyState(INITIAL)
+  , mMode(aMode)
+  , mAborted(false)
+  , mCreating(false)
+  DEBUGONLY(, mSentFinishOrAbort(false))
+  DEBUGONLY(, mFiredCompleteOrAbort(false))
 {
-public:
-  FlushHelper(FileHandleBase* aFileHandle,
-              FileRequestBase* aFileRequest)
-  : FileHelper(aFileHandle, aFileRequest)
-  { }
-
-  nsresult
-  DoAsyncRun(nsISupports* aStream);
-
-private:
-  class AsyncFlusher : public AsyncHelper
-  {
-  public:
-    explicit AsyncFlusher(nsISupports* aStream)
-    : AsyncHelper(aStream)
-    { }
-  protected:
-    nsresult
-    DoStreamWork(nsISupports* aStream) override;
-  };
-};
-
-class OpenStreamHelper : public FileHelper
-{
-public:
-  OpenStreamHelper(FileHandleBase* aFileHandle,
-                   bool aWholeFile,
-                   uint64_t aStart,
-                   uint64_t aLength)
-  : FileHelper(aFileHandle, nullptr),
-    mWholeFile(aWholeFile), mStart(aStart), mLength(aLength)
-  { }
-
-  nsresult
-  DoAsyncRun(nsISupports* aStream);
-
-  nsCOMPtr<nsIInputStream>&
-  Result()
-  {
-    return mStream;
-  }
-
-private:
-  bool mWholeFile;
-  uint64_t mStart;
-  uint64_t mLength;
-
-  nsCOMPtr<nsIInputStream> mStream;
-};
-
-} // namespace
-
-FileHandleBase::FileHandleBase(FileMode aMode,
-                               RequestMode aRequestMode)
-: mReadyState(INITIAL),
-  mMode(aMode),
-  mRequestMode(aRequestMode),
-  mLocation(0),
-  mPendingRequests(0),
-  mAborted(false),
-  mCreating(false)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 }
 
 FileHandleBase::~FileHandleBase()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mPendingRequestCount);
+  MOZ_ASSERT(!mCreating);
+  MOZ_ASSERT(mSentFinishOrAbort);
+  MOZ_ASSERT_IF(mBackgroundActor, mFiredCompleteOrAbort);
+
+  if (mBackgroundActor) {
+    mBackgroundActor->SendDeleteMeInternal();
+
+    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
+  }
+}
+
+// static
+FileHandleBase*
+FileHandleBase::GetCurrent()
+{
+  MOZ_ASSERT(BackgroundChild::GetForCurrentThread());
+
+  BackgroundChildImpl::ThreadLocal* threadLocal =
+    BackgroundChildImpl::GetThreadLocalForCurrentThread();
+  MOZ_ASSERT(threadLocal);
+
+  return threadLocal->mCurrentFileHandle;
+}
+
+void
+FileHandleBase::SetBackgroundActor(BackgroundFileHandleChild* aActor)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
+  MOZ_ASSERT(!mBackgroundActor);
+
+  mBackgroundActor = aActor;
+}
+
+void
+FileHandleBase::StartRequest(FileRequestBase* aFileRequest,
+                             const FileRequestParams& aParams)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aFileRequest);
+  MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+  BackgroundFileRequestChild* actor =
+    new BackgroundFileRequestChild(DEBUGONLY(mBackgroundActor->OwningThread(),)
+                                   aFileRequest);
+
+  mBackgroundActor->SendPBackgroundFileRequestConstructor(actor, aParams);
+
+  // Balanced in BackgroundFileRequestChild::Recv__delete__().
+  OnNewRequest();
 }
 
 void
 FileHandleBase::OnNewRequest()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  if (!mPendingRequests) {
-    MOZ_ASSERT(mReadyState == INITIAL, "Reusing a file handle!");
+  AssertIsOnOwningThread();
+
+  if (!mPendingRequestCount) {
+    MOZ_ASSERT(mReadyState == INITIAL);
     mReadyState = LOADING;
   }
-  ++mPendingRequests;
+
+  ++mPendingRequestCount;
 }
 
 void
-FileHandleBase::OnRequestFinished()
+FileHandleBase::OnRequestFinished(bool aActorDestroyedNormally)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(mPendingRequests, "Mismatched calls!");
-  --mPendingRequests;
-  if (!mPendingRequests) {
-    MOZ_ASSERT(mAborted || mReadyState == LOADING, "Bad state!");
-    mReadyState = FileHandleBase::FINISHING;
-    Finish();
-  }
-}
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mPendingRequestCount);
 
-nsresult
-FileHandleBase::CreateParallelStream(nsISupports** aStream)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
-  MutableFileBase* mutableFile = MutableFile();
+  --mPendingRequestCount;
 
-  if (mutableFile->IsInvalid()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  nsCOMPtr<nsISupports> stream =
-    mutableFile->CreateStream(mMode == FileMode::Readonly);
-  NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
+  if (!mPendingRequestCount && !MutableFile()->IsInvalidated()) {
+    mReadyState = FINISHING;
 
-  mParallelStreams.AppendElement(stream);
-
-  stream.forget(aStream);
-  return NS_OK;
-}
-
-nsresult
-FileHandleBase::GetOrCreateStream(nsISupports** aStream)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
-  MutableFileBase* mutableFile = MutableFile();
-
-  if (mutableFile->IsInvalid()) {
-    return NS_ERROR_NOT_AVAILABLE;
+    if (aActorDestroyedNormally) {
+      if (!mAborted) {
+        SendFinish();
+      } else {
+        SendAbort();
+      }
+    } else {
+      // Don't try to send any more messages to the parent if the request actor
+      // was killed.
+#ifdef DEBUG
+      MOZ_ASSERT(!mSentFinishOrAbort);
+      mSentFinishOrAbort = true;
+#endif
+    }
   }
-
-  if (!mStream) {
-    nsCOMPtr<nsISupports> stream =
-      mutableFile->CreateStream(mMode == FileMode::Readonly);
-    NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE);
-
-    stream.swap(mStream);
-  }
-
-  nsCOMPtr<nsISupports> stream(mStream);
-  stream.forget(aStream);
-
-  return NS_OK;
 }
 
 bool
 FileHandleBase::IsOpen() const
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // If we haven't started anything then we're open.
   if (mReadyState == INITIAL) {
-    MOZ_ASSERT(FileHelper::GetCurrentFileHandle() != this,
-               "This should be some other file handle (or null)!");
     return true;
   }
 
   // If we've already started then we need to check to see if we still have the
   // mCreating flag set. If we do (i.e. we haven't returned to the event loop
   // from the time we were created) then we are open. Otherwise check the
   // currently running file handles to see if it's the same. We only allow other
   // requests to be made if this file handle is currently running.
-  if (mReadyState == LOADING) {
-    if (mCreating) {
-      return true;
-    }
-
-    if (FileHelper::GetCurrentFileHandle() == this) {
-      return true;
-    }
+  if (mReadyState == LOADING && (mCreating || GetCurrent() == this)) {
+    return true;
   }
 
   return false;
 }
 
+void
+FileHandleBase::Abort()
+{
+  AssertIsOnOwningThread();
+
+  if (IsFinishingOrDone()) {
+    // Already started (and maybe finished) the finish or abort so there is
+    // nothing to do here.
+    return;
+  }
+
+  const bool isInvalidated = MutableFile()->IsInvalidated();
+  bool needToSendAbort = mReadyState == INITIAL && !isInvalidated;
+
+#ifdef DEBUG
+  if (isInvalidated) {
+    mSentFinishOrAbort = true;
+  }
+#endif
+
+  mAborted = true;
+  mReadyState = DONE;
+
+  // Fire the abort event if there are no outstanding requests. Otherwise the
+  // abort event will be fired when all outstanding requests finish.
+  if (needToSendAbort) {
+    SendAbort();
+  }
+}
+
 already_AddRefed<FileRequestBase>
 FileHandleBase::Read(uint64_t aSize, bool aHasEncoding,
                      const nsAString& aEncoding, ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // State and argument checking for read
   if (!CheckStateAndArgumentsForRead(aSize, aRv)) {
     return nullptr;
   }
 
   // Do nothing if the window is closed
   if (!CheckWindow()) {
     return nullptr;
   }
 
-  nsRefPtr<FileRequestBase> fileRequest = GenerateFileRequest();
+  FileRequestReadParams params;
+  params.offset() = mLocation;
+  params.size() = aSize;
 
-  nsRefPtr<ReadHelper> helper;
+  nsRefPtr<FileRequestBase> fileRequest = GenerateFileRequest();
   if (aHasEncoding) {
-    helper = new ReadTextHelper(this, fileRequest, mLocation, aSize, aEncoding);
-  } else {
-    helper = new ReadHelper(this, fileRequest, mLocation, aSize);
+    fileRequest->SetEncoding(aEncoding);
   }
 
-  if (NS_WARN_IF(NS_FAILED(helper->Init())) ||
-      NS_WARN_IF(NS_FAILED(helper->Enqueue()))) {
-    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-    return nullptr;
-  }
+  StartRequest(fileRequest, params);
 
   mLocation += aSize;
 
   return fileRequest.forget();
 }
 
 already_AddRefed<FileRequestBase>
 FileHandleBase::Truncate(const Optional<uint64_t>& aSize, ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // State checking for write
   if (!CheckStateForWrite(aRv)) {
     return nullptr;
   }
 
   // Getting location and additional state checking for truncate
   uint64_t location;
@@ -370,137 +247,96 @@ FileHandleBase::Truncate(const Optional<
     location = mLocation;
   }
 
   // Do nothing if the window is closed
   if (!CheckWindow()) {
     return nullptr;
   }
 
+  FileRequestTruncateParams params;
+  params.offset() = location;
+
   nsRefPtr<FileRequestBase> fileRequest = GenerateFileRequest();
 
-  nsRefPtr<TruncateHelper> helper =
-    new TruncateHelper(this, fileRequest, location);
-
-  if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) {
-    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-    return nullptr;
-  }
+  StartRequest(fileRequest, params);
 
   if (aSize.WasPassed()) {
     mLocation = aSize.Value();
   }
 
   return fileRequest.forget();
 }
 
 already_AddRefed<FileRequestBase>
 FileHandleBase::Flush(ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // State checking for write
   if (!CheckStateForWrite(aRv)) {
     return nullptr;
   }
 
   // Do nothing if the window is closed
   if (!CheckWindow()) {
     return nullptr;
   }
 
+  FileRequestFlushParams params;
+
   nsRefPtr<FileRequestBase> fileRequest = GenerateFileRequest();
 
-  nsRefPtr<FlushHelper> helper = new FlushHelper(this, fileRequest);
-
-  if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) {
-    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-    return nullptr;
-  }
+  StartRequest(fileRequest, params);
 
   return fileRequest.forget();
 }
 
 void
 FileHandleBase::Abort(ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // This method is special enough for not using generic state checking methods.
 
-  // We can't use IsOpen here since we need it to be possible to call Abort()
-  // even from outside of transaction callbacks.
-  if (mReadyState != FileHandleBase::INITIAL &&
-      mReadyState != FileHandleBase::LOADING) {
+  if (IsFinishingOrDone()) {
     aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR);
     return;
   }
 
-  bool needToFinish = mReadyState == INITIAL;
+  Abort();
+}
 
-  mAborted = true;
+void
+FileHandleBase::HandleCompleteOrAbort(bool aAborted)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mFiredCompleteOrAbort);
+
   mReadyState = DONE;
 
-  // Fire the abort event if there are no outstanding requests. Otherwise the
-  // abort event will be fired when all outstanding requests finish.
-  if (needToFinish) {
-    aRv = Finish();
-  }
+  DEBUGONLY(mFiredCompleteOrAbort = true;)
 }
 
 void
 FileHandleBase::OnReturnToEventLoop()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
 
   // We're back at the event loop, no longer newborn.
   mCreating = false;
 
-  // Maybe set the readyState to DONE if there were no requests generated.
+  // Maybe finish if there were no requests generated.
   if (mReadyState == INITIAL) {
     mReadyState = DONE;
 
-    if (NS_FAILED(Finish())) {
-      NS_WARNING("Failed to finish!");
-    }
+    SendFinish();
   }
 }
 
-nsresult
-FileHandleBase::OpenInputStream(bool aWholeFile, uint64_t aStart,
-                                uint64_t aLength, nsIInputStream** aResult)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(mRequestMode == PARALLEL,
-             "Don't call me in other than parallel mode!");
-
-  // Common state checking
-  ErrorResult error;
-  if (!CheckState(error)) {
-    return error.StealNSResult();
-  }
-
-  // Do nothing if the window is closed
-  if (!CheckWindow()) {
-    return NS_OK;
-  }
-
-  nsRefPtr<OpenStreamHelper> helper =
-    new OpenStreamHelper(this, aWholeFile, aStart, aLength);
-
-  nsresult rv = helper->Enqueue();
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-
-  nsCOMPtr<nsIInputStream>& result = helper->Result();
-  NS_ENSURE_TRUE(result, NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-
-  result.forget(aResult);
-  return NS_OK;
-}
-
 bool
 FileHandleBase::CheckState(ErrorResult& aRv)
 {
   if (!IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR);
     return false;
   }
 
@@ -542,360 +378,259 @@ FileHandleBase::CheckStateForWrite(Error
   if (mMode != FileMode::Readwrite) {
     aRv.Throw(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR);
     return false;
   }
 
   return true;
 }
 
+bool
+FileHandleBase::CheckStateForWriteOrAppend(bool aAppend, ErrorResult& aRv)
+{
+  // State checking for write
+  if (!CheckStateForWrite(aRv)) {
+    return false;
+  }
+
+  // Additional state checking for write
+  if (!aAppend && mLocation == UINT64_MAX) {
+    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR);
+    return false;
+  }
+
+  return true;
+}
+
 already_AddRefed<FileRequestBase>
-FileHandleBase::WriteInternal(nsIInputStream* aInputStream,
-                              uint64_t aInputLength, bool aAppend,
+FileHandleBase::WriteOrAppend(
+                       const StringOrArrayBufferOrArrayBufferViewOrBlob& aValue,
+                       bool aAppend,
+                       ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+
+  if (aValue.IsString()) {
+    return WriteOrAppend(aValue.GetAsString(), aAppend, aRv);
+  }
+
+  if (aValue.IsArrayBuffer()) {
+    return WriteOrAppend(aValue.GetAsArrayBuffer(), aAppend, aRv);
+  }
+
+  if (aValue.IsArrayBufferView()) {
+    return WriteOrAppend(aValue.GetAsArrayBufferView(), aAppend, aRv);
+  }
+
+  MOZ_ASSERT(aValue.IsBlob());
+  return WriteOrAppend(aValue.GetAsBlob(), aAppend, aRv);
+}
+
+already_AddRefed<FileRequestBase>
+FileHandleBase::WriteOrAppend(const nsAString& aValue,
+                              bool aAppend,
+                              ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+
+  // State checking for write or append
+  if (!CheckStateForWriteOrAppend(aAppend, aRv)) {
+    return nullptr;
+  }
+
+  NS_ConvertUTF16toUTF8 cstr(aValue);
+
+  uint64_t dataLength = cstr.Length();;
+  if (!dataLength) {
+    return nullptr;
+  }
+
+  FileRequestStringData stringData(cstr);
+
+  // Do nothing if the window is closed
+  if (!CheckWindow()) {
+    return nullptr;
+  }
+
+  return WriteInternal(stringData, dataLength, aAppend, aRv);
+}
+
+already_AddRefed<FileRequestBase>
+FileHandleBase::WriteOrAppend(const ArrayBuffer& aValue,
+                              bool aAppend,
                               ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+  AssertIsOnOwningThread();
+
+  // State checking for write or append
+  if (!CheckStateForWriteOrAppend(aAppend, aRv)) {
+    return nullptr;
+  }
+
+  aValue.ComputeLengthAndData();
+
+  uint64_t dataLength = aValue.Length();;
+  if (!dataLength) {
+    return nullptr;
+  }
+
+  const char* data = reinterpret_cast<const char*>(aValue.Data());
+
+  FileRequestStringData stringData;
+  if (NS_WARN_IF(!stringData.string().Assign(data, aValue.Length(),
+                                             fallible_t()))) {
+    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
+    return nullptr;
+  }
+
+  // Do nothing if the window is closed
+  if (!CheckWindow()) {
+    return nullptr;
+  }
+
+  return WriteInternal(stringData, dataLength, aAppend, aRv);
+}
+
+already_AddRefed<FileRequestBase>
+FileHandleBase::WriteOrAppend(const ArrayBufferView& aValue,
+                              bool aAppend,
+                              ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+
+  // State checking for write or append
+  if (!CheckStateForWriteOrAppend(aAppend, aRv)) {
+    return nullptr;
+  }
+
+  aValue.ComputeLengthAndData();
+
+  uint64_t dataLength = aValue.Length();;
+  if (!dataLength) {
+    return nullptr;
+  }
+
+  const char* data = reinterpret_cast<const char*>(aValue.Data());
+
+  FileRequestStringData stringData;
+  if (NS_WARN_IF(!stringData.string().Assign(data, aValue.Length(),
+                                             fallible_t()))) {
+    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
+    return nullptr;
+  }
+
+  // Do nothing if the window is closed
+  if (!CheckWindow()) {
+    return nullptr;
+  }
+
+  return WriteInternal(stringData, dataLength, aAppend, aRv);
+}
+
+already_AddRefed<FileRequestBase>
+FileHandleBase::WriteOrAppend(Blob& aValue,
+                              bool aAppend,
+                              ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+
+  // State checking for write or append
+  if (!CheckStateForWriteOrAppend(aAppend, aRv)) {
+    return nullptr;
+  }
+
+  ErrorResult rv;
+  uint64_t dataLength = aValue.GetSize(rv);
+  if (NS_WARN_IF(rv.Failed())) {
+    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
+    return nullptr;
+  }
+
+  if (!dataLength) {
+    return nullptr;
+  }
+
+  PBackgroundChild* backgroundActor = BackgroundChild::GetForCurrentThread();
+  MOZ_ASSERT(backgroundActor);
+
+  PBlobChild* blobActor =
+    BackgroundChild::GetOrCreateActorForBlob(backgroundActor, &aValue);
+  if (NS_WARN_IF(!blobActor)) {
+    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
+    return nullptr;
+  }
+
+  FileRequestBlobData blobData;
+  blobData.blobChild() = blobActor;
+
+  // Do nothing if the window is closed
+  if (!CheckWindow()) {
+    return nullptr;
+  }
+
+  return WriteInternal(blobData, dataLength, aAppend, aRv);
+}
+
+already_AddRefed<FileRequestBase>
+FileHandleBase::WriteInternal(const FileRequestData& aData,
+                              uint64_t aDataLength,
+                              bool aAppend,
+                              ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
 
   DebugOnly<ErrorResult> error;
   MOZ_ASSERT(CheckStateForWrite(error));
   MOZ_ASSERT_IF(!aAppend, mLocation != UINT64_MAX);
-  MOZ_ASSERT(aInputStream);
-  MOZ_ASSERT(aInputLength);
+  MOZ_ASSERT(aDataLength);
   MOZ_ASSERT(CheckWindow());
 
+  FileRequestWriteParams params;
+  params.offset() = aAppend ? UINT64_MAX : mLocation;
+  params.data() = aData;
+  params.dataLength() = aDataLength;
+
   nsRefPtr<FileRequestBase> fileRequest = GenerateFileRequest();
-
-  uint64_t location = aAppend ? UINT64_MAX : mLocation;
+  MOZ_ASSERT(fileRequest);
 
-  nsRefPtr<WriteHelper> helper =
-    new WriteHelper(this, fileRequest, location, aInputStream, aInputLength);
-
-  if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) {
-    aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
-    return nullptr;
-  }
+  StartRequest(fileRequest, params);
 
   if (aAppend) {
     mLocation = UINT64_MAX;
   }
   else {
-    mLocation += aInputLength;
+    mLocation += aDataLength;
   }
 
   return fileRequest.forget();
 }
 
-nsresult
-FileHandleBase::Finish()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
-  nsRefPtr<FinishHelper> helper(new FinishHelper(this));
-
-  FileService* service = FileService::Get();
-  MOZ_ASSERT(service, "This should never be null");
-
-  nsIEventTarget* target = service->ThreadPoolTarget();
-
-  nsresult rv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-// static
-already_AddRefed<nsIInputStream>
-FileHandleBase::GetInputStream(const ArrayBuffer& aValue,
-                               uint64_t* aInputLength, ErrorResult& aRv)
+void
+FileHandleBase::SendFinish()
 {
-  aValue.ComputeLengthAndData();
-  const char* data = reinterpret_cast<const char*>(aValue.Data());
-  uint32_t length = aValue.Length();
-
-  nsCOMPtr<nsIInputStream> stream;
-  aRv = NS_NewByteInputStream(getter_AddRefs(stream), data, length,
-                              NS_ASSIGNMENT_COPY);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  *aInputLength = length;
-  return stream.forget();
-}
-
-// static
-already_AddRefed<nsIInputStream>
-FileHandleBase::GetInputStream(const Blob& aValue, uint64_t* aInputLength,
-                               ErrorResult& aRv)
-{
-  Blob& file = const_cast<Blob&>(aValue);
-  uint64_t length = file.GetSize(aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIInputStream> stream;
-  file.GetInternalStream(getter_AddRefs(stream), aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  *aInputLength = length;
-  return stream.forget();
-}
-
-// static
-already_AddRefed<nsIInputStream>
-FileHandleBase::GetInputStream(const nsAString& aValue, uint64_t* aInputLength,
-                               ErrorResult& aRv)
-{
-  NS_ConvertUTF16toUTF8 cstr(aValue);
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mAborted);
+  MOZ_ASSERT(IsFinishingOrDone());
+  MOZ_ASSERT(!mSentFinishOrAbort);
+  MOZ_ASSERT(!mPendingRequestCount);
 
-  nsCOMPtr<nsIInputStream> stream;
-  aRv = NS_NewCStringInputStream(getter_AddRefs(stream), cstr);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
-  *aInputLength = cstr.Length();
-  return stream.forget();
-}
-
-FinishHelper::FinishHelper(FileHandleBase* aFileHandle)
-: mFileHandle(aFileHandle),
-  mAborted(aFileHandle->mAborted)
-{
-  mParallelStreams.SwapElements(aFileHandle->mParallelStreams);
-  mStream.swap(aFileHandle->mStream);
-}
-
-NS_IMPL_ISUPPORTS(FinishHelper, nsIRunnable)
-
-NS_IMETHODIMP
-FinishHelper::Run()
-{
-  if (NS_IsMainThread()) {
-    mFileHandle->mReadyState = FileHandleBase::DONE;
-
-    FileService* service = FileService::Get();
-    if (service) {
-      service->NotifyFileHandleCompleted(mFileHandle);
-    }
-
-    nsresult rv = mFileHandle->OnCompleteOrAbort(mAborted);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    mFileHandle = nullptr;
+  MOZ_ASSERT(mBackgroundActor);
+  mBackgroundActor->SendFinish();
 
-    return NS_OK;
-  }
-
-  if (mFileHandle->MutableFile()->IsInvalid()) {
-    mAborted = true;
-  }
-
-  for (uint32_t index = 0; index < mParallelStreams.Length(); index++) {
-    nsCOMPtr<nsIInputStream> stream =
-      do_QueryInterface(mParallelStreams[index]);
-
-    if (NS_FAILED(stream->Close())) {
-      NS_WARNING("Failed to close stream!");
-    }
-
-    mParallelStreams[index] = nullptr;
-  }
-
-  if (mStream) {
-    nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
-
-    if (NS_FAILED(stream->Close())) {
-      NS_WARNING("Failed to close stream!");
-    }
-
-    mStream = nullptr;
-  }
-
-  return NS_DispatchToMainThread(this);
-}
-
-nsresult
-ReadHelper::Init()
-{
-  mStream = MemoryOutputStream::Create(mSize);
-  NS_ENSURE_TRUE(mStream, NS_ERROR_FAILURE);
-
-  return NS_OK;
+  DEBUGONLY(mSentFinishOrAbort = true;)
 }
 
-nsresult
-ReadHelper::DoAsyncRun(nsISupports* aStream)
-{
-  MOZ_ASSERT(aStream, "Passed a null stream!");
-
-  uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
-
-  nsCOMPtr<nsIInputStream> istream =
-    new FileInputStreamWrapper(aStream, this, mLocation, mSize, flags);
-
-  FileService* service = FileService::Get();
-  MOZ_ASSERT(service, "This should never be null");
-
-  nsIEventTarget* target = service->ThreadPoolTarget();
-
-  nsCOMPtr<nsIAsyncStreamCopier> copier;
-  nsresult rv =
-    NS_NewAsyncStreamCopier(getter_AddRefs(copier), istream, mStream, target,
-                            false, true, STREAM_COPY_BLOCK_SIZE);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = copier->AsyncCopy(this, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mRequest = do_QueryInterface(copier);
-
-  return NS_OK;
-}
-
-nsresult
-ReadHelper::GetSuccessResult(JSContext* aCx,
-                             JS::MutableHandle<JS::Value> aVal)
+void
+FileHandleBase::SendAbort()
 {
-  JS::Rooted<JSObject*> arrayBuffer(aCx);
-  nsresult rv =
-    nsContentUtils::CreateArrayBuffer(aCx, mStream->Data(), arrayBuffer.address());
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  aVal.setObject(*arrayBuffer);
-  return NS_OK;
-}
-
-nsresult
-ReadTextHelper::GetSuccessResult(JSContext* aCx,
-                                 JS::MutableHandle<JS::Value> aVal)
-{
-  nsAutoCString encoding;
-  const nsCString& data = mStream->Data();
-  // The BOM sniffing is baked into the "decode" part of the Encoding
-  // Standard, which the File API references.
-  if (!nsContentUtils::CheckForBOM(
-        reinterpret_cast<const unsigned char *>(data.get()),
-        data.Length(),
-        encoding)) {
-    // BOM sniffing failed. Try the API argument.
-    if (!EncodingUtils::FindEncodingForLabel(mEncoding, encoding)) {
-      // API argument failed. Since we are dealing with a file system file,
-      // we don't have a meaningful type attribute for the blob available,
-      // so proceeding to the next step, which is defaulting to UTF-8.
-      encoding.AssignLiteral("UTF-8");
-    }
-  }
-
-  nsString tmpString;
-  nsresult rv = nsContentUtils::ConvertStringFromEncoding(encoding, data,
-                                                          tmpString);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!xpc::StringToJsval(aCx, tmpString, aVal)) {
-    NS_WARNING("Failed to convert string!");
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
-nsresult
-WriteHelper::DoAsyncRun(nsISupports* aStream)
-{
-  MOZ_ASSERT(aStream, "Passed a null stream!");
-
-  uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mAborted);
+  MOZ_ASSERT(IsFinishingOrDone());
+  MOZ_ASSERT(!mSentFinishOrAbort);
 
-  nsCOMPtr<nsIOutputStream> ostream =
-    new FileOutputStreamWrapper(aStream, this, mLocation, mLength, flags);
-
-  FileService* service = FileService::Get();
-  MOZ_ASSERT(service, "This should never be null");
-
-  nsIEventTarget* target = service->ThreadPoolTarget();
-
-  nsCOMPtr<nsIAsyncStreamCopier> copier;
-  nsresult rv =
-    NS_NewAsyncStreamCopier(getter_AddRefs(copier), mStream, ostream, target,
-                            true, false, STREAM_COPY_BLOCK_SIZE);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = copier->AsyncCopy(this, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mRequest = do_QueryInterface(copier);
-
-  return NS_OK;
-}
-
-nsresult
-TruncateHelper::DoAsyncRun(nsISupports* aStream)
-{
-  MOZ_ASSERT(aStream, "Passed a null stream!");
-
-  nsRefPtr<AsyncTruncator> truncator = new AsyncTruncator(aStream, mOffset);
-
-  nsresult rv = truncator->AsyncWork(this, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-TruncateHelper::AsyncTruncator::DoStreamWork(nsISupports* aStream)
-{
-  nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(aStream);
-
-  nsresult rv = sstream->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_ASSERT(mBackgroundActor);
+  mBackgroundActor->SendAbort();
 
-  rv = sstream->SetEOF();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-FlushHelper::DoAsyncRun(nsISupports* aStream)
-{
-  MOZ_ASSERT(aStream, "Passed a null stream!");
-
-  nsRefPtr<AsyncFlusher> flusher = new AsyncFlusher(aStream);
-
-  nsresult rv = flusher->AsyncWork(this, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-FlushHelper::AsyncFlusher::DoStreamWork(nsISupports* aStream)
-{
-  nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(aStream);
-
-  nsresult rv = ostream->Flush();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-OpenStreamHelper::DoAsyncRun(nsISupports* aStream)
-{
-  MOZ_ASSERT(aStream, "Passed a null stream!");
-
-  uint32_t flags = FileStreamWrapper::NOTIFY_CLOSE |
-                   FileStreamWrapper::NOTIFY_DESTROY;
-
-  mStream = mWholeFile ?
-    new FileInputStreamWrapper(aStream, this, 0, mLength, flags) :
-    new FileInputStreamWrapper(aStream, this, mStart, mLength, flags);
-
-  return NS_OK;
+  DEBUGONLY(mSentFinishOrAbort = true;)
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/filehandle/FileHandle.h
rename to dom/filehandle/FileHandleBase.h
--- a/dom/filehandle/FileHandle.h
+++ b/dom/filehandle/FileHandleBase.h
@@ -2,148 +2,161 @@
 /* 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_FileHandle_h
 #define mozilla_dom_FileHandle_h
 
-#include "MainThreadUtils.h"
-#include "mozilla/Assertions.h"
+#include "FileHandleCommon.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/FileModeBinding.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/TypedArray.h"
-#include "mozilla/ErrorResult.h"
-#include "nsAutoPtr.h"
-#include "nsCOMPtr.h"
-#include "nsIInputStream.h"
-#include "nsIRunnable.h"
-#include "nsTArray.h"
 
+template <class> struct already_AddRefed;
 class nsAString;
+struct PRThread;
 
 namespace mozilla {
+
+class ErrorResult;
+
 namespace dom {
 
+class BackgroundFileHandleChild;
 class Blob;
-class FileHelper;
 class FileRequestBase;
-class FileService;
-class FinishHelper;
-class MetadataHelper;
+class FileRequestData;
+class FileRequestParams;
 class MutableFileBase;
+class StringOrArrayBufferOrArrayBufferViewOrBlob;
 
 /**
  * This class provides a base for FileHandle implementations.
  */
 class FileHandleBase
+  : public RefCountedThreadObject
 {
 public:
-  enum RequestMode
-  {
-    NORMAL = 0, // Sequential
-    PARALLEL
-  };
-
   enum ReadyState
   {
     INITIAL = 0,
     LOADING,
     FINISHING,
     DONE
   };
 
 private:
-  friend class FileHelper;
-  friend class FileService;
-  friend class FinishHelper;
-  friend class MetadataHelper;
+  BackgroundFileHandleChild* mBackgroundActor;
+
+  uint64_t mLocation;
+
+  uint32_t mPendingRequestCount;
 
   ReadyState mReadyState;
   FileMode mMode;
-  RequestMode mRequestMode;
-  uint64_t mLocation;
-  uint32_t mPendingRequests;
-
-  nsTArray<nsCOMPtr<nsISupports>> mParallelStreams;
-  nsCOMPtr<nsISupports> mStream;
 
   bool mAborted;
   bool mCreating;
 
+  DEBUGONLY(bool mSentFinishOrAbort;)
+  DEBUGONLY(bool mFiredCompleteOrAbort;)
+
 public:
-  NS_IMETHOD_(MozExternalRefCountType)
-  AddRef() = 0;
+  static FileHandleBase*
+  GetCurrent();
+
+  void
+  SetBackgroundActor(BackgroundFileHandleChild* aActor);
 
-  NS_IMETHOD_(MozExternalRefCountType)
-  Release() = 0;
+  void
+  ClearBackgroundActor()
+  {
+    AssertIsOnOwningThread();
+
+    mBackgroundActor = nullptr;
+  }
 
-  nsresult
-  CreateParallelStream(nsISupports** aStream);
+  void
+  StartRequest(FileRequestBase* aFileRequest, const FileRequestParams& aParams);
 
-  nsresult
-  GetOrCreateStream(nsISupports** aStream);
+  void
+  OnNewRequest();
+
+  void
+  OnRequestFinished(bool aActorDestroyedNormally);
 
   bool
   IsOpen() const;
 
   bool
+  IsFinishingOrDone() const
+  {
+    AssertIsOnOwningThread();
+
+    return mReadyState == FINISHING || mReadyState == DONE;
+  }
+
+  bool
+  IsDone() const
+  {
+    AssertIsOnOwningThread();
+
+    return mReadyState == DONE;
+  }
+
+  bool
   IsAborted() const
   {
+    AssertIsOnOwningThread();
     return mAborted;
   }
 
   void
   SetCreating()
   {
     mCreating = true;
   }
 
-  virtual MutableFileBase*
-  MutableFile() const = 0;
-
-  nsresult
-  OpenInputStream(bool aWholeFile, uint64_t aStart, uint64_t aLength,
-                  nsIInputStream** aResult);
+  void
+  Abort();
 
   // Shared WebIDL (IndexedDB FileHandle and FileSystem FileHandle)
   FileMode
   Mode() const
   {
-    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
+    AssertIsOnOwningThread();
     return mMode;
   }
 
   bool
   Active() const
   {
-    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
+    AssertIsOnOwningThread();
     return IsOpen();
   }
 
   Nullable<uint64_t>
   GetLocation() const
   {
-    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+    AssertIsOnOwningThread();
 
     if (mLocation == UINT64_MAX) {
       return Nullable<uint64_t>();
     }
 
     return Nullable<uint64_t>(mLocation);
   }
 
   void
   SetLocation(const Nullable<uint64_t>& aLocation)
   {
-    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
+    AssertIsOnOwningThread();
 
     // Null means the end-of-file.
     if (aLocation.IsNull()) {
       mLocation = UINT64_MAX;
     } else {
       mLocation = aLocation.Value();
     }
   }
@@ -156,119 +169,78 @@ public:
   Truncate(const Optional<uint64_t>& aSize, ErrorResult& aRv);
 
   already_AddRefed<FileRequestBase>
   Flush(ErrorResult& aRv);
 
   void
   Abort(ErrorResult& aRv);
 
+  //  Must be overridden in subclasses.
+  virtual MutableFileBase*
+  MutableFile() const = 0;
+
+  //  May be overridden in subclasses.
+  virtual void
+  HandleCompleteOrAbort(bool aAborted);
+
 protected:
-  FileHandleBase(FileMode aMode,
-                 RequestMode aRequestMode);
+  FileHandleBase(DEBUGONLY(PRThread* aOwningThread,)
+                 FileMode aMode);
+
   ~FileHandleBase();
 
   void
-  OnNewRequest();
-
-  void
-  OnRequestFinished();
-
-  void
   OnReturnToEventLoop();
 
-  virtual nsresult
-  OnCompleteOrAbort(bool aAborted) = 0;
-
   bool
   CheckState(ErrorResult& aRv);
 
   bool
   CheckStateAndArgumentsForRead(uint64_t aSize, ErrorResult& aRv);
 
   bool
   CheckStateForWrite(ErrorResult& aRv);
 
+  bool
+  CheckStateForWriteOrAppend(bool aAppend, ErrorResult& aRv);
+
+  already_AddRefed<FileRequestBase>
+  WriteOrAppend(const StringOrArrayBufferOrArrayBufferViewOrBlob& aValue,
+                bool aAppend,
+                ErrorResult& aRv);
+
+  //  Must be overridden in subclasses.
   virtual bool
   CheckWindow() = 0;
 
+  //  Must be overridden in subclasses.
   virtual already_AddRefed<FileRequestBase>
   GenerateFileRequest() = 0;
 
-  template<class T>
+private:
   already_AddRefed<FileRequestBase>
-  WriteOrAppend(const T& aValue, bool aAppend, ErrorResult& aRv)
-  {
-    MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-
-    // State checking for write
-    if (!CheckStateForWrite(aRv)) {
-      return nullptr;
-    }
-
-    // Additional state checking for write
-    if (!aAppend && mLocation == UINT64_MAX) {
-      aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR);
-      return nullptr;
-    }
+  WriteOrAppend(const nsAString& aValue, bool aAppend, ErrorResult& aRv);
 
-    uint64_t length;
-    nsCOMPtr<nsIInputStream> stream = GetInputStream(aValue, &length, aRv);
-    if (aRv.Failed()) {
-      return nullptr;
-    }
+  already_AddRefed<FileRequestBase>
+  WriteOrAppend(const ArrayBuffer& aValue, bool aAppend, ErrorResult& aRv);
 
-    if (!length) {
-      return nullptr;
-    }
-
-    // Do nothing if the window is closed
-    if (!CheckWindow()) {
-      return nullptr;
-    }
-
-    return WriteInternal(stream, length, aAppend, aRv);
-  }
+  already_AddRefed<FileRequestBase>
+  WriteOrAppend(const ArrayBufferView& aValue, bool aAppend, ErrorResult& aRv);
 
   already_AddRefed<FileRequestBase>
-  WriteInternal(nsIInputStream* aInputStream, uint64_t aInputLength,
+  WriteOrAppend(Blob& aValue, bool aAppend, ErrorResult& aRv);
+
+  already_AddRefed<FileRequestBase>
+  WriteInternal(const FileRequestData& aData, uint64_t aDataLength,
                 bool aAppend, ErrorResult& aRv);
 
-  nsresult
-  Finish();
-
-  static already_AddRefed<nsIInputStream>
-  GetInputStream(const ArrayBuffer& aValue, uint64_t* aInputLength,
-                 ErrorResult& aRv);
-
-  static already_AddRefed<nsIInputStream>
-  GetInputStream(const Blob& aValue, uint64_t* aInputLength,
-                 ErrorResult& aRv);
-
-  static already_AddRefed<nsIInputStream>
-  GetInputStream(const nsAString& aValue, uint64_t* aInputLength,
-                 ErrorResult& aRv);
-};
+  void
+  SendFinish();
 
-class FinishHelper final : public nsIRunnable
-{
-  friend class FileHandleBase;
-
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-private:
-  explicit FinishHelper(FileHandleBase* aFileHandle);
-  ~FinishHelper()
-  { }
-
-  nsRefPtr<FileHandleBase> mFileHandle;
-  nsTArray<nsCOMPtr<nsISupports>> mParallelStreams;
-  nsCOMPtr<nsISupports> mStream;
-
-  bool mAborted;
+  void
+  SendAbort();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileHandle_h
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/FileHandleCommon.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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/. */
+
+#include "FileHandleCommon.h"
+
+#include "mozilla/Assertions.h"
+#include "prthread.h"
+
+namespace mozilla {
+namespace dom {
+
+#ifdef DEBUG
+
+void
+ThreadObject::AssertIsOnOwningThread() const
+{
+  MOZ_ASSERT(mOwningThread);
+  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+}
+
+PRThread*
+ThreadObject::OwningThread() const
+{
+  MOZ_ASSERT(mOwningThread);
+  return mOwningThread;
+}
+
+#endif // DEBUG
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/FileHandleCommon.h
@@ -0,0 +1,73 @@
+/* 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_FileHandleCommon_h
+#define mozilla_dom_FileHandleCommon_h
+
+#include "nscore.h"
+
+#ifdef DEBUG
+#define DEBUGONLY(...)  __VA_ARGS__
+#else
+#define DEBUGONLY(...)  /* nothing */
+#endif
+
+struct PRThread;
+
+namespace mozilla {
+namespace dom {
+
+class RefCountedObject
+{
+public:
+  NS_IMETHOD_(MozExternalRefCountType)
+  AddRef() = 0;
+
+  NS_IMETHOD_(MozExternalRefCountType)
+  Release() = 0;
+
+protected:
+  virtual ~RefCountedObject()
+  { }
+};
+
+class ThreadObject
+{
+  DEBUGONLY(PRThread* mOwningThread;)
+
+public:
+  explicit ThreadObject(DEBUGONLY(PRThread* aOwningThread))
+    DEBUGONLY(: mOwningThread(aOwningThread))
+  { }
+
+  virtual ~ThreadObject()
+  { }
+
+#ifdef DEBUG
+  void
+  AssertIsOnOwningThread() const;
+
+  PRThread*
+  OwningThread() const;
+#else
+  void
+  AssertIsOnOwningThread() const
+  { }
+#endif
+};
+
+class RefCountedThreadObject
+  : public RefCountedObject
+  , public ThreadObject
+{
+public:
+  explicit RefCountedThreadObject(DEBUGONLY(PRThread* aOwningThread))
+    : ThreadObject(DEBUGONLY(aOwningThread))
+  { }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileHandleCommon_h
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/FileHandleStorage.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_FileHandleStorage_h
+#define mozilla_dom_FileHandleStorage_h
+
+namespace mozilla {
+namespace dom {
+
+enum FileHandleStorage
+{
+  FILE_HANDLE_STORAGE_IDB = 0,
+  // A placeholder for bug 997471
+  //FILE_HANDLE_STORAGE_FS
+  FILE_HANDLE_STORAGE_MAX
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FileHandleStorage_h
deleted file mode 100644
--- a/dom/filehandle/FileHelper.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "FileHelper.h"
-
-#include "FileHandle.h"
-#include "FileRequest.h"
-#include "FileService.h"
-#include "js/Value.h"
-#include "MainThreadUtils.h"
-#include "mozilla/Assertions.h"
-#include "MutableFile.h"
-#include "nsDebug.h"
-#include "nsError.h"
-#include "nsIRequest.h"
-
-namespace mozilla {
-namespace dom {
-
-namespace {
-
-FileHandleBase* gCurrentFileHandle = nullptr;
-
-} // namespace
-
-FileHelper::FileHelper(FileHandleBase* aFileHandle,
-                       FileRequestBase* aFileRequest)
-: mMutableFile(aFileHandle->MutableFile()),
-  mFileHandle(aFileHandle),
-  mFileRequest(aFileRequest),
-  mResultCode(NS_OK),
-  mFinished(false)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-}
-
-FileHelper::~FileHelper()
-{
-  MOZ_ASSERT(!mMutableFile && !mFileHandle && !mFileRequest && !mListener &&
-             !mRequest, "Should have cleared this!");
-}
-
-NS_IMPL_ISUPPORTS(FileHelper, nsIRequestObserver)
-
-nsresult
-FileHelper::Enqueue()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  FileService* service = FileService::GetOrCreate();
-  NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
-
-  nsresult rv = service->Enqueue(mFileHandle, this);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (mFileHandle) {
-    mFileHandle->OnNewRequest();
-  }
-
-  return NS_OK;
-}
-
-nsresult
-FileHelper::AsyncRun(FileHelperListener* aListener)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  // Assign the listener early, so we can use it synchronously if the code
-  // below fails.
-  mListener = aListener;
-
-  nsresult rv;
-
-  nsCOMPtr<nsISupports> stream;
-  if (mFileHandle->mRequestMode == FileHandleBase::PARALLEL) {
-    rv = mFileHandle->CreateParallelStream(getter_AddRefs(stream));
-  }
-  else {
-    rv = mFileHandle->GetOrCreateStream(getter_AddRefs(stream));
-  }
-
-  if (NS_SUCCEEDED(rv)) {
-    NS_ASSERTION(stream, "This should never be null!");
-
-    rv = DoAsyncRun(stream);
-  }
-
-  if (NS_FAILED(rv)) {
-    mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
-    Finish();
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileHelper::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileHelper::OnStopRequest(nsIRequest* aRequest, nsISupports* aCtxt,
-                          nsresult aStatus)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (NS_FAILED(aStatus)) {
-    if (aStatus == NS_ERROR_FILE_NO_DEVICE_SPACE) {
-      mResultCode = NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR;
-    }
-    else {
-      mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
-    }
-  }
-
-  Finish();
-
-  return NS_OK;
-}
-
-void
-FileHelper::OnStreamProgress(uint64_t aProgress, uint64_t aProgressMax)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (mFileHandle->IsAborted()) {
-    NS_ASSERTION(mRequest, "Should have a request!\n");
-
-    nsresult rv = mRequest->Cancel(NS_BINDING_ABORTED);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Failed to cancel the request!");
-    }
-
-    return;
-  }
-
-  if (mFileRequest) {
-    mFileRequest->OnProgress(aProgress, aProgressMax);
-  }
-}
-
-// static
-FileHandleBase*
-FileHelper::GetCurrentFileHandle()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  return gCurrentFileHandle;
-}
-
-nsresult
-FileHelper::GetSuccessResult(JSContext* aCx,
-                             JS::MutableHandle<JS::Value> aVal)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  aVal.setUndefined();
-  return NS_OK;
-}
-
-void
-FileHelper::ReleaseObjects()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mMutableFile = nullptr;
-  mFileHandle = nullptr;
-  mFileRequest = nullptr;
-  mListener = nullptr;
-  mRequest = nullptr;
-}
-
-void
-FileHelper::Finish()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (mFinished) {
-    return;
-  }
-
-  mFinished = true;
-
-  if (mFileHandle->IsAborted()) {
-    // Always fire a "error" event with ABORT_ERR if the transaction was
-    // aborted, even if the request succeeded or failed with another error.
-    mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
-  }
-
-  FileHandleBase* oldFileHandle = gCurrentFileHandle;
-  gCurrentFileHandle = mFileHandle;
-
-  if (mFileRequest) {
-    nsresult rv = mFileRequest->NotifyHelperCompleted(this);
-    if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) {
-      mResultCode = rv;
-    }
-  }
-
-  MOZ_ASSERT(gCurrentFileHandle == mFileHandle, "Should be unchanged!");
-  gCurrentFileHandle = oldFileHandle;
-
-  mFileHandle->OnRequestFinished();
-
-  mListener->OnFileHelperComplete(this);
-
-  ReleaseObjects();
-
-  MOZ_ASSERT(!(mMutableFile || mFileHandle || mFileRequest || mListener ||
-               mRequest), "Subclass didn't call FileHelper::ReleaseObjects!");
-
-}
-
-void
-FileHelper::OnStreamClose()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  Finish();
-}
-
-void
-FileHelper::OnStreamDestroy()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  Finish();
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/FileHelper.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_FileHelper_h
-#define mozilla_dom_FileHelper_h
-
-#include "js/TypeDecls.h"
-#include "nsAutoPtr.h"
-#include "nsCOMPtr.h"
-#include "nsIRequestObserver.h"
-
-namespace mozilla {
-namespace dom {
-
-class FileHandleBase;
-class FileHelper;
-class FileRequestBase;
-class FileOutputStreamWrapper;
-class MutableFileBase;
-
-class FileHelperListener
-{
-public:
-  NS_IMETHOD_(MozExternalRefCountType)
-  AddRef() = 0;
-
-  NS_IMETHOD_(MozExternalRefCountType)
-  Release() = 0;
-
-  virtual void
-  OnFileHelperComplete(FileHelper* aFileHelper) = 0;
-};
-
-/**
- * Must be subclassed. The subclass must implement DoAsyncRun. It may then
- * choose to implement GetSuccessResult to properly set the result of the
- * success event. Call Enqueue to start the file operation.
- */
-class FileHelper : public nsIRequestObserver
-{
-  friend class FileOutputStreamWrapper;
-
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIREQUESTOBSERVER
-
-  nsresult
-  ResultCode() const
-  {
-    return mResultCode;
-  }
-
-  nsresult
-  Enqueue();
-
-  nsresult
-  AsyncRun(FileHelperListener* aListener);
-
-  virtual nsresult
-  GetSuccessResult(JSContext* aCx, JS::MutableHandle<JS::Value> aVal);
-
-  void
-  OnStreamProgress(uint64_t aProgress, uint64_t aProgressMax);
-
-  void
-  OnStreamClose();
-
-  void
-  OnStreamDestroy();
-
-  static FileHandleBase*
-  GetCurrentFileHandle();
-
-protected:
-  FileHelper(FileHandleBase* aFileHandle, FileRequestBase* aRequest);
-
-  virtual ~FileHelper();
-
-  virtual nsresult
-  DoAsyncRun(nsISupports* aStream) = 0;
-
-  virtual void
-  ReleaseObjects();
-
-  void
-  Finish();
-
-  nsRefPtr<MutableFileBase> mMutableFile;
-  nsRefPtr<FileHandleBase> mFileHandle;
-  nsRefPtr<FileRequestBase> mFileRequest;
-
-  nsRefPtr<FileHelperListener> mListener;
-  nsCOMPtr<nsIRequest> mRequest;
-
-private:
-  nsresult mResultCode;
-  bool mFinished;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_FileHelper_h
deleted file mode 100644
--- a/dom/filehandle/FileRequest.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "FileRequest.h"
-
-#include "MainThreadUtils.h"
-#include "mozilla/Assertions.h"
-
-namespace mozilla {
-namespace dom {
-
-FileRequestBase::FileRequestBase()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-FileRequestBase::~FileRequestBase()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-} // namespace dom
-} // namespace mozilla
rename from dom/filehandle/FileRequest.h
rename to dom/filehandle/FileRequestBase.h
--- a/dom/filehandle/FileRequest.h
+++ b/dom/filehandle/FileRequestBase.h
@@ -2,43 +2,92 @@
 /* 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_FileRequest_h
 #define mozilla_dom_FileRequest_h
 
-#include "nscore.h"
+#include "FileHandleCommon.h"
+#include "js/TypeDecls.h"
+#include "nsString.h"
+
+struct PRThread;
 
 namespace mozilla {
 namespace dom {
 
-class FileHelper;
+class FileHandleBase;
 
 /**
  * This class provides a base for FileRequest implementations.
  */
 class FileRequestBase
+  : public RefCountedThreadObject
 {
+  nsString mEncoding;
+
+  bool mHasEncoding;
+
 public:
-  NS_IMETHOD_(MozExternalRefCountType)
-  AddRef() = 0;
+  class ResultCallback;
+
+  void
+  SetEncoding(const nsAString& aEncoding)
+  {
+    mEncoding = aEncoding;
+    mHasEncoding = true;
+  }
 
-  NS_IMETHOD_(MozExternalRefCountType)
-  Release() = 0;
+  const nsAString&
+  GetEncoding() const
+  {
+    return mEncoding;
+  }
+
+  bool
+  HasEncoding() const
+  {
+    return mHasEncoding;
+  }
+
+  virtual FileHandleBase*
+  FileHandle() const = 0;
 
   virtual void
   OnProgress(uint64_t aProgress, uint64_t aProgressMax) = 0;
 
-  virtual nsresult
-  NotifyHelperCompleted(FileHelper* aFileHelper) = 0;
+  virtual void
+  SetResultCallback(ResultCallback* aCallback) = 0;
+
+  virtual void
+  SetError(nsresult aError) = 0;
 
 protected:
-  FileRequestBase();
+  FileRequestBase(DEBUGONLY(PRThread* aOwningThread))
+    : RefCountedThreadObject(DEBUGONLY(aOwningThread))
+    , mHasEncoding(false)
+  {
+    AssertIsOnOwningThread();
+  }
 
-  virtual ~FileRequestBase();
+  virtual ~FileRequestBase()
+  {
+    AssertIsOnOwningThread();
+  }
+};
+
+class NS_NO_VTABLE FileRequestBase::ResultCallback
+{
+public:
+  virtual nsresult
+  GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) = 0;
+
+protected:
+  ResultCallback()
+  { }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileRequest_h
deleted file mode 100644
--- a/dom/filehandle/FileService.cpp
+++ /dev/null
@@ -1,479 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "FileService.h"
-
-#include "FileHandle.h"
-#include "MainThreadUtils.h"
-#include "mozilla/Assertions.h"
-#include "MutableFile.h"
-#include "nsError.h"
-#include "nsIEventTarget.h"
-#include "nsNetCID.h"
-#include "nsServiceManagerUtils.h"
-#include "nsThreadPool.h"
-#include "nsThreadUtils.h"
-
-namespace mozilla {
-namespace dom {
-
-namespace {
-
-const uint32_t kThreadLimit = 5;
-const uint32_t kIdleThreadLimit = 1;
-const uint32_t kIdleThreadTimeoutMs = 30000;
-
-StaticAutoPtr<FileService> gInstance;
-bool gShutdown = false;
-
-} // namespace
-
-FileService::FileService()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!gInstance);
-}
-
-FileService::~FileService()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-}
-
-nsresult
-FileService::Init()
-{
-  mThreadPool = new nsThreadPool();
-
-  nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandleTrans"));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = mThreadPool->SetThreadLimit(kThreadLimit);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-FileService::Cleanup()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsIThread* thread = NS_GetCurrentThread();
-  MOZ_ASSERT(thread);
-
-  nsresult rv = mThreadPool->Shutdown();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Make sure the service is still accessible while any generated callbacks
-  // are processed.
-  rv = NS_ProcessPendingEvents(thread);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!mCompleteCallbacks.IsEmpty()) {
-    // Run all callbacks manually now.
-    for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) {
-      mCompleteCallbacks[index].mCallback->Run();
-    }
-    mCompleteCallbacks.Clear();
-
-    // And make sure they get processed.
-    rv = NS_ProcessPendingEvents(thread);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
-// static
-FileService*
-FileService::GetOrCreate()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (gShutdown) {
-    NS_WARNING("Calling GetOrCreate() after shutdown!");
-    return nullptr;
-  }
-
-  if (!gInstance) {
-    nsAutoPtr<FileService> service(new FileService());
-
-    nsresult rv = service->Init();
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    gInstance = service.forget();
-  }
-
-  return gInstance;
-}
-
-// static
-FileService*
-FileService::Get()
-{
-  // Does not return an owning reference.
-  return gInstance;
-}
-
-// static
-void
-FileService::Shutdown()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  gShutdown = true;
-
-  if (gInstance) {
-    if (NS_FAILED(gInstance->Cleanup())) {
-      NS_WARNING("Failed to shutdown file service!");
-    }
-    gInstance = nullptr;
-  }
-}
-
-// static
-bool
-FileService::IsShuttingDown()
-{
-  return gShutdown;
-}
-
-nsresult
-FileService::Enqueue(FileHandleBase* aFileHandle, FileHelper* aFileHelper)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(aFileHandle, "Null pointer!");
-
-  MutableFileBase* mutableFile = aFileHandle->MutableFile();
-
-  if (mutableFile->IsInvalid()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  const nsACString& storageId = mutableFile->mStorageId;
-  const nsAString& fileName = mutableFile->mFileName;
-  bool modeIsWrite = aFileHandle->mMode == FileMode::Readwrite;
-
-  StorageInfo* storageInfo;
-  if (!mStorageInfos.Get(storageId, &storageInfo)) {
-    nsAutoPtr<StorageInfo> newStorageInfo(new StorageInfo());
-
-    mStorageInfos.Put(storageId, newStorageInfo);
-
-    storageInfo = newStorageInfo.forget();
-  }
-
-  FileHandleQueue* existingFileHandleQueue =
-    storageInfo->GetFileHandleQueue(aFileHandle);
-
-  if (existingFileHandleQueue) {
-    existingFileHandleQueue->Enqueue(aFileHelper);
-    return NS_OK;
-  }
-
-  bool lockedForReading = storageInfo->IsFileLockedForReading(fileName);
-  bool lockedForWriting = storageInfo->IsFileLockedForWriting(fileName);
-
-  if (modeIsWrite) {
-    if (!lockedForWriting) {
-      storageInfo->LockFileForWriting(fileName);
-    }
-  }
-  else {
-    if (!lockedForReading) {
-      storageInfo->LockFileForReading(fileName);
-    }
-  }
-
-  if (lockedForWriting || (lockedForReading && modeIsWrite)) {
-    storageInfo->CreateDelayedEnqueueInfo(aFileHandle, aFileHelper);
-  }
-  else {
-    FileHandleQueue* fileHandleQueue =
-      storageInfo->CreateFileHandleQueue(aFileHandle);
-
-    if (aFileHelper) {
-      // Enqueue() will queue the file helper if there's already something
-      // running. That can't fail, so no need to eventually remove
-      // storageInfo from the hash table.
-      //
-      // If the file helper is free to run then AsyncRun() is called on the
-      // file helper. AsyncRun() is responsible for calling all necessary
-      // callbacks when something fails. We're propagating the error here,
-      // however there's no need to eventually remove storageInfo from
-      // the hash table. Code behind AsyncRun() will take care of it. The last
-      // item in the code path is NotifyFileHandleCompleted() which removes
-      // storageInfo from the hash table if there are no file handles for
-      // the file storage.
-      nsresult rv = fileHandleQueue->Enqueue(aFileHelper);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-
-  return NS_OK;
-}
-
-void
-FileService::NotifyFileHandleCompleted(FileHandleBase* aFileHandle)
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(aFileHandle, "Null pointer!");
-
-  MutableFileBase* mutableFile = aFileHandle->MutableFile();
-  const nsACString& storageId = mutableFile->mStorageId;
-
-  StorageInfo* storageInfo;
-  if (!mStorageInfos.Get(storageId, &storageInfo)) {
-    NS_ERROR("We don't know anyting about this file handle?!");
-    return;
-  }
-
-  storageInfo->RemoveFileHandleQueue(aFileHandle);
-
-  if (!storageInfo->HasRunningFileHandles()) {
-    mStorageInfos.Remove(storageId);
-
-    // See if we need to fire any complete callbacks.
-    uint32_t index = 0;
-    while (index < mCompleteCallbacks.Length()) {
-      if (MaybeFireCallback(mCompleteCallbacks[index])) {
-        mCompleteCallbacks.RemoveElementAt(index);
-      }
-      else {
-        index++;
-      }
-    }
-  }
-}
-
-void
-FileService::WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
-                                       nsIRunnable* aCallback)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aStorageIds.IsEmpty());
-  MOZ_ASSERT(aCallback);
-
-  StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
-  callback->mCallback = aCallback;
-  callback->mStorageIds.SwapElements(aStorageIds);
-
-  if (MaybeFireCallback(*callback)) {
-    mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
-  }
-}
-
-nsIEventTarget*
-FileService::ThreadPoolTarget() const
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mThreadPool);
-
-  return mThreadPool;
-}
-
-bool
-FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  for (uint32_t index = 0; index < aCallback.mStorageIds.Length(); index++) {
-    if (mStorageInfos.Get(aCallback.mStorageIds[index], nullptr)) {
-      return false;
-    }
-  }
-
-  aCallback.mCallback->Run();
-  return true;
-}
-
-FileService::FileHandleQueue::FileHandleQueue(FileHandleBase* aFileHandle)
-: mFileHandle(aFileHandle)
-{
-  MOZ_ASSERT(aFileHandle, "Null pointer!");
-}
-
-FileService::FileHandleQueue::~FileHandleQueue()
-{
-}
-
-NS_IMPL_ADDREF(FileService::FileHandleQueue)
-NS_IMPL_RELEASE(FileService::FileHandleQueue)
-
-nsresult
-FileService::FileHandleQueue::Enqueue(FileHelper* aFileHelper)
-{
-  mQueue.AppendElement(aFileHelper);
-
-  nsresult rv;
-  if (mFileHandle->mRequestMode == FileHandleBase::PARALLEL) {
-    rv = aFileHelper->AsyncRun(this);
-  }
-  else {
-    rv = ProcessQueue();
-  }
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-void
-FileService::
-FileHandleQueue::OnFileHelperComplete(FileHelper* aFileHelper)
-{
-  if (mFileHandle->mRequestMode == FileHandleBase::PARALLEL) {
-    int32_t index = mQueue.IndexOf(aFileHelper);
-    NS_ASSERTION(index != -1, "We don't know anything about this helper!");
-
-    mQueue.RemoveElementAt(index);
-  }
-  else {
-    NS_ASSERTION(mCurrentHelper == aFileHelper, "How can this happen?!");
-
-    mCurrentHelper = nullptr;
-
-    nsresult rv = ProcessQueue();
-    if (NS_FAILED(rv)) {
-      return;
-    }
-  }
-}
-
-nsresult
-FileService::FileHandleQueue::ProcessQueue()
-{
-  if (mQueue.IsEmpty() || mCurrentHelper) {
-    return NS_OK;
-  }
-
-  mCurrentHelper = mQueue[0];
-  mQueue.RemoveElementAt(0);
-
-  nsresult rv = mCurrentHelper->AsyncRun(this);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-FileService::DelayedEnqueueInfo::DelayedEnqueueInfo()
-{
-}
-
-FileService::DelayedEnqueueInfo::~DelayedEnqueueInfo()
-{
-}
-
-FileService::FileHandleQueue*
-FileService::StorageInfo::CreateFileHandleQueue(FileHandleBase* aFileHandle)
-{
-  nsRefPtr<FileHandleQueue>* fileHandleQueue =
-    mFileHandleQueues.AppendElement();
-  *fileHandleQueue = new FileHandleQueue(aFileHandle);
-  return fileHandleQueue->get();
-}
-
-FileService::FileHandleQueue*
-FileService::StorageInfo::GetFileHandleQueue(FileHandleBase* aFileHandle)
-{
-  uint32_t count = mFileHandleQueues.Length();
-  for (uint32_t index = 0; index < count; index++) {
-    nsRefPtr<FileHandleQueue>& fileHandleQueue = mFileHandleQueues[index];
-    if (fileHandleQueue->mFileHandle == aFileHandle) {
-      return fileHandleQueue;
-    }
-  }
-  return nullptr;
-}
-
-void
-FileService::StorageInfo::RemoveFileHandleQueue(FileHandleBase* aFileHandle)
-{
-  for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
-    if (mDelayedEnqueueInfos[index].mFileHandle == aFileHandle) {
-      MOZ_ASSERT(!mDelayedEnqueueInfos[index].mFileHelper, "Should be null!");
-      mDelayedEnqueueInfos.RemoveElementAt(index);
-      return;
-    }
-  }
-
-  uint32_t fileHandleCount = mFileHandleQueues.Length();
-
-  // We can't just remove entries from lock hash tables, we have to rebuild
-  // them instead. Multiple FileHandle objects may lock the same file
-  // (one entry can represent multiple locks).
-
-  mFilesReading.Clear();
-  mFilesWriting.Clear();
-
-  for (uint32_t index = 0, count = fileHandleCount; index < count; index++) {
-    FileHandleBase* fileHandle = mFileHandleQueues[index]->mFileHandle;
-    if (fileHandle == aFileHandle) {
-      MOZ_ASSERT(count == fileHandleCount, "More than one match?!");
-
-      mFileHandleQueues.RemoveElementAt(index);
-      index--;
-      count--;
-
-      continue;
-    }
-
-    const nsAString& fileName = fileHandle->MutableFile()->mFileName;
-
-    if (fileHandle->mMode == FileMode::Readwrite) {
-      if (!IsFileLockedForWriting(fileName)) {
-        LockFileForWriting(fileName);
-      }
-    }
-    else {
-      if (!IsFileLockedForReading(fileName)) {
-        LockFileForReading(fileName);
-      }
-    }
-  }
-
-  MOZ_ASSERT(mFileHandleQueues.Length() == fileHandleCount - 1,
-             "Didn't find the file handle we were looking for!");
-
-  nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos;
-  delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos);
-
-  for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) {
-    DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index];
-    if (NS_FAILED(gInstance->Enqueue(delayedEnqueueInfo.mFileHandle,
-                                     delayedEnqueueInfo.mFileHelper))) {
-      NS_WARNING("Enqueue failed!");
-    }
-  }
-}
-
-FileService::DelayedEnqueueInfo*
-FileService::StorageInfo::CreateDelayedEnqueueInfo(FileHandleBase* aFileHandle,
-                                                   FileHelper* aFileHelper)
-{
-  DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
-  info->mFileHandle = aFileHandle;
-  info->mFileHelper = aFileHelper;
-  return info;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/FileService.h
+++ /dev/null
@@ -1,193 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_FileService_h
-#define mozilla_dom_FileService_h
-
-#include "mozilla/Attributes.h"
-#include "mozilla/dom/FileHelper.h"
-#include "mozilla/StaticPtr.h"
-#include "nsClassHashtable.h"
-#include "nsAutoPtr.h"
-#include "nsCOMPtr.h"
-#include "nsDebug.h"
-#include "nsHashKeys.h"
-#include "nsTArray.h"
-#include "nsTHashtable.h"
-
-class nsAString;
-class nsIEventTarget;
-class nsIRunnable;
-class nsThreadPool;
-
-namespace mozilla {
-namespace dom {
-
-class FileHandleBase;
-
-class FileService final
-{
-  friend class nsAutoPtr<FileService>;
-  friend class StaticAutoPtr<FileService>;
-
-public:
-  // Returns a non-owning reference!
-  static FileService*
-  GetOrCreate();
-
-  // Returns a non-owning reference!
-  static FileService*
-  Get();
-
-  static void
-  Shutdown();
-
-  // Returns true if we've begun the shutdown process.
-  static bool
-  IsShuttingDown();
-
-  nsresult
-  Enqueue(FileHandleBase* aFileHandle, FileHelper* aFileHelper);
-
-  void
-  NotifyFileHandleCompleted(FileHandleBase* aFileHandle);
-
-  void
-  WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
-                            nsIRunnable* aCallback);
-
-  nsIEventTarget*
-  ThreadPoolTarget() const;
-
-private:
-  class FileHandleQueue final : public FileHelperListener
-  {
-    friend class FileService;
-
-  public:
-    NS_IMETHOD_(MozExternalRefCountType)
-    AddRef() override;
-
-    NS_IMETHOD_(MozExternalRefCountType)
-    Release() override;
-
-    inline nsresult
-    Enqueue(FileHelper* aFileHelper);
-
-    virtual void
-    OnFileHelperComplete(FileHelper* aFileHelper) override;
-
-  private:
-    inline
-    explicit FileHandleQueue(FileHandleBase* aFileHandle);
-
-    ~FileHandleQueue();
-
-    nsresult
-    ProcessQueue();
-
-    ThreadSafeAutoRefCnt mRefCnt;
-    NS_DECL_OWNINGTHREAD
-    nsRefPtr<FileHandleBase> mFileHandle;
-    nsTArray<nsRefPtr<FileHelper> > mQueue;
-    nsRefPtr<FileHelper> mCurrentHelper;
-  };
-
-  struct DelayedEnqueueInfo
-  {
-    DelayedEnqueueInfo();
-    ~DelayedEnqueueInfo();
-
-    nsRefPtr<FileHandleBase> mFileHandle;
-    nsRefPtr<FileHelper> mFileHelper;
-  };
-
-  class StorageInfo
-  {
-    friend class FileService;
-
-  public:
-    inline FileHandleQueue*
-    CreateFileHandleQueue(FileHandleBase* aFileHandle);
-
-    inline FileHandleQueue*
-    GetFileHandleQueue(FileHandleBase* aFileHandle);
-
-    void
-    RemoveFileHandleQueue(FileHandleBase* aFileHandle);
-
-    bool
-    HasRunningFileHandles()
-    {
-      return !mFileHandleQueues.IsEmpty();
-    }
-
-    inline DelayedEnqueueInfo*
-    CreateDelayedEnqueueInfo(FileHandleBase* aFileHandle,
-                             FileHelper* aFileHelper);
-
-    void
-    LockFileForReading(const nsAString& aFileName)
-    {
-      mFilesReading.PutEntry(aFileName);
-    }
-
-    void
-    LockFileForWriting(const nsAString& aFileName)
-    {
-      mFilesWriting.PutEntry(aFileName);
-    }
-
-    bool
-    IsFileLockedForReading(const nsAString& aFileName)
-    {
-      return mFilesReading.Contains(aFileName);
-    }
-
-    bool
-    IsFileLockedForWriting(const nsAString& aFileName)
-    {
-      return mFilesWriting.Contains(aFileName);
-    }
-
-  private:
-    StorageInfo()
-    {
-    }
-
-    nsTArray<nsRefPtr<FileHandleQueue>> mFileHandleQueues;
-    nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
-    nsTHashtable<nsStringHashKey> mFilesReading;
-    nsTHashtable<nsStringHashKey> mFilesWriting;
-  };
-
-  struct StoragesCompleteCallback
-  {
-    nsTArray<nsCString> mStorageIds;
-    nsCOMPtr<nsIRunnable> mCallback;
-  };
-
-  FileService();
-  ~FileService();
-
-  nsresult
-  Init();
-
-  nsresult
-  Cleanup();
-
-  bool
-  MaybeFireCallback(StoragesCompleteCallback& aCallback);
-
-  nsRefPtr<nsThreadPool> mThreadPool;
-  nsClassHashtable<nsCStringHashKey, StorageInfo> mStorageInfos;
-  nsTArray<StoragesCompleteCallback> mCompleteCallbacks;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_FileService_h
deleted file mode 100644
--- a/dom/filehandle/FileStreamWrappers.cpp
+++ /dev/null
@@ -1,415 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "FileStreamWrappers.h"
-
-#include "FileHelper.h"
-#include "MainThreadUtils.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/ipc/InputStreamParams.h"
-#include "MutableFile.h"
-#include "nsDebug.h"
-#include "nsError.h"
-#include "nsIRunnable.h"
-#include "nsISeekableStream.h"
-#include "nsThreadUtils.h"
-#include "nsQueryObject.h"
-
-#ifdef DEBUG
-#include "nsXULAppAPI.h"
-#endif
-
-namespace mozilla {
-namespace dom {
-
-namespace {
-
-class ProgressRunnable final : public nsIRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-  ProgressRunnable(FileHelper* aFileHelper,
-                   uint64_t aProgress,
-                   uint64_t aProgressMax)
-  : mFileHelper(aFileHelper),
-    mProgress(aProgress),
-    mProgressMax(aProgressMax)
-  {
-  }
-
-private:
-  ~ProgressRunnable() {}
-
-  nsRefPtr<FileHelper> mFileHelper;
-  uint64_t mProgress;
-  uint64_t mProgressMax;
-};
-
-class CloseRunnable final : public nsIRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-  explicit CloseRunnable(FileHelper* aFileHelper)
-  : mFileHelper(aFileHelper)
-  { }
-
-private:
-  ~CloseRunnable() {}
-
-  nsRefPtr<FileHelper> mFileHelper;
-};
-
-class DestroyRunnable final : public nsIRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-  explicit DestroyRunnable(FileHelper* aFileHelper)
-  : mFileHelper(aFileHelper)
-  { }
-
-private:
-  ~DestroyRunnable() {}
-
-  nsRefPtr<FileHelper> mFileHelper;
-};
-
-} // namespace
-
-FileStreamWrapper::FileStreamWrapper(nsISupports* aFileStream,
-                                     FileHelper* aFileHelper,
-                                     uint64_t aOffset,
-                                     uint64_t aLimit,
-                                     uint32_t aFlags)
-: mFileStream(aFileStream),
-  mFileHelper(aFileHelper),
-  mOffset(aOffset),
-  mLimit(aLimit),
-  mFlags(aFlags),
-  mFirstTime(true)
-{
-  NS_ASSERTION(mFileStream, "Must have a file stream!");
-  NS_ASSERTION(mFileHelper, "Must have a file helper!");
-}
-
-FileStreamWrapper::~FileStreamWrapper()
-{
-  if (mFlags & NOTIFY_DESTROY) {
-    if (NS_IsMainThread()) {
-      mFileHelper->OnStreamDestroy();
-    }
-    else {
-      nsCOMPtr<nsIRunnable> runnable =
-        new DestroyRunnable(mFileHelper);
-
-      nsresult rv = NS_DispatchToMainThread(runnable);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to dispatch to the main thread!");
-      }
-    }
-  }
-}
-
-NS_IMPL_ISUPPORTS0(FileStreamWrapper)
-
-FileInputStreamWrapper::FileInputStreamWrapper(nsISupports* aFileStream,
-                                               FileHelper* aFileHelper,
-                                               uint64_t aOffset,
-                                               uint64_t aLimit,
-                                               uint32_t aFlags)
-: FileStreamWrapper(aFileStream, aFileHelper, aOffset, aLimit, aFlags)
-{
-  mInputStream = do_QueryInterface(mFileStream);
-  NS_ASSERTION(mInputStream, "This should always succeed!");
-}
-
-NS_IMPL_ISUPPORTS_INHERITED(FileInputStreamWrapper,
-                            FileStreamWrapper,
-                            nsIInputStream,
-                            nsIIPCSerializableInputStream)
-
-NS_IMETHODIMP
-FileInputStreamWrapper::Close()
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  if (mFlags & NOTIFY_CLOSE) {
-    nsCOMPtr<nsIRunnable> runnable = new CloseRunnable(mFileHelper);
-
-    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
-      NS_WARNING("Failed to dispatch to the main thread!");
-    }
-  }
-
-  mOffset = 0;
-  mLimit = 0;
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileInputStreamWrapper::Available(uint64_t* _retval)
-{
-  // Performing sync IO on the main thread is generally not allowed.
-  // However, the input stream wrapper is also used to track reads performed by
-  // other APIs like FileReader, XHR, etc.
-  // In that case nsInputStreamChannel::OpenContentStream() calls Available()
-  // before setting the content length property. This property is not important
-  // to perform reads properly, so we can just return NS_BASE_STREAM_CLOSED
-  // here. It causes OpenContentStream() to set the content length property to
-  // zero.
-
-  if (NS_IsMainThread()) {
-    return NS_BASE_STREAM_CLOSED;
-  }
-
-  return mInputStream->Available(_retval);
-}
-
-NS_IMETHODIMP
-FileInputStreamWrapper::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  nsresult rv;
-
-  if (mFirstTime) {
-    mFirstTime = false;
-
-    if (mOffset != UINT64_MAX) {
-      nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mInputStream);
-      if (seekable) {
-        rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-    }
-
-    mOffset = 0;
-  }
-
-  uint64_t max = mLimit - mOffset;
-  if (max == 0) {
-    *_retval = 0;
-    return NS_OK;
-  }
-
-  if (aCount > max) {
-    aCount = max;
-  }
-
-  rv = mInputStream->Read(aBuf, aCount, _retval);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mOffset += *_retval;
-
-  if (mFlags & NOTIFY_PROGRESS) {
-    nsCOMPtr<nsIRunnable> runnable =
-      new ProgressRunnable(mFileHelper, mOffset, mLimit);
-
-    rv = NS_DispatchToMainThread(runnable);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("Failed to dispatch to the main thread!");
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileInputStreamWrapper::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
-                                     uint32_t aCount, uint32_t* _retval)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-FileInputStreamWrapper::IsNonBlocking(bool* _retval)
-{
-  *_retval = false;
-  return NS_OK;
-}
-
-void
-FileInputStreamWrapper::Serialize(InputStreamParams& aParams,
-                                  FileDescriptorArray& /* aFDs */)
-{
-  MOZ_ASSERT(XRE_IsParentProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIInputStream> thisStream = do_QueryObject(this);
-
-  aParams = mozilla::ipc::SameProcessInputStreamParams(
-    reinterpret_cast<intptr_t>(thisStream.forget().take()));
-}
-
-bool
-FileInputStreamWrapper::Deserialize(const InputStreamParams& /* aParams */,
-                                    const FileDescriptorArray& /* aFDs */)
-{
-  MOZ_CRASH("Should never get here!");
-}
-
-FileOutputStreamWrapper::FileOutputStreamWrapper(nsISupports* aFileStream,
-                                                 FileHelper* aFileHelper,
-                                                 uint64_t aOffset,
-                                                 uint64_t aLimit,
-                                                 uint32_t aFlags)
-: FileStreamWrapper(aFileStream, aFileHelper, aOffset, aLimit, aFlags)
-{
-  mOutputStream = do_QueryInterface(mFileStream);
-  NS_ASSERTION(mOutputStream, "This should always succeed!");
-}
-
-NS_IMPL_ISUPPORTS_INHERITED(FileOutputStreamWrapper,
-                            FileStreamWrapper,
-                            nsIOutputStream)
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::Close()
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  nsresult rv = NS_OK;
-
-  if (mFlags & NOTIFY_CLOSE) {
-    nsCOMPtr<nsIRunnable> runnable = new CloseRunnable(mFileHelper);
-
-    if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
-      NS_WARNING("Failed to dispatch to the main thread!");
-    }
-  }
-
-  mOffset = 0;
-  mLimit = 0;
-
-  return rv;
-}
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::Write(const char* aBuf, uint32_t aCount,
-                               uint32_t* _retval)
-{
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-
-  nsresult rv;
-
-  if (mFirstTime) {
-    mFirstTime = false;
-
-    nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mOutputStream);
-    if (seekable) {
-      if (mOffset == UINT64_MAX) {
-        rv = seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
-      }
-      else {
-        rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
-      }
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-
-    mOffset = 0;
-  }
-
-  uint64_t max = mLimit - mOffset;
-  if (max == 0) {
-    *_retval = 0;
-    return NS_OK;
-  }
-
-  if (aCount > max) {
-    aCount = max;
-  }
-
-  rv = mOutputStream->Write(aBuf, aCount, _retval);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mOffset += *_retval;
-
-  if (mFlags & NOTIFY_PROGRESS) {
-    nsCOMPtr<nsIRunnable> runnable =
-      new ProgressRunnable(mFileHelper, mOffset, mLimit);
-
-    NS_DispatchToMainThread(runnable);
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::Flush()
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::WriteFrom(nsIInputStream* aFromStream,
-                                   uint32_t aCount, uint32_t* _retval)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::WriteSegments(nsReadSegmentFun aReader,
-                                       void* aClosure, uint32_t aCount,
-                                       uint32_t* _retval)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-FileOutputStreamWrapper::IsNonBlocking(bool* _retval)
-{
-  *_retval = false;
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(ProgressRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-ProgressRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mFileHelper->OnStreamProgress(mProgress, mProgressMax);
-  mFileHelper = nullptr;
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(CloseRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-CloseRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mFileHelper->OnStreamClose();
-  mFileHelper = nullptr;
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(DestroyRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-DestroyRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mFileHelper->OnStreamDestroy();
-  mFileHelper = nullptr;
-
-  return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/FileStreamWrappers.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_FileStreamWrappers_h
-#define mozilla_dom_FileStreamWrappers_h
-
-#include "nsAutoPtr.h"
-#include "nsCOMPtr.h"
-#include "nsIInputStream.h"
-#include "nsIOutputStream.h"
-#include "nsIIPCSerializableInputStream.h"
-
-namespace mozilla {
-namespace ipc {
-class InputStreamParams;
-} // namespace ipc
-
-namespace dom {
-
-class FileHelper;
-
-class FileStreamWrapper : public nsISupports
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  FileStreamWrapper(nsISupports* aFileStream,
-                    FileHelper* aFileHelper,
-                    uint64_t aOffset,
-                    uint64_t aLimit,
-                    uint32_t aFlags);
-
-  enum {
-    NOTIFY_PROGRESS = 1 << 0,
-    NOTIFY_CLOSE = 1 << 1,
-    NOTIFY_DESTROY = 1 << 2
-  };
-
-protected:
-  virtual ~FileStreamWrapper();
-
-  nsCOMPtr<nsISupports> mFileStream;
-  nsRefPtr<FileHelper> mFileHelper;
-  uint64_t mOffset;
-  uint64_t mLimit;
-  uint32_t mFlags;
-  bool mFirstTime;
-};
-
-class FileInputStreamWrapper : public FileStreamWrapper,
-                               public nsIInputStream,
-                               public nsIIPCSerializableInputStream
-{
-  typedef mozilla::ipc::InputStreamParams InputStreamParams;
-
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIINPUTSTREAM
-  NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
-
-  FileInputStreamWrapper(nsISupports* aFileStream,
-                         FileHelper* aFileHelper,
-                         uint64_t aOffset,
-                         uint64_t aLimit,
-                         uint32_t aFlags);
-
-protected:
-  virtual ~FileInputStreamWrapper()
-  { }
-
-private:
-  nsCOMPtr<nsIInputStream> mInputStream;
-};
-
-class FileOutputStreamWrapper : public FileStreamWrapper,
-                                public nsIOutputStream
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIOUTPUTSTREAM
-
-  FileOutputStreamWrapper(nsISupports* aFileStream,
-                          FileHelper* aFileHelper,
-                          uint64_t aOffset,
-                          uint64_t aLimit,
-                          uint32_t aFlags);
-
-protected:
-  virtual ~FileOutputStreamWrapper()
-  { }
-
-private:
-  nsCOMPtr<nsIOutputStream> mOutputStream;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_FileStreamWrappers_h
deleted file mode 100644
--- a/dom/filehandle/MemoryStreams.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "MemoryStreams.h"
-
-#include "nsAutoPtr.h"
-#include "nsCOMPtr.h"
-#include "nsDebug.h"
-#include "nsError.h"
-#include "nsStreamUtils.h"
-
-namespace mozilla {
-namespace dom {
-
-// static
-already_AddRefed<MemoryOutputStream>
-MemoryOutputStream::Create(uint64_t aSize)
-{
-  NS_ASSERTION(aSize, "Passed zero size!");
-
-  NS_ENSURE_TRUE(aSize <= UINT32_MAX, nullptr);
-
-  nsRefPtr<MemoryOutputStream> stream = new MemoryOutputStream();
-
-  char* dummy;
-  uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible);
-  NS_ENSURE_TRUE(length == aSize, nullptr);
-
-  return stream.forget();
-}
-
-NS_IMPL_ISUPPORTS(MemoryOutputStream, nsIOutputStream)
-
-NS_IMETHODIMP
-MemoryOutputStream::Close()
-{
-  mData.Truncate(mOffset);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval)
-{
-  return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval);
-}
-
-NS_IMETHODIMP
-MemoryOutputStream::Flush()
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
-                              uint32_t* _retval)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
-MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
-                                  uint32_t aCount, uint32_t* _retval)
-{
-  NS_ASSERTION(mData.Length() >= mOffset, "Bad stream state!");
-
-  uint32_t maxCount = mData.Length() - mOffset;
-  if (maxCount == 0) {
-    *_retval = 0;
-    return NS_OK;
-  }
-
-  if (aCount > maxCount) {
-    aCount = maxCount;
-  }
-
-  nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0,
-                        aCount, _retval);
-  if (NS_SUCCEEDED(rv)) {
-    NS_ASSERTION(*_retval <= aCount,
-                 "Reader should not read more than we asked it to read!");
-    mOffset += *_retval;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MemoryOutputStream::IsNonBlocking(bool* _retval)
-{
-  *_retval = false;
-  return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/MemoryStreams.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_MemoryStreams_h
-#define mozilla_dom_MemoryStreams_h
-
-#include "nsIOutputStream.h"
-#include "nsString.h"
-
-template <class> struct already_AddRefed;
-
-namespace mozilla {
-namespace dom {
-
-class MemoryOutputStream : public nsIOutputStream
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIOUTPUTSTREAM
-
-  static already_AddRefed<MemoryOutputStream>
-  Create(uint64_t aSize);
-
-  const nsCString&
-  Data() const
-  {
-    return mData;
-  }
-
-private:
-  MemoryOutputStream()
-  : mOffset(0)
-  { }
-
-  virtual ~MemoryOutputStream()
-  { }
-
-  nsCString mData;
-  uint64_t mOffset;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_MemoryStreams_h
deleted file mode 100644
--- a/dom/filehandle/MetadataHelper.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "MetadataHelper.h"
-
-#include "FileHandle.h"
-#include "js/Value.h"
-#include "js/RootingAPI.h"
-#include "jsapi.h"
-#include "js/Date.h"
-#include "mozilla/dom/FileModeBinding.h"
-#include "nsDebug.h"
-#include "nsIFileStreams.h"
-#include "nsIOutputStream.h"
-
-namespace mozilla {
-namespace dom {
-
-nsresult
-MetadataHelper::DoAsyncRun(nsISupports* aStream)
-{
-  bool readWrite = mFileHandle->mMode == FileMode::Readwrite;
-
-  nsRefPtr<AsyncMetadataGetter> getter =
-    new AsyncMetadataGetter(aStream, mParams, readWrite);
-
-  nsresult rv = getter->AsyncWork(this, nullptr);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult
-MetadataHelper::GetSuccessResult(JSContext* aCx,
-                                 JS::MutableHandle<JS::Value> aVal)
-{
-  JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
-  NS_ENSURE_TRUE(obj, NS_ERROR_OUT_OF_MEMORY);
-
-  if (mParams->SizeRequested()) {
-    JS::Rooted<JS::Value> val(aCx, JS_NumberValue(mParams->Size()));
-
-    if (!JS_DefineProperty(aCx, obj, "size", val, JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  if (mParams->LastModifiedRequested()) {
-    double msec = mParams->LastModified();
-    JSObject *date = JS::NewDateObject(aCx, JS::TimeClip(msec));
-    NS_ENSURE_TRUE(date, NS_ERROR_OUT_OF_MEMORY);
-
-    JS::Rooted<JS::Value> dateRoot(aCx, JS::ObjectValue(*date));
-    if (!JS_DefineProperty(aCx, obj, "lastModified", dateRoot,
-                           JSPROP_ENUMERATE)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  aVal.setObject(*obj);
-  return NS_OK;
-}
-
-nsresult
-MetadataHelper::AsyncMetadataGetter::DoStreamWork(nsISupports* aStream)
-{
-  nsresult rv;
-
-  if (mReadWrite) {
-    // Force a flush (so all pending writes are flushed to the disk and file
-    // metadata is updated too).
-
-    nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(aStream);
-    NS_ASSERTION(ostream, "This should always succeed!");
-
-    rv = ostream->Flush();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  nsCOMPtr<nsIFileMetadata> metadata = do_QueryInterface(aStream);
-
-  if (mParams->SizeRequested()) {
-    int64_t size;
-    rv = metadata->GetSize(&size);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    NS_ENSURE_TRUE(size >= 0, NS_ERROR_FAILURE);
-
-    mParams->mSize = uint64_t(size);
-  }
-
-  if (mParams->LastModifiedRequested()) {
-    int64_t lastModified;
-    rv = metadata->GetLastModified(&lastModified);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    mParams->mLastModified = lastModified;
-  }
-
-  return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/filehandle/MetadataHelper.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_MetadataHelper_h
-#define mozilla_dom_MetadataHelper_h
-
-#include "FileHelper.h"
-#include "js/TypeDecls.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/dom/AsyncHelper.h"
-#include "nsAutoPtr.h"
-
-namespace mozilla {
-namespace dom {
-
-class MetadataHelper;
-
-class MetadataParameters final
-{
-  friend class MetadataHelper;
-
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataParameters)
-
-  MetadataParameters(bool aSizeRequested, bool aLastModifiedRequested)
-    : mSizeRequested(aSizeRequested)
-    , mLastModifiedRequested(aLastModifiedRequested)
-  {
-  }
-
-  bool
-  IsConfigured() const
-  {
-    return mSizeRequested || mLastModifiedRequested;
-  }
-
-  bool
-  SizeRequested() const
-  {
-    return mSizeRequested;
-  }
-
-  bool
-  LastModifiedRequested() const
-  {
-    return mLastModifiedRequested;
-  }
-
-  uint64_t
-  Size() const
-  {
-    return mSize;
-  }
-
-  int64_t
-  LastModified() const
-  {
-    return mLastModified;
-  }
-
-private:
-  // Private destructor, to discourage deletion outside of Release():
-  ~MetadataParameters()
-  {
-  }
-
-  uint64_t mSize;
-  int64_t mLastModified;
-  bool mSizeRequested;
-  bool mLastModifiedRequested;
-};
-
-class MetadataHelper : public FileHelper
-{
-public:
-  MetadataHelper(FileHandleBase* aFileHandle,
-                 FileRequestBase* aFileRequest,
-                 MetadataParameters* aParams)
-  : FileHelper(aFileHandle, aFileRequest),
-    mParams(aParams)
-  { }
-
-  nsresult
-  DoAsyncRun(nsISupports* aStream) override;
-
-  nsresult
-  GetSuccessResult(JSContext* aCx,
-                   JS::MutableHandle<JS::Value> aVal) override;
-
-protected:
-  class AsyncMetadataGetter : public AsyncHelper
-  {
-  public:
-    AsyncMetadataGetter(nsISupports* aStream, MetadataParameters* aParams,
-                        bool aReadWrite)
-    : AsyncHelper(aStream),
-      mParams(aParams), mReadWrite(aReadWrite)
-    { }
-
-  protected:
-    nsresult
-    DoStreamWork(nsISupports* aStream) override;
-
-  private:
-    nsRefPtr<MetadataParameters> mParams;
-    bool mReadWrite;
-  };
-
-  nsRefPtr<MetadataParameters> mParams;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_MetadataHelper_h
rename from dom/filehandle/MutableFile.cpp
rename to dom/filehandle/MutableFileBase.cpp
--- a/dom/filehandle/MutableFile.cpp
+++ b/dom/filehandle/MutableFileBase.cpp
@@ -1,52 +1,36 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#include "MutableFile.h"
+#include "MutableFileBase.h"
 
-#include "nsDebug.h"
-#include "nsIFile.h"
-#include "nsIFileStreams.h"
-#include "nsIInputStream.h"
-#include "nsNetUtil.h"
+#include "ActorsChild.h"
+#include "mozilla/Assertions.h"
+#include "prthread.h"
 
 namespace mozilla {
 namespace dom {
 
-MutableFileBase::MutableFileBase()
+MutableFileBase::MutableFileBase(DEBUGONLY(PRThread* aOwningThread,)
+                                 BackgroundMutableFileChildBase* aActor)
+  : RefCountedThreadObject(DEBUGONLY(aOwningThread))
+  , mBackgroundActor(aActor)
 {
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aActor);
 }
 
 MutableFileBase::~MutableFileBase()
 {
-}
-
-// virtual
-already_AddRefed<nsISupports>
-MutableFileBase::CreateStream(bool aReadOnly)
-{
-  nsresult rv;
+  AssertIsOnOwningThread();
 
-  if (aReadOnly) {
-    nsCOMPtr<nsIInputStream> stream;
-    rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), mFile, -1, -1,
-                                    nsIFileInputStream::DEFER_OPEN);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return nullptr;
-    }
-    return stream.forget();
+  if (mBackgroundActor) {
+    mBackgroundActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
   }
-
-  nsCOMPtr<nsIFileStream> stream;
-  rv = NS_NewLocalFileStream(getter_AddRefs(stream), mFile, -1, -1,
-                             nsIFileStream::DEFER_OPEN);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
-  return stream.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/filehandle/MutableFile.h
rename to dom/filehandle/MutableFileBase.h
--- a/dom/filehandle/MutableFile.h
+++ b/dom/filehandle/MutableFileBase.h
@@ -2,65 +2,75 @@
 /* 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_MutableFile_h
 #define mozilla_dom_MutableFile_h
 
-#include "nsCOMPtr.h"
-#include "nsString.h"
+#include "mozilla/dom/FileHandleCommon.h"
+#include "nscore.h"
 
-class nsIFile;
-class nsIOfflineStorage;
+template <class> struct already_AddRefed;
+class nsISupports;
+class nsString;
+struct PRThread;
 
 namespace mozilla {
 namespace dom {
 
-class FileService;
+class BackgroundMutableFileChildBase;
+class BlobImpl;
+class File;
+class FileHandleBase;
 
 /**
  * This class provides a base for MutableFile implementations.
- * The subclasses can override implementation of IsInvalid and CreateStream.
  * (for example IDBMutableFile provides IndexedDB specific implementation).
  */
 class MutableFileBase
+  : public RefCountedThreadObject
 {
-  friend class FileService;
+protected:
+  BackgroundMutableFileChildBase* mBackgroundActor;
 
 public:
-  NS_IMETHOD_(MozExternalRefCountType)
-  AddRef() = 0;
+  BackgroundMutableFileChildBase*
+  GetBackgroundActor() const
+  {
+    AssertIsOnOwningThread();
 
-  NS_IMETHOD_(MozExternalRefCountType)
-  Release() = 0;
-
-  virtual bool
-  IsInvalid()
-  {
-    return false;
+    return mBackgroundActor;
   }
 
-  // A temporary method that will be removed along with nsIOfflineStorage
-  // interface.
-  virtual nsIOfflineStorage*
-  Storage() = 0;
+  void
+  ClearBackgroundActor()
+  {
+    AssertIsOnOwningThread();
+
+    mBackgroundActor = nullptr;
+  }
 
-  virtual already_AddRefed<nsISupports>
-  CreateStream(bool aReadOnly);
+  virtual const nsString&
+  Name() const = 0;
+
+  virtual const nsString&
+  Type() const = 0;
 
+  virtual bool
+  IsInvalidated() = 0;
+
+  virtual already_AddRefed<File>
+  CreateFileFor(BlobImpl* aBlobImpl,
+                FileHandleBase* aFileHandle) = 0;
 
 protected:
-  MutableFileBase();
+  MutableFileBase(DEBUGONLY(PRThread* aOwningThread,)
+                  BackgroundMutableFileChildBase* aActor);
 
   virtual ~MutableFileBase();
-
-  nsCOMPtr<nsIFile> mFile;
-
-  nsCString mStorageId;
-  nsString mFileName;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_MutableFile_h
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/PBackgroundFileHandle.ipdl
@@ -0,0 +1,91 @@
+/* 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 protocol PBackgroundFileRequest;
+include protocol PBackgroundMutableFile;
+include protocol PBlob;
+
+namespace mozilla {
+namespace dom {
+
+struct FileRequestGetMetadataParams
+{
+  bool size;
+  bool lastModified;
+};
+
+struct FileRequestReadParams
+{
+  uint64_t offset;
+  uint64_t size;
+};
+
+struct FileRequestStringData
+{
+  nsCString string;
+};
+
+struct FileRequestBlobData
+{
+  PBlob blob;
+};
+
+union FileRequestData
+{
+  FileRequestStringData;
+  FileRequestBlobData;
+};
+
+struct FileRequestWriteParams
+{
+  uint64_t offset;
+  FileRequestData data;
+  uint64_t dataLength;
+};
+
+struct FileRequestTruncateParams
+{
+  uint64_t offset;
+};
+
+struct FileRequestFlushParams
+{
+};
+
+struct FileRequestGetFileParams
+{
+};
+
+union FileRequestParams
+{
+  FileRequestGetMetadataParams;
+  FileRequestReadParams;
+  FileRequestWriteParams;
+  FileRequestTruncateParams;
+  FileRequestFlushParams;
+  FileRequestGetFileParams;
+};
+
+protocol PBackgroundFileHandle
+{
+  manager PBackgroundMutableFile;
+
+  manages PBackgroundFileRequest;
+
+parent:
+  DeleteMe();
+
+  Finish();
+  Abort();
+
+  PBackgroundFileRequest(FileRequestParams params);
+
+child:
+  __delete__();
+
+  Complete(bool aborted);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/PBackgroundFileRequest.ipdl
@@ -0,0 +1,83 @@
+/* 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 protocol PBackgroundFileHandle;
+include protocol PBlob;
+
+using struct mozilla::void_t
+  from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+union FileRequestSize
+{
+  void_t;
+  uint64_t;
+};
+
+union FileRequestLastModified
+{
+  void_t;
+  int64_t;
+};
+
+struct FileRequestMetadata
+{
+  FileRequestSize size;
+  FileRequestLastModified lastModified;
+};
+
+struct FileRequestGetMetadataResponse
+{
+  FileRequestMetadata metadata;
+};
+
+struct FileRequestReadResponse
+{
+  nsCString data;
+};
+
+struct FileRequestWriteResponse
+{
+};
+
+struct FileRequestTruncateResponse
+{
+};
+
+struct FileRequestFlushResponse
+{
+};
+
+struct FileRequestGetFileResponse
+{
+  PBlob file;
+  FileRequestMetadata metadata;
+};
+
+union FileRequestResponse
+{
+  nsresult;
+  FileRequestGetMetadataResponse;
+  FileRequestReadResponse;
+  FileRequestWriteResponse;
+  FileRequestTruncateResponse;
+  FileRequestFlushResponse;
+  FileRequestGetFileResponse;
+};
+
+protocol PBackgroundFileRequest
+{
+  manager PBackgroundFileHandle;
+
+child:
+  __delete__(FileRequestResponse response);
+
+  Progress(uint64_t progress,
+           uint64_t progressMax);
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/PBackgroundMutableFile.ipdl
@@ -0,0 +1,36 @@
+/* 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 protocol PBackgroundFileHandle;
+include protocol PBackgroundIDBDatabase;
+
+include "mozilla/dom/filehandle/SerializationHelpers.h";
+
+using mozilla::dom::FileMode
+  from "mozilla/dom/FileModeBinding.h";
+
+namespace mozilla {
+namespace dom {
+
+sync protocol PBackgroundMutableFile
+{
+  manager PBackgroundIDBDatabase;
+
+  manages PBackgroundFileHandle;
+
+parent:
+  DeleteMe();
+
+  PBackgroundFileHandle(FileMode mode);
+
+  // Use only for testing!
+  sync GetFileId()
+    returns (int64_t fileId);
+
+child:
+  __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/filehandle/SerializationHelpers.h
@@ -0,0 +1,23 @@
+/* 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_filehandle_SerializationHelpers_h
+#define mozilla_dom_filehandle_SerializationHelpers_h
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/dom/FileModeBinding.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::FileMode> :
+  public ContiguousEnumSerializer<mozilla::dom::FileMode,
+                                  mozilla::dom::FileMode::Readonly,
+                                  mozilla::dom::FileMode::EndGuard_>
+{ };
+
+} // namespace IPC
+
+#endif // mozilla_dom_filehandle_SerializationHelpers_h
--- a/dom/filehandle/moz.build
+++ b/dom/filehandle/moz.build
@@ -1,34 +1,40 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+EXPORTS.mozilla.dom.filehandle += [
+    'ActorsChild.h',
+    'ActorsParent.h',
+    'SerializationHelpers.h',
+]
+
 EXPORTS.mozilla.dom += [
-    'AsyncHelper.h',
-    'FileHandle.h',
-    'FileHelper.h',
-    'FileRequest.h',
-    'FileService.h',
-    'MetadataHelper.h',
-    'MutableFile.h',
+    'FileHandleBase.h',
+    'FileHandleCommon.h',
+    'FileHandleStorage.h',
+    'FileRequestBase.h',
+    'MutableFileBase.h',
 ]
 
 UNIFIED_SOURCES += [
-    'AsyncHelper.cpp',
-    'FileHandle.cpp',
-    'FileHelper.cpp',
-    'FileRequest.cpp',
-    'FileService.cpp',
-    'FileStreamWrappers.cpp',
-    'MemoryStreams.cpp',
-    'MetadataHelper.cpp',
-    'MutableFile.cpp',
+    'ActorsChild.cpp',
+    'ActorsParent.cpp',
+    'FileHandleBase.cpp',
+    'FileHandleCommon.cpp',
+    'MutableFileBase.cpp',
+]
+
+IPDL_SOURCES += [
+    'PBackgroundFileHandle.ipdl',
+    'PBackgroundFileRequest.ipdl',
+    'PBackgroundMutableFile.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '../base',
 ]
 
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -7,16 +7,17 @@
 #include "ActorsChild.h"
 
 #include "BackgroundChildImpl.h"
 #include "FileManager.h"
 #include "IDBDatabase.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBIndex.h"
+#include "IDBMutableFile.h"
 #include "IDBObjectStore.h"
 #include "IDBMutableFile.h"
 #include "IDBRequest.h"
 #include "IDBTransaction.h"
 #include "IndexedDatabase.h"
 #include "IndexedDatabaseInlines.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/Maybe.h"
@@ -592,52 +593,93 @@ protected:
 
 void
 ConvertActorsToBlobs(IDBDatabase* aDatabase,
                      const SerializedStructuredCloneReadInfo& aCloneReadInfo,
                      nsTArray<StructuredCloneFile>& aFiles)
 {
   MOZ_ASSERT(aFiles.IsEmpty());
 
-  const nsTArray<PBlobChild*>& blobs = aCloneReadInfo.blobsChild();
-  const nsTArray<intptr_t>& fileInfos = aCloneReadInfo.fileInfos();
-
-  MOZ_ASSERT_IF(IndexedDatabaseManager::IsMainProcess(),
-                blobs.Length() == fileInfos.Length());
-  MOZ_ASSERT_IF(!IndexedDatabaseManager::IsMainProcess(), fileInfos.IsEmpty());
+  const nsTArray<BlobOrMutableFile>& blobs = aCloneReadInfo.blobs();
 
   if (!blobs.IsEmpty()) {
     const uint32_t count = blobs.Length();
     aFiles.SetCapacity(count);
 
     for (uint32_t index = 0; index < count; index++) {
-      BlobChild* actor = static_cast<BlobChild*>(blobs[index]);
-
-      nsRefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
-      MOZ_ASSERT(blobImpl);
-
-      nsRefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
-
-      nsRefPtr<FileInfo> fileInfo;
-      if (!fileInfos.IsEmpty()) {
-        fileInfo = dont_AddRef(reinterpret_cast<FileInfo*>(fileInfos[index]));
-
-        MOZ_ASSERT(fileInfo);
-        MOZ_ASSERT(fileInfo->Id() > 0);
-
-        blob->AddFileInfo(fileInfo);
+      const BlobOrMutableFile& blobOrMutableFile = blobs[index];
+
+      switch (blobOrMutableFile.type()) {
+        case BlobOrMutableFile::TPBlobChild: {
+          auto* actor =
+            static_cast<BlobChild*>(blobOrMutableFile.get_PBlobChild());
+
+          nsRefPtr<BlobImpl> blobImpl = actor->GetBlobImpl();
+          MOZ_ASSERT(blobImpl);
+
+          nsRefPtr<Blob> blob = Blob::Create(aDatabase->GetOwner(), blobImpl);
+
+          aDatabase->NoteReceivedBlob(blob);
+
+          StructuredCloneFile* file = aFiles.AppendElement();
+          MOZ_ASSERT(file);
+
+          file->mMutable = false;
+          file->mBlob.swap(blob);
+
+          break;
+        }
+
+        case BlobOrMutableFile::TNullableMutableFile: {
+          const NullableMutableFile& nullableMutableFile =
+            blobOrMutableFile.get_NullableMutableFile();
+
+          switch (nullableMutableFile.type()) {
+            case NullableMutableFile::Tnull_t: {
+              StructuredCloneFile* file = aFiles.AppendElement();
+              MOZ_ASSERT(file);
+
+              file->mMutable = true;
+
+              break;
+            }
+
+            case NullableMutableFile::TPBackgroundMutableFileChild: {
+              auto* actor =
+                static_cast<BackgroundMutableFileChild*>(
+                  nullableMutableFile.get_PBackgroundMutableFileChild());
+              MOZ_ASSERT(actor);
+
+              actor->EnsureDOMObject();
+
+              auto* mutableFile =
+                static_cast<IDBMutableFile*>(actor->GetDOMObject());
+              MOZ_ASSERT(mutableFile);
+
+              StructuredCloneFile* file = aFiles.AppendElement();
+              MOZ_ASSERT(file);
+
+              file->mMutable = true;
+              file->mMutableFile = mutableFile;
+
+              actor->ReleaseDOMObject();
+
+              break;
+            }
+
+            default:
+              MOZ_CRASH("Should never get here!");
+          }
+
+          break;
+        }
+
+        default:
+          MOZ_CRASH("Should never get here!");
       }
-
-      aDatabase->NoteReceivedBlob(blob);
-
-      StructuredCloneFile* file = aFiles.AppendElement();
-      MOZ_ASSERT(file);
-
-      file->mBlob.swap(blob);
-      file->mFileInfo.swap(fileInfo);
     }
   }
 }
 
 void
 DispatchErrorEvent(IDBRequest* aRequest,
                    nsresult aErrorCode,
                    IDBTransaction* aTransaction = nullptr,
@@ -1120,16 +1162,23 @@ BackgroundFactoryChild::AssertIsOnOwning
 {
   MOZ_ASSERT(mOwningThread);
 
   bool current;
   MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
   MOZ_ASSERT(current);
 }
 
+nsIEventTarget*
+BackgroundFactoryChild::OwningThread() const
+{
+  MOZ_ASSERT(mOwningThread);
+  return mOwningThread;
+}
+
 #endif // DEBUG
 
 void
 BackgroundFactoryChild::SendDeleteMeInternal()
 {
   AssertIsOnOwningThread();
 
   if (mFactory) {
@@ -1521,17 +1570,17 @@ BackgroundDatabaseChild::EnsureDOMObject
 
   if (mTemporaryStrongDatabase) {
     MOZ_ASSERT(!mSpec);
     return;
   }
 
   MOZ_ASSERT(mSpec);
 
-  auto request = mOpenRequestActor->GetDOMObject();
+  auto request = mOpenRequestActor->GetOpenDBRequest();
   MOZ_ASSERT(request);
 
   auto factory =
     static_cast<BackgroundFactoryChild*>(Manager())->GetDOMObject();
   MOZ_ASSERT(factory);
 
   mTemporaryStrongDatabase =
     IDBDatabase::Create(request, factory, this, mSpec);
@@ -1588,16 +1637,34 @@ BackgroundDatabaseChild::DeallocPBackgro
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aActor);
 
   delete aActor;
   return true;
 }
 
+PBackgroundIDBDatabaseRequestChild*
+BackgroundDatabaseChild::AllocPBackgroundIDBDatabaseRequestChild(
+                                           const DatabaseRequestParams& aParams)
+{
+  MOZ_CRASH("PBackgroundIDBDatabaseRequestChild actors should be manually "
+            "constructed!");
+}
+
+bool
+BackgroundDatabaseChild::DeallocPBackgroundIDBDatabaseRequestChild(
+                                     PBackgroundIDBDatabaseRequestChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<BackgroundDatabaseRequestChild*>(aActor);
+  return true;
+}
+
 PBackgroundIDBTransactionChild*
 BackgroundDatabaseChild::AllocPBackgroundIDBTransactionChild(
                                     const nsTArray<nsString>& aObjectStoreNames,
                                     const Mode& aMode)
 {
   MOZ_CRASH("PBackgroundIDBTransactionChild actors should be manually "
             "constructed!");
 }
@@ -1693,16 +1760,44 @@ BackgroundDatabaseChild::DeallocPBackgro
                             PBackgroundIDBVersionChangeTransactionChild* aActor)
 {
   MOZ_ASSERT(aActor);
 
   delete static_cast<BackgroundVersionChangeTransactionChild*>(aActor);
   return true;
 }
 
+PBackgroundMutableFileChild*
+BackgroundDatabaseChild::AllocPBackgroundMutableFileChild(const nsString& aName,
+                                                          const nsString& aType)
+{
+  AssertIsOnOwningThread();
+
+#ifdef DEBUG
+  nsCOMPtr<nsIThread> owningThread = do_QueryInterface(OwningThread());
+
+  PRThread* owningPRThread;
+  owningThread->GetPRThread(&owningPRThread);
+#endif
+
+  return new BackgroundMutableFileChild(DEBUGONLY(owningPRThread,)
+                                        aName,
+                                        aType);
+}
+
+bool
+BackgroundDatabaseChild::DeallocPBackgroundMutableFileChild(
+                                            PBackgroundMutableFileChild* aActor)
+{
+  MOZ_ASSERT(aActor);
+
+  delete static_cast<BackgroundMutableFileChild*>(aActor);
+  return true;
+}
+
 bool
 BackgroundDatabaseChild::RecvVersionChange(const uint64_t& aOldVersion,
                                            const NullableVersion& aNewVersion)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
@@ -1786,16 +1881,101 @@ BackgroundDatabaseChild::RecvInvalidate(
   if (mDatabase) {
     mDatabase->Invalidate();
   }
 
   return true;
 }
 
 /*******************************************************************************
+ * BackgroundDatabaseRequestChild
+ ******************************************************************************/
+
+BackgroundDatabaseRequestChild::BackgroundDatabaseRequestChild(
+                                                         IDBDatabase* aDatabase,
+                                                         IDBRequest* aRequest)
+  : BackgroundRequestChildBase(aRequest)
+  , mDatabase(aDatabase)
+{
+  // Can't assert owning thread here because IPDL has not yet set our manager!
+  MOZ_ASSERT(aDatabase);
+  aDatabase->AssertIsOnOwningThread();
+  MOZ_ASSERT(aRequest);
+
+  MOZ_COUNT_CTOR(indexedDB::BackgroundDatabaseRequestChild);
+}
+
+BackgroundDatabaseRequestChild::~BackgroundDatabaseRequestChild()
+{
+  MOZ_COUNT_DTOR(indexedDB::BackgroundDatabaseRequestChild);
+}
+
+bool
+BackgroundDatabaseRequestChild::HandleResponse(nsresult aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(NS_FAILED(aResponse));
+  MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
+
+  mRequest->Reset();
+
+  DispatchErrorEvent(mRequest, aResponse);
+
+  return true;
+}
+
+bool
+BackgroundDatabaseRequestChild::HandleResponse(
+                                     const CreateFileRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+
+  mRequest->Reset();
+
+  auto mutableFileActor =
+    static_cast<BackgroundMutableFileChild*>(aResponse.mutableFileChild());
+  MOZ_ASSERT(mutableFileActor);
+
+  mutableFileActor->EnsureDOMObject();
+
+  auto mutableFile =
+    static_cast<IDBMutableFile*>(mutableFileActor->GetDOMObject());
+  MOZ_ASSERT(mutableFile);
+
+  ResultHelper helper(mRequest, nullptr, mutableFile);
+
+  DispatchSuccessEvent(&helper);
+
+  mutableFileActor->ReleaseDOMObject();
+
+  return true;
+}
+
+bool
+BackgroundDatabaseRequestChild::Recv__delete__(
+                                       const DatabaseRequestResponse& aResponse)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mRequest);
+
+  switch (aResponse.type()) {
+    case DatabaseRequestResponse::Tnsresult:
+      return HandleResponse(aResponse.get_nsresult());
+
+    case DatabaseRequestResponse::TCreateFileRequestResponse:
+      return HandleResponse(aResponse.get_CreateFileRequestResponse());
+
+    default:
+      MOZ_CRASH("Unknown response type!");
+  }
+
+  MOZ_CRASH("Should never get here!");
+}
+
+/*******************************************************************************
  * BackgroundTransactionBase
  ******************************************************************************/
 
 BackgroundTransactionBase::BackgroundTransactionBase()
 : mTransaction(nullptr)
 {
   MOZ_COUNT_CTOR(indexedDB::BackgroundTransactionBase);
 }
@@ -2090,16 +2270,50 @@ BackgroundVersionChangeTransactionChild:
 {
   MOZ_ASSERT(aActor);
 
   delete static_cast<BackgroundCursorChild*>(aActor);
   return true;
 }
 
 /*******************************************************************************
+ * BackgroundMutableFileChild
+ ******************************************************************************/
+
+BackgroundMutableFileChild::BackgroundMutableFileChild(
+                                             DEBUGONLY(PRThread* aOwningThread,)
+                                             const nsAString& aName,
+                                             const nsAString& aType)
+  : BackgroundMutableFileChildBase(DEBUGONLY(aOwningThread))
+  , mName(aName)
+  , mType(aType)
+{
+  // Can't assert owning thread here because IPDL has not yet set our manager!
+  MOZ_COUNT_CTOR(indexedDB::BackgroundMutableFileChild);
+}
+
+BackgroundMutableFileChild::~BackgroundMutableFileChild()
+{
+  MOZ_COUNT_DTOR(indexedDB::BackgroundMutableFileChild);
+}
+
+already_AddRefed<MutableFileBase>
+BackgroundMutableFileChild::CreateMutableFile()
+{
+  auto database =
+    static_cast<BackgroundDatabaseChild*>(Manager())->GetDOMObject();
+  MOZ_ASSERT(database);
+
+  nsRefPtr<IDBMutableFile> mutableFile =
+    new IDBMutableFile(database, this, mName, mType);
+
+  return mutableFile.forget();
+}
+
+/*******************************************************************************
  * BackgroundRequestChild
  ******************************************************************************/
 
 BackgroundRequestChild::BackgroundRequestChild(IDBRequest* aRequest)
   : BackgroundRequestChildBase(aRequest)
   , mTransaction(aRequest->GetTransaction())
 {
   MOZ_ASSERT(mTransaction);
@@ -2112,26 +2326,16 @@ BackgroundRequestChild::~BackgroundReque
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mTransaction);
 
   MOZ_COUNT_DTOR(indexedDB::BackgroundRequestChild);
 }
 
 void
-BackgroundRequestChild::HoldFileInfosUntilComplete(
-                                       nsTArray<nsRefPtr<FileInfo>>& aFileInfos)
-{
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mFileInfos.IsEmpty());
-
-  mFileInfos.SwapElements(aFileInfos);
-}
-
-void
 BackgroundRequestChild::HandleResponse(nsresult aResponse)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(aResponse));
   MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
   MOZ_ASSERT(mTransaction);
 
   DispatchErrorEvent(mRequest, aResponse, mTransaction);
@@ -2163,17 +2367,16 @@ BackgroundRequestChild::HandleResponse(
 {
   AssertIsOnOwningThread();
 
   // XXX Fix this somehow...
   auto& serializedCloneInfo =
     const_cast<SerializedStructuredCloneReadInfo&>(aResponse);
 
   StructuredCloneReadInfo cloneReadInfo(Move(serializedCloneInfo));
-  cloneReadInfo.mDatabase = mTransaction->Database();
 
   ConvertActorsToBlobs(mTransaction->Database(),
                        aResponse,
                        cloneReadInfo.mFiles);
 
   ResultHelper helper(mRequest, mTransaction, &cloneReadInfo);
 
   DispatchSuccessEvent(&helper);
@@ -2198,18 +2401,16 @@ BackgroundRequestChild::HandleResponse(
       // XXX Fix this somehow...
       auto& serializedCloneInfo =
         const_cast<SerializedStructuredCloneReadInfo&>(aResponse[index]);
 
       StructuredCloneReadInfo* cloneReadInfo = cloneReadInfos.AppendElement();
 
       *cloneReadInfo = Move(serializedCloneInfo);
 
-      cloneReadInfo->mDatabase = mTransaction->Database();
-
       ConvertActorsToBlobs(database,
                            serializedCloneInfo,
                            cloneReadInfo->mFiles);
     }
   }
 
   ResultHelper helper(mRequest, mTransaction, &cloneReadInfos);
 
@@ -2809,36 +3010,16 @@ BackgroundCursorChild::RecvResponse(cons
       MOZ_CRASH("Should never get here!");
   }
 
   mTransaction->OnRequestFinished(/* aActorDestroyedNormally */ true);
 
   return true;
 }
 
-// XXX This doesn't belong here. However, we're not yet porting MutableFile
-//     stuff to PBackground so this is necessary for the time being.
-void
-DispatchMutableFileResult(IDBRequest* aRequest,
-                          nsresult aResultCode,
-                          IDBMutableFile* aMutableFile)
-{
-  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aRequest);
-  MOZ_ASSERT_IF(NS_SUCCEEDED(aResultCode), aMutableFile);
-
-  if (NS_SUCCEEDED(aResultCode)) {
-    ResultHelper helper(aRequest, nullptr, aMutableFile);
-    DispatchSuccessEvent(&helper);
-  } else {
-    DispatchErrorEvent(aRequest, aResultCode);
-  }
-}
-
 NS_IMPL_ISUPPORTS(BackgroundCursorChild::DelayedActionRunnable,
                   nsIRunnable,
                   nsICancelableRunnable)
 
 NS_IMETHODIMP
 BackgroundCursorChild::
 DelayedActionRunnable::Run()
 {
--- a/dom/indexedDB/ActorsChild.h
+++ b/dom/indexedDB/ActorsChild.h
@@ -5,18 +5,20 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_indexeddb_actorschild_h__
 #define mozilla_dom_indexeddb_actorschild_h__
 
 #include "IDBTransaction.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/dom/filehandle/ActorsChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseChild.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionChild.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
@@ -31,17 +33,16 @@ namespace ipc {
 
 class BackgroundChildImpl;
 
 } // namespace ipc
 
 namespace dom {
 namespace indexedDB {
 
-class FileInfo;
 class IDBCursor;
 class IDBDatabase;
 class IDBFactory;
 class IDBMutableFile;
 class IDBOpenDBRequest;
 class IDBRequest;
 class Key;
 class PermissionRequestChild;
@@ -150,21 +151,25 @@ class BackgroundFactoryChild final
 
   IDBFactory* mFactory;
 
 #ifdef DEBUG
   nsCOMPtr<nsIEventTarget> mOwningThread;
 #endif
 
 public:
+#ifdef DEBUG
+  void
+  AssertIsOnOwningThread() const;
+
+  nsIEventTarget*
+  OwningThread() const;
+#else
   void
   AssertIsOnOwningThread() const
-#ifdef DEBUG
-  ;
-#else
   { }
 #endif
 
   IDBFactory*
   GetDOMObject() const
   {
     AssertIsOnOwningThread();
     return mFactory;
@@ -316,16 +321,24 @@ class BackgroundDatabaseChild final
 
 public:
   void
   AssertIsOnOwningThread() const
   {
     static_cast<BackgroundFactoryChild*>(Manager())->AssertIsOnOwningThread();
   }
 
+#ifdef DEBUG
+  nsIEventTarget*
+  OwningThread() const
+  {
+    return static_cast<BackgroundFactoryChild*>(Manager())->OwningThread();
+  }
+#endif
+
   const DatabaseSpec*
   Spec() const
   {
     AssertIsOnOwningThread();
     return mSpec;
   }
 
   IDBDatabase*
@@ -360,16 +373,25 @@ private:
   AllocPBackgroundIDBDatabaseFileChild(PBlobChild* aBlobChild)
                                        override;
 
   virtual bool
   DeallocPBackgroundIDBDatabaseFileChild(
                                         PBackgroundIDBDatabaseFileChild* aActor)
                                         override;
 
+  virtual PBackgroundIDBDatabaseRequestChild*
+  AllocPBackgroundIDBDatabaseRequestChild(const DatabaseRequestParams& aParams)
+                                          override;
+
+  virtual bool
+  DeallocPBackgroundIDBDatabaseRequestChild(
+                                     PBackgroundIDBDatabaseRequestChild* aActor)
+                                     override;
+
   virtual PBackgroundIDBTransactionChild*
   AllocPBackgroundIDBTransactionChild(
                                     const nsTArray<nsString>& aObjectStoreNames,
                                     const Mode& aMode)
                                     override;
 
   virtual bool
   DeallocPBackgroundIDBTransactionChild(PBackgroundIDBTransactionChild* aActor)
@@ -392,28 +414,64 @@ private:
                             const int64_t& aNextIndexId)
                             override;
 
   virtual bool
   DeallocPBackgroundIDBVersionChangeTransactionChild(
                             PBackgroundIDBVersionChangeTransactionChild* aActor)
                             override;
 
+  virtual PBackgroundMutableFileChild*
+  AllocPBackgroundMutableFileChild(const nsString& aName,
+                                   const nsString& aType) override;
+
+  virtual bool
+  DeallocPBackgroundMutableFileChild(PBackgroundMutableFileChild* aActor)
+                                     override;
+
   virtual bool
   RecvVersionChange(const uint64_t& aOldVersion,
                     const NullableVersion& aNewVersion)
                     override;
 
   virtual bool
   RecvInvalidate() override;
 
   bool
   SendDeleteMe() = delete;
 };
 
+class BackgroundDatabaseRequestChild final
+  : public BackgroundRequestChildBase
+  , public PBackgroundIDBDatabaseRequestChild
+{
+  friend class BackgroundDatabaseChild;
+  friend class IDBDatabase;
+
+  nsRefPtr<IDBDatabase> mDatabase;
+
+private:
+  // Only created by IDBDatabase.
+  BackgroundDatabaseRequestChild(IDBDatabase* aDatabase,
+                                 IDBRequest* aRequest);
+
+  // Only destroyed by BackgroundDatabaseChild.
+  ~BackgroundDatabaseRequestChild();
+
+  bool
+  HandleResponse(nsresult aResponse);
+
+  bool
+  HandleResponse(const CreateFileRequestResponse& aResponse);
+
+  // IPDL methods are only called by IPDL.
+  virtual bool
+  Recv__delete__(const DatabaseRequestResponse& aResponse) override;
+};
+
 class BackgroundVersionChangeTransactionChild;
 
 class BackgroundTransactionBase
 {
   friend class BackgroundVersionChangeTransactionChild;
 
   // mTemporaryStrongTransaction is strong and is only valid until the end of
   // NoteComplete() member function or until the NoteActorDestroyed() member
@@ -560,30 +618,47 @@ private:
   virtual bool
   DeallocPBackgroundIDBCursorChild(PBackgroundIDBCursorChild* aActor)
                                    override;
 
   bool
   SendDeleteMe() = delete;
 };
 
+class BackgroundMutableFileChild final
+  : public mozilla::dom::BackgroundMutableFileChildBase
+{
+  friend class BackgroundDatabaseChild;
+
+  nsString mName;
+  nsString mType;
+
+private:
+  // Only constructed by BackgroundDatabaseChild.
+  BackgroundMutableFileChild(DEBUGONLY(PRThread* aOwningThread,)
+                             const nsAString& aName,
+                             const nsAString& aType);
+
+  // Only destroyed by BackgroundDatabaseChild.
+  ~BackgroundMutableFileChild();
+
+  // BackgroundMutableFileChildBase
+  virtual already_AddRefed<MutableFileBase>
+  CreateMutableFile() override;
+};
+
 class BackgroundRequestChild final
   : public BackgroundRequestChildBase
   , public PBackgroundIDBRequestChild
 {
   friend class BackgroundTransactionChild;
   friend class BackgroundVersionChangeTransactionChild;
   friend class IDBTransaction;
 
   nsRefPtr<IDBTransaction> mTransaction;
-  nsTArray<nsRefPtr<FileInfo>> mFileInfos;
-
-public:
-  void
-  HoldFileInfosUntilComplete(nsTArray<nsRefPtr<FileInfo>>& aFileInfos);
 
 private:
   // Only created by IDBTransaction.
   explicit
   BackgroundRequestChild(IDBRequest* aRequest);
 
   // Only destroyed by BackgroundTransactionChild or
   // BackgroundVersionChangeTransactionChild.
@@ -749,20 +824,13 @@ private:
   // Force callers to use SendContinueInternal.
   bool
   SendContinue(const CursorRequestParams& aParams, const Key& aKey) = delete;
 
   bool
   SendDeleteMe() = delete;
 };
 
-// XXX This doesn't belong here. However, we're not yet porting MutableFile
-//     stuff to PBackground so this is necessary for the time being.
-void
-DispatchMutableFileResult(IDBRequest* aRequest,
-                          nsresult aResultCode,
-                          IDBMutableFile* aMutableFile);
-
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_indexeddb_actorschild_h__
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -27,22 +27,23 @@
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/storage.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/File.h"
-#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/filehandle/ActorsParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestParent.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/dom/quota/Client.h"
@@ -124,32 +125,33 @@ namespace {
 
 class ConnectionPool;
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseLoggingInfo;
 class DatabaseFile;
 class Factory;
+class MutableFile;
 class OpenDatabaseOp;
 class TransactionBase;
 class TransactionDatabaseOperationBase;
 class VersionChangeTransaction;
 
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 static_assert(JS_STRUCTURED_CLONE_VERSION == 5,
               "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 19;
+const uint32_t kMajorSchemaVersion = 20;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -223,16 +225,18 @@ const uint32_t kFileCopyBufferSize = 327
 
 const char kFileManagerDirectoryNameSuffix[] = ".files";
 const char kSQLiteJournalSuffix[] = ".sqlite-journal";
 const char kSQLiteSHMSuffix[] = ".sqlite-shm";
 const char kSQLiteWALSuffix[] = ".sqlite-wal";
 
 const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
 
+const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
+
 #define IDB_PREFIX "indexedDB"
 
 #define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
 #define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
 #define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
 
 const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1";
 
@@ -3674,16 +3678,183 @@ UpgradeSchemaFrom18_0To19_0(mozIStorageC
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(19, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+#if !defined(MOZ_B2G)
+
+class NormalJSRuntime;
+
+class UpgradeFileIdsFunction final
+  : public mozIStorageFunction
+{
+  nsRefPtr<FileManager> mFileManager;
+  nsAutoPtr<NormalJSRuntime> mRuntime;
+
+public:
+  UpgradeFileIdsFunction()
+  {
+    AssertIsOnIOThread();
+  }
+
+  nsresult
+  Init(nsIFile* aFMDirectory,
+       mozIStorageConnection* aConnection);
+
+  NS_DECL_ISUPPORTS
+
+private:
+  ~UpgradeFileIdsFunction()
+  {
+    AssertIsOnIOThread();
+
+    if (mFileManager) {
+      mFileManager->Invalidate();
+    }
+  }
+
+  NS_IMETHOD
+  OnFunctionCall(mozIStorageValueArray* aArguments,
+                 nsIVariant** aResult) override;
+};
+
+#endif // MOZ_B2G
+
+nsresult
+UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
+                            mozIStorageConnection* aConnection)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  PROFILER_LABEL("IndexedDB",
+                 "UpgradeSchemaFrom18_0To19_0",
+                 js::ProfileEntry::Category::STORAGE);
+
+#if defined(MOZ_B2G)
+
+  // We don't have to do the upgrade of file ids on B2G. The old format was
+  // only used by the previous single process implementation and B2G was
+  // always multi process. This is a nice optimization since the upgrade needs
+  // to deserialize all structured clones which reference a stored file or
+  // a mutable file.
+  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#else // MOZ_B2G
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT count(*) "
+    "FROM object_data "
+    "WHERE file_ids IS NOT NULL"
+  ), getter_AddRefs(stmt));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  int64_t count;
+
+  {
+    mozStorageStatementScoper scoper(stmt);
+
+    bool hasResult;
+    rv = stmt->ExecuteStep(&hasResult);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(!hasResult)) {
+      MOZ_ASSERT(false, "This should never be possible!");
+      return NS_ERROR_FAILURE;
+    }
+
+    count = stmt->AsInt64(0);
+    if (NS_WARN_IF(count < 0)) {
+      MOZ_ASSERT(false, "This should never be possible!");
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  if (count == 0) {
+    // Nothing to upgrade.
+    rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
+  nsRefPtr<UpgradeFileIdsFunction> function = new UpgradeFileIdsFunction();
+
+  rv = function->Init(aFMDirectory, aConnection);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  NS_NAMED_LITERAL_CSTRING(functionName, "upgrade");
+
+  rv = aConnection->CreateFunction(functionName, 2, function);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Disable update trigger.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "DROP TRIGGER object_data_update_trigger;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "UPDATE object_data "
+      "SET file_ids = upgrade(file_ids, data) "
+      "WHERE file_ids IS NOT NULL;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Enable update trigger.
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TRIGGER object_data_update_trigger "
+    "AFTER UPDATE OF file_ids ON object_data "
+    "FOR EACH ROW "
+    "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+    "BEGIN "
+      "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+    "END;"
+  ));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->RemoveFunction(functionName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+#endif // MOZ_B2G
+
+  return NS_OK;
+}
+
 nsresult
 GetDatabaseFileURL(nsIFile* aDatabaseFile,
                    PersistenceType aPersistenceType,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    uint32_t aTelemetryId,
                    nsIFileURL** aResult)
 {
@@ -4166,17 +4337,17 @@ CreateStorageConnection(nsIFile* aDBFile
       }
 
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else  {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((19 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((20 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         } else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
         } else if (schemaVersion == 6) {
@@ -4202,16 +4373,18 @@ CreateStorageConnection(nsIFile* aDBFile
           rv = UpgradeSchemaFrom15_0To16_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(16, 0)) {
           rv = UpgradeSchemaFrom16_0To17_0(connection);
         } else if (schemaVersion == MakeSchemaVersion(17, 0)) {
           rv = UpgradeSchemaFrom17_0To18_0(connection, aOrigin);
           vacuumNeeded = true;
         } else if (schemaVersion == MakeSchemaVersion(18, 0)) {
           rv = UpgradeSchemaFrom18_0To19_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(19, 0)) {
+          rv = UpgradeSchemaFrom19_0To20_0(aFMDirectory, connection);
         } else {
           IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
                       "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
@@ -5295,16 +5468,18 @@ private:
 /*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
 class DatabaseOperationBase
   : public nsRunnable
   , public mozIStorageProgressHandler
 {
+  friend class UpgradeFileIdsFunction;
+
 protected:
   class AutoSetProgressHandler;
 
   typedef nsDataHashtable<nsUint64HashKey, bool> UniqueIndexTable;
 
   nsCOMPtr<nsIEventTarget> mOwningThread;
   const nsID mBackgroundChildLoggingId;
   const uint64_t mLoggingSerialNumber;
@@ -5680,19 +5855,17 @@ class WaitForTransactionsHelper final
   nsCOMPtr<nsIEventTarget> mOwningThread;
   const nsCString mDatabaseId;
   nsCOMPtr<nsIRunnable> mCallback;
 
   enum class State
   {
     Initial = 0,
     WaitingForTransactions,
-    DispatchToMainThread,
     WaitingForFileHandles,
-    DispatchToOwningThread,
     Complete
   } mState;
 
 public:
   WaitForTransactionsHelper(const nsCString& aDatabaseId,
                             nsIRunnable* aCallback)
     : mOwningThread(NS_GetCurrentThread())
     , mDatabaseId(aDatabaseId)
@@ -5715,25 +5888,19 @@ private:
     MOZ_ASSERT(!mCallback);
     MOZ_ASSERT(mState == State::Complete);
   }
 
   void
   MaybeWaitForTransactions();
 
   void
-  DispatchToMainThread();
-
-  void
   MaybeWaitForFileHandles();
 
   void
-  DispatchToOwningThread();
-
-  void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
 class UnlockDirectoryRunnable final
   : public nsRunnable
 {
@@ -5763,26 +5930,28 @@ class Database final
   class StartTransactionOp;
 
 private:
   nsRefPtr<Factory> mFactory;
   nsRefPtr<FullDatabaseMetadata> mMetadata;
   nsRefPtr<FileManager> mFileManager;
   nsRefPtr<DirectoryLock> mDirectoryLock;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
+  nsTHashtable<nsPtrHashKey<MutableFile>> mMutableFiles;
   nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
   const OptionalContentId mOptionalContentParentId;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
-  uint32_t mFileHandleCount;
+  uint32_t mActiveMutableFileCount;
   const uint32_t mTelemetryId;
   const PersistenceType mPersistenceType;
+  const bool mFileHandleDisabled;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
   bool mInvalidated;
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
 public:
@@ -5791,16 +5960,17 @@ public:
            const PrincipalInfo& aPrincipalInfo,
            const OptionalContentId& aOptionalContentParentId,
            const nsACString& aGroup,
            const nsACString& aOrigin,
            uint32_t aTelemetryId,
            FullDatabaseMetadata* aMetadata,
            FileManager* aFileManager,
            already_AddRefed<DirectoryLock> aDirectoryLock,
+           bool aFileHandleDisabled,
            bool aChromeWriteAccessAllowed);
 
   void
   AssertIsOnConnectionThread() const
   {
 #ifdef DEBUG
     if (mConnection) {
       MOZ_ASSERT(mConnection);
@@ -5908,16 +6078,34 @@ public:
   ReleaseBackgroundThreadObjects();
 
   bool
   RegisterTransaction(TransactionBase* aTransaction);
 
   void
   UnregisterTransaction(TransactionBase* aTransaction);
 
+  bool
+  IsFileHandleDisabled() const
+  {
+    return mFileHandleDisabled;
+  }
+
+  bool
+  RegisterMutableFile(MutableFile* aMutableFile);
+
+  void
+  UnregisterMutableFile(MutableFile* aMutableFile);
+
+  void
+  NoteActiveMutableFile();
+
+  void
+  NoteInactiveMutableFile();
+
   void
   SetActorAlive();
 
   bool
   IsActorAlive() const
   {
     AssertIsOnBackgroundThread();
 
@@ -5978,29 +6166,47 @@ private:
   MaybeCloseConnection();
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
+  bool
+  VerifyRequestParams(const DatabaseRequestParams& aParams) const;
+
   // IPDL methods are only called by IPDL.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual PBackgroundIDBDatabaseFileParent*
   AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
                                         override;
 
   virtual bool
   DeallocPBackgroundIDBDatabaseFileParent(
                                        PBackgroundIDBDatabaseFileParent* aActor)
                                        override;
 
+  virtual PBackgroundIDBDatabaseRequestParent*
+  AllocPBackgroundIDBDatabaseRequestParent(const DatabaseRequestParams& aParams)
+                                           override;
+
+  virtual bool
+  RecvPBackgroundIDBDatabaseRequestConstructor(
+                                    PBackgroundIDBDatabaseRequestParent* aActor,
+                                    const DatabaseRequestParams& aParams)
+                                    override;
+
+  virtual bool
+  DeallocPBackgroundIDBDatabaseRequestParent(
+                                    PBackgroundIDBDatabaseRequestParent* aActor)
+                                    override;
+
   virtual PBackgroundIDBTransactionParent*
   AllocPBackgroundIDBTransactionParent(
                                     const nsTArray<nsString>& aObjectStoreNames,
                                     const Mode& aMode)
                                     override;
 
   virtual bool
   RecvPBackgroundIDBTransactionConstructor(
@@ -6022,30 +6228,32 @@ private:
                                               const int64_t& aNextIndexId)
                                               override;
 
   virtual bool
   DeallocPBackgroundIDBVersionChangeTransactionParent(
                            PBackgroundIDBVersionChangeTransactionParent* aActor)
                            override;
 
+  virtual PBackgroundMutableFileParent*
+  AllocPBackgroundMutableFileParent(const nsString& aName,
+                                    const nsString& aType) override;
+
+  virtual bool
+  DeallocPBackgroundMutableFileParent(PBackgroundMutableFileParent* aActor)
+                                      override;
+
   virtual bool
   RecvDeleteMe() override;
 
   virtual bool
   RecvBlocked() override;
 
   virtual bool
   RecvClose() override;
-
-  virtual bool
-  RecvNewFileHandle() override;
-
-  virtual bool
-  RecvFileHandleFinished() override;
 };
 
 class Database::StartTransactionOp final
   : public TransactionDatabaseOperationBase
 {
   friend class Database;
 
 private:
@@ -6602,16 +6810,79 @@ private:
                                       const OpenCursorParams& aParams)
                                       override;
 
   virtual bool
   DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
                                     override;
 };
 
+class MutableFile
+  : public BackgroundMutableFileParentBase
+{
+  nsRefPtr<Database> mDatabase;
+  nsRefPtr<FileInfo> mFileInfo;
+
+public:
+  static already_AddRefed<MutableFile>
+  Create(nsIFile* aFile,
+         Database* aDatabase,
+         FileInfo* aFileInfo);
+
+  Database*
+  GetDatabase() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(mDatabase);
+
+    return mDatabase;
+  }
+
+  FileInfo*
+  GetFileInfo() const
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(mFileInfo);
+
+    return mFileInfo;
+  }
+
+  virtual void
+  NoteActiveState() override;
+
+  virtual void
+  NoteInactiveState() override;
+
+  virtual PBackgroundParent*
+  GetBackgroundParent() const override;
+
+  virtual already_AddRefed<nsISupports>
+  CreateStream(bool aReadOnly) override;
+
+  virtual already_AddRefed<BlobImpl>
+  CreateBlobImpl() override;
+
+private:
+  MutableFile(nsIFile* aFile,
+              Database* aDatabase,
+              FileInfo* aFileInfo);
+
+  ~MutableFile();
+
+  virtual PBackgroundFileHandleParent*
+  AllocPBackgroundFileHandleParent(const FileMode& aMode) override;
+
+  virtual bool
+  RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor,
+                                       const FileMode& aMode) override;
+
+  virtual bool
+  RecvGetFileId(int64_t* aFileId) override;
+};
+
 class FactoryOp
   : public DatabaseOperationBase
   , public OpenDirectoryListener
   , public PBackgroundIDBFactoryRequestParent
 {
 public:
   struct MaybeBlockedDatabaseInfo;
 
@@ -6700,16 +6971,17 @@ protected:
   nsCString mOrigin;
   nsCString mDatabaseId;
   State mState;
   bool mIsApp;
   bool mEnforcingQuota;
   const bool mDeleting;
   bool mBlockedDatabaseOpen;
   bool mChromeWriteAccessAllowed;
+  bool mFileHandleDisabled;
 
 public:
   void
   NoteDatabaseBlocked(Database* aDatabase);
 
   virtual void
   NoteDatabaseClosed(Database* aDatabase) = 0;
 
@@ -7083,16 +7355,96 @@ private:
   nsresult
   DeleteFile(nsIFile* aDirectory,
              const nsAString& aFilename,
              QuotaManager* aQuotaManager);
 
   NS_DECL_NSIRUNNABLE
 };
 
+class DatabaseOp
+  : public DatabaseOperationBase
+  , public PBackgroundIDBDatabaseRequestParent
+{
+protected:
+  nsRefPtr<Database> mDatabase;
+
+  enum class State
+  {
+    // Just created on the PBackground thread, dispatched to the main thread.
+    // Next step is DatabaseWork.
+    Initial,
+
+    // Waiting to do/doing work on the QuotaManager IO thread. Next step is
+    // SendingResults.
+    DatabaseWork,
+
+    // Waiting to send/sending results on the PBackground thread. Next step is
+    // Completed.
+    SendingResults,
+
+    // All done.
+    Completed
+  };
+
+  State mState;
+
+  DatabaseOp(Database* aDatabase);
+
+  virtual
+  ~DatabaseOp()
+  {
+    MOZ_ASSERT_IF(OperationMayProceed(),
+                  mState == State::Initial || mState == State::Completed);
+  }
+
+  nsresult
+  SendToIOThread();
+
+  // Methods that subclasses must implement.
+  virtual nsresult
+  DoDatabaseWork() = 0;
+
+  virtual void
+  SendResults() = 0;
+
+  // Common nsIRunnable implementation that subclasses may not override.
+  NS_IMETHOD
+  Run() final;
+
+  // IPDL methods.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class CreateFileOp final
+  : public DatabaseOp
+{
+  const CreateFileParams mParams;
+
+  nsRefPtr<FileInfo> mFileInfo;
+
+public:
+  CreateFileOp(Database* aDatabase,
+               const DatabaseRequestParams& aParams);
+
+private:
+  ~CreateFileOp()
+  { }
+
+  nsresult
+  CreateMutableFile(MutableFile** aMutableFile);
+
+  virtual nsresult
+  DoDatabaseWork() override;
+
+  virtual void
+  SendResults() override;
+};
+
 class VersionChangeTransactionOp
   : public TransactionDatabaseOperationBase
 {
 public:
   virtual void
   Cleanup() override;
 
 protected:
@@ -7199,70 +7551,91 @@ private:
 
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
-class CreateIndexOp::ThreadLocalJSRuntime final
-{
-  friend class CreateIndexOp;
-  friend class nsAutoPtr<ThreadLocalJSRuntime>;
+class NormalJSRuntime
+{
+  friend class nsAutoPtr<NormalJSRuntime>;
 
   static const JSClass kGlobalClass;
   static const uint32_t kRuntimeHeapSize = 768 * 1024;
 
   JSRuntime* mRuntime;
   JSContext* mContext;
   JSObject* mGlobal;
 
 public:
-  static ThreadLocalJSRuntime*
-  GetOrCreate();
+  static NormalJSRuntime*
+  Create();
 
   JSContext*
   Context() const
   {
     return mContext;
   }
 
   JSObject*
   Global() const
   {
     return mGlobal;
   }
 
-private:
-  ThreadLocalJSRuntime()
+protected:
+  NormalJSRuntime()
     : mRuntime(nullptr)
     , mContext(nullptr)
     , mGlobal(nullptr)
   {
-    MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSRuntime);
-  }
-
-  ~ThreadLocalJSRuntime()
-  {
-    MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSRuntime);
+    MOZ_COUNT_CTOR(NormalJSRuntime);
+  }
+
+  ~NormalJSRuntime()
+  {
+    MOZ_COUNT_DTOR(NormalJSRuntime);
 
     if (mContext) {
       JS_DestroyContext(mContext);
     }
 
     if (mRuntime) {
       JS_DestroyRuntime(mRuntime);
     }
   }
 
   bool
   Init();
 };
 
+class CreateIndexOp::ThreadLocalJSRuntime final
+  : public NormalJSRuntime
+{
+  friend class CreateIndexOp;
+  friend class nsAutoPtr<ThreadLocalJSRuntime>;
+
+public:
+  static ThreadLocalJSRuntime*
+  GetOrCreate();
+
+private:
+  ThreadLocalJSRuntime()
+  {
+    MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSRuntime);
+  }
+
+  ~ThreadLocalJSRuntime()
+  {
+    MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSRuntime);
+  }
+};
+
 class CreateIndexOp::UpdateIndexDataValuesFunction final
   : public mozIStorageFunction
 {
   nsRefPtr<CreateIndexOp> mOp;
   nsRefPtr<DatabaseConnection> mConnection;
   JSContext* mCx;
 
 public:
@@ -7417,20 +7790,22 @@ private:
   Cleanup() override;
 };
 
 struct ObjectStoreAddOrPutRequestOp::StoredFileInfo final
 {
   nsRefPtr<DatabaseFile> mFileActor;
   nsRefPtr<FileInfo> mFileInfo;
   nsCOMPtr<nsIInputStream> mInputStream;
+  bool mMutable;
   bool mCopiedSuccessfully;
 
   StoredFileInfo()
-    : mCopiedSuccessfully(false)
+    : mMutable(false)
+    , mCopiedSuccessfully(false)
   {
     AssertIsOnBackgroundThread();
 
     MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
   }
 
   ~StoredFileInfo()
   {
@@ -7441,17 +7816,17 @@ struct ObjectStoreAddOrPutRequestOp::Sto
 };
 
 class ObjectStoreGetRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const uint32_t mObjectStoreId;
-  nsRefPtr<FileManager> mFileManager;
+  nsRefPtr<Database> mDatabase;
   const OptionalKeyRange mOptionalKeyRange;
   AutoFallibleTArray<StructuredCloneReadInfo, 1> mResponse;
   PBackgroundParent* mBackgroundParent;
   const uint32_t mLimit;
   const bool mGetAll;
 
 private:
   // Only created by TransactionBase.
@@ -7602,17 +7977,17 @@ private:
                          const RequestParams& aParams);
 };
 
 class IndexGetRequestOp final
   : public IndexRequestOpBase
 {
   friend class TransactionBase;
 
-  nsRefPtr<FileManager> mFileManager;
+  nsRefPtr<Database> mDatabase;
   const OptionalKeyRange mOptionalKeyRange;
   AutoFallibleTArray<StructuredCloneReadInfo, 1> mResponse;
   PBackgroundParent* mBackgroundParent;
   const uint32_t mLimit;
   const bool mGetAll;
 
 private:
   // Only created by TransactionBase.
@@ -7694,16 +8069,17 @@ class Cursor final :
   class CursorOpBase;
   class OpenOp;
 
 public:
   typedef OpenCursorParams::Type Type;
 
 private:
   nsRefPtr<TransactionBase> mTransaction;
+  nsRefPtr<Database> mDatabase;
   nsRefPtr<FileManager> mFileManager;
   PBackgroundParent* mBackgroundParent;
 
   // These should only be touched on the PBackground thread to check whether the
   // objectStore or index has been deleted. Holding these saves a hash lookup
   // for every call to continue()/advance().
   nsRefPtr<FullObjectStoreMetadata> mObjectStoreMetadata;
   nsRefPtr<FullIndexMetadata> mIndexMetadata;
@@ -8001,19 +8377,22 @@ public:
 
 private:
   ~DatabaseLoggingInfo();
 };
 
 class NonMainThreadHackBlobImpl final
   : public BlobImplFile
 {
-public:
-  NonMainThreadHackBlobImpl(nsIFile* aFile, FileInfo* aFileInfo)
+  bool mSnapshot;
+
+public:
+  NonMainThreadHackBlobImpl(nsIFile* aFile, FileInfo* aFileInfo, bool aSnapshot)
     : BlobImplFile(aFile, aFileInfo)
+    , mSnapshot(aSnapshot)
   {
     // Getting the content type is not currently supported off the main thread.
     // This isn't a problem here because:
     //
     //   1. The real content type is stored in the structured clone data and
     //      that's all that the DOM will see. This blob's data will be updated
     //      during RecvSetMysteryBlobInfo().
     //   2. The nsExternalHelperAppService guesses the content type based only
@@ -8022,16 +8401,22 @@ public:
     //      string.
     //
     // So, this is a hack to keep the nsExternalHelperAppService out of the
     // picture entirely. Eventually we should probably fix this some other way.
     mContentType.Truncate();
     mIsFile = false;
   }
 
+  virtual bool
+  IsSnapshot() const override
+  {
+    return mSnapshot;
+  }
+
 private:
   ~NonMainThreadHackBlobImpl()
   { }
 };
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
   , public nsIObserver
@@ -8528,16 +8913,26 @@ private:
   // OpenDirectoryListener overrides.
   virtual void
   DirectoryLockAcquired(DirectoryLock* aLock) override;
 
   virtual void
   DirectoryLockFailed() override;
 };
 
+class IntString : public nsAutoString
+{
+public:
+  explicit
+  IntString(int64_t aInteger)
+  {
+    AppendInt(aInteger);
+  }
+};
+
 #ifdef DEBUG
 
 class DEBUGThreadSlower final
   : public nsIThreadObserver
 {
 public:
   DEBUGThreadSlower()
   {
@@ -8612,32 +9007,32 @@ GetDatabaseBaseFilename(const nsAString&
   aDatabaseBaseFilename.Rebind(aFilename,
                                0,
                                aFilename.Length() - sqlite.Length());
   return true;
 }
 
 nsresult
 ConvertBlobsToActors(PBackgroundParent* aBackgroundActor,
-                     FileManager* aFileManager,
+                     Database* aDatabase,
                      const nsTArray<StructuredCloneFile>& aFiles,
-                     FallibleTArray<PBlobParent*>& aActors,
-                     FallibleTArray<intptr_t>& aFileInfos)
+                     FallibleTArray<BlobOrMutableFile>& aActors)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aBackgroundActor);
-  MOZ_ASSERT(aFileManager);
+  MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(aActors.IsEmpty());
-  MOZ_ASSERT(aFileInfos.IsEmpty());
 
   if (aFiles.IsEmpty()) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIFile> directory = aFileManager->GetDirectory();
+  FileManager* fileManager = aDatabase->GetFileManager();
+
+  nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
   if (NS_WARN_IF(!directory)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   DebugOnly<bool> exists;
   MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)));
   MOZ_ASSERT(exists);
@@ -8647,68 +9042,99 @@ ConvertBlobsToActors(PBackgroundParent* 
   MOZ_ASSERT(isDirectory);
 
   const uint32_t count = aFiles.Length();
 
   if (NS_WARN_IF(!aActors.SetCapacity(count, fallible))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  const bool collectFileInfos =
-    !BackgroundParent::IsOtherProcessActor(aBackgroundActor);
-
-  if (collectFileInfos &&
-      NS_WARN_IF(!aFileInfos.SetCapacity(count, fallible))) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
   for (uint32_t index = 0; index < count; index++) {
     const StructuredCloneFile& file = aFiles[index];
 
     const int64_t fileId = file.mFileInfo->Id();
     MOZ_ASSERT(fileId > 0);
 
     nsCOMPtr<nsIFile> nativeFile =
-      aFileManager->GetFileForId(directory, fileId);
+      fileManager->GetFileForId(directory, fileId);
     if (NS_WARN_IF(!nativeFile)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
     MOZ_ASSERT(NS_SUCCEEDED(nativeFile->Exists(&exists)));
     MOZ_ASSERT(exists);
 
     DebugOnly<bool> isFile;
     MOZ_ASSERT(NS_SUCCEEDED(nativeFile->IsFile(&isFile)));
     MOZ_ASSERT(isFile);
 
-    nsRefPtr<BlobImpl> impl =
-      new NonMainThreadHackBlobImpl(nativeFile, file.mFileInfo);
-
-    PBlobParent* actor =
-      BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor, impl);
-    if (!actor) {
-      // This can only fail if the child has crashed.
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-
-    MOZ_ALWAYS_TRUE(aActors.AppendElement(actor, fallible));
-
-    if (collectFileInfos) {
-      nsRefPtr<FileInfo> fileInfo = file.mFileInfo;
-
-      // Transfer a reference to the receiver.
-      auto transferedFileInfo =
-        reinterpret_cast<intptr_t>(fileInfo.forget().take());
-      MOZ_ALWAYS_TRUE(aFileInfos.AppendElement(transferedFileInfo, fallible));
-    }
-  }
-
-  return NS_OK;
+    if (file.mMutable) {
+      if (aDatabase->IsFileHandleDisabled()) {
+        MOZ_ALWAYS_TRUE(aActors.AppendElement(NullableMutableFile(null_t()),
+                                              fallible));
+      } else {
+        nsRefPtr<MutableFile> actor =
+          MutableFile::Create(nativeFile, aDatabase, file.mFileInfo);
+        if (!actor) {
+          IDB_REPORT_INTERNAL_ERR();
+          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+
+        // Transfer ownership to IPDL.
+        actor->SetActorAlive();
+
+        if (!aDatabase->SendPBackgroundMutableFileConstructor(actor,
+                                                              EmptyString(),
+                                                              EmptyString())) {
+          // This can only fail if the child has crashed.
+          IDB_REPORT_INTERNAL_ERR();
+          return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+        }
+
+        MOZ_ALWAYS_TRUE(aActors.AppendElement(NullableMutableFile(actor),
+                                              fallible));
+      }
+    } else {
+      nsRefPtr<BlobImpl> impl =
+        new NonMainThreadHackBlobImpl(nativeFile,
+                                      file.mFileInfo,
+                                      /* aSnapshot */ false);
+
+      PBlobParent* actor =
+        BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor, impl);
+      if (!actor) {
+        // This can only fail if the child has crashed.
+        IDB_REPORT_INTERNAL_ERR();
+        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+      }
+
+      MOZ_ALWAYS_TRUE(aActors.AppendElement(actor, fallible));
+    }
+  }
+
+  return NS_OK;
+}
+
+already_AddRefed<nsIFile>
+GetFileForFileInfo(FileInfo* aFileInfo)
+{
+  FileManager* fileManager = aFileInfo->Manager();
+  nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
+  if (NS_WARN_IF(!directory)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIFile> file = fileManager->GetFileForId(directory,
+                                                     aFileInfo->Id());
+  if (NS_WARN_IF(!file)) {
+    return nullptr;
+  }
+
+  return file.forget();
 }
 
 /*******************************************************************************
  * Globals
  ******************************************************************************/
 
 typedef nsTArray<nsRefPtr<FactoryOp>> FactoryOpArray;
 
@@ -8717,16 +9143,18 @@ StaticAutoPtr<FactoryOpArray> gFactoryOp
 // Maps a database id to information about live database actors.
 typedef nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>
         DatabaseActorHashtable;
 
 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
 
 StaticRefPtr<ConnectionPool> gConnectionPool;
 
+StaticRefPtr<FileHandleThreadPool> gFileHandleThreadPool;
+
 typedef nsDataHashtable<nsIDHashKey, DatabaseLoggingInfo*>
         DatabaseLoggingInfoHashtable;
 
 StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
 
 typedef nsDataHashtable<nsUint32HashKey, uint32_t> TelemetryIdHashtable;
 
 StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable;
@@ -8922,16 +9350,22 @@ already_AddRefed<mozilla::dom::quota::Cl
 CreateQuotaClient()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<QuotaClient> client = new QuotaClient();
   return client.forget();
 }
 
+FileHandleThreadPool*
+GetFileHandleThreadPool()
+{
+  return gFileHandleThreadPool;
+}
+
 /*******************************************************************************
  * DatabaseConnection implementation
  ******************************************************************************/
 
 DatabaseConnection::DatabaseConnection(
                                       mozIStorageConnection* aStorageConnection,
                                       FileManager* aFileManager)
   : mStorageConnection(aStorageConnection)
@@ -9934,17 +10368,18 @@ UpdateRefcountFunction::ProcessValue(moz
 
   nsTArray<int64_t> fileIds;
   rv = ConvertFileIdsToArray(ids, fileIds);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   for (uint32_t i = 0; i < fileIds.Length(); i++) {
-    int64_t id = fileIds.ElementAt(i);
+    MOZ_ASSERT(fileIds[i] != 0);
+    int64_t id = Abs(fileIds[i]);
 
     FileInfoEntry* entry;
     if (!mFileInfoEntries.Get(id, &entry)) {
       nsRefPtr<FileInfo> fileInfo = mFileManager->GetFileInfo(id);
       MOZ_ASSERT(fileInfo);
 
       entry = new FileInfoEntry(fileInfo);
       mFileInfoEntries.Put(id, entry);
@@ -12225,70 +12660,47 @@ WaitForTransactionsHelper::MaybeWaitForT
     ids.AppendElement(mDatabaseId);
 
     mState = State::WaitingForTransactions;
 
     connectionPool->WaitForDatabasesToComplete(Move(ids), this);
     return;
   }
 
-  DispatchToMainThread();
-}
-
-void
-WaitForTransactionsHelper::DispatchToMainThread()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State::Initial || mState == State::WaitingForTransactions);
-
-  mState = State::DispatchToMainThread;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  MaybeWaitForFileHandles();
 }
 
 void
 WaitForTransactionsHelper::MaybeWaitForFileHandles()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::DispatchToMainThread);
-
-  FileService* service = FileService::Get();
-  if (service) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State::Initial || mState == State::WaitingForTransactions);
+
+  nsRefPtr<FileHandleThreadPool> fileHandleThreadPool =
+    gFileHandleThreadPool.get();
+  if (fileHandleThreadPool) {
     nsTArray<nsCString> ids(1);
     ids.AppendElement(mDatabaseId);
 
     mState = State::WaitingForFileHandles;
 
-    service->WaitForStoragesToComplete(ids, this);
-
-    MOZ_ASSERT(ids.IsEmpty());
-    return;
-  }
-
-  DispatchToOwningThread();
-}
-
-void
-WaitForTransactionsHelper::DispatchToOwningThread()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::DispatchToMainThread ||
-             mState == State::WaitingForFileHandles);
-
-  mState = State::DispatchToOwningThread;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
-                                                       NS_DISPATCH_NORMAL)));
+    fileHandleThreadPool->WaitForDirectoriesToComplete(Move(ids), this);