Bug 1125102 - Make QuotaManager and FileService to be independent of each other; r=bent
authorJan Varga <jan.varga@gmail.com>
Tue, 14 Apr 2015 10:57:41 +0200
changeset 238984 c2c326d0ba80043d17aa86acd3c6f5d21f5fa79a
parent 238983 d19d1522ce46c7ffaceabe17a047d425554ab324
child 238985 607cfbb06eb83175f7546ff585e191359511b52d
push idunknown
push userunknown
push dateunknown
reviewersbent
bugs1125102
milestone40.0a1
Bug 1125102 - Make QuotaManager and FileService to be independent of each other; r=bent
dom/asmjscache/AsmJSCache.cpp
dom/cache/QuotaClient.cpp
dom/filehandle/AsyncHelper.cpp
dom/filehandle/FileHandle.cpp
dom/filehandle/FileService.cpp
dom/filehandle/FileService.h
dom/filehandle/moz.build
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBDatabase.h
dom/indexedDB/IDBFileHandle.cpp
dom/indexedDB/PBackgroundIDBDatabase.ipdl
dom/quota/Client.h
dom/quota/QuotaManager.cpp
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1830,37 +1830,25 @@ public:
                          const nsACString& aOrigin)
                          override
   { }
 
   virtual void
   ReleaseIOThreadObjects() override
   { }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return false;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT_UNREACHABLE("There are no storages");
   }
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   { }
 
 private:
   nsAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 };
 
 NS_IMPL_ADDREF(asmjscache::Client)
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -193,28 +193,16 @@ public:
 
   virtual void
   ReleaseIOThreadObjects() override
   {
     // Nothing to do here as the Context handles cleaning everything up
     // automatically.
   }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return true;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!aStorages.IsEmpty());
 
     nsCOMPtr<nsIRunnable> callback =
@@ -226,17 +214,17 @@ public:
       nsRefPtr<OfflineStorage> storage =
         static_cast<OfflineStorage*>(aStorages[i]);
       storage->AddDestroyCallback(callback);
     }
   }
 
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // spins the event loop and synchronously shuts down all Managers
     Manager::ShutdownAllOnMainThread();
   }
 
 private:
--- a/dom/filehandle/AsyncHelper.cpp
+++ b/dom/filehandle/AsyncHelper.cpp
@@ -27,17 +27,17 @@ AsyncHelper::AsyncWork(nsIRequestObserve
     // 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->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/filehandle/FileHandle.cpp
+++ b/dom/filehandle/FileHandle.cpp
@@ -587,17 +587,17 @@ 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->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsresult rv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
@@ -734,17 +734,17 @@ ReadHelper::DoAsyncRun(nsISupports* aStr
   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->StreamTransportTarget();
+  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);
@@ -809,17 +809,17 @@ WriteHelper::DoAsyncRun(nsISupports* aSt
   uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
 
   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->StreamTransportTarget();
+  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);
--- a/dom/filehandle/FileService.cpp
+++ b/dom/filehandle/FileService.cpp
@@ -7,70 +7,90 @@
 #include "FileService.h"
 
 #include "FileHandle.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "MutableFile.h"
 #include "nsError.h"
 #include "nsIEventTarget.h"
-#include "nsIObserverService.h"
-#include "nsIOfflineStorage.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
+#include "nsThreadPool.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-FileService* gInstance = nullptr;
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+StaticAutoPtr<FileService> gInstance;
 bool gShutdown = false;
 
 } // anonymous namespace
 
 FileService::FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gInstance);
 }
 
 FileService::~FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
 }
 
 nsresult
 FileService::Init()
 {
-  nsresult rv;
-  mStreamTransportTarget =
-    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+  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;
+  }
 
-  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()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(NS_IsMainThread());
 
   nsIThread* thread = NS_GetCurrentThread();
-  while (mStorageInfos.Count()) {
-    if (!NS_ProcessNextEvent(thread)) {
-      NS_ERROR("Failed to process next event!");
-      break;
-    }
+  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.
-  nsresult rv = NS_ProcessPendingEvents(thread);
+  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();
@@ -90,30 +110,22 @@ FileService::GetOrCreate()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (gShutdown) {
     NS_WARNING("Calling GetOrCreate() after shutdown!");
     return nullptr;
   }
 
   if (!gInstance) {
-    nsRefPtr<FileService> service(new FileService);
+    nsAutoPtr<FileService> service(new FileService());
 
     nsresult rv = service->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
 
-    nsCOMPtr<nsIObserverService> obs =
-      do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    rv = obs->AddObserver(service, "profile-before-change", false);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    // The observer service now owns us.
-    gInstance = service;
+    gInstance = service.forget();
   }
 
   return gInstance;
 }
 
 // static
 FileService*
 FileService::Get()
@@ -249,74 +261,48 @@ FileService::NotifyFileHandleCompleted(F
       else {
         index++;
       }
     }
   }
 }
 
 void
-FileService::WaitForStoragesToComplete(
-                                 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
-                                 nsIRunnable* aCallback)
+FileService::WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
+                                       nsIRunnable* aCallback)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!");
-  NS_ASSERTION(aCallback, "Null pointer!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aStorageIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
 
   StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
   callback->mCallback = aCallback;
-  callback->mStorages.SwapElements(aStorages);
+  callback->mStorageIds.SwapElements(aStorageIds);
 
   if (MaybeFireCallback(*callback)) {
     mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
   }
 }
 
-void
-FileService::AbortFileHandlesForStorage(nsIOfflineStorage* aStorage)
+nsIEventTarget*
+FileService::ThreadPoolTarget() const
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(aStorage, "Null pointer!");
-
-  StorageInfo* storageInfo;
-  if (!mStorageInfos.Get(aStorage->Id(), &storageInfo)) {
-    return;
-  }
-
-  nsAutoTArray<nsRefPtr<FileHandleBase>, 10> fileHandles;
-  storageInfo->CollectRunningAndDelayedFileHandles(aStorage, fileHandles);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mThreadPool);
 
-  for (uint32_t index = 0; index < fileHandles.Length(); index++) {
-    ErrorResult ignored;
-    fileHandles[index]->Abort(ignored);
-  }
-}
-
-NS_IMPL_ISUPPORTS(FileService, nsIObserver)
-
-NS_IMETHODIMP
-FileService::Observe(nsISupports* aSubject, const char*  aTopic,
-                     const char16_t* aData)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!");
-
-  Shutdown();
-
-  return NS_OK;
+  return mThreadPool;
 }
 
 bool
 FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) {
-    if (mStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) {
+  for (uint32_t index = 0; index < aCallback.mStorageIds.Length(); index++) {
+    if (mStorageInfos.Get(aCallback.mStorageIds[index], nullptr)) {
       return false;
     }
   }
 
   aCallback.mCallback->Run();
   return true;
 }
 
@@ -484,30 +470,10 @@ FileService::StorageInfo::CreateDelayedE
                                                    FileHelper* aFileHelper)
 {
   DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
   info->mFileHandle = aFileHandle;
   info->mFileHelper = aFileHelper;
   return info;
 }
 
-void
-FileService::StorageInfo::CollectRunningAndDelayedFileHandles(
-                               nsIOfflineStorage* aStorage,
-                               nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles)
-{
-  for (uint32_t index = 0; index < mFileHandleQueues.Length(); index++) {
-    FileHandleBase* fileHandle = mFileHandleQueues[index]->mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-
-  for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
-    FileHandleBase* fileHandle = mDelayedEnqueueInfos[index].mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/filehandle/FileService.h
+++ b/dom/filehandle/FileService.h
@@ -4,41 +4,41 @@
  * 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 "nsIObserver.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsAString;
 class nsIEventTarget;
-class nsIOfflineStorage;
 class nsIRunnable;
+class nsThreadPool;
 
 namespace mozilla {
 namespace dom {
 
 class FileHandleBase;
 
-class FileService final : public nsIObserver
+class FileService final
 {
+  friend class nsAutoPtr<FileService>;
+  friend class StaticAutoPtr<FileService>;
+
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
   // Returns a non-owning reference!
   static FileService*
   GetOrCreate();
 
   // Returns a non-owning reference!
   static FileService*
   Get();
 
@@ -51,28 +51,21 @@ public:
 
   nsresult
   Enqueue(FileHandleBase* aFileHandle, FileHelper* aFileHelper);
 
   void
   NotifyFileHandleCompleted(FileHandleBase* aFileHandle);
 
   void
-  WaitForStoragesToComplete(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
+  WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
                             nsIRunnable* aCallback);
 
-  void
-  AbortFileHandlesForStorage(nsIOfflineStorage* aStorage);
-
   nsIEventTarget*
-  StreamTransportTarget()
-  {
-    NS_ASSERTION(mStreamTransportTarget, "This should never be null!");
-    return mStreamTransportTarget;
-  }
+  ThreadPoolTarget() const;
 
 private:
   class FileHandleQueue final : public FileHelperListener
   {
     friend class FileService;
 
   public:
     NS_IMETHOD_(MozExternalRefCountType)
@@ -131,21 +124,16 @@ private:
     {
       return !mFileHandleQueues.IsEmpty();
     }
 
     inline DelayedEnqueueInfo*
     CreateDelayedEnqueueInfo(FileHandleBase* aFileHandle,
                              FileHelper* aFileHelper);
 
-    inline void
-    CollectRunningAndDelayedFileHandles(
-                              nsIOfflineStorage* aStorage,
-                              nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles);
-
     void
     LockFileForReading(const nsAString& aFileName)
     {
       mFilesReading.PutEntry(aFileName);
     }
 
     void
     LockFileForWriting(const nsAString& aFileName)
@@ -173,33 +161,33 @@ private:
     nsTArray<nsRefPtr<FileHandleQueue>> mFileHandleQueues;
     nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
     nsTHashtable<nsStringHashKey> mFilesReading;
     nsTHashtable<nsStringHashKey> mFilesWriting;
   };
 
   struct StoragesCompleteCallback
   {
-    nsTArray<nsCOMPtr<nsIOfflineStorage> > mStorages;
+    nsTArray<nsCString> mStorageIds;
     nsCOMPtr<nsIRunnable> mCallback;
   };
 
   FileService();
   ~FileService();
 
   nsresult
   Init();
 
   nsresult
   Cleanup();
 
   bool
   MaybeFireCallback(StoragesCompleteCallback& aCallback);
 
-  nsCOMPtr<nsIEventTarget> mStreamTransportTarget;
+  nsRefPtr<nsThreadPool> mThreadPool;
   nsClassHashtable<nsCStringHashKey, StorageInfo> mStorageInfos;
   nsTArray<StoragesCompleteCallback> mCompleteCallbacks;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileService_h
--- a/dom/filehandle/moz.build
+++ b/dom/filehandle/moz.build
@@ -30,8 +30,12 @@ FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '../base',
 ]
 
 FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/xpcom/threads',
+]
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -24,16 +24,17 @@
 #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/indexedDB/PBackgroundIDBCursorParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
@@ -5447,16 +5448,76 @@ private:
                                    PBackgroundIDBFactoryRequestParent* aRequest)
                                    override;
 
   virtual bool
   DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
                                       override;
 };
 
+class WaitForTransactionsHelper final
+  : public nsRunnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  const nsCString mDatabaseId;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+  enum
+  {
+    State_Initial = 0,
+    State_WaitingForTransactions,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
+    State_DispatchToOwningThread,
+    State_Complete
+  } mState;
+
+public:
+  WaitForTransactionsHelper(const nsCString& aDatabaseId,
+                            nsIRunnable* aCallback)
+    : mOwningThread(NS_GetCurrentThread())
+    , mDatabaseId(aDatabaseId)
+    , mCallback(aCallback)
+    , mState(State_Initial)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!aDatabaseId.IsEmpty());
+    MOZ_ASSERT(aCallback);
+  }
+
+  void
+  WaitForTransactions();
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~WaitForTransactionsHelper()
+  {
+    MOZ_ASSERT(!mCallback);
+    MOZ_ASSERT(mState = State_Complete);
+  }
+
+  void
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
+
+  void
+  DispatchToOwningThread();
+
+  void
+  CallCallback();
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
   class StartTransactionOp;
 
 private:
@@ -5466,16 +5527,17 @@ private:
   nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
   nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
+  uint32_t mFileHandleCount;
   const PersistenceType mPersistenceType;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
   bool mInvalidated;
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
@@ -5646,17 +5708,17 @@ private:
     MOZ_ASSERT(mClosed);
     MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
   }
 
   bool
   CloseInternal();
 
   void
-  CloseConnection();
+  MaybeCloseConnection();
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
   // IPDL methods are only called by IPDL.
@@ -5706,16 +5768,22 @@ private:
   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:
@@ -6329,21 +6397,21 @@ protected:
 
     // Waiting to do/doing work on the "work thread". This involves waiting for
     // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
     // different implementation) to do its work. Eventually the state will
     // transition to State_SendingResults.
     State_DatabaseWorkVersionChange,
 
     // Waiting to send/sending results on the PBackground thread. Next step is
-    // UnblockingQuotaManager.
+    // State_UnblockingQuotaManager.
     State_SendingResults,
 
     // Notifying the QuotaManager that it can proceed to the next operation on
-    // the main thread. Next step is Completed.
+    // the main thread. Next step is State_Completed.
     State_UnblockingQuotaManager,
 
     // All done.
     State_Completed
   };
 
   // Must be released on the background thread!
   nsRefPtr<Factory> mFactory;
@@ -7625,26 +7693,26 @@ public:
 private:
   ~NonMainThreadHackBlobImpl()
   { }
 };
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
 {
-  class ShutdownTransactionThreadPoolRunnable;
-  friend class ShutdownTransactionThreadPoolRunnable;
+  class ShutdownWorkThreadsRunnable;
+  friend class ShutdownWorkThreadsRunnable;
 
   class WaitForTransactionsRunnable;
   friend class WaitForTransactionsRunnable;
 
   static QuotaClient* sInstance;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
-  nsRefPtr<ShutdownTransactionThreadPoolRunnable> mShutdownRunnable;
+  nsRefPtr<ShutdownWorkThreadsRunnable> mShutdownRunnable;
 
   bool mShutdownRequested;
 
 public:
   QuotaClient();
 
   static QuotaClient*
   GetInstance()
@@ -7705,65 +7773,59 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin)
                          override;
 
   virtual void
   ReleaseIOThreadObjects() override;
 
-  virtual bool
-  IsFileServiceUtilized() override;
-
-  virtual bool
-  IsTransactionServiceActivated() override;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override;
 
   virtual void
-  ShutdownTransactionService() override;
+  ShutdownWorkThreads() override;
 
 private:
   ~QuotaClient();
 
   nsresult
   GetDirectory(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                nsIFile** aDirectory);
 
   nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 };
 
-class QuotaClient::ShutdownTransactionThreadPoolRunnable final
+class QuotaClient::ShutdownWorkThreadsRunnable final
   : public nsRunnable
 {
   nsRefPtr<QuotaClient> mQuotaClient;
   bool mHasRequestedShutDown;
 
 public:
 
-  explicit ShutdownTransactionThreadPoolRunnable(QuotaClient* aQuotaClient)
+  explicit ShutdownWorkThreadsRunnable(QuotaClient* aQuotaClient)
     : mQuotaClient(aQuotaClient)
     , mHasRequestedShutDown(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
     MOZ_ASSERT(aQuotaClient->mShutdownRequested);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
-  ~ShutdownTransactionThreadPoolRunnable()
+  ~ShutdownWorkThreadsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
   }
 
   NS_DECL_NSIRUNNABLE
 };
 
 class QuotaClient::WaitForTransactionsRunnable final
@@ -7772,17 +7834,18 @@ class QuotaClient::WaitForTransactionsRu
   nsRefPtr<QuotaClient> mQuotaClient;
   nsTArray<nsCString> mDatabaseIds;
   nsCOMPtr<nsIRunnable> mCallback;
 
   enum
   {
     State_Initial = 0,
     State_WaitingForTransactions,
-    State_CallingCallback,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
     State_Complete
   } mState;
 
 public:
   WaitForTransactionsRunnable(QuotaClient* aQuotaClient,
                               nsTArray<nsCString>& aDatabaseIds,
                               nsIRunnable* aCallback)
     : mQuotaClient(aQuotaClient)
@@ -7804,20 +7867,23 @@ private:
   ~WaitForTransactionsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
     MOZ_ASSERT(!mCallback);
     MOZ_ASSERT(mState = State_Complete);
   }
 
   void
-  MaybeWait();
-
-  void
-  SendToMainThread();
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
 
   void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
 class DatabaseOfflineStorage final
@@ -11234,16 +11300,145 @@ Factory::DeallocPBackgroundIDBDatabasePa
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   nsRefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
   return true;
 }
 
 /*******************************************************************************
+ * WaitForTransactionsHelper
+ ******************************************************************************/
+
+void
+WaitForTransactionsHelper::WaitForTransactions()
+{
+  MOZ_ASSERT(mState == State_Initial);
+
+  unused << this->Run();
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForTransactions()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial);
+
+  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+  if (connectionPool) {
+    nsTArray<nsCString> ids(1);
+    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)));
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    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)));
+}
+
+void
+WaitForTransactionsHelper::CallCallback()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_DispatchToOwningThread);
+
+  nsCOMPtr<nsIRunnable> callback;
+  mCallback.swap(callback);
+
+  callback->Run();
+
+  mState = State_Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WaitForTransactionsHelper,
+                             nsRunnable)
+
+NS_IMETHODIMP
+WaitForTransactionsHelper::Run()
+{
+  MOZ_ASSERT(mState != State_Complete);
+  MOZ_ASSERT(mCallback);
+
+  switch (mState) {
+    case State_Initial:
+      MaybeWaitForTransactions();
+      break;
+
+    case State_WaitingForTransactions:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
+      DispatchToOwningThread();
+      break;
+
+    case State_DispatchToOwningThread:
+      CallCallback();
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return NS_OK;
+}
+
+/*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    FullDatabaseMetadata* aMetadata,
@@ -11254,16 +11449,17 @@ Database::Database(Factory* aFactory,
   , mMetadata(aMetadata)
   , mFileManager(aFileManager)
   , mOfflineStorage(Move(aOfflineStorage))
   , mPrincipalInfo(aPrincipalInfo)
   , mGroup(aGroup)
   , mOrigin(aOrigin)
   , mId(aMetadata->mDatabaseId)
   , mFilePath(aMetadata->mFilePath)
+  , mFileHandleCount(0)
   , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
   , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
   , mClosed(false)
   , mInvalidated(false)
   , mActorWasAlive(false)
   , mActorDestroyed(false)
   , mMetadataCleanedUp(false)
 {
@@ -11399,19 +11595,17 @@ void
 Database::UnregisterTransaction(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(mTransactions.GetEntry(aTransaction));
 
   mTransactions.RemoveEntry(aTransaction);
 
-  if (!mTransactions.Count() && IsClosed() && mOfflineStorage) {
-    CloseConnection();
-  }
+  MaybeCloseConnection();
 }
 
 void
 Database::SetActorAlive()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorWasAlive);
   MOZ_ASSERT(!mActorDestroyed);
@@ -11450,50 +11644,48 @@ Database::CloseInternal()
   MOZ_ASSERT(info->mLiveDatabases.Contains(this));
 
   if (info->mWaitingFactoryOp) {
     info->mWaitingFactoryOp->NoteDatabaseClosed(this);
   }
 
   if (mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
-
-    if (!mTransactions.Count()) {
-      CloseConnection();
-    }
-  }
-
-  return true;
-}
-
-void
-Database::CloseConnection()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mClosed);
-  MOZ_ASSERT(!mTransactions.Count());
-
-  if (gConnectionPool) {
-    nsTArray<nsCString> ids(1);
-    ids.AppendElement(Id());
-
+  }
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
+void
+Database::MaybeCloseConnection()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mTransactions.Count() &&
+      !mFileHandleCount &&
+      IsClosed() &&
+      mOfflineStorage) {
     nsCOMPtr<nsIRunnable> callback =
       NS_NewRunnableMethod(this, &Database::ConnectionClosedCallback);
-    gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-  } else {
-    ConnectionClosedCallback();
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(Id(), callback);
+    helper->WaitForTransactions();
   }
 }
 
 void
 Database::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mTransactions.Count());
+  MOZ_ASSERT(!mFileHandleCount);
 
   if (mOfflineStorage) {
     DatabaseOfflineStorage::UnregisterOnOwningThread(mOfflineStorage.forget());
   }
 
   CleanupMetadata();
 }
 
@@ -11799,16 +11991,53 @@ Database::RecvClose()
   if (NS_WARN_IF(!CloseInternal())) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   return true;
 }
 
+bool
+Database::RecvNewFileHandle()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mOfflineStorage) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (mFileHandleCount == UINT32_MAX) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  ++mFileHandleCount;
+
+  return true;
+}
+
+bool
+Database::RecvFileHandleFinished()
+{
+  AssertIsOnBackgroundThread();
+
+  if (mFileHandleCount == 0) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  --mFileHandleCount;
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
 void
 Database::
 StartTransactionOp::RunOnConnectionThread()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(Transaction());
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
 
@@ -14756,32 +14985,16 @@ QuotaClient::ReleaseIOThreadObjects()
 {
   AssertIsOnIOThread();
 
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
     mgr->InvalidateAllFileManagers();
   }
 }
 
-bool
-QuotaClient::IsFileServiceUtilized()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
-bool
-QuotaClient::IsTransactionServiceActivated()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
 void
 QuotaClient::WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                                        nsIRunnable* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aStorages.IsEmpty());
   MOZ_ASSERT(aCallback);
 
@@ -14822,42 +15035,44 @@ QuotaClient::WaitForStoragesToComplete(n
 
   nsCOMPtr<nsIRunnable> runnable =
     new WaitForTransactionsRunnable(this, databaseIds, aCallback);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
     backgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 }
 
 void
-QuotaClient::ShutdownTransactionService()
+QuotaClient::ShutdownWorkThreads()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShutdownRunnable);
   MOZ_ASSERT(!mShutdownRequested);
 
   mShutdownRequested = true;
 
   if (mBackgroundThread) {
-    nsRefPtr<ShutdownTransactionThreadPoolRunnable> runnable =
-      new ShutdownTransactionThreadPoolRunnable(this);
+    nsRefPtr<ShutdownWorkThreadsRunnable> runnable =
+      new ShutdownWorkThreadsRunnable(this);
 
     if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
       // This can happen if the thread has shut down already.
       return;
     }
 
     nsIThread* currentThread = NS_GetCurrentThread();
     MOZ_ASSERT(currentThread);
 
     mShutdownRunnable.swap(runnable);
 
     while (mShutdownRunnable) {
       MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
     }
   }
+
+  FileService::Shutdown();
 }
 
 nsresult
 QuotaClient::GetDirectory(PersistenceType aPersistenceType,
                           const nsACString& aOrigin, nsIFile** aDirectory)
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
@@ -14972,53 +15187,78 @@ QuotaClient::GetUsageForDirectoryInterna
   }
 
   return NS_OK;
 }
 
 
 void
 QuotaClient::
-WaitForTransactionsRunnable::MaybeWait()
+WaitForTransactionsRunnable::MaybeWaitForTransactions()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_Initial);
   MOZ_ASSERT(mQuotaClient);
 
   nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
   if (connectionPool) {
+    // Have to copy here in case the file service needs a list too.
+    nsTArray<nsCString> databaseIds(mDatabaseIds);
+
     mState = State_WaitingForTransactions;
 
-    connectionPool->WaitForDatabasesToComplete(Move(mDatabaseIds), this);
+    connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
+    return;
+  }
+
+  DispatchToMainThread();
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::DispatchToMainThread()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
+
+  mState = State_DispatchToMainThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    mState = State_WaitingForFileHandles;
+
+    service->WaitForStoragesToComplete(mDatabaseIds, this);
+
+    MOZ_ASSERT(mDatabaseIds.IsEmpty());
     return;
   }
 
   mDatabaseIds.Clear();
 
-  SendToMainThread();
-}
-
-void
-QuotaClient::
-WaitForTransactionsRunnable::SendToMainThread()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
-
-  mState = State_CallingCallback;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  mState = State_WaitingForFileHandles;
+
+  CallCallback();
 }
 
 void
 QuotaClient::
 WaitForTransactionsRunnable::CallCallback()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_CallingCallback);
+  MOZ_ASSERT(mState == State_WaitingForFileHandles);
 
   nsRefPtr<QuotaClient> quotaClient;
   mQuotaClient.swap(quotaClient);
 
   nsCOMPtr<nsIRunnable> callback;
   mCallback.swap(callback);
 
   callback->Run();
@@ -15033,40 +15273,44 @@ NS_IMETHODIMP
 QuotaClient::
 WaitForTransactionsRunnable::Run()
 {
   MOZ_ASSERT(mState != State_Complete);
   MOZ_ASSERT(mCallback);
 
   switch (mState) {
     case State_Initial:
-      MaybeWait();
+      MaybeWaitForTransactions();
       break;
 
     case State_WaitingForTransactions:
-      SendToMainThread();
-      break;
-
-    case State_CallingCallback:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
       CallCallback();
       break;
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownTransactionThreadPoolRunnable,
+NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownWorkThreadsRunnable,
                              nsRunnable)
 
 NS_IMETHODIMP
 QuotaClient::
-ShutdownTransactionThreadPoolRunnable::Run()
+ShutdownWorkThreadsRunnable::Run()
 {
   if (NS_IsMainThread()) {
     MOZ_ASSERT(mHasRequestedShutDown);
     MOZ_ASSERT(QuotaClient::GetInstance() == mQuotaClient);
     MOZ_ASSERT(mQuotaClient->mShutdownRunnable == this);
 
     mQuotaClient->mShutdownRunnable = nullptr;
     mQuotaClient = nullptr;
@@ -16341,26 +16585,21 @@ FactoryOp::WaitForTransactions()
   MOZ_ASSERT(mState == State_BeginVersionChange ||
              mState == State_WaitingForOtherDatabasesToClose);
   MOZ_ASSERT(!mDatabaseId.IsEmpty());
   MOZ_ASSERT(!IsActorDestroyed());
 
   nsTArray<nsCString> databaseIds;
   databaseIds.AppendElement(mDatabaseId);
 
-  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
-  MOZ_ASSERT(connectionPool);
-
-  // WaitForDatabasesToComplete() will run this op immediately if there are no
-  // transactions blocking it, so be sure to set the next state here before
-  // calling it.
   mState = State_WaitingForTransactionsToComplete;
 
-  connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
-  return;
+  nsRefPtr<WaitForTransactionsHelper> helper =
+    new WaitForTransactionsHelper(mDatabaseId, this);
+  helper->WaitForTransactions();
 }
 
 void
 FactoryOp::FinishSendResults()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_SendingResults);
   MOZ_ASSERT(mFactory);
@@ -17638,27 +17877,22 @@ OpenDatabaseOp::SendResults()
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
   if (NS_FAILED(mResultCode) && mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
 
-    if (gConnectionPool) {
-      nsTArray<nsCString> ids(1);
-      ids.AppendElement(mDatabaseId);
-
-      nsCOMPtr<nsIRunnable> callback =
-        NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
-
-      gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-    } else {
-      ConnectionClosedCallback();
-    }
+    nsCOMPtr<nsIRunnable> callback =
+      NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(mDatabaseId, callback);
+    helper->WaitForTransactions();
   }
 
   // Make sure to release the database on this thread.
   nsRefPtr<Database> database;
   mDatabase.swap(database);
 
   FinishSendResults();
 }
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -1279,16 +1279,36 @@ IDBDatabase::NoteFinishedMutableFile(IDB
 
   // It's ok if this is called more than once, so don't assert that aMutableFile
   // is in the list already.
 
   mLiveMutableFiles.RemoveElement(aMutableFile);
 }
 
 void
+IDBDatabase::OnNewFileHandle()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendNewFileHandle();
+}
+
+void
+IDBDatabase::OnFileHandleFinished()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendFileHandleFinished();
+}
+
+void
 IDBDatabase::InvalidateMutableFiles()
 {
   if (!mLiveMutableFiles.IsEmpty()) {
     MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
     MOZ_ASSERT(NS_IsMainThread());
 
     for (uint32_t count = mLiveMutableFiles.Length(), index = 0;
          index < count;
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -195,16 +195,22 @@ public:
   GetQuotaInfo(nsACString& aOrigin, PersistenceType* aPersistenceType);
 
   void
   NoteLiveMutableFile(IDBMutableFile* aMutableFile);
 
   void
   NoteFinishedMutableFile(IDBMutableFile* aMutableFile);
 
+  void
+  OnNewFileHandle();
+
+  void
+  OnFileHandleFinished();
+
   nsPIDOMWindow*
   GetParentObject() const;
 
   already_AddRefed<DOMStringList>
   ObjectStoreNames() const;
 
   already_AddRefed<IDBObjectStore>
   CreateObjectStore(const nsAString& aName,
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -68,16 +68,18 @@ IDBFileHandle::Create(FileMode aMode,
     return nullptr;
   }
 
   rv = service->Enqueue(fileHandle, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
+  aMutableFile->Database()->OnNewFileHandle();
+
   return fileHandle.forget();
 }
 
 mozilla::dom::MutableFileBase*
 IDBFileHandle::MutableFile() const
 {
   return mMutableFile;
 }
@@ -168,16 +170,18 @@ IDBFileHandle::OnCompleteOrAbort(bool aA
     return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
   }
 
   bool dummy;
   if (NS_FAILED(DispatchEvent(event, &dummy))) {
     NS_WARNING("Dispatch failed!");
   }
 
+  mMutableFile->Database()->OnFileHandleFinished();
+
   return NS_OK;
 }
 
 bool
 IDBFileHandle::CheckWindow()
 {
   return GetOwner();
 }
--- a/dom/indexedDB/PBackgroundIDBDatabase.ipdl
+++ b/dom/indexedDB/PBackgroundIDBDatabase.ipdl
@@ -39,16 +39,20 @@ protocol PBackgroundIDBDatabase
 
 parent:
   DeleteMe();
 
   Blocked();
 
   Close();
 
+  NewFileHandle();
+
+  FileHandleFinished();
+
   PBackgroundIDBDatabaseFile(PBlob blob);
 
   PBackgroundIDBTransaction(nsString[] objectStoreNames, Mode mode);
 
 child:
   __delete__();
 
   VersionChange(uint64_t oldVersion, NullableVersion newVersion);
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -107,28 +107,22 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin) = 0;
 
   virtual void
   ReleaseIOThreadObjects() = 0;
 
   // Methods which are called on the main thred.
-  virtual bool
-  IsFileServiceUtilized() = 0;
-
-  virtual bool
-  IsTransactionServiceActivated() = 0;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) = 0;
 
   virtual void
-  ShutdownTransactionService() = 0;
+  ShutdownWorkThreads() = 0;
 
 protected:
   virtual ~Client()
   { }
 };
 
 END_QUOTA_NAMESPACE
 
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -24,17 +24,16 @@
 #include "nsIUsageCallback.h"
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
-#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
@@ -85,17 +84,16 @@
 
 #define KB * 1024ULL
 #define MB * 1024ULL KB
 #define GB * 1024ULL MB
 
 USING_QUOTA_NAMESPACE
 using namespace mozilla;
 using namespace mozilla::dom;
-using mozilla::dom::FileService;
 
 static_assert(
   static_cast<uint32_t>(StorageType::Persistent) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
   "Enum values should match.");
 
 static_assert(
   static_cast<uint32_t>(StorageType::Temporary) ==
@@ -541,36 +539,16 @@ public:
   }
 
 private:
   // The QuotaManager holds this alive.
   SynchronizedOp* mOp;
   uint32_t mCountdown;
 };
 
-class WaitForFileHandlesToFinishRunnable final : public nsRunnable
-{
-public:
-  WaitForFileHandlesToFinishRunnable()
-  : mBusy(true)
-  { }
-
-  NS_IMETHOD
-  Run();
-
-  bool
-  IsBusy() const
-  {
-    return mBusy;
-  }
-
-private:
-  bool mBusy;
-};
-
 class SaveOriginAccessTimeRunnable final : public nsRunnable
 {
 public:
   SaveOriginAccessTimeRunnable(PersistenceType aPersistenceType,
                                const nsACString& aOrigin,
                                int64_t aTimestamp)
   : mPersistenceType(aPersistenceType), mOrigin(aOrigin), mTimestamp(aTimestamp)
   { }
@@ -1705,37 +1683,31 @@ QuotaManager::UnregisterStorage(nsIOffli
 }
 
 void
 QuotaManager::AbortCloseStoragesForProcess(ContentParent* aContentParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aContentParent);
 
-  FileService* service = FileService::Get();
+  // FileHandle API is not yet supported in child processes, so we don't have
+  // to worry about aborting file handles for given child process.
 
   StorageMatcher<ArrayCluster<nsIOfflineStorage*>> liveStorages;
   liveStorages.Find(mLiveStorages);
 
   for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
-    nsRefPtr<Client>& client = mClients[i];
-    bool utilized = service && client->IsFileServiceUtilized();
-
     nsTArray<nsIOfflineStorage*>& array = liveStorages[i];
     for (uint32_t j = 0; j < array.Length(); j++) {
       nsCOMPtr<nsIOfflineStorage> storage = array[j];
 
       if (storage->IsOwnedByProcess(aContentParent)) {
         if (NS_FAILED(storage->Close())) {
           NS_WARNING("Failed to close storage for dying process!");
         }
-
-        if (utilized) {
-          service->AbortFileHandlesForStorage(storage);
-        }
       }
     }
   }
 }
 
 nsresult
 QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
                                  Nullable<PersistenceType> aPersistenceType,
@@ -2870,61 +2842,26 @@ QuotaManager::Observe(nsISupports* aSubj
   if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) {
     // Setting this flag prevents the service from being recreated and prevents
     // further storagess from being created.
     if (gShutdown.exchange(true)) {
       NS_ERROR("Shutdown more than once?!");
     }
 
     if (IsMainProcess()) {
-      FileService* service = FileService::Get();
-      if (service) {
-        // This should only wait for storages registered in this manager
-        // to complete. Other storages may still have running file handles.
-        // If the necko service (thread pool) gets the shutdown notification
-        // first then the sync loop won't be processed at all, otherwise it will
-        // lock the main thread until all storages registered in this manager
-        // are finished.
-
-        nsTArray<uint32_t> indexes;
-        for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
-          if (mClients[index]->IsFileServiceUtilized()) {
-            indexes.AppendElement(index);
-          }
-        }
-
-        StorageMatcher<nsTArray<nsCOMPtr<nsIOfflineStorage>>> liveStorages;
-        liveStorages.Find(mLiveStorages, &indexes);
-
-        if (!liveStorages.IsEmpty()) {
-          nsRefPtr<WaitForFileHandlesToFinishRunnable> runnable =
-            new WaitForFileHandlesToFinishRunnable();
-
-          service->WaitForStoragesToComplete(liveStorages, runnable);
-
-          nsIThread* thread = NS_GetCurrentThread();
-          while (runnable->IsBusy()) {
-            if (!NS_ProcessNextEvent(thread)) {
-              NS_ERROR("Failed to process next event!");
-              break;
-            }
-          }
-        }
-      }
-
       // Kick off the shutdown timer.
       if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
                                          nsITimer::TYPE_ONE_SHOT))) {
         NS_WARNING("Failed to initialize shutdown timer!");
       }
 
       // Each client will spin the event loop while we wait on all the threads
       // to close. Our timer may fire during that loop.
       for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
-        mClients[index]->ShutdownTransactionService();
+        mClients[index]->ShutdownWorkThreads();
       }
 
       // Cancel the timer regardless of whether it actually fired.
       if (NS_FAILED(mShutdownTimer->Cancel())) {
         NS_WARNING("Failed to cancel shutdown timer!");
       }
 
       // Give clients a chance to cleanup IO thread only objects.
@@ -3087,43 +3024,21 @@ QuotaManager::AcquireExclusiveAccess(con
   }
 
   op->mRunnable = aRunnable;
 
   nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
     new WaitForTransactionsToFinishRunnable(op);
 
   if (!liveStorages.IsEmpty()) {
-    // Ask the file service to call us back when it's done with this storage.
-    FileService* service = FileService::Get();
-
-    if (service) {
-      // Have to copy here in case a transaction service needs a list too.
-      nsTArray<nsCOMPtr<nsIOfflineStorage>> array;
-
-      for (uint32_t index = 0; index < Client::TYPE_MAX; index++)  {
-        if (!liveStorages[index].IsEmpty() &&
-            mClients[index]->IsFileServiceUtilized()) {
-          array.AppendElements(liveStorages[index]);
-        }
-      }
-
-      if (!array.IsEmpty()) {
-        runnable->AddRun();
-
-        service->WaitForStoragesToComplete(array, runnable);
-      }
-    }
-
     // Ask each transaction service to call us back when they're done with this
     // storage.
     for (uint32_t index = 0; index < Client::TYPE_MAX; index++)  {
       nsRefPtr<Client>& client = mClients[index];
-      if (!liveStorages[index].IsEmpty() &&
-          client->IsTransactionServiceActivated()) {
+      if (!liveStorages[index].IsEmpty()) {
         runnable->AddRun();
 
         client->WaitForStoragesToComplete(liveStorages[index], runnable);
       }
     }
   }
 
   nsresult rv = runnable->Run();
@@ -4421,26 +4336,16 @@ WaitForTransactionsToFinishRunnable::Run
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The listener is responsible for calling
   // QuotaManager::AllowNextSynchronizedOp.
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WaitForFileHandlesToFinishRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  mBusy = false;
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 SaveOriginAccessTimeRunnable::Run()
 {
   AssertIsOnIOThread();
 
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
 
   nsCOMPtr<nsIFile> directory;