Bug 1130775 - Convert synchronized ops and storage registration into unified directory locks; r=bent
authorJan Varga <jan.varga@gmail.com>
Tue, 30 Jun 2015 14:59:27 +0200
changeset 250735 c9b3704bd0ed90f0e737a25f889c25a425075455
parent 250734 8c0027994ca49a93bf00d12d356f00dda4aa26d7
child 250736 c1bc9e9c1c92d51907af9a772ed4358bcfc3cc57
push id13792
push userkwierso@gmail.com
push dateWed, 01 Jul 2015 00:00:06 +0000
treeherderfx-team@ec66a98df9d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1130775
milestone42.0a1
Bug 1130775 - Convert synchronized ops and storage registration into unified directory locks; r=bent
dom/asmjscache/AsmJSCache.cpp
dom/base/nsDOMWindowUtils.cpp
dom/cache/Context.cpp
dom/cache/Context.h
dom/cache/Manager.cpp
dom/cache/Manager.h
dom/cache/ManagerId.cpp
dom/cache/ManagerId.h
dom/cache/OfflineStorage.cpp
dom/cache/OfflineStorage.h
dom/cache/QuotaClient.cpp
dom/cache/Types.h
dom/cache/moz.build
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/FileManager.h
dom/indexedDB/IDBMutableFile.cpp
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
dom/indexedDB/test/file.js
dom/indexedDB/test/test_file_os_delete.html
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/quota/ArrayCluster.h
dom/quota/Client.h
dom/quota/OriginCollection.h
dom/quota/OriginOrPatternString.h
dom/quota/OriginScope.h
dom/quota/PersistenceType.h
dom/quota/QuotaManager.cpp
dom/quota/QuotaManager.h
dom/quota/QuotaObject.cpp
dom/quota/StorageMatcher.h
dom/quota/UsageInfo.h
dom/quota/moz.build
dom/quota/nsIOfflineStorage.h
storage/moz.build
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -12,17 +12,16 @@
 #include "jsfriendapi.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/quota/Client.h"
-#include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/unused.h"
 #include "nsIAtom.h"
 #include "nsIFile.h"
 #include "nsIPermissionManager.h"
@@ -37,17 +36,16 @@
 #include "prio.h"
 #include "private/pprio.h"
 #include "mozilla/Services.h"
 
 #define ASMJSCACHE_METADATA_FILE_NAME "metadata"
 #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
 
 using mozilla::dom::quota::AssertIsOnIOThread;
-using mozilla::dom::quota::OriginOrPatternString;
 using mozilla::dom::quota::PersistenceType;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::QuotaObject;
 using mozilla::dom::quota::UsageInfo;
 using mozilla::unused;
 using mozilla::HashString;
 
 namespace mozilla {
@@ -293,18 +291,18 @@ public:
   void*
   MappedMemory() const
   {
     MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
     return mMappedMemory;
   }
 
 protected:
-  // This method must be called before AllowNextSynchronizedOp (which releases
-  // the lock protecting these resources). It is idempotent, so it is ok to call
+  // This method must be called before the directory lock is released (the lock
+  // is protecting these resources). It is idempotent, so it is ok to call
   // multiple times (or before the file has been fully opened).
   void
   Finish()
   {
     if (mMappedMemory) {
       PR_MemUnmap(mMappedMemory, mFileSize);
       mMappedMemory = nullptr;
     }
@@ -477,44 +475,47 @@ private:
   bool mWaiting;
   bool mOpened;
   JS::AsmJSCacheResult mResult;
 };
 
 // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
 // that factors out the runnable state machine required to open a cache entry
 // that runs in the main process.
-class MainProcessRunnable : public virtual FileDescriptorHolder
+class MainProcessRunnable
+  : public virtual FileDescriptorHolder
+  , public quota::OpenDirectoryListener
 {
+  typedef mozilla::dom::quota::QuotaManager::DirectoryLock DirectoryLock;
+
 public:
   NS_DECL_NSIRUNNABLE
 
   // MainProcessRunnable runnable assumes that the derived class ensures
   // (through ref-counting or preconditions) that aPrincipal is kept alive for
   // the lifetime of the MainProcessRunnable.
   MainProcessRunnable(nsIPrincipal* aPrincipal,
                       OpenMode aOpenMode,
                       WriteParams aWriteParams)
   : mPrincipal(aPrincipal),
     mOpenMode(aOpenMode),
     mWriteParams(aWriteParams),
-    mNeedAllowNextSynchronizedOp(false),
     mPersistence(quota::PERSISTENCE_TYPE_INVALID),
     mState(eInitial),
     mResult(JS::AsmJSCache_InternalError),
     mIsApp(false),
     mEnforcingQuota(true)
   {
     MOZ_ASSERT(IsMainProcess());
   }
 
   virtual ~MainProcessRunnable()
   {
     MOZ_ASSERT(mState == eFinished);
-    MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
+    MOZ_ASSERT(!mDirectoryLock);
   }
 
 protected:
   // This method is called by the derived class on the main thread when a
   // cache entry has been selected to open.
   void
   OpenForRead(unsigned aModuleIndex)
   {
@@ -632,26 +633,32 @@ private:
 
     nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
     if (NS_FAILED(rv)) {
       Fail();
       return;
     }
   }
 
+  // OpenDirectoryListener overrides.
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
+
   nsIPrincipal* const mPrincipal;
   const OpenMode mOpenMode;
   const WriteParams mWriteParams;
 
   // State initialized during eInitial:
-  bool mNeedAllowNextSynchronizedOp;
   quota::PersistenceType mPersistence;
   nsCString mGroup;
   nsCString mOrigin;
-  nsCString mStorageId;
+  nsRefPtr<DirectoryLock> mDirectoryLock;
 
   // State initialized during eReadyToReadMetadata
   nsCOMPtr<nsIFile> mDirectory;
   nsCOMPtr<nsIFile> mMetadataFile;
   Metadata mMetadata;
 
   // State initialized during eWaitingToOpenCacheFileForRead
   unsigned mModuleIndex;
@@ -736,19 +743,16 @@ MainProcessRunnable::InitOnMainThread()
     QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup, &mOrigin, &mIsApp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   InitPersistenceType();
 
   mEnforcingQuota =
     QuotaManager::IsQuotaEnforced(mPersistence, mOrigin, mIsApp);
 
-  QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
-                             NS_LITERAL_STRING("asmjs"), mStorageId);
-
   return NS_OK;
 }
 
 nsresult
 MainProcessRunnable::ReadMetadata()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == eReadyToReadMetadata);
@@ -916,28 +920,20 @@ MainProcessRunnable::OpenCacheFileForRea
 }
 
 void
 MainProcessRunnable::FinishOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Per FileDescriptorHolder::Finish()'s comment, call before
-  // AllowNextSynchronizedOp.
+  // releasing the directory lock.
   FileDescriptorHolder::Finish();
 
-  if (mNeedAllowNextSynchronizedOp) {
-    mNeedAllowNextSynchronizedOp = false;
-    QuotaManager* qm = QuotaManager::Get();
-    if (qm) {
-      qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
-                                  Nullable<PersistenceType>(mPersistence),
-                                  mStorageId);
-    }
-  }
+  mDirectoryLock = nullptr;
 }
 
 NS_IMETHODIMP
 MainProcessRunnable::Run()
 {
   nsresult rv;
 
   // All success/failure paths must eventually call Finish() to avoid leaving
@@ -948,34 +944,26 @@ MainProcessRunnable::Run()
 
       rv = InitOnMainThread();
       if (NS_FAILED(rv)) {
         Fail();
         return NS_OK;
       }
 
       mState = eWaitingToOpenMetadata;
-      rv = QuotaManager::Get()->WaitForOpenAllowed(
-                                     OriginOrPatternString::FromOrigin(mOrigin),
-                                     Nullable<PersistenceType>(mPersistence),
-                                     mStorageId, this);
-      if (NS_FAILED(rv)) {
-        Fail();
-        return NS_OK;
-      }
 
-      mNeedAllowNextSynchronizedOp = true;
-      return NS_OK;
-    }
+      // XXX The exclusive lock shouldn't be needed for read operations.
+      QuotaManager::Get()->OpenDirectory(mPersistence,
+                                         mGroup,
+                                         mOrigin,
+                                         mIsApp,
+                                         quota::Client::ASMJS,
+                                         /* aExclusive */ true,
+                                         this);
 
-    case eWaitingToOpenMetadata: {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      mState = eReadyToReadMetadata;
-      DispatchToIOThread();
       return NS_OK;
     }
 
     case eReadyToReadMetadata: {
       AssertIsOnIOThread();
 
       rv = ReadMetadata();
       if (NS_FAILED(rv)) {
@@ -1056,27 +1044,51 @@ MainProcessRunnable::Run()
     case eClosing: {
       MOZ_ASSERT(NS_IsMainThread());
 
       mState = eFinished;
       OnClose();
       return NS_OK;
     }
 
+    case eWaitingToOpenMetadata:
     case eWaitingToOpenCacheFileForRead:
     case eOpened:
     case eFinished: {
       MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
     }
   }
 
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
   return NS_OK;
 }
 
+void
+MainProcessRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  mState = eReadyToReadMetadata;
+  DispatchToIOThread();
+}
+
+void
+MainProcessRunnable::DirectoryLockFailed()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  Fail();
+}
+
 bool
 FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
               unsigned* aModuleIndex)
 {
   // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
   // also stores an mFastHash of its first sNumFastHashChars so this gives us a
   // fast way to probabilistically determine whether we have a cache hit. We
   // still do a full hash of all the chars before returning the cache file to
@@ -1116,16 +1128,18 @@ FindHashMatch(const Metadata& aMetadata,
   return false;
 }
 
 // A runnable that executes for a cache access originating in the main process.
 class SingleProcessRunnable final : public File,
                                     private MainProcessRunnable
 {
 public:
+  NS_DECL_ISUPPORTS_INHERITED
+
   // In the single-process case, the calling JS compilation thread holds the
   // nsIPrincipal alive indirectly (via the global object -> compartment ->
   // principal) so we don't have to ref-count it here. This is fortunate since
   // we are off the main thread and nsIPrincipals can only be ref-counted on
   // the main thread.
   SingleProcessRunnable(nsIPrincipal* aPrincipal,
                         OpenMode aOpenMode,
                         WriteParams aWriteParams,
@@ -1187,23 +1201,27 @@ private:
   Run() override
   {
     return MainProcessRunnable::Run();
   }
 
   ReadParams mReadParams;
 };
 
+NS_IMPL_ISUPPORTS_INHERITED0(SingleProcessRunnable, File)
+
 // A runnable that executes in a parent process for a cache access originating
 // in the content process. This runnable gets registered as an IPDL subprotocol
 // actor so that it can communicate with the corresponding ChildProcessRunnable.
 class ParentProcessRunnable final : public PAsmJSCacheEntryParent,
                                     public MainProcessRunnable
 {
 public:
+  NS_DECL_ISUPPORTS_INHERITED
+
   // The given principal comes from an IPC::Principal which will be dec-refed
   // at the end of the message, so we must ref-count it here. Fortunately, we
   // are on the main thread (where PContent messages are delivered).
   ParentProcessRunnable(nsIPrincipal* aPrincipal,
                         OpenMode aOpenMode,
                         WriteParams aWriteParams)
   : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
     mPrincipalHolder(aPrincipal),
@@ -1334,16 +1352,18 @@ private:
   }
 
   nsCOMPtr<nsIPrincipal> mPrincipalHolder;
   bool mActorDestroyed;
   bool mOpened;
   bool mFinished;
 };
 
+NS_IMPL_ISUPPORTS_INHERITED0(ParentProcessRunnable, FileDescriptorHolder)
+
 } // unnamed namespace
 
 PAsmJSCacheEntryParent*
 AllocEntryParent(OpenMode aOpenMode,
                  WriteParams aWriteParams,
                  nsIPrincipal* aPrincipal)
 {
   nsRefPtr<ParentProcessRunnable> runnable =
@@ -1515,18 +1535,18 @@ ChildProcessRunnable::Run()
       mState = eOpening;
       return NS_OK;
     }
 
     case eClosing: {
       MOZ_ASSERT(NS_IsMainThread());
 
       // Per FileDescriptorHolder::Finish()'s comment, call before
-      // AllowNextSynchronizedOp (which happens in the parent upon receipt of
-      // the Send__delete__ message).
+      // releasing the directory lock (which happens in the parent upon receipt
+      // of the Send__delete__ message).
       File::OnClose();
 
       if (!mActorDestroyed) {
         unused << Send__delete__(this, JS::AsmJSCache_Success);
       }
 
       mState = eFinished;
       return NS_OK;
@@ -1839,21 +1859,22 @@ public:
                          override
   { }
 
   virtual void
   ReleaseIOThreadObjects() override
   { }
 
   virtual void
-  WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
-                            nsIRunnable* aCallback) override
-  {
-    MOZ_ASSERT_UNREACHABLE("There are no storages");
-  }
+  AbortOperations(const nsACString& aOrigin) override
+  { }
+
+  virtual void
+  AbortOperationsForProcess(ContentParentId aContentParentId) override
+  { }
 
   virtual void
   PerformIdleMaintenance() override
   { }
 
   virtual void
   ShutdownWorkThreads() override
   { }
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2979,16 +2979,34 @@ nsDOMWindowUtils::GetFileReferences(cons
     *aRefCnt = *aDBRefCnt = *aSliceRefCnt = -1;
     *aResult = false;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::FlushPendingFileDeletions()
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+  using mozilla::dom::indexedDB::IndexedDatabaseManager;
+
+  nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+  if (mgr) {
+    nsresult rv = mgr->FlushPendingFileDeletions();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::IsIncrementalGCEnabled(JSContext* cx, bool* aResult)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   *aResult = JS::IsIncrementalGCEnabled(JS_GetRuntime(cx));
   return NS_OK;
 }
 
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -7,60 +7,26 @@
 #include "mozilla/dom/cache/Context.h"
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/Action.h"
 #include "mozilla/dom/cache/FileUtils.h"
 #include "mozilla/dom/cache/Manager.h"
 #include "mozilla/dom/cache/ManagerId.h"
-#include "mozilla/dom/cache/OfflineStorage.h"
-#include "mozilla/dom/quota/OriginOrPatternString.h"
-#include "mozilla/dom/quota/QuotaManager.h"
 #include "mozIStorageConnection.h"
 #include "nsIFile.h"
 #include "nsIPrincipal.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 
 namespace {
 
-using mozilla::dom::Nullable;
 using mozilla::dom::cache::Action;
 using mozilla::dom::cache::QuotaInfo;
-using mozilla::dom::quota::Client;
-using mozilla::dom::quota::OriginOrPatternString;
-using mozilla::dom::quota::QuotaManager;
-using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
-using mozilla::dom::quota::PersistenceType;
-
-// Release our lock on the QuotaManager directory asynchronously.
-class QuotaReleaseRunnable final : public nsRunnable
-{
-public:
-  explicit QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo)
-    : mQuotaInfo(aQuotaInfo)
-  { }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    QuotaManager* qm = QuotaManager::Get();
-    MOZ_ASSERT(qm);
-    qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
-                                Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
-                                mQuotaInfo.mStorageId);
-    return NS_OK;
-  }
-
-private:
-  ~QuotaReleaseRunnable() { }
-
-  const QuotaInfo mQuotaInfo;
-};
 
 class NullAction final : public Action
 {
 public:
   NullAction()
   {
   }
 
@@ -75,17 +41,18 @@ public:
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::DebugOnly;
-using mozilla::dom::quota::OriginOrPatternString;
+using mozilla::dom::quota::AssertIsOnIOThread;
+using mozilla::dom::quota::OpenDirectoryListener;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
 using mozilla::dom::quota::PersistenceType;
 
 class Context::Data final : public Action::Data
 {
 public:
   explicit Data(nsIThread* aTarget)
@@ -127,16 +94,17 @@ private:
   // and destroyed on the target IO thread.
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Context::Data)
 };
 
 // Executed to perform the complicated dance of steps necessary to initialize
 // the QuotaManager.  This must be performed for each origin before any disk
 // IO occurrs.
 class Context::QuotaInitRunnable final : public nsIRunnable
+                                       , public OpenDirectoryListener
 {
 public:
   QuotaInitRunnable(Context* aContext,
                     Manager* aManager,
                     Data* aData,
                     nsIThread* aTarget,
                     Action* aInitAction)
     : mContext(aContext)
@@ -144,48 +112,54 @@ public:
     , mManager(aManager)
     , mData(aData)
     , mTarget(aTarget)
     , mInitAction(aInitAction)
     , mInitiatingThread(NS_GetCurrentThread())
     , mResult(NS_OK)
     , mState(STATE_INIT)
     , mCanceled(false)
-    , mNeedsQuotaRelease(false)
   {
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mManager);
     MOZ_ASSERT(mData);
     MOZ_ASSERT(mTarget);
     MOZ_ASSERT(mInitiatingThread);
     MOZ_ASSERT(mInitAction);
   }
 
   nsresult Dispatch()
   {
     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(mState == STATE_INIT);
 
-    mState = STATE_CALL_WAIT_FOR_OPEN_ALLOWED;
+    mState = STATE_OPEN_DIRECTORY;
     nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
   void Cancel()
   {
     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(!mCanceled);
     mCanceled = true;
     mInitAction->CancelOnInitiatingThread();
   }
 
+  // OpenDirectoryListener methods
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
+
 private:
   class SyncResolver final : public Action::Resolver
   {
   public:
     SyncResolver()
       : mResolved(false)
       , mResult(NS_OK)
     { }
@@ -215,25 +189,37 @@ private:
     MOZ_ASSERT(mState == STATE_COMPLETE);
     MOZ_ASSERT(!mContext);
     MOZ_ASSERT(!mInitAction);
   }
 
   enum State
   {
     STATE_INIT,
-    STATE_CALL_WAIT_FOR_OPEN_ALLOWED,
-    STATE_WAIT_FOR_OPEN_ALLOWED,
+    STATE_OPEN_DIRECTORY,
+    STATE_WAIT_FOR_DIRECTORY_LOCK,
     STATE_ENSURE_ORIGIN_INITIALIZED,
     STATE_RUN_ON_TARGET,
     STATE_RUNNING,
     STATE_COMPLETING,
     STATE_COMPLETE
   };
 
+  void Complete(nsresult aResult)
+  {
+    MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(aResult));
+
+    MOZ_ASSERT(NS_SUCCEEDED(mResult));
+    mResult = aResult;
+
+    mState = STATE_COMPLETING;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
+  }
+
   void Clear()
   {
     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(mContext);
     mContext = nullptr;
     mManager = nullptr;
     mInitAction = nullptr;
   }
@@ -242,44 +228,80 @@ private:
   nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
   nsRefPtr<Manager> mManager;
   nsRefPtr<Data> mData;
   nsCOMPtr<nsIThread> mTarget;
   nsRefPtr<Action> mInitAction;
   nsCOMPtr<nsIThread> mInitiatingThread;
   nsresult mResult;
   QuotaInfo mQuotaInfo;
-  nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
+  nsMainThreadPtrHandle<DirectoryLock> mDirectoryLock;
   State mState;
   Atomic<bool> mCanceled;
-  bool mNeedsQuotaRelease;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 };
 
+void
+Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = new nsMainThreadPtrHolder<DirectoryLock>(aLock);
+
+  if (mCanceled) {
+    Complete(NS_ERROR_ABORT);
+    return;
+  }
+
+  QuotaManager* qm = QuotaManager::Get();
+  MOZ_ASSERT(qm);
+
+  mState = STATE_ENSURE_ORIGIN_INITIALIZED;
+  nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Complete(rv);
+    return;
+  }
+}
+
+void
+Context::QuotaInitRunnable::DirectoryLockFailed()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  NS_WARNING("Failed to acquire a directory lock!");
+
+  Complete(NS_ERROR_FAILURE);
+}
+
 NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
 
 // The QuotaManager init state machine is represented in the following diagram:
 //
 //    +---------------+
 //    |     Start     |      Resolve(error)
 //    | (Orig Thread) +---------------------+
 //    +-------+-------+                     |
 //            |                             |
 // +----------v-----------+                 |
-// |CallWaitForOpenAllowed|  Resolve(error) |
+// |    OpenDirectory     |  Resolve(error) |
 // |    (Main Thread)     +-----------------+
 // +----------+-----------+                 |
 //            |                             |
-//   +--------v---------+                   |
-//   |WaitForOpenAllowed|    Resolve(error) |
-//   |  (Main Thread)   +-------------------+
-//   +--------+---------+                   |
+// +----------v-----------+                 |
+// | WaitForDirectoryLock |  Resolve(error) |
+// |    (Main Thread)     +-----------------+
+// +----------+-----------+                 |
 //            |                             |
 // +----------v------------+                |
 // |EnsureOriginInitialized| Resolve(error) |
 // |   (Quota IO Thread)   +----------------+
 // +----------+------------+                |
 //            |                             |
 // +----------v------------+                |
 // |     RunOnTarget       | Resolve(error) |
@@ -302,17 +324,17 @@ Context::QuotaInitRunnable::Run()
 {
   // May run on different threads depending on the state.  See individual
   // state cases for thread assertions.
 
   nsRefPtr<SyncResolver> resolver = new SyncResolver();
 
   switch(mState) {
     // -----------------------------------
-    case STATE_CALL_WAIT_FOR_OPEN_ALLOWED:
+    case STATE_OPEN_DIRECTORY:
     {
       MOZ_ASSERT(NS_IsMainThread());
 
       if (mCanceled || QuotaManager::IsShuttingDown()) {
         resolver->Resolve(NS_ERROR_ABORT);
         break;
       }
 
@@ -328,69 +350,33 @@ Context::QuotaInitRunnable::Run()
                                              &mQuotaInfo.mGroup,
                                              &mQuotaInfo.mOrigin,
                                              &mQuotaInfo.mIsApp);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         resolver->Resolve(rv);
         break;
       }
 
-      QuotaManager::GetStorageId(PERSISTENCE_TYPE_DEFAULT,
-                                 mQuotaInfo.mOrigin,
-                                 Client::DOMCACHE,
-                                 NS_LITERAL_STRING("cache"),
-                                 mQuotaInfo.mStorageId);
-
-      // QuotaManager::WaitForOpenAllowed() will hold a reference to us as
-      // a callback.  We will then get executed again on the main thread when
-      // it is safe to open the quota directory.
-      mState = STATE_WAIT_FOR_OPEN_ALLOWED;
-      rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
-                                  Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
-                                  mQuotaInfo.mStorageId, this);
-      if (NS_FAILED(rv)) {
-        resolver->Resolve(rv);
-        break;
-      }
-      break;
-    }
-    // ------------------------------
-    case STATE_WAIT_FOR_OPEN_ALLOWED:
-    {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      mNeedsQuotaRelease = true;
-
-      if (mCanceled) {
-        resolver->Resolve(NS_ERROR_ABORT);
-        break;
-      }
-
-      QuotaManager* qm = QuotaManager::Get();
-      MOZ_ASSERT(qm);
-
-      nsRefPtr<OfflineStorage> offlineStorage =
-        OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo);
-      mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage);
-
-      mState = STATE_ENSURE_ORIGIN_INITIALIZED;
-      nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        resolver->Resolve(rv);
-        break;
-      }
+      // QuotaManager::OpenDirectory() will hold a reference to us as
+      // a listener.  We will then get DirectoryLockAcquired() on the main
+      // thread when it is safe to access our storage directory.
+      mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
+      qm->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
+                        mQuotaInfo.mGroup,
+                        mQuotaInfo.mOrigin,
+                        mQuotaInfo.mIsApp,
+                        quota::Client::DOMCACHE,
+                        /* aExclusive */ false,
+                        this);
       break;
     }
     // ----------------------------------
     case STATE_ENSURE_ORIGIN_INITIALIZED:
     {
-      // Can't assert quota IO thread because its an idle thread that can get
-      // recreated.  At least assert we're not on main thread or owning thread.
-      MOZ_ASSERT(!NS_IsMainThread());
-      MOZ_ASSERT(_mOwningThread.GetThread() != PR_GetCurrentThread());
+      AssertIsOnIOThread();
 
       if (mCanceled) {
         resolver->Resolve(NS_ERROR_ABORT);
         break;
       }
 
       QuotaManager* qm = QuotaManager::Get();
       MOZ_ASSERT(qm);
@@ -433,46 +419,34 @@ Context::QuotaInitRunnable::Run()
 
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
       NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
       mInitAction->CompleteOnInitiatingThread(mResult);
-      mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
+      mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock);
       mState = STATE_COMPLETE;
 
-      if (mNeedsQuotaRelease) {
-        // Unlock the quota dir if we locked it previously
-        nsCOMPtr<nsIRunnable> runnable = new QuotaReleaseRunnable(mQuotaInfo);
-        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
-      }
-
       // Explicitly cleanup here as the destructor could fire on any of
       // the threads we have bounced through.
       Clear();
       break;
     }
     // -----
+    case STATE_WAIT_FOR_DIRECTORY_LOCK:
     default:
     {
       MOZ_CRASH("unexpected state in QuotaInitRunnable");
     }
   }
 
   if (resolver->Resolved()) {
-    MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(resolver->Result()));
-
-    MOZ_ASSERT(NS_SUCCEEDED(mResult));
-    mResult = resolver->Result();
-
-    mState = STATE_COMPLETING;
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
+    Complete(resolver->Result());
   }
 
   return NS_OK;
 }
 
 // Runnable wrapper around Action objects dispatched on the Context.  This
 // runnable executes the Action on the appropriate threads while the Context
 // is initialized.
@@ -1002,29 +976,29 @@ Context::DispatchAction(Action* aAction,
     // for this invariant violation.
     MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
   }
   AddActivity(runnable);
 }
 
 void
 Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
-                     nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage)
+                     nsMainThreadPtrHandle<DirectoryLock>& aDirectoryLock)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
 
   MOZ_ASSERT(mInitRunnable);
   mInitRunnable = nullptr;
 
   mQuotaInfo = aQuotaInfo;
 
-  // Always save the offline storage to ensure QuotaManager does not shutdown
+  // Always save the directory lock to ensure QuotaManager does not shutdown
   // before the Context has gone away.
-  MOZ_ASSERT(!mOfflineStorage);
-  mOfflineStorage = aOfflineStorage;
+  MOZ_ASSERT(!mDirectoryLock);
+  mDirectoryLock = aDirectoryLock;
 
   if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) {
     for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
       mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
     }
     mPendingActions.Clear();
     mThreadsafeHandle->AllowToClose();
     // Context will destruct after return here and last ref is released.
--- a/dom/cache/Context.h
+++ b/dom/cache/Context.h
@@ -3,16 +3,17 @@
 /* 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_cache_Context_h
 #define mozilla_dom_cache_Context_h
 
 #include "mozilla/dom/cache/Types.h"
+#include "mozilla/dom/quota/QuotaManager.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISupportsImpl.h"
 #include "nsProxyRelease.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsTObserverArray.h"
 
@@ -20,50 +21,51 @@ class nsIEventTarget;
 class nsIThread;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class Action;
 class Manager;
-class OfflineStorage;
 
 // The Context class is RAII-style class for managing IO operations within the
 // Cache.
 //
 // When a Context is created it performs the complicated steps necessary to
 // initialize the QuotaManager.  Action objects dispatched on the Context are
 // delayed until this initialization is complete.  They are then allow to
 // execute on any specified thread.  Once all references to the Context are
 // gone, then the steps necessary to release the QuotaManager are performed.
 // After initialization the Context holds a self reference, so it will stay
 // alive until one of three conditions occur:
 //
 //  1) The Manager will call Context::AllowToClose() when all of the actors
 //     have removed themselves as listener.  This means an idle context with
 //     no active DOM objects will close gracefully.
-//  2) The QuotaManager invalidates the storage area so it can delete the
-//     files.  In this case the OfflineStorage calls Cache::Invalidate() which
+//  2) The QuotaManager aborts all operations so it can delete the files.
+//     In this case the QuotaManager calls Client::AbortOperations() which
 //     in turn cancels all existing Action objects and then marks the Manager
 //     as invalid.
 //  3) Browser shutdown occurs and the Manager calls Context::CancelAll().
 //
 // In either case, though, the Action objects must be destroyed first to
 // allow the Context to be destroyed.
 //
 // While the Context performs operations asynchronously on threads, all of
 // methods in its public interface must be called on the same thread
 // originally used to create the Context.
 //
 // As an invariant, all Context objects must be destroyed before permitting
 // the "profile-before-change" shutdown event to complete.  This is ensured
 // via the code in ShutdownObserver.cpp.
 class Context final
 {
+  typedef mozilla::dom::quota::QuotaManager::DirectoryLock DirectoryLock;
+
 public:
   // Define a class allowing other threads to hold the Context alive.  This also
   // allows these other threads to safely close or cancel the Context.
   class ThreadsafeHandle final
   {
     friend class Context;
   public:
     void AllowToClose();
@@ -177,17 +179,17 @@ private:
   };
 
   Context(Manager* aManager, nsIThread* aTarget);
   ~Context();
   void Init(Action* aInitAction, Context* aOldContext);
   void Start();
   void DispatchAction(Action* aAction, bool aDoomData = false);
   void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
-                   nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage);
+                   nsMainThreadPtrHandle<DirectoryLock>& aDirectoryLock);
 
   already_AddRefed<ThreadsafeHandle>
   CreateThreadsafeHandle();
 
   void
   SetNextContext(Context* aNextContext);
 
   void
@@ -207,17 +209,17 @@ private:
   typedef nsTObserverArray<Activity*> ActivityList;
   ActivityList mActivityList;
 
   // The ThreadsafeHandle may have a strong ref back to us.  This creates
   // a ref-cycle that keeps the Context alive.  The ref-cycle is broken
   // when ThreadsafeHandle::AllowToClose() is called.
   nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
 
-  nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
+  nsMainThreadPtrHandle<DirectoryLock> mDirectoryLock;
   nsRefPtr<Context> mNextContext;
 
 public:
   NS_INLINE_DECL_REFCOUNTING(cache::Context)
 };
 
 } // namespace cache
 } // namespace dom
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -244,16 +244,36 @@ public:
 
     MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(aManager));
 
     // clean up the factory singleton if there are no more managers
     MaybeDestroyInstance();
   }
 
   static void
+  StartAbortOnMainThread(const nsACString& aOrigin)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // Lock for sBackgroundThread.
+    StaticMutexAutoLock lock(sMutex);
+
+    if (!sBackgroundThread) {
+      return;
+    }
+
+    // Guaranteed to succeed because we should get abort only before the
+    // background thread is destroyed.
+    nsCOMPtr<nsIRunnable> runnable = new AbortRunnable(aOrigin);
+    nsresult rv = sBackgroundThread->Dispatch(runnable,
+                                              nsIThread::DISPATCH_NORMAL);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+  }
+
+  static void
   StartShutdownAllOnMainThread()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // Lock for sFactoryShutdown and sBackgroundThread.
     StaticMutexAutoLock lock(sMutex);
 
     sFactoryShutdown = true;
@@ -353,16 +373,46 @@ private:
       MOZ_ASSERT(sBackgroundThread);
       sBackgroundThread = nullptr;
     }
 
     sFactory = nullptr;
   }
 
   static void
+  AbortOnBackgroundThread(const nsACString& aOrigin)
+  {
+    mozilla::ipc::AssertIsOnBackgroundThread();
+
+    // The factory was destroyed between when abort started on main thread and
+    // when we could start abort on the worker thread.  Just declare abort
+    // complete.
+    if (!sFactory) {
+#ifdef DEBUG
+      StaticMutexAutoLock lock(sMutex);
+      MOZ_ASSERT(!sBackgroundThread);
+#endif
+      return;
+    }
+
+    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+    {
+      ManagerList::ForwardIterator iter(sFactory->mManagerList);
+      while (iter.HasMore()) {
+        nsRefPtr<Manager> manager = iter.GetNext();
+        if (aOrigin.IsVoid() ||
+            manager->mManagerId->ExtendedOrigin() == aOrigin) {
+          manager->Abort();
+        }
+      }
+    }
+  }
+
+  static void
   ShutdownAllOnBackgroundThread()
   {
     mozilla::ipc::AssertIsOnBackgroundThread();
 
     // The factory shutdown between when shutdown started on main thread and
     // when we could start shutdown on the worker thread.  Just declare
     // shutdown complete.  The sFactoryShutdown flag prevents the factory
     // from racing to restart here.
@@ -388,16 +438,36 @@ private:
         nsRefPtr<Manager> manager = iter.GetNext();
         manager->Shutdown();
       }
     }
 
     MaybeDestroyInstance();
   }
 
+  class AbortRunnable final : public nsRunnable
+  {
+  public:
+    explicit AbortRunnable(const nsACString& aOrigin)
+      : mOrigin(aOrigin)
+    { }
+
+    NS_IMETHOD
+    Run() override
+    {
+      mozilla::ipc::AssertIsOnBackgroundThread();
+      AbortOnBackgroundThread(mOrigin);
+      return NS_OK;
+    }
+  private:
+    ~AbortRunnable() { }
+
+    const nsCString mOrigin;
+  };
+
   class ShutdownAllRunnable final : public nsRunnable
   {
   public:
     NS_IMETHOD
     Run() override
     {
       mozilla::ipc::AssertIsOnBackgroundThread();
       ShutdownAllOnBackgroundThread();
@@ -1477,16 +1547,25 @@ Manager::ShutdownAllOnMainThread()
   while (!Factory::IsShutdownAllCompleteOnMainThread()) {
     if (!NS_ProcessNextEvent()) {
       NS_WARNING("Something bad happened!");
       break;
     }
   }
 }
 
+// static
+void
+Manager::AbortOnMainThread(const nsACString& aOrigin)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  Factory::StartAbortOnMainThread(aOrigin);
+}
+
 void
 Manager::RemoveListener(Listener* aListener)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   // There may not be a listener here in the case where an actor is killed
   // before it can perform any actual async requests on Manager.
   mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
   MOZ_ASSERT(!mListeners.Contains(aListener,
@@ -1852,16 +1931,32 @@ Manager::Shutdown()
   // its cleaned up.
   if (mContext) {
     nsRefPtr<Context> context = mContext;
     context->CancelAll();
     return;
   }
 }
 
+void
+Manager::Abort()
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  MOZ_ASSERT(mContext);
+
+  // Note that we are closing to prevent any new requests from coming in and
+  // creating a new Context.  We must ensure all Contexts and IO operations are
+  // complete before origin clear proceeds.
+  NoteClosing();
+
+  // Cancel and only note that we are done after the context is cleaned up.
+  nsRefPtr<Context> context = mContext;
+  context->CancelAll();
+}
+
 Manager::ListenerId
 Manager::SaveListener(Listener* aListener)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
 
   // Once a Listener is added, we keep a reference to it until its
   // removed.  Since the same Listener might make multiple requests,
   // ensure we only have a single reference in our list.
--- a/dom/cache/Manager.h
+++ b/dom/cache/Manager.h
@@ -129,16 +129,19 @@ public:
   };
 
   static nsresult GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut);
   static already_AddRefed<Manager> Get(ManagerId* aManagerId);
 
   // Synchronously shutdown from main thread.  This spins the event loop.
   static void ShutdownAllOnMainThread();
 
+  // Cancel actions for given origin or all actions if passed string is null.
+  static void AbortOnMainThread(const nsACString& aOrigin);
+
   // Must be called by Listener objects before they are destroyed.
   void RemoveListener(Listener* aListener);
 
   // Must be called by Context objects before they are destroyed.
   void RemoveContext(Context* aContext);
 
   // Marks the Manager "invalid".  Once the Context completes no new operations
   // will be permitted with this Manager.  New actors will get a new Manager.
@@ -192,17 +195,18 @@ private:
   class StorageKeysAction;
 
   typedef uint64_t ListenerId;
 
   Manager(ManagerId* aManagerId, nsIThread* aIOThread);
   ~Manager();
   void Init(Manager* aOldManager);
   void Shutdown();
-  already_AddRefed<Context> CurrentContext();
+
+  void Abort();
 
   ListenerId SaveListener(Listener* aListener);
   Listener* GetListener(ListenerId aListenerId) const;
 
   bool SetCacheIdOrphanedIfRefed(CacheId aCacheId);
   bool SetBodyIdOrphanedIfRefed(const nsID& aBodyId);
   void NoteOrphanedBodyIdList(const nsTArray<nsID>& aDeletedBodyIdList);
 
--- a/dom/cache/ManagerId.cpp
+++ b/dom/cache/ManagerId.cpp
@@ -27,49 +27,42 @@ ManagerId::Create(nsIPrincipal* aPrincip
   // pages, so we don't need those checks either.
   //
   // But, if we get the same QuotaManager directory for different about:
   // origins, we probably only want one Manager instance.  So, we might
   // want to start using the QM's concept of origin uniqueness here.
   //
   // TODO: consider using QuotaManager's modified origin here (bug 1112071)
 
-  nsAutoCString origin;
+  nsCString origin;
   nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  uint32_t appId;
-  rv = aPrincipal->GetAppId(&appId);
+  nsCString jarPrefix;
+  rv = aPrincipal->GetJarPrefix(jarPrefix);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  bool inBrowserElement;
-  rv = aPrincipal->GetIsInBrowserElement(&inBrowserElement);
-  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-  nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, origin, appId,
-                                          inBrowserElement);
+  nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, origin, jarPrefix);
   ref.forget(aManagerIdOut);
 
   return NS_OK;
 }
 
 already_AddRefed<nsIPrincipal>
 ManagerId::Principal() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsIPrincipal> ref = mPrincipal;
   return ref.forget();
 }
 
 ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
-                     uint32_t aAppId, bool aInBrowserElement)
+                     const nsACString& aJarPrefix)
     : mPrincipal(aPrincipal)
-    , mOrigin(aOrigin)
-    , mAppId(aAppId)
-    , mInBrowserElement(aInBrowserElement)
+    , mExtendedOrigin(aJarPrefix + aOrigin)
 {
   MOZ_ASSERT(mPrincipal);
 }
 
 ManagerId::~ManagerId()
 {
   // If we're already on the main thread, then default destruction is fine
   if (NS_IsMainThread()) {
--- a/dom/cache/ManagerId.h
+++ b/dom/cache/ManagerId.h
@@ -18,62 +18,42 @@ class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class ManagerId final
 {
 public:
-  // nsTArray comparator that compares by value instead of pointer values.
-  class MOZ_STACK_CLASS Comparator final
-  {
-  public:
-    bool Equals(ManagerId *aA, ManagerId* aB) const { return *aA == *aB; }
-    bool LessThan(ManagerId *aA, ManagerId* aB) const { return *aA < *aB; }
-  };
-
   // Main thread only
   static nsresult Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut);
 
   // Main thread only
   already_AddRefed<nsIPrincipal> Principal() const;
 
-  const nsACString& Origin() const { return mOrigin; }
+  const nsACString& ExtendedOrigin() const { return mExtendedOrigin; }
 
   bool operator==(const ManagerId& aOther) const
   {
-    return mOrigin == aOther.mOrigin &&
-           mAppId == aOther.mAppId &&
-           mInBrowserElement == aOther.mInBrowserElement;
-  }
-
-  bool operator<(const ManagerId& aOther) const
-  {
-    return mOrigin < aOther.mOrigin ||
-           (mOrigin == aOther.mOrigin && mAppId < aOther.mAppId) ||
-           (mOrigin == aOther.mOrigin && mAppId == aOther.mAppId &&
-            mInBrowserElement < aOther.mInBrowserElement);
+    return mExtendedOrigin == aOther.mExtendedOrigin;
   }
 
 private:
   ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
-            uint32_t aAppId, bool aInBrowserElement);
+            const nsACString& aJarPrefix);
   ~ManagerId();
 
   ManagerId(const ManagerId&) = delete;
   ManagerId& operator=(const ManagerId&) = delete;
 
   // only accessible on main thread
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // immutable to allow threadsfe access
-  const nsCString mOrigin;
-  const uint32_t mAppId;
-  const bool mInBrowserElement;
+  const nsCString mExtendedOrigin;
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::ManagerId)
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
deleted file mode 100644
--- a/dom/cache/OfflineStorage.cpp
+++ /dev/null
@@ -1,135 +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 "mozilla/dom/cache/OfflineStorage.h"
-
-#include "mozilla/dom/cache/Context.h"
-#include "mozilla/dom/cache/QuotaClient.h"
-#include "mozilla/dom/quota/QuotaManager.h"
-#include "nsThreadUtils.h"
-
-namespace mozilla {
-namespace dom {
-namespace cache {
-
-using mozilla::dom::quota::Client;
-using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
-using mozilla::dom::quota::QuotaManager;
-
-NS_IMPL_ISUPPORTS(OfflineStorage, nsIOfflineStorage);
-
-// static
-already_AddRefed<OfflineStorage>
-OfflineStorage::Register(Context::ThreadsafeHandle* aContext,
-                         const QuotaInfo& aQuotaInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  QuotaManager* qm = QuotaManager::Get();
-  if (NS_WARN_IF(!qm)) {
-    return nullptr;
-  }
-
-  nsRefPtr<Client> client = qm->GetClient(Client::DOMCACHE);
-
-  nsRefPtr<OfflineStorage> storage =
-    new OfflineStorage(aContext, aQuotaInfo, client);
-
-  if (NS_WARN_IF(!qm->RegisterStorage(storage))) {
-    return nullptr;
-  }
-
-  return storage.forget();
-}
-
-void
-OfflineStorage::AddDestroyCallback(nsIRunnable* aCallback)
-{
-  MOZ_ASSERT(aCallback);
-  MOZ_ASSERT(!mDestroyCallbacks.Contains(aCallback));
-  mDestroyCallbacks.AppendElement(aCallback);
-}
-
-OfflineStorage::OfflineStorage(Context::ThreadsafeHandle* aContext,
-                               const QuotaInfo& aQuotaInfo,
-                               Client* aClient)
-  : mContext(aContext)
-  , mQuotaInfo(aQuotaInfo)
-  , mClient(aClient)
-{
-  MOZ_ASSERT(mContext);
-  MOZ_ASSERT(mClient);
-
-  mPersistenceType = PERSISTENCE_TYPE_DEFAULT;
-  mGroup = mQuotaInfo.mGroup;
-}
-
-OfflineStorage::~OfflineStorage()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  QuotaManager* qm = QuotaManager::Get();
-  MOZ_ASSERT(qm);
-  qm->UnregisterStorage(this);
-  for (uint32_t i = 0; i < mDestroyCallbacks.Length(); ++i) {
-    mDestroyCallbacks[i]->Run();
-  }
-}
-
-NS_IMETHODIMP_(const nsACString&)
-OfflineStorage::Id()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mQuotaInfo.mStorageId;
-}
-
-NS_IMETHODIMP_(Client*)
-OfflineStorage::GetClient()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mClient;
-}
-
-NS_IMETHODIMP_(bool)
-OfflineStorage::IsOwnedByProcess(ContentParent* aOwner)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  // The Cache and Context can be shared by multiple client processes.  They
-  // are not exclusively owned by a single process.
-  //
-  // As far as I can tell this is used by QuotaManager to shutdown storages
-  // when a particular process goes away.  We definitely don't want this
-  // since we are shared.  Also, the Cache actor code already properly
-  // handles asynchronous actor destruction when the child process dies.
-  //
-  // Therefore, always return false here.
-  return false;
-}
-
-NS_IMETHODIMP_(const nsACString&)
-OfflineStorage::Origin()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mQuotaInfo.mOrigin;
-}
-
-NS_IMETHODIMP_(nsresult)
-OfflineStorage::Close()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mContext->AllowToClose();
-  return NS_OK;
-}
-
-NS_IMETHODIMP_(void)
-OfflineStorage::Invalidate()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  mContext->InvalidateAndAllowToClose();
-}
-
-} // namespace cache
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/cache/OfflineStorage.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_cache_QuotaOfflineStorage_h
-#define mozilla_dom_cache_QuotaOfflineStorage_h
-
-#include "nsISupportsImpl.h"
-#include "mozilla/AlreadyAddRefed.h"
-#include "mozilla/dom/cache/Context.h"
-#include "nsIOfflineStorage.h"
-#include "nsTArray.h"
-
-namespace mozilla {
-namespace dom {
-namespace cache {
-
-class OfflineStorage final : public nsIOfflineStorage
-{
-public:
-  static already_AddRefed<OfflineStorage>
-  Register(Context::ThreadsafeHandle* aContext, const QuotaInfo& aQuotaInfo);
-
-  void
-  AddDestroyCallback(nsIRunnable* aCallback);
-
-private:
-  OfflineStorage(Context::ThreadsafeHandle* aContext,
-                 const QuotaInfo& aQuotaInfo,
-                 Client* aClient);
-  ~OfflineStorage();
-
-  nsRefPtr<Context::ThreadsafeHandle> mContext;
-  const QuotaInfo mQuotaInfo;
-  nsRefPtr<Client> mClient;
-  nsTArray<nsCOMPtr<nsIRunnable>> mDestroyCallbacks;
-
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOFFLINESTORAGE
-};
-
-} // namespace cache
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_cache_QuotaOfflineStorage_h
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -3,28 +3,27 @@
 /* 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 "mozilla/dom/cache/QuotaClient.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/Manager.h"
-#include "mozilla/dom/cache/OfflineStorage.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 #include "nsThreadUtils.h"
 
 namespace {
 
 using mozilla::DebugOnly;
+using mozilla::dom::ContentParentId;
 using mozilla::dom::cache::Manager;
-using mozilla::dom::cache::OfflineStorage;
 using mozilla::dom::quota::Client;
 using mozilla::dom::quota::PersistenceType;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::UsageInfo;
 
 static nsresult
 GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
 {
@@ -57,51 +56,16 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* a
     MOZ_ASSERT(fileSize >= 0);
 
     aUsageInfo->AppendToFileUsage(fileSize);
   }
 
   return NS_OK;
 }
 
-class StoragesDestroyedRunnable final : public nsRunnable
-{
-  uint32_t mExpectedCalls;
-  nsCOMPtr<nsIRunnable> mCallback;
-
-public:
-  StoragesDestroyedRunnable(uint32_t aExpectedCalls, nsIRunnable* aCallback)
-    : mExpectedCalls(aExpectedCalls)
-    , mCallback(aCallback)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mExpectedCalls);
-    MOZ_ASSERT(mCallback);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mExpectedCalls);
-    mExpectedCalls -= 1;
-    if (!mExpectedCalls) {
-      mCallback->Run();
-    }
-    return NS_OK;
-  }
-
-private:
-  ~StoragesDestroyedRunnable()
-  {
-    // This is a callback runnable and not used for thread dispatch.  It should
-    // always be destroyed on the main thread.
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-};
-
 class CacheQuotaClient final : public Client
 {
 public:
   virtual Type
   GetType() override
   {
     return DOMCACHE;
   }
@@ -204,32 +168,35 @@ public:
   virtual void
   ReleaseIOThreadObjects() override
   {
     // Nothing to do here as the Context handles cleaning everything up
     // automatically.
   }
 
   virtual void
-  WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
-                            nsIRunnable* aCallback) override
+  AbortOperations(const nsACString& aOrigin) override
   {
     MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(!aStorages.IsEmpty());
 
-    nsCOMPtr<nsIRunnable> callback =
-      new StoragesDestroyedRunnable(aStorages.Length(), aCallback);
+    Manager::AbortOnMainThread(aOrigin);
+  }
 
-    for (uint32_t i = 0; i < aStorages.Length(); ++i) {
-      MOZ_ASSERT(aStorages[i]->GetClient());
-      MOZ_ASSERT(aStorages[i]->GetClient()->GetType() == Client::DOMCACHE);
-      nsRefPtr<OfflineStorage> storage =
-        static_cast<OfflineStorage*>(aStorages[i]);
-      storage->AddDestroyCallback(callback);
-    }
+  virtual void
+  AbortOperationsForProcess(ContentParentId aContentParentId) override
+  {
+    // The Cache and Context can be shared by multiple client processes.  They
+    // are not exclusively owned by a single process.
+    //
+    // As far as I can tell this is used by QuotaManager to abort operations
+    // when a particular process goes away.  We definitely don't want this
+    // since we are shared.  Also, the Cache actor code already properly
+    // handles asynchronous actor destruction when the child process dies.
+    //
+    // Therefore, do nothing here.
   }
 
   virtual void
   PerformIdleMaintenance() override
   { }
 
   virtual void
   ShutdownWorkThreads() override
--- a/dom/cache/Types.h
+++ b/dom/cache/Types.h
@@ -28,17 +28,16 @@ typedef int64_t CacheId;
 static const CacheId INVALID_CACHE_ID = -1;
 
 struct QuotaInfo
 {
   QuotaInfo() : mIsApp(false) { }
   nsCOMPtr<nsIFile> mDir;
   nsCString mGroup;
   nsCString mOrigin;
-  nsCString mStorageId;
   bool mIsApp;
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_Types_h
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -25,17 +25,16 @@ EXPORTS.mozilla.dom.cache += [
     'Context.h',
     'DBAction.h',
     'DBSchema.h',
     'Feature.h',
     'FileUtils.h',
     'IPCUtils.h',
     'Manager.h',
     'ManagerId.h',
-    'OfflineStorage.h',
     'PrincipalVerifier.h',
     'QuotaClient.h',
     'ReadStream.h',
     'SavedTypes.h',
     'StreamControl.h',
     'StreamList.h',
     'Types.h',
     'TypeUtils.h',
@@ -60,17 +59,16 @@ UNIFIED_SOURCES += [
     'Connection.cpp',
     'Context.cpp',
     'DBAction.cpp',
     'DBSchema.cpp',
     'Feature.cpp',
     'FileUtils.cpp',
     'Manager.cpp',
     'ManagerId.cpp',
-    'OfflineStorage.cpp',
     'PrincipalVerifier.cpp',
     'QuotaClient.cpp',
     'ReadStream.cpp',
     'StreamControl.cpp',
     'StreamList.cpp',
     'TypeUtils.cpp',
 ]
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -42,17 +42,16 @@
 #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"
 #include "mozilla/dom/quota/FileStreams.h"
-#include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/InputStreamParams.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "mozilla/ipc/PBackground.h"
 #include "mozilla/storage/Variant.h"
@@ -68,17 +67,16 @@
 #include "nsIFile.h"
 #include "nsIFileURL.h"
 #include "nsIIdleService.h"
 #include "nsIInputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsInterfaceHashtable.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
-#include "nsIOfflineStorage.h"
 #include "nsIOutputStream.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsISupportsPriority.h"
 #include "nsIThread.h"
 #include "nsITimer.h"
@@ -124,17 +122,16 @@ using namespace mozilla::ipc;
 namespace {
 
 class ConnectionPool;
 class Cursor;
 class Database;
 struct DatabaseActorInfo;
 class DatabaseLoggingInfo;
 class DatabaseFile;
-class DatabaseOfflineStorage;
 class Factory;
 class OpenDatabaseOp;
 class TransactionBase;
 class TransactionDatabaseOperationBase;
 class VersionChangeTransaction;
 
 /*******************************************************************************
  * Constants
@@ -5588,31 +5585,55 @@ private:
   DispatchToOwningThread();
 
   void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
+using DirectoryLock = mozilla::dom::quota::QuotaManager::DirectoryLock;
+
+class UnlockDirectoryRunnable final
+  : public nsRunnable
+{
+  nsRefPtr<DirectoryLock> mDirectoryLock;
+
+public:
+  explicit
+  UnlockDirectoryRunnable(already_AddRefed<DirectoryLock> aDirectoryLock)
+    : mDirectoryLock(Move(aDirectoryLock))
+  { }
+
+private:
+  ~UnlockDirectoryRunnable()
+  {
+    MOZ_ASSERT(!mDirectoryLock);
+  }
+
+  NS_IMETHOD
+  Run() override;
+};
+
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
   class StartTransactionOp;
 
 private:
   nsRefPtr<Factory> mFactory;
   nsRefPtr<FullDatabaseMetadata> mMetadata;
   nsRefPtr<FileManager> mFileManager;
-  nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
+  nsRefPtr<DirectoryLock> mDirectoryLock;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
   nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
+  const OptionalContentId mOptionalContentParentId;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
   uint32_t mFileHandleCount;
   const uint32_t mTelemetryId;
   const PersistenceType mPersistenceType;
   const bool mChromeWriteAccessAllowed;
@@ -5621,22 +5642,23 @@ private:
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
 public:
   // Created by OpenDatabaseOp.
   Database(Factory* aFactory,
            const PrincipalInfo& aPrincipalInfo,
+           const OptionalContentId& aOptionalContentParentId,
            const nsACString& aGroup,
            const nsACString& aOrigin,
            uint32_t aTelemetryId,
            FullDatabaseMetadata* aMetadata,
            FileManager* aFileManager,
-           already_AddRefed<DatabaseOfflineStorage> aOfflineStorage,
+           already_AddRefed<DirectoryLock> aDirectoryLock,
            bool aChromeWriteAccessAllowed);
 
   void
   AssertIsOnConnectionThread() const
   {
 #ifdef DEBUG
     if (mConnection) {
       MOZ_ASSERT(mConnection);
@@ -5655,16 +5677,26 @@ public:
   Invalidate();
 
   const PrincipalInfo&
   GetPrincipalInfo() const
   {
     return mPrincipalInfo;
   }
 
+  bool
+  IsOwnedByProcess(ContentParentId aContentParentId) const
+  {
+    MOZ_ASSERT(mOptionalContentParentId.type() != OptionalContentId::T__None);
+
+    return
+      mOptionalContentParentId.type() == OptionalContentId::TContentParentId &&
+      mOptionalContentParentId.get_ContentParentId() == aContentParentId;
+  }
+
   const nsCString&
   Group() const
   {
     return mGroup;
   }
 
   const nsCString&
   Origin() const
@@ -6428,55 +6460,65 @@ private:
 
   virtual bool
   DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
                                     override;
 };
 
 class FactoryOp
   : public DatabaseOperationBase
+  , public OpenDirectoryListener
   , public PBackgroundIDBFactoryRequestParent
 {
 public:
   struct MaybeBlockedDatabaseInfo;
 
 protected:
   enum State
   {
     // Just created on the PBackground thread, dispatched to the main thread.
-    // Next step is State_OpenPending.
+    // Next step is either State_SendingResults if permission is denied,
+    // State_PermissionChallenge if the permission is unknown, or
+    // State_DirectoryOpenPending if permission is granted.
     State_Initial,
 
-    // Waiting for open allowed on the main thread. The next step is either
-    // State_SendingResults if permission is denied,
-    // State_PermissionChallenge if the permission is unknown, or
-    // State_DatabaseWorkOpen if permission is granted.
-    State_OpenPending,
-
     // Sending a permission challenge message to the child on the PBackground
-    // thread. Next step is State_PermissionRetryReady.
+    // thread. Next step is State_PermissionRetry.
     State_PermissionChallenge,
 
     // Retrying permission check after a challenge on the main thread. Next step
     // is either State_SendingResults if permission is denied or
-    // State_DatabaseWorkOpen if permission is granted.
+    // State_DirectoryOpenPending if permission is granted.
     State_PermissionRetry,
 
+    // Waiting for directory open allowed on the main thread. The next step is
+    // either State_SendingResults if directory lock failed to acquire, or
+    // State_DirectoryWorkOpen if directory lock is acquired.
+    State_DirectoryOpenPending,
+
+    // Checking if database open needs to wait on the PBackground thread.
+    // The next step is State_DatabaseOpenPending.
+    State_DirectoryWorkOpen,
+
+    // Waiting for database open allowed on the main thread. The next step is
+    // State_DatabaseWorkOpen.
+    State_DatabaseOpenPending,
+
     // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
     // either State_BeginVersionChange if the requested version doesn't match
     // the existing database version or State_SendingResults if the versions
     // match.
     State_DatabaseWorkOpen,
 
     // Starting a version change transaction or deleting a database on the
     // PBackground thread. We need to notify other databases that a version
     // change is about to happen, and maybe tell the request that a version
     // change has been blocked. If databases are notified then the next step is
     // State_WaitingForOtherDatabasesToClose. Otherwise the next step is
-    // State_DispatchToWorkThread.
+    // State_WaitingForTransactionsToComplete.
     State_BeginVersionChange,
 
     // Waiting for other databases to close on the PBackground thread. This
     // state may persist until all databases are closed. The next state is
     // State_WaitingForTransactionsToComplete.
     State_WaitingForOtherDatabasesToClose,
 
     // Waiting for all transactions that could interfere with this operation to
@@ -6486,44 +6528,44 @@ 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
-    // State_UnblockingQuotaManager.
+    // State_Completed.
     State_SendingResults,
 
-    // Notifying the QuotaManager that it can proceed to the next operation on
-    // the main thread. Next step is State_Completed.
-    State_UnblockingQuotaManager,
-
     // All done.
     State_Completed
   };
 
   // Must be released on the background thread!
   nsRefPtr<Factory> mFactory;
 
   // Must be released on the main thread!
   nsRefPtr<ContentParent> mContentParent;
 
+  // Must be released on the main thread!
+  nsRefPtr<DirectoryLock> mDirectoryLock;
+
+  nsRefPtr<FactoryOp> mDelayedOp;
   nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
 
   const CommonFactoryRequestParams mCommonParams;
   nsCString mGroup;
   nsCString mOrigin;
   nsCString mDatabaseId;
   State mState;
   bool mIsApp;
   bool mEnforcingQuota;
   const bool mDeleting;
-  bool mBlockedQuotaManager;
+  bool mBlockedDatabaseOpen;
   bool mChromeWriteAccessAllowed;
 
 public:
   void
   NoteDatabaseBlocked(Database* aDatabase);
 
   virtual void
   NoteDatabaseClosed(Database* aDatabase) = 0;
@@ -6556,53 +6598,62 @@ protected:
 
   nsresult
   ChallengePermission();
 
   nsresult
   RetryCheckPermission();
 
   nsresult
+  DirectoryOpen();
+
+  nsresult
   SendToIOThread();
 
   void
   WaitForTransactions();
 
   void
   FinishSendResults();
 
-  void
-  UnblockQuotaManager();
-
   nsresult
   SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
                             Database* aOpeningDatabase,
                             uint64_t aOldVersion,
                             const NullableVersion& aNewVersion);
 
   // Methods that subclasses must implement.
   virtual nsresult
-  QuotaManagerOpen() = 0;
+  DatabaseOpen() = 0;
 
   virtual nsresult
   DoDatabaseWork() = 0;
 
   virtual nsresult
   BeginVersionChange() = 0;
 
   virtual nsresult
   DispatchToWorkThread() = 0;
 
   virtual void
   SendResults() = 0;
 
+  NS_DECL_ISUPPORTS_INHERITED
+
   // Common nsIRunnable implementation that subclasses may not override.
   NS_IMETHOD
   Run() final;
 
+  // OpenDirectoryListener overrides.
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
+
   // IPDL methods.
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual bool
   RecvPermissionRetry() override;
 
   virtual void
@@ -6614,16 +6665,20 @@ private:
                   PermissionRequestBase::PermissionValue* aPermission);
 
   static bool
   CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
                                   const nsACString& aPermissionString);
 
   nsresult
   FinishOpen();
+
+  // Test whether this FactoryOp needs to wait for the given op.
+  bool
+  MustWaitFor(const FactoryOp& aExistingOp);
 };
 
 struct FactoryOp::MaybeBlockedDatabaseInfo final
 {
   nsRefPtr<Database> mDatabase;
   bool mBlocked;
 
   MOZ_IMPLICIT MaybeBlockedDatabaseInfo(Database* aDatabase)
@@ -6673,18 +6728,16 @@ class OpenDatabaseOp final
 
   uint64_t mRequestedVersion;
   nsString mDatabaseFilePath;
   nsRefPtr<FileManager> mFileManager;
 
   nsRefPtr<Database> mDatabase;
   nsRefPtr<VersionChangeTransaction> mVersionChangeTransaction;
 
-  nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
-
   // This is only set while a VersionChangeOp is live. It holds a strong
   // reference to its OpenDatabaseOp object so this is a weak pointer to avoid
   // cycles.
   VersionChangeOp* mVersionChangeOp;
 
   uint32_t mTelemetryId;
 
 public:
@@ -6732,17 +6785,17 @@ private:
 
   void
   ConnectionClosedCallback();
 
   virtual void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual nsresult
-  QuotaManagerOpen() override;
+  DatabaseOpen() override;
 
   virtual nsresult
   DoDatabaseWork() override;
 
   virtual nsresult
   BeginVersionChange() override;
 
   virtual void
@@ -6819,17 +6872,17 @@ public:
 private:
   ~DeleteDatabaseOp()
   { }
 
   void
   LoadPreviousVersion(nsIFile* aDatabaseFile);
 
   virtual nsresult
-  QuotaManagerOpen() override;
+  DatabaseOpen() override;
 
   virtual nsresult
   DoDatabaseWork() override;
 
   virtual nsresult
   BeginVersionChange() override;
 
   virtual void
@@ -7827,22 +7880,23 @@ class QuotaClient final
   // The number of freelist pages beyond which we will favor an incremental
   // vacuum over a full vacuum.
   static const int32_t kMaxFreelistThreshold = 5;
 
   // If the percent of unused file bytes in the database exceeds this percentage
   // then we will attempt a full vacuum.
   static const int32_t kPercentUnusedThreshold = 20;
 
+  class AbortOperationsRunnable;
   class AutoProgressHandler;
+  class GetDirectoryLockListener;
   struct MaintenanceInfoBase;
   struct MultipleMaintenanceInfo;
   class ShutdownWorkThreadsRunnable;
   struct SingleMaintenanceInfo;
-  class WaitForTransactionsRunnable;
 
   typedef nsClassHashtable<nsCStringHashKey, MultipleMaintenanceInfo>
           MaintenanceInfoHashtable;
 
   enum MaintenanceAction {
     MaintenanceAction_Nothing = 0,
     MaintenanceAction_IncrementalVacuum,
     MaintenanceAction_FullVacuum
@@ -7933,18 +7987,20 @@ public:
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin)
                          override;
 
   virtual void
   ReleaseIOThreadObjects() override;
 
   virtual void
-  WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
-                            nsIRunnable* aCallback) override;
+  AbortOperations(const nsACString& aOrigin) override;
+
+  virtual void
+  AbortOperationsForProcess(ContentParentId aContentParentId) override;
 
   virtual void
   PerformIdleMaintenance() override;
 
   virtual void
   ShutdownWorkThreads() override;
 
 private:
@@ -7965,35 +8021,37 @@ private:
 
   void
   StartIdleMaintenance();
 
   void
   StopIdleMaintenance();
 
   // Runs on mMaintenanceThreadPool. Once it finds databases it will queue a
-  // runnable that calls BlockQuotaManagerForIdleMaintenance.
+  // runnable that calls GetDirectoryLockForIdleMaintenance.
   void
   FindDatabasesForIdleMaintenance(uint32_t aRunId);
 
-  // Runs on the main thread. Once QuotaManager has blocked it will call
+  // Runs on the main thread. Once QuotaManager has given a lock it will call
   // ScheduleIdleMaintenance.
   void
-  BlockQuotaManagerForIdleMaintenance(
+  GetDirectoryLockForIdleMaintenance(
                                     uint32_t aRunId,
                                     MultipleMaintenanceInfo&& aMaintenanceInfo);
 
   // Runs on the main thread. It dispatches a runnable for each database that
   // will call PerformIdleMaintenanceOnDatabase.
   void
-  ScheduleIdleMaintenance(uint32_t aRunId, const nsACString& aKey);
+  ScheduleIdleMaintenance(uint32_t aRunId,
+                          const nsACString& aKey,
+                          const MultipleMaintenanceInfo& aMaintenanceInfo);
 
   // Runs on mMaintenanceThreadPool. Does maintenance on one database and then
   // dispatches a runnable back to the main thread to call
-  // MaybeUnblockQuotaManagerForIdleMaintenance.
+  // MaybeReleaseDirectoryLockForIdleMaintenance.
   void
   PerformIdleMaintenanceOnDatabase(uint32_t aRunId,
                                    const nsACString& aKey,
                                    SingleMaintenanceInfo&& aMaintenanceInfo);
 
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
   void
   PerformIdleMaintenanceOnDatabaseInternal(
@@ -8015,27 +8073,22 @@ private:
   IncrementalVacuum(mozIStorageConnection* aConnection);
 
   // Runs on mMaintenanceThreadPool as part of PerformIdleMaintenanceOnDatabase.
   void
   FullVacuum(mozIStorageConnection* aConnection,
              nsIFile* aDatabaseFile);
 
   // Runs on the main thread. Checks to see if all database maintenance has
-  // finished and then calls UnblockQuotaManagerForIdleMaintenance.
-  void
-  MaybeUnblockQuotaManagerForIdleMaintenance(
+  // finished and then releases the directory lock.
+  void
+  MaybeReleaseDirectoryLockForIdleMaintenance(
                                      const nsACString& aKey,
                                      const nsAString& aDatabasePath);
 
-  // Runs on the main thread.
-  void
-  UnblockQuotaManagerForIdleMaintenance(
-                               const MultipleMaintenanceInfo& aMaintenanceInfo);
-
   NS_DECL_NSIOBSERVER
 };
 
 class MOZ_STACK_CLASS QuotaClient::AutoProgressHandler final
   : public mozIStorageProgressHandler
 {
   QuotaClient* mQuotaClient;
   mozIStorageConnection* mConnection;
@@ -8157,35 +8210,41 @@ struct QuotaClient::SingleMaintenanceInf
 
   SingleMaintenanceInfo(const SingleMaintenanceInfo& aOther) = delete;
 };
 
 struct QuotaClient::MultipleMaintenanceInfo final
   : public MaintenanceInfoBase
 {
   nsTArray<nsString> mDatabasePaths;
+  nsRefPtr<DirectoryLock> mDirectoryLock;
+  const bool mIsApp;
 
   MultipleMaintenanceInfo(const nsACString& aGroup,
                           const nsACString& aOrigin,
                           PersistenceType aPersistenceType,
+                          bool aIsApp,
                           nsTArray<nsString>&& aDatabasePaths)
    : MaintenanceInfoBase(aGroup, aOrigin, aPersistenceType)
    , mDatabasePaths(Move(aDatabasePaths))
+   , mIsApp(aIsApp)
   {
 #ifdef DEBUG
     MOZ_ASSERT(!mDatabasePaths.IsEmpty());
     for (const nsString& databasePath : mDatabasePaths) {
       MOZ_ASSERT(!databasePath.IsEmpty());
     }
 #endif
   }
 
   MultipleMaintenanceInfo(MultipleMaintenanceInfo&& aOther)
     : MaintenanceInfoBase(Move(aOther))
     , mDatabasePaths(Move(aOther.mDatabasePaths))
+    , mDirectoryLock(Move(aOther.mDirectoryLock))
+    , mIsApp(Move(aOther.mIsApp))
   {
 #ifdef DEBUG
     MOZ_ASSERT(!mDatabasePaths.IsEmpty());
     for (const nsString& databasePath : mDatabasePaths) {
       MOZ_ASSERT(!databasePath.IsEmpty());
     }
 #endif
   }
@@ -8214,168 +8273,92 @@ private:
   ~ShutdownWorkThreadsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
   }
 
   NS_DECL_NSIRUNNABLE
 };
 
-class QuotaClient::WaitForTransactionsRunnable final
+class QuotaClient::AbortOperationsRunnable final
   : public nsRunnable
 {
+  const ContentParentId mContentParentId;
+  const nsCString mOrigin;
+
+  nsTArray<nsRefPtr<Database>> mDatabases;
+
+public:
+  explicit AbortOperationsRunnable(const nsACString& aOrigin)
+    : mOrigin(aOrigin)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!mOrigin.IsEmpty());
+  }
+
+  explicit AbortOperationsRunnable(ContentParentId aContentParentId)
+    : mContentParentId(aContentParentId)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mOrigin.IsEmpty());
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~AbortOperationsRunnable()
+  { }
+
+  static PLDHashOperator
+  MatchOrigin(const nsACString& aKey,
+              DatabaseActorInfo* aValue,
+              void* aClosure);
+
+  static PLDHashOperator
+  MatchContentParentId(const nsACString& aKey,
+                       DatabaseActorInfo* aValue,
+                       void* aClosure);
+
+  NS_DECL_NSIRUNNABLE
+};
+
+class QuotaClient::GetDirectoryLockListener final
+  : public OpenDirectoryListener
+{
   nsRefPtr<QuotaClient> mQuotaClient;
-  nsTArray<nsCString> mDatabaseIds;
-  nsCOMPtr<nsIRunnable> mCallback;
-
-  enum
-  {
-    State_Initial = 0,
-    State_WaitingForTransactions,
-    State_DispatchToMainThread,
-    State_WaitingForFileHandles,
-    State_Complete
-  } mState;
-
-public:
-  WaitForTransactionsRunnable(QuotaClient* aQuotaClient,
-                              nsTArray<nsCString>& aDatabaseIds,
-                              nsIRunnable* aCallback)
+  const uint32_t mRunId;
+  const nsCString mKey;
+
+public:
+  GetDirectoryLockListener(QuotaClient* aQuotaClient,
+                           uint32_t aRunId,
+                           const nsACString& aKey)
     : mQuotaClient(aQuotaClient)
-    , mCallback(aCallback)
-    , mState(State_Initial)
+    , mRunId(aRunId)
+    , mKey(aKey)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
-    MOZ_ASSERT(!aDatabaseIds.IsEmpty());
-    MOZ_ASSERT(aCallback);
-
-    mDatabaseIds.SwapElements(aDatabaseIds);
-  }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-private:
-  ~WaitForTransactionsRunnable()
-  {
-    MOZ_ASSERT(!mQuotaClient);
-    MOZ_ASSERT(!mCallback);
-    MOZ_ASSERT(mState == State_Complete);
-  }
-
-  void
-  MaybeWaitForTransactions();
-
-  void
-  DispatchToMainThread();
-
-  void
-  MaybeWaitForFileHandles();
-
-  void
-  CallCallback();
-
-  NS_DECL_NSIRUNNABLE
-};
-
-class DatabaseOfflineStorage final
-  : public nsIOfflineStorage
-{
-  // Must be released on the main thread!
-  nsRefPtr<QuotaClient> mStrongQuotaClient;
-
-  // Only used on the main thread.
-  QuotaClient* mWeakQuotaClient;
-
-  // Only used on the background thread.
-  Database* mDatabase;
-
-  const OptionalContentId mOptionalContentParentId;
-  const nsCString mOrigin;
-  const nsCString mId;
-  nsCOMPtr<nsIEventTarget> mOwningThread;
-
-  bool mClosedOnMainThread;
-  bool mClosedOnOwningThread;
-  bool mInvalidatedOnMainThread;
-  bool mInvalidatedOnOwningThread;
-
-  bool mRegisteredWithQuotaManager;
-
-public:
-  DatabaseOfflineStorage(QuotaClient* aQuotaClient,
-                         const OptionalContentId& aOptionalContentParentId,
-                         const nsACString& aGroup,
-                         const nsACString& aOrigin,
-                         const nsACString& aId,
-                         PersistenceType aPersistenceType,
-                         nsIEventTarget* aOwningThread);
-
-  static void
-  UnregisterOnOwningThread(
-                      already_AddRefed<DatabaseOfflineStorage> aOfflineStorage);
-
-  void
-  SetDatabase(Database* aDatabase)
-  {
-    AssertIsOnBackgroundThread();
-    MOZ_ASSERT(aDatabase);
-    MOZ_ASSERT(!mDatabase);
-
-    mDatabase = aDatabase;
-  }
-
-  nsIEventTarget*
-  OwningThread() const
-  {
-    return mOwningThread;
-  }
-
-  void
-  NoteRegisteredWithQuotaManager()
+  }
+
+  NS_INLINE_DECL_REFCOUNTING(QuotaClient::GetDirectoryLockListener)
+
+private:
+  ~GetDirectoryLockListener()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(!mRegisteredWithQuotaManager);
-
-    mRegisteredWithQuotaManager = true;
-  }
-
-  void
-  CloseOnOwningThread();
-
-  void
-  AssertInvalidatedOnMainThread() const
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mInvalidatedOnMainThread);
-  }
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-private:
-  ~DatabaseOfflineStorage()
-  {
-    MOZ_ASSERT(!mDatabase);
-    MOZ_RELEASE_ASSERT(!mRegisteredWithQuotaManager);
-  }
-
-  void
-  CloseOnMainThread();
-
-  void
-  InvalidateOnMainThread();
-
-  void
-  InvalidateOnOwningThread();
-
-  void
-  UnregisterOnMainThread();
-
-  NS_DECL_NSIOFFLINESTORAGE
+  }
+
+  // OpenDirectoryListener overrides.
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
 };
 
 #ifdef DEBUG
 
 class DEBUGThreadSlower final
   : public nsIThreadObserver
 {
 public:
@@ -8545,16 +8528,20 @@ ConvertBlobsToActors(PBackgroundParent* 
 
   return NS_OK;
 }
 
 /*******************************************************************************
  * Globals
  ******************************************************************************/
 
+typedef nsTArray<nsRefPtr<FactoryOp>> FactoryOpArray;
+
+StaticAutoPtr<FactoryOpArray> gFactoryOps;
+
 // Maps a database id to information about live database actors.
 typedef nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>
         DatabaseActorHashtable;
 
 StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
 
 StaticRefPtr<ConnectionPool> gConnectionPool;
 
@@ -11900,16 +11887,19 @@ Factory::~Factory()
 already_AddRefed<Factory>
 Factory::Create(const LoggingInfo& aLoggingInfo)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
 
   // If this is the first instance then we need to do some initialization.
   if (!sFactoryInstanceCount) {
+    MOZ_ASSERT(!gFactoryOps);
+    gFactoryOps = new FactoryOpArray();
+
     MOZ_ASSERT(!gLiveDatabaseHashtable);
     gLiveDatabaseHashtable = new DatabaseActorHashtable();
 
     MOZ_ASSERT(!gLoggingInfoHashtable);
     gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
 
 #ifdef DEBUG
     if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
@@ -11977,16 +11967,20 @@ Factory::ActorDestroy(ActorDestroyReason
   if (!(--sFactoryInstanceCount)) {
     MOZ_ASSERT(gLoggingInfoHashtable);
     gLoggingInfoHashtable = nullptr;
 
     MOZ_ASSERT(gLiveDatabaseHashtable);
     MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
     gLiveDatabaseHashtable = nullptr;
 
+    MOZ_ASSERT(gFactoryOps);
+    MOZ_ASSERT(gFactoryOps->IsEmpty());
+    gFactoryOps = nullptr;
+
 #ifdef DEBUG
     if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
       nsCOMPtr<nsISupportsPriority> thread =
         do_QueryInterface(NS_GetCurrentThread());
       MOZ_ASSERT(thread);
 
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
         thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL)));
@@ -12268,34 +12262,47 @@ WaitForTransactionsHelper::Run()
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+UnlockDirectoryRunnable::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mDirectoryLock);
+
+  mDirectoryLock = nullptr;
+
+  return NS_OK;
+}
+
 /*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
+                   const OptionalContentId& aOptionalContentParentId,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    uint32_t aTelemetryId,
                    FullDatabaseMetadata* aMetadata,
                    FileManager* aFileManager,
-                   already_AddRefed<DatabaseOfflineStorage> aOfflineStorage,
+                   already_AddRefed<DirectoryLock> aDirectoryLock,
                    bool aChromeWriteAccessAllowed)
   : mFactory(aFactory)
   , mMetadata(aMetadata)
   , mFileManager(aFileManager)
-  , mOfflineStorage(Move(aOfflineStorage))
+  , mDirectoryLock(Move(aDirectoryLock))
   , mPrincipalInfo(aPrincipalInfo)
+  , mOptionalContentParentId(aOptionalContentParentId)
   , mGroup(aGroup)
   , mOrigin(aOrigin)
   , mId(aMetadata->mDatabaseId)
   , mFilePath(aMetadata->mFilePath)
   , mFileHandleCount(0)
   , mTelemetryId(aTelemetryId)
   , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
   , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
@@ -12306,18 +12313,16 @@ Database::Database(Factory* aFactory,
   , mMetadataCleanedUp(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
   MOZ_ASSERT(aMetadata);
   MOZ_ASSERT(aFileManager);
   MOZ_ASSERT_IF(aChromeWriteAccessAllowed,
                 aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo);
-
-  mOfflineStorage->SetDatabase(this);
 }
 
 void
 Database::Invalidate()
 {
   AssertIsOnBackgroundThread();
 
   class MOZ_STACK_CLASS Helper final
@@ -12419,17 +12424,17 @@ Database::EnsureConnection()
 }
 
 bool
 Database::RegisterTransaction(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(!mTransactions.GetEntry(aTransaction));
-  MOZ_ASSERT(mOfflineStorage);
+  MOZ_ASSERT(mDirectoryLock);
 
   if (NS_WARN_IF(!mTransactions.PutEntry(aTransaction, fallible))) {
     return false;
   }
 
   return true;
 }
 
@@ -12484,34 +12489,30 @@ Database::CloseInternal()
   MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
 
   MOZ_ASSERT(info->mLiveDatabases.Contains(this));
 
   if (info->mWaitingFactoryOp) {
     info->mWaitingFactoryOp->NoteDatabaseClosed(this);
   }
 
-  if (mOfflineStorage) {
-    mOfflineStorage->CloseOnOwningThread();
-  }
-
   MaybeCloseConnection();
 
   return true;
 }
 
 void
 Database::MaybeCloseConnection()
 {
   AssertIsOnBackgroundThread();
 
   if (!mTransactions.Count() &&
       !mFileHandleCount &&
       IsClosed() &&
-      mOfflineStorage) {
+      mDirectoryLock) {
     nsCOMPtr<nsIRunnable> callback =
       NS_NewRunnableMethod(this, &Database::ConnectionClosedCallback);
 
     nsRefPtr<WaitForTransactionsHelper> helper =
       new WaitForTransactionsHelper(Id(), callback);
     helper->WaitForTransactions();
   }
 }
@@ -12519,18 +12520,21 @@ Database::MaybeCloseConnection()
 void
 Database::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mTransactions.Count());
   MOZ_ASSERT(!mFileHandleCount);
 
-  if (mOfflineStorage) {
-    DatabaseOfflineStorage::UnregisterOnOwningThread(mOfflineStorage.forget());
+  if (mDirectoryLock) {
+    nsRefPtr<UnlockDirectoryRunnable> runnable =
+      new UnlockDirectoryRunnable(mDirectoryLock.forget());
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
   }
 
   CleanupMetadata();
 }
 
 void
 Database::CleanupMetadata()
 {
@@ -12842,17 +12846,17 @@ Database::RecvClose()
   return true;
 }
 
 bool
 Database::RecvNewFileHandle()
 {
   AssertIsOnBackgroundThread();
 
-  if (!mOfflineStorage) {
+  if (!mDirectoryLock) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   if (mFileHandleCount == UINT32_MAX) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
@@ -14938,23 +14942,25 @@ Cursor::RecvContinue(const CursorRequest
 
 /*******************************************************************************
  * FileManager
  ******************************************************************************/
 
 FileManager::FileManager(PersistenceType aPersistenceType,
                          const nsACString& aGroup,
                          const nsACString& aOrigin,
+                         bool aIsApp,
                          const nsAString& aDatabaseName,
                          bool aEnforcingQuota)
   : mPersistenceType(aPersistenceType)
   , mGroup(aGroup)
   , mOrigin(aOrigin)
   , mDatabaseName(aDatabaseName)
   , mLastFileId(0)
+  , mIsApp(aIsApp)
   , mEnforcingQuota(aEnforcingQuota)
   , mInvalidated(false)
 { }
 
 FileManager::~FileManager()
 { }
 
 nsresult
@@ -15852,62 +15858,41 @@ QuotaClient::ReleaseIOThreadObjects()
   AssertIsOnIOThread();
 
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
     mgr->InvalidateAllFileManagers();
   }
 }
 
 void
-QuotaClient::WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
-                                       nsIRunnable* aCallback)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aStorages.IsEmpty());
-  MOZ_ASSERT(aCallback);
-
-  nsCOMPtr<nsIEventTarget> backgroundThread;
-  nsTArray<nsCString> databaseIds;
-
-  for (uint32_t count = aStorages.Length(), index = 0; index < count; index++) {
-    nsIOfflineStorage* storage = aStorages[index];
-    MOZ_ASSERT(storage);
-    MOZ_ASSERT(storage->GetClient() == this);
-
-    const nsACString& databaseId = storage->Id();
-
-    if (!databaseIds.Contains(databaseId)) {
-      databaseIds.AppendElement(databaseId);
-
-      if (!backgroundThread) {
-        backgroundThread =
-          static_cast<DatabaseOfflineStorage*>(storage)->OwningThread();
-        MOZ_ASSERT(backgroundThread);
-      }
-#ifdef DEBUG
-      else {
-        MOZ_ASSERT(backgroundThread ==
-                     static_cast<DatabaseOfflineStorage*>(storage)->
-                       OwningThread());
-      }
-#endif
-    }
-  }
-
-  if (databaseIds.IsEmpty()) {
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(aCallback)));
-    return;
-  }
-
-  MOZ_ASSERT(backgroundThread);
-
-  nsCOMPtr<nsIRunnable> runnable =
-    new WaitForTransactionsRunnable(this, databaseIds, aCallback);
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    backgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
+QuotaClient::AbortOperations(const nsACString& aOrigin)
+{
+  if (mBackgroundThread) {
+    nsRefPtr<AbortOperationsRunnable> runnable =
+      new AbortOperationsRunnable(aOrigin);
+
+    if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
+      // This can happen if the thread has shut down already.
+      return;
+    }
+  }
+}
+
+void
+QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+  if (mBackgroundThread) {
+    nsRefPtr<AbortOperationsRunnable> runnable =
+      new AbortOperationsRunnable(aContentParentId);
+
+    if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
+      // This can happen if the thread has shut down already.
+      return;
+    }
+  }
 }
 
 void
 QuotaClient::PerformIdleMaintenance()
 {
   using namespace mozilla::hal;
 
   MOZ_ASSERT(NS_IsMainThread());
@@ -16342,16 +16327,17 @@ QuotaClient::FindDatabasesForIdleMainten
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
         idbDir->GetDirectoryEntries(getter_AddRefs(idbDirEntries))));
       if (!idbDirEntries) {
         continue;
       }
 
       nsCString group;
       nsCString origin;
+      bool isApp;
       nsTArray<nsString> databasePaths;
 
       while (true) {
         // Loop over files in the "idb" directory.
         if (IdleMaintenanceMustEnd(aRunId)) {
           return;
         }
 
@@ -16386,53 +16372,53 @@ QuotaClient::FindDatabasesForIdleMainten
         }
 
         // Found a database.
         if (databasePaths.IsEmpty()) {
           MOZ_ASSERT(group.IsEmpty());
           MOZ_ASSERT(origin.IsEmpty());
 
           int64_t dummyTimeStamp;
-          bool dummyIsApp;
           if (NS_WARN_IF(NS_FAILED(
                 QuotaManager::GetDirectoryMetadata(originDir,
                                                    &dummyTimeStamp,
                                                    group,
                                                    origin,
-                                                   &dummyIsApp)))) {
+                                                   &isApp)))) {
             // Not much we can do here...
             continue;
           }
         }
 
         MOZ_ASSERT(!databasePaths.Contains(idbFilePath));
 
         databasePaths.AppendElement(idbFilePath);
       }
 
       if (!databasePaths.IsEmpty()) {
         nsCOMPtr<nsIRunnable> runnable =
           NS_NewRunnableMethodWithArgs<uint32_t, MultipleMaintenanceInfo&&>(
             this,
-            &QuotaClient::BlockQuotaManagerForIdleMaintenance,
+            &QuotaClient::GetDirectoryLockForIdleMaintenance,
             aRunId,
             MultipleMaintenanceInfo(group,
                                     origin,
                                     persistenceType,
+                                    isApp,
                                     Move(databasePaths)));
         MOZ_ASSERT(runnable);
 
         MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
       }
     }
   }
 }
 
 void
-QuotaClient::BlockQuotaManagerForIdleMaintenance(
+QuotaClient::GetDirectoryLockForIdleMaintenance(
                                      uint32_t aRunId,
                                      MultipleMaintenanceInfo&& aMaintenanceInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (IdleMaintenanceMustEnd(aRunId)) {
     return;
   }
@@ -16441,79 +16427,58 @@ QuotaClient::BlockQuotaManagerForIdleMai
 
   nsAutoCString key;
   key.AppendInt(aMaintenanceInfo.mPersistenceType);
   key.Append('*');
   key.Append(aMaintenanceInfo.mOrigin);
 
   MOZ_ASSERT(!mMaintenanceInfoHashtable->Get(key));
 
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethodWithArgs<uint32_t, const nsCString>(
-      this,
-      &QuotaClient::ScheduleIdleMaintenance,
-      aRunId,
-      key);
-  MOZ_ASSERT(runnable);
+  MultipleMaintenanceInfo* maintenanceInfo =
+    new MultipleMaintenanceInfo(Move(aMaintenanceInfo));
+
+  mMaintenanceInfoHashtable->Put(key, maintenanceInfo);
+
+  nsRefPtr<GetDirectoryLockListener> listener =
+    new GetDirectoryLockListener(this, aRunId, key);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
-  OriginOrPatternString oops =
-    OriginOrPatternString::FromOrigin(aMaintenanceInfo.mOrigin);
-
-  Nullable<PersistenceType> npt(aMaintenanceInfo.mPersistenceType);
-
-  nsresult rv =
-    quotaManager->WaitForOpenAllowed(oops, npt, EmptyCString(), runnable);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  mMaintenanceInfoHashtable->Put(
-    key,
-    new MultipleMaintenanceInfo(Move(aMaintenanceInfo)));
-}
-
-void
-QuotaClient::ScheduleIdleMaintenance(uint32_t aRunId, const nsACString& aKey)
+  quotaManager->OpenDirectory(maintenanceInfo->mPersistenceType,
+                              maintenanceInfo->mGroup,
+                              maintenanceInfo->mOrigin,
+                              maintenanceInfo->mIsApp,
+                              Client::IDB,
+                              /* aExclusive */ false,
+                              listener);
+}
+
+void
+QuotaClient::ScheduleIdleMaintenance(uint32_t aRunId,
+                                     const nsACString& aKey,
+                                     const MultipleMaintenanceInfo& aMaintenanceInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aKey.IsEmpty());
 
-  MultipleMaintenanceInfo* maintenanceInfo;
-  MOZ_ALWAYS_TRUE(mMaintenanceInfoHashtable->Get(aKey, &maintenanceInfo));
-  MOZ_ASSERT(maintenanceInfo);
-
-  if (IdleMaintenanceMustEnd(aRunId)) {
-    // We got canceled, unblock the QuotaManager.
-#ifdef DEBUG
-    maintenanceInfo->mDatabasePaths.Clear();
-#endif
-
-    UnblockQuotaManagerForIdleMaintenance(*maintenanceInfo);
-
-    mMaintenanceInfoHashtable->Remove(aKey);
-    return;
-  }
-
   MOZ_ASSERT(mMaintenanceThreadPool);
 
-  for (const nsString& databasePath : maintenanceInfo->mDatabasePaths) {
+  for (const nsString& databasePath : aMaintenanceInfo.mDatabasePaths) {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethodWithArgs<uint32_t,
                                    nsCString,
                                    SingleMaintenanceInfo&&>(
         this,
         &QuotaClient::PerformIdleMaintenanceOnDatabase,
         aRunId,
         aKey,
-        SingleMaintenanceInfo(maintenanceInfo->mGroup,
-                              maintenanceInfo->mOrigin,
-                              maintenanceInfo->mPersistenceType,
+        SingleMaintenanceInfo(aMaintenanceInfo.mGroup,
+                              aMaintenanceInfo.mOrigin,
+                              aMaintenanceInfo.mPersistenceType,
                               databasePath));
     MOZ_ASSERT(runnable);
 
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
       mMaintenanceThreadPool->Dispatch(runnable, NS_DISPATCH_NORMAL)));
   }
 }
 
@@ -16531,17 +16496,17 @@ QuotaClient::PerformIdleMaintenanceOnDat
   MOZ_ASSERT(!aMaintenanceInfo.mGroup.IsEmpty());
   MOZ_ASSERT(!aMaintenanceInfo.mOrigin.IsEmpty());
 
   PerformIdleMaintenanceOnDatabaseInternal(aRunId, aMaintenanceInfo);
 
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethodWithArgs<nsCString, nsString>(
       this,
-      &QuotaClient::MaybeUnblockQuotaManagerForIdleMaintenance,
+      &QuotaClient::MaybeReleaseDirectoryLockForIdleMaintenance,
       aKey,
       aMaintenanceInfo.mDatabasePath);
   MOZ_ASSERT(runnable);
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
 }
 
 void
@@ -17027,58 +16992,40 @@ QuotaClient::FullVacuum(mozIStorageConne
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 }
 
 void
-QuotaClient::MaybeUnblockQuotaManagerForIdleMaintenance(
+QuotaClient::MaybeReleaseDirectoryLockForIdleMaintenance(
                                                  const nsACString& aKey,
                                                  const nsAString& aDatabasePath)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aKey.IsEmpty());
   MOZ_ASSERT(!aDatabasePath.IsEmpty());
   MOZ_ASSERT(mMaintenanceInfoHashtable);
 
   MultipleMaintenanceInfo* maintenanceInfo;
   MOZ_ALWAYS_TRUE(mMaintenanceInfoHashtable->Get(aKey, &maintenanceInfo));
   MOZ_ASSERT(maintenanceInfo);
 
   MOZ_ALWAYS_TRUE(maintenanceInfo->mDatabasePaths.RemoveElement(aDatabasePath));
 
   if (maintenanceInfo->mDatabasePaths.IsEmpty()) {
-    UnblockQuotaManagerForIdleMaintenance(*maintenanceInfo);
+    // That's it!
+    maintenanceInfo->mDirectoryLock = nullptr;
 
     // This will delete |maintenanceInfo|.
     mMaintenanceInfoHashtable->Remove(aKey);
   }
 }
 
-void
-QuotaClient::UnblockQuotaManagerForIdleMaintenance(
-                                const MultipleMaintenanceInfo& aMaintenanceInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aMaintenanceInfo.mOrigin.IsEmpty());
-  MOZ_ASSERT(aMaintenanceInfo.mDatabasePaths.IsEmpty());
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  OriginOrPatternString oops =
-    OriginOrPatternString::FromOrigin(aMaintenanceInfo.mOrigin);
-
-  Nullable<PersistenceType> npt(aMaintenanceInfo.mPersistenceType);
-
-  quotaManager->AllowNextSynchronizedOp(oops, npt, EmptyCString());
-}
-
 NS_IMETHODIMP
 QuotaClient::Observe(nsISupports* aSubject,
                      const char* aTopic,
                      const char16_t* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE)) {
@@ -17090,126 +17037,16 @@ QuotaClient::Observe(nsISupports* aSubje
     StopIdleMaintenance();
     return NS_OK;
   }
 
   MOZ_ASSERT_UNREACHABLE("Should never get here!");
   return NS_OK;
 }
 
-void
-QuotaClient::
-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(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();
-
-  mState = State_WaitingForFileHandles;
-
-  CallCallback();
-}
-
-void
-QuotaClient::
-WaitForTransactionsRunnable::CallCallback()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_WaitingForFileHandles);
-
-  nsRefPtr<QuotaClient> quotaClient;
-  mQuotaClient.swap(quotaClient);
-
-  nsCOMPtr<nsIRunnable> callback;
-  mCallback.swap(callback);
-
-  callback->Run();
-
-  mState = State_Complete;
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::WaitForTransactionsRunnable,
-                             nsRunnable)
-
-NS_IMETHODIMP
-QuotaClient::
-WaitForTransactionsRunnable::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:
-      CallCallback();
-      break;
-
-    default:
-      MOZ_CRASH("Should never get here!");
-  }
-
-  return NS_OK;
-}
-
 nsresult
 QuotaClient::
 AutoProgressHandler::Register(mozIStorageConnection* aConnection)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aConnection);
 
@@ -17309,211 +17146,128 @@ ShutdownWorkThreadsRunnable::Run()
     gConnectionPool = nullptr;
   }
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
 
   return NS_OK;
 }
 
-/*******************************************************************************
- * DatabaseOfflineStorage
- ******************************************************************************/
-
-DatabaseOfflineStorage::DatabaseOfflineStorage(
-                              QuotaClient* aQuotaClient,
-                              const OptionalContentId& aOptionalContentParentId,
-                              const nsACString& aGroup,
-                              const nsACString& aOrigin,
-                              const nsACString& aId,
-                              PersistenceType aPersistenceType,
-                              nsIEventTarget* aOwningThread)
-  : mStrongQuotaClient(aQuotaClient)
-  , mWeakQuotaClient(aQuotaClient)
-  , mDatabase(nullptr)
-  , mOptionalContentParentId(aOptionalContentParentId)
-  , mOrigin(aOrigin)
-  , mId(aId)
-  , mOwningThread(aOwningThread)
-  , mClosedOnMainThread(false)
-  , mClosedOnOwningThread(false)
-  , mInvalidatedOnMainThread(false)
-  , mInvalidatedOnOwningThread(false)
-  , mRegisteredWithQuotaManager(false)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aQuotaClient);
-  MOZ_ASSERT(aOwningThread);
-
-  DebugOnly<bool> current;
-  MOZ_ASSERT(NS_SUCCEEDED(aOwningThread->IsOnCurrentThread(&current)));
-  MOZ_ASSERT(!current);
-
-  mGroup = aGroup;
-  mPersistenceType = aPersistenceType;
+// static
+PLDHashOperator
+QuotaClient::
+AbortOperationsRunnable::MatchOrigin(const nsACString& aKey,
+                                     DatabaseActorInfo* aValue,
+                                     void* aClosure)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!aKey.IsEmpty());
+  MOZ_ASSERT(aValue);
+  MOZ_ASSERT(aClosure);
+
+  auto* closure = static_cast<AbortOperationsRunnable*>(aClosure);
+
+  for (Database* database : aValue->mLiveDatabases) {
+    if (closure->mOrigin.IsVoid() || closure->mOrigin == database->Origin()) {
+      closure->mDatabases.AppendElement(database);
+    }
+  }
+
+  return PL_DHASH_NEXT;
 }
 
 // static
-void
-DatabaseOfflineStorage::UnregisterOnOwningThread(
-                       already_AddRefed<DatabaseOfflineStorage> aOfflineStorage)
-{
-  AssertIsOnBackgroundThread();
-
-  nsRefPtr<DatabaseOfflineStorage> offlineStorage = Move(aOfflineStorage);
-  MOZ_ASSERT(offlineStorage);
-  MOZ_ASSERT(offlineStorage->mClosedOnOwningThread);
-
-  offlineStorage->mDatabase = nullptr;
-
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethod(offlineStorage.get(),
-                         &DatabaseOfflineStorage::UnregisterOnMainThread);
-  MOZ_ASSERT(runnable);
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
-}
-
-void
-DatabaseOfflineStorage::CloseOnOwningThread()
-{
-  AssertIsOnBackgroundThread();
-
-  if (mClosedOnOwningThread) {
-    return;
-  }
-
-  mClosedOnOwningThread = true;
-
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethod(this, &DatabaseOfflineStorage::CloseOnMainThread);
-  MOZ_ASSERT(runnable);
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
-}
-
-void
-DatabaseOfflineStorage::CloseOnMainThread()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mClosedOnMainThread) {
-    return;
-  }
-
-  mClosedOnMainThread = true;
-}
-
-void
-DatabaseOfflineStorage::InvalidateOnMainThread()
+PLDHashOperator
+QuotaClient::
+AbortOperationsRunnable::MatchContentParentId(const nsACString& aKey,
+                                              DatabaseActorInfo* aValue,
+                                              void* aClosure)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!aKey.IsEmpty());
+  MOZ_ASSERT(aValue);
+  MOZ_ASSERT(aClosure);
+
+  auto* closure = static_cast<AbortOperationsRunnable*>(aClosure);
+
+  for (Database* database : aValue->mLiveDatabases) {
+    if (database->IsOwnedByProcess(closure->mContentParentId)) {
+      closure->mDatabases.AppendElement(database);
+    }
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::AbortOperationsRunnable, nsRunnable)
+
+NS_IMETHODIMP
+QuotaClient::
+AbortOperationsRunnable::Run()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!gLiveDatabaseHashtable) {
+    return NS_OK;
+  }
+
+  if (mOrigin.IsEmpty()) {
+    gLiveDatabaseHashtable->EnumerateRead(MatchContentParentId, this);
+  } else {
+    gLiveDatabaseHashtable->EnumerateRead(MatchOrigin, this);
+  }
+
+  for (Database* database : mDatabases) {
+    database->Invalidate();
+  }
+
+  mDatabases.Clear();
+
+  return NS_OK;
+}
+
+void
+QuotaClient::
+GetDirectoryLockListener::DirectoryLockAcquired(DirectoryLock* aLock)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mInvalidatedOnMainThread) {
-    return;
-  }
-
-  mInvalidatedOnMainThread = true;
-
-  nsCOMPtr<nsIRunnable> runnable =
-    NS_NewRunnableMethod(this,
-                         &DatabaseOfflineStorage::InvalidateOnOwningThread);
-  MOZ_ASSERT(runnable);
-
-  nsCOMPtr<nsIEventTarget> owningThread = mOwningThread;
-  MOZ_ASSERT(owningThread);
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(owningThread->Dispatch(runnable,
-                                                      NS_DISPATCH_NORMAL)));
-}
-
-void
-DatabaseOfflineStorage::InvalidateOnOwningThread()
-{
-  AssertIsOnBackgroundThread();
-
-  if (mInvalidatedOnOwningThread) {
-    return;
-  }
-
-  mInvalidatedOnOwningThread = true;
-
-  if (nsRefPtr<Database> database = mDatabase) {
-    mDatabase = nullptr;
-
-    database->Invalidate();
-  }
-}
-
-void
-DatabaseOfflineStorage::UnregisterOnMainThread()
+  MultipleMaintenanceInfo* maintenanceInfo;
+  MOZ_ALWAYS_TRUE(
+    mQuotaClient->mMaintenanceInfoHashtable->Get(mKey, &maintenanceInfo));
+  MOZ_ASSERT(maintenanceInfo);
+  MOZ_ASSERT(!maintenanceInfo->mDirectoryLock);
+
+  if (mQuotaClient->IdleMaintenanceMustEnd(mRunId)) {
+#ifdef DEBUG
+    maintenanceInfo->mDatabasePaths.Clear();
+#endif
+
+    mQuotaClient->mMaintenanceInfoHashtable->Remove(mKey);
+    return;
+  }
+
+  maintenanceInfo->mDirectoryLock = aLock;
+
+  mQuotaClient->ScheduleIdleMaintenance(mRunId, mKey, *maintenanceInfo);
+}
+
+void
+QuotaClient::
+GetDirectoryLockListener::DirectoryLockFailed()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mOwningThread);
-  MOZ_ASSERT(mRegisteredWithQuotaManager);
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  quotaManager->UnregisterStorage(this);
-  mRegisteredWithQuotaManager = false;
-
-  mStrongQuotaClient = nullptr;
-
-  mOwningThread = nullptr;
-}
-
-NS_IMPL_ISUPPORTS(DatabaseOfflineStorage, nsIOfflineStorage)
-
-NS_IMETHODIMP_(const nsACString&)
-DatabaseOfflineStorage::Id()
-{
-  return mId;
-}
-
-NS_IMETHODIMP_(Client*)
-DatabaseOfflineStorage::GetClient()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return mWeakQuotaClient;
-}
-
-NS_IMETHODIMP_(bool)
-DatabaseOfflineStorage::IsOwnedByProcess(ContentParent* aOwner)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aOwner);
-
-  return mOptionalContentParentId.type() ==
-           OptionalContentId::TContentParentId &&
-         mOptionalContentParentId.get_ContentParentId() == aOwner->ChildID();
-}
-
-NS_IMETHODIMP_(const nsACString&)
-DatabaseOfflineStorage::Origin()
-{
-  return mOrigin;
-}
-
-NS_IMETHODIMP_(nsresult)
-DatabaseOfflineStorage::Close()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  InvalidateOnMainThread();
-  return NS_OK;
-}
-
-NS_IMETHODIMP_(void)
-DatabaseOfflineStorage::Invalidate()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  InvalidateOnMainThread();
+
+  DebugOnly<MultipleMaintenanceInfo*> maintenanceInfo;
+  MOZ_ASSERT(
+    mQuotaClient->mMaintenanceInfoHashtable->Get(mKey, &maintenanceInfo));
+  MOZ_ASSERT(maintenanceInfo);
+  MOZ_ASSERT(!maintenanceInfo->mDirectoryLock);
+
+  mQuotaClient->mMaintenanceInfoHashtable->Remove(mKey);
 }
 
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
 NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
@@ -18399,17 +18153,17 @@ FactoryOp::FactoryOp(Factory* aFactory,
                           aFactory->GetLoggingInfo()->NextRequestSN())
   , mFactory(aFactory)
   , mContentParent(Move(aContentParent))
   , mCommonParams(aCommonParams)
   , mState(State_Initial)
   , mIsApp(false)
   , mEnforcingQuota(true)
   , mDeleting(aDeleting)
-  , mBlockedQuotaManager(false)
+  , mBlockedDatabaseOpen(false)
   , mChromeWriteAccessAllowed(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
 }
 
 nsresult
@@ -18456,24 +18210,34 @@ FactoryOp::Open()
     }
 
     if (NS_WARN_IF(!QuotaManager::GetOrCreate())) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
   }
 
+  QuotaClient* quotaClient = QuotaClient::GetInstance();
+  if (NS_WARN_IF(!quotaClient)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  quotaClient->NoteBackgroundThread(mOwningThread);
+
   const DatabaseMetadata& metadata = mCommonParams.metadata();
 
   QuotaManager::GetStorageId(metadata.persistenceType(),
                              mOrigin,
                              Client::IDB,
-                             metadata.name(),
                              mDatabaseId);
 
+  mDatabaseId.Append('*');
+  mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name()));
+
   if (permission == PermissionRequestBase::kPermissionPrompt) {
     mState = State_PermissionChallenge;
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
                                                          NS_DISPATCH_NORMAL)));
     return NS_OK;
   }
 
   MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
@@ -18541,20 +18305,60 @@ FactoryOp::RetryCheckPermission()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult
+FactoryOp::DirectoryOpen()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State_DirectoryWorkOpen);
+  MOZ_ASSERT(mDirectoryLock);
+
+  // gFactoryOps could be null here if the child process crashed or something
+  // and that cleaned up the last Factory actor.
+  if (!gFactoryOps) {
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  // See if this FactoryOp needs to wait.
+  bool delayed = false;
+  for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
+    nsRefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
+    if (MustWaitFor(*existingOp)) {
+      // Only one op can be delayed.
+      MOZ_ASSERT(!existingOp->mDelayedOp);
+      existingOp->mDelayedOp = this;
+      delayed = true;
+      break;
+    }
+  }
+
+  // Adding this to the factory ops list will block any additional ops from
+  // proceeding until this one is done.
+  gFactoryOps->AppendElement(this);
+
+  mBlockedDatabaseOpen = true;
+
+  mState = State_DatabaseOpenPending;
+  if (!delayed) {
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  }
+
+  return NS_OK;
+}
+
+nsresult
 FactoryOp::SendToIOThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_OpenPending);
+  MOZ_ASSERT(mState == State_DatabaseOpenPending);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnMainThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
@@ -18597,43 +18401,24 @@ FactoryOp::FinishSendResults()
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_SendingResults);
   MOZ_ASSERT(mFactory);
 
   // Make sure to release the factory on this thread.
   nsRefPtr<Factory> factory;
   mFactory.swap(factory);
 
-  if (mBlockedQuotaManager) {
-    // Must set mState before dispatching otherwise we will race with the main
-    // thread.
-    mState = State_UnblockingQuotaManager;
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
-  } else {
-    mState = State_Completed;
-  }
-}
-
-void
-FactoryOp::UnblockQuotaManager()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_UnblockingQuotaManager);
-
-  Nullable<PersistenceType> persistenceType(
-    const_cast<PersistenceType&>(mCommonParams.metadata().persistenceType()));
-
-  if (QuotaManager* quotaManager = QuotaManager::Get()) {
-    quotaManager->
-      AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
-                              persistenceType,
-                              mDatabaseId);
-  } else {
-    NS_WARNING("QuotaManager went away before we could unblock it!");
+  if (mBlockedDatabaseOpen) {
+    if (mDelayedOp) {
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mDelayedOp)));
+      mDelayedOp = nullptr;
+    }
+
+    MOZ_ASSERT(gFactoryOps);
+    gFactoryOps->RemoveElement(this);
   }
 
   mState = State_Completed;
 }
 
 nsresult
 FactoryOp::CheckPermission(ContentParent* aContentParent,
                            PermissionRequestBase::PermissionValue* aPermission)
@@ -18934,42 +18719,50 @@ FactoryOp::CheckAtLeastOneAppHasPermissi
 
 nsresult
 FactoryOp::FinishOpen()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == State_Initial || mState == State_PermissionRetry);
   MOZ_ASSERT(!mOrigin.IsEmpty());
   MOZ_ASSERT(!mDatabaseId.IsEmpty());
-  MOZ_ASSERT(!mBlockedQuotaManager);
+  MOZ_ASSERT(!mDirectoryLock);
   MOZ_ASSERT(!mContentParent);
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnMainThread());
 
   QuotaManager* quotaManager = QuotaManager::GetOrCreate();
   if (NS_WARN_IF(!quotaManager)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
-  PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
-
-  nsresult rv =
-    quotaManager->
-      WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mOrigin),
-                         Nullable<PersistenceType>(persistenceType),
-                         mDatabaseId,
-                         this);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  mBlockedQuotaManager = true;
-
-  mState = State_OpenPending;
-  return NS_OK;
+  mState = State_DirectoryOpenPending;
+
+  quotaManager->OpenDirectory(mCommonParams.metadata().persistenceType(),
+                              mGroup,
+                              mOrigin,
+                              mIsApp,
+                              Client::IDB,
+                              /* aExclusive */ false,
+                              this);
+
+  return NS_OK;
+}
+
+bool
+FactoryOp::MustWaitFor(const FactoryOp& aExistingOp)
+{
+  AssertIsOnOwningThread();
+
+  // Things for the same persistence type, the same origin and the same
+  // database must wait.
+  return aExistingOp.mCommonParams.metadata().persistenceType() ==
+           mCommonParams.metadata().persistenceType() &&
+         aExistingOp.mOrigin == mOrigin &&
+         aExistingOp.mDatabaseId == mDatabaseId;
 }
 
 void
 FactoryOp::NoteDatabaseBlocked(Database* aDatabase)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose);
   MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
@@ -18993,58 +18786,60 @@ FactoryOp::NoteDatabaseBlocked(Database*
     }
   }
 
   if (sendBlockedEvent) {
     SendBlockedNotification();
   }
 }
 
+NS_IMPL_ISUPPORTS_INHERITED0(FactoryOp, DatabaseOperationBase)
+
 NS_IMETHODIMP
 FactoryOp::Run()
 {
   nsresult rv;
 
   switch (mState) {
     case State_Initial:
       rv = Open();
       break;
 
-    case State_OpenPending:
-      rv = QuotaManagerOpen();
-      break;
-
     case State_PermissionChallenge:
       rv = ChallengePermission();
       break;
 
     case State_PermissionRetry:
       rv = RetryCheckPermission();
       break;
 
+    case State_DirectoryWorkOpen:
+      rv = DirectoryOpen();
+      break;
+
+    case State_DatabaseOpenPending:
+      rv = DatabaseOpen();
+      break;
+
     case State_DatabaseWorkOpen:
       rv = DoDatabaseWork();
       break;
 
     case State_BeginVersionChange:
       rv = BeginVersionChange();
       break;
 
     case State_WaitingForTransactionsToComplete:
       rv = DispatchToWorkThread();
       break;
 
     case State_SendingResults:
       SendResults();
       return NS_OK;
 
-    case State_UnblockingQuotaManager:
-      UnblockQuotaManager();
-      return NS_OK;
-
     default:
       MOZ_CRASH("Bad state!");
   }
 
   if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_SendingResults) {
     if (NS_SUCCEEDED(mResultCode)) {
       mResultCode = rv;
     }
@@ -19056,16 +18851,47 @@ FactoryOp::Run()
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
                                                          NS_DISPATCH_NORMAL)));
   }
 
   return NS_OK;
 }
 
 void
+FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  mState = State_DirectoryWorkOpen;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
+}
+
+void
+FactoryOp::DirectoryLockFailed()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  if (NS_SUCCEEDED(mResultCode)) {
+    IDB_REPORT_INTERNAL_ERR();
+    mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  mState = State_SendingResults;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
+}
+
+void
 FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   NoteActorDestroyed();
 }
 
 bool
@@ -19112,51 +18938,20 @@ OpenDatabaseOp::ActorDestroy(ActorDestro
   FactoryOp::ActorDestroy(aWhy);
 
   if (mVersionChangeOp) {
     mVersionChangeOp->NoteActorDestroyed();
   }
 }
 
 nsresult
-OpenDatabaseOp::QuotaManagerOpen()
+OpenDatabaseOp::DatabaseOpen()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_OpenPending);
-  MOZ_ASSERT(!mOfflineStorage);
-
-  QuotaClient* quotaClient = QuotaClient::GetInstance();
-  if (NS_WARN_IF(!quotaClient) ||
-      NS_WARN_IF(quotaClient->IsShuttingDown())) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  nsRefPtr<DatabaseOfflineStorage> offlineStorage =
-    new DatabaseOfflineStorage(quotaClient,
-                               mOptionalContentParentId,
-                               mGroup,
-                               mOrigin,
-                               mDatabaseId,
-                               mCommonParams.metadata().persistenceType(),
-                               mOwningThread);
-
-  if (NS_WARN_IF(!quotaManager->RegisterStorage(offlineStorage))) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  offlineStorage->NoteRegisteredWithQuotaManager();
-
-  quotaClient->NoteBackgroundThread(mOwningThread);
-
-  mOfflineStorage.swap(offlineStorage);
+  MOZ_ASSERT(mState == State_DatabaseOpenPending);
 
   nsresult rv = SendToIOThread();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
@@ -19303,16 +19098,17 @@ OpenDatabaseOp::DoDatabaseWork()
   MOZ_ASSERT(mgr);
 
   nsRefPtr<FileManager> fileManager =
     mgr->GetFileManager(persistenceType, mOrigin, databaseName);
   if (!fileManager) {
     fileManager = new FileManager(persistenceType,
                                   mGroup,
                                   mOrigin,
+                                  mIsApp,
                                   databaseName,
                                   mEnforcingQuota);
 
     rv = fileManager->Init(fmDirectory, connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
@@ -19905,46 +19701,47 @@ OpenDatabaseOp::SendResults()
       response = ClampResultCode(mResultCode);
     }
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
   if (mDatabase) {
-    MOZ_ASSERT(!mOfflineStorage);
+    MOZ_ASSERT(!mDirectoryLock);
 
     if (NS_FAILED(mResultCode)) {
       mDatabase->Invalidate();
     }
 
     // Make sure to release the database on this thread.
     mDatabase = nullptr;
-  } else if (mOfflineStorage) {
-    mOfflineStorage->CloseOnOwningThread();
-
+  } else if (mDirectoryLock) {
     nsCOMPtr<nsIRunnable> callback =
       NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
 
     nsRefPtr<WaitForTransactionsHelper> helper =
       new WaitForTransactionsHelper(mDatabaseId, callback);
     helper->WaitForTransactions();
   }
 
   FinishSendResults();
 }
 
 void
 OpenDatabaseOp::ConnectionClosedCallback()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(mResultCode));
-  MOZ_ASSERT(mOfflineStorage);
-
-  DatabaseOfflineStorage::UnregisterOnOwningThread(mOfflineStorage.forget());
+  MOZ_ASSERT(mDirectoryLock);
+
+  nsRefPtr<UnlockDirectoryRunnable> runnable =
+    new UnlockDirectoryRunnable(mDirectoryLock.forget());
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
 }
 
 void
 OpenDatabaseOp::EnsureDatabaseActor()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_BeginVersionChange ||
              mState == State_DatabaseWorkVersionChange ||
@@ -19968,22 +19765,23 @@ OpenDatabaseOp::EnsureDatabaseActor()
     AssertMetadataConsistency(info->mMetadata);
     mMetadata = info->mMetadata;
   }
 
   auto factory = static_cast<Factory*>(Manager());
 
   mDatabase = new Database(factory,
                            mCommonParams.principalInfo(),
+                           mOptionalContentParentId,
                            mGroup,
                            mOrigin,
                            mTelemetryId,
                            mMetadata,
                            mFileManager,
-                           mOfflineStorage.forget(),
+                           mDirectoryLock.forget(),
                            mChromeWriteAccessAllowed);
 
   if (info) {
     info->mLiveDatabases.AppendElement(mDatabase);
   } else {
     info = new DatabaseActorInfo(mMetadata, mDatabase);
     gLiveDatabaseHashtable->Put(mDatabaseId, info);
   }
@@ -20417,20 +20215,20 @@ DeleteDatabaseOp::LoadPreviousVersion(ns
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   mPreviousVersion = uint64_t(version);
 }
 
 nsresult
-DeleteDatabaseOp::QuotaManagerOpen()
+DeleteDatabaseOp::DatabaseOpen()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_OpenPending);
+  MOZ_ASSERT(mState == State_DatabaseOpenPending);
 
   // Swap this to the stack now to ensure that we release it on this thread.
   nsRefPtr<ContentParent> contentParent;
   mContentParent.swap(contentParent);
 
   nsresult rv = SendToIOThread();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -20644,16 +20442,23 @@ DeleteDatabaseOp::SendResults()
     } else {
       response = ClampResultCode(mResultCode);
     }
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
+  if (mDirectoryLock) {
+    nsRefPtr<UnlockDirectoryRunnable> runnable =
+      new UnlockDirectoryRunnable(mDirectoryLock.forget());
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+  }
+
   FinishSendResults();
 }
 
 nsresult
 DeleteDatabaseOp::
 VersionChangeOp::RunOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/indexedDB/FileManager.h
+++ b/dom/indexedDB/FileManager.h
@@ -37,16 +37,17 @@ class FileManager final
   nsString mDirectoryPath;
   nsString mJournalDirectoryPath;
 
   int64_t mLastFileId;
 
   // Protected by IndexedDatabaseManager::FileMutex()
   nsDataHashtable<nsUint64HashKey, FileInfo*> mFileInfos;
 
+  const bool mIsApp;
   const bool mEnforcingQuota;
   bool mInvalidated;
 
 public:
   static already_AddRefed<nsIFile>
   GetFileForId(nsIFile* aDirectory, int64_t aId);
 
   static nsresult
@@ -58,16 +59,17 @@ public:
                 uint32_t aTelemetryId);
 
   static nsresult
   GetUsage(nsIFile* aDirectory, uint64_t* aUsage);
 
   FileManager(PersistenceType aPersistenceType,
               const nsACString& aGroup,
               const nsACString& aOrigin,
+              bool aIsApp,
               const nsAString& aDatabaseName,
               bool aEnforcingQuota);
 
   PersistenceType
   Type() const
   {
     return mPersistenceType;
   }
@@ -79,16 +81,22 @@ public:
   }
 
   const nsACString&
   Origin() const
   {
     return mOrigin;
   }
 
+  bool
+  IsApp() const
+  {
+    return mIsApp;
+  }
+
   const nsAString&
   DatabaseName() const
   {
     return mDatabaseName;
   }
 
   bool
   EnforcingQuota() const
--- a/dom/indexedDB/IDBMutableFile.cpp
+++ b/dom/indexedDB/IDBMutableFile.cpp
@@ -163,25 +163,29 @@ IDBMutableFile::Create(IDBDatabase* aDat
                                                               &origin,
                                                               nullptr)))) {
     return nullptr;
   }
 
   const DatabaseSpec* spec = aDatabase->Spec();
   MOZ_ASSERT(spec);
 
-  PersistenceType persistenceType = spec->metadata().persistenceType();
+  const DatabaseMetadata& metadata = spec->metadata();
+
+  PersistenceType persistenceType = metadata.persistenceType();
 
   nsCString storageId;
   QuotaManager::GetStorageId(persistenceType,
                              origin,
                              Client::IDB,
-                             aDatabase->Name(),
                              storageId);
 
+  storageId.Append('*');
+  storageId.Append(NS_ConvertUTF16toUTF8(metadata.name()));
+
   nsCOMPtr<nsIFile> file = GetFileFor(fileInfo);
   if (NS_WARN_IF(!file)) {
     return nullptr;
   }
 
   nsRefPtr<IDBMutableFile> newFile =
     new IDBMutableFile(aDatabase,
                        aName,
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -20,23 +20,17 @@
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
-#include "mozilla/dom/PBlobChild.h"
-#include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
-#include "mozilla/dom/quota/Utilities.h"
-#include "mozilla/dom/TabContext.h"
-#include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/PBackgroundChild.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBKeyRange.h"
@@ -116,16 +110,18 @@ private:
   nsTArray<nsRefPtr<FileManager> > mTemporaryStorageFileManagers;
   nsTArray<nsRefPtr<FileManager> > mDefaultStorageFileManagers;
 };
 
 namespace {
 
 NS_DEFINE_IID(kIDBRequestIID, PRIVATE_IDBREQUEST_IID);
 
+const uint32_t kDeleteTimeoutMs = 1000;
+
 #define IDB_PREF_BRANCH_ROOT "dom.indexedDB."
 
 const char kTestingPref[] = IDB_PREF_BRANCH_ROOT "testing";
 const char kPrefExperimental[] = IDB_PREF_BRANCH_ROOT "experimental";
 
 #define IDB_PREF_LOGGING_BRANCH_ROOT IDB_PREF_BRANCH_ROOT "logging."
 
 const char kPrefLoggingEnabled[] = IDB_PREF_LOGGING_BRANCH_ROOT "enabled";
@@ -141,29 +137,83 @@ const char kPrefLoggingProfiler[] =
 
 StaticRefPtr<IndexedDatabaseManager> gDBManager;
 
 Atomic<bool> gInitialized(false);
 Atomic<bool> gClosed(false);
 Atomic<bool> gTestingMode(false);
 Atomic<bool> gExperimentalFeaturesEnabled(false);
 
-class AsyncDeleteFileRunnable final : public nsIRunnable
+class DeleteFilesRunnable final
+  : public nsIRunnable
+  , public OpenDirectoryListener
 {
+  typedef mozilla::dom::quota::QuotaManager::DirectoryLock DirectoryLock;
+
+  enum State
+  {
+    // Just created on the main thread. Next step is State_DirectoryOpenPending.
+    State_Initial,
+
+    // Waiting for directory open allowed on the main thread. The next step is
+    // State_DatabaseWorkOpen.
+    State_DirectoryOpenPending,
+
+    // Waiting to do/doing work on the QuotaManager IO thread. The next step is
+    // State_UnblockingOpen.
+    State_DatabaseWorkOpen,
+
+    // Notifying the QuotaManager that it can proceed to the next operation on
+    // the main thread. Next step is State_Completed.
+    State_UnblockingOpen,
+
+    // All done.
+    State_Completed
+  };
+
+  nsRefPtr<FileManager> mFileManager;
+  nsTArray<int64_t> mFileIds;
+
+  nsRefPtr<DirectoryLock> mDirectoryLock;
+
+  nsCOMPtr<nsIFile> mDirectory;
+  nsCOMPtr<nsIFile> mJournalDirectory;
+
+  State mState;
+
 public:
+  DeleteFilesRunnable(FileManager* aFileManager,
+                      nsTArray<int64_t>& aFileIds);
+
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
-  AsyncDeleteFileRunnable(FileManager* aFileManager, int64_t aFileId);
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
 
 private:
-  ~AsyncDeleteFileRunnable() {}
+  ~DeleteFilesRunnable() {}
+
+  nsresult
+  Open();
+
+  nsresult
+  DeleteFile(int64_t aFileId);
 
-  nsRefPtr<FileManager> mFileManager;
-  int64_t mFileId;
+  nsresult
+  DoDatabaseWork();
+
+  void
+  Finish();
+
+  void
+  UnblockOpen();
 };
 
 class GetFileReferencesHelper final : public nsIRunnable
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
@@ -310,16 +360,19 @@ IndexedDatabaseManager::Init()
   // directly.
   if (sIsMainProcess) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     NS_ENSURE_STATE(obs);
 
     nsresult rv =
       obs->AddObserver(this, DISKSPACEWATCHER_OBSERVER_TOPIC, false);
     NS_ENSURE_SUCCESS(rv, rv);
+
+    mDeleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    NS_ENSURE_STATE(mDeleteTimer);
   }
 
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
                                        kTestingPref,
                                        &gTestingMode);
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
                                        kPrefExperimental,
                                        &gExperimentalFeaturesEnabled);
@@ -348,16 +401,24 @@ void
 IndexedDatabaseManager::Destroy()
 {
   // Setting the closed flag prevents the service from being recreated.
   // Don't set it though if there's no real instance created.
   if (gInitialized && gClosed.exchange(true)) {
     NS_ERROR("Shutdown more than once?!");
   }
 
+  if (sIsMainProcess && mDeleteTimer) {
+    if (NS_FAILED(mDeleteTimer->Cancel())) {
+      NS_WARNING("Failed to cancel timer!");
+    }
+
+    mDeleteTimer = nullptr;
+  }
+
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
                                   kTestingPref,
                                   &gTestingMode);
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
                                   kPrefExperimental,
                                   &gExperimentalFeaturesEnabled);
 
   Preferences::UnregisterCallback(LoggingModePrefChangedCallback,
@@ -512,35 +573,16 @@ IndexedDatabaseManager::CommonPostHandle
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(consoleService->LogMessage(scriptError)));
 
   return NS_OK;
 }
 
 // static
 bool
-IndexedDatabaseManager::TabContextMayAccessOrigin(const TabContext& aContext,
-                                                  const nsACString& aOrigin)
-{
-  NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
-
-  // If aContext is for a browser element, it's allowed only to access other
-  // browser elements.  But if aContext is not for a browser element, it may
-  // access both browser and non-browser elements.
-  nsAutoCString pattern;
-  QuotaManager::GetOriginPatternStringMaybeIgnoreBrowser(
-                                                aContext.OwnOrContainingAppId(),
-                                                aContext.IsBrowserElement(),
-                                                pattern);
-
-  return PatternMatchesOrigin(pattern, aOrigin);
-}
-
-// static
-bool
 IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx,
                                         JS::Handle<JSObject*> aGlobal)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(nsContentUtils::IsCallerChrome(), "Only for chrome!");
   MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
              "Passed object is not a global object!");
 
@@ -729,19 +771,18 @@ IndexedDatabaseManager::InvalidateAllFil
     }
   };
 
   mFileManagerInfos.EnumerateRead(Helper::Enumerate, nullptr);
   mFileManagerInfos.Clear();
 }
 
 void
-IndexedDatabaseManager::InvalidateFileManagers(
-                                  PersistenceType aPersistenceType,
-                                  const nsACString& aOrigin)
+IndexedDatabaseManager::InvalidateFileManagers(PersistenceType aPersistenceType,
+                                               const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
 
   FileManagerInfo* info;
   if (!mFileManagerInfos.Get(aOrigin, &info)) {
     return;
   }
@@ -771,52 +812,57 @@ IndexedDatabaseManager::InvalidateFileMa
     mFileManagerInfos.Remove(aOrigin);
   }
 }
 
 nsresult
 IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
                                         int64_t aFileId)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  NS_ENSURE_ARG_POINTER(aFileManager);
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "Shouldn't be null!");
+  MOZ_ASSERT(IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aFileManager);
+  MOZ_ASSERT(aFileId > 0);
+  MOZ_ASSERT(mDeleteTimer);
 
-  // See if we're currently clearing the storages for this origin. If so then
-  // we pretend that we've already deleted everything.
-  if (quotaManager->IsClearOriginPending(
-                             aFileManager->Origin(),
-                             Nullable<PersistenceType>(aFileManager->Type()))) {
-    return NS_OK;
+  nsresult rv = mDeleteTimer->Cancel();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
-  nsRefPtr<AsyncDeleteFileRunnable> runnable =
-    new AsyncDeleteFileRunnable(aFileManager, aFileId);
+  rv = mDeleteTimer->InitWithCallback(this, kDeleteTimeoutMs,
+                                      nsITimer::TYPE_ONE_SHOT);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  nsresult rv =
-    quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsTArray<int64_t>* array;
+  if (!mPendingDeleteInfos.Get(aFileManager, &array)) {
+    array = new nsTArray<int64_t>();
+    mPendingDeleteInfos.Put(aFileManager, array);
+  }
+
+  array->AppendElement(aFileId);
 
   return NS_OK;
 }
 
 nsresult
 IndexedDatabaseManager::BlockAndGetFileReferences(
                                                PersistenceType aPersistenceType,
                                                const nsACString& aOrigin,
                                                const nsAString& aDatabaseName,
                                                int64_t aFileId,
                                                int32_t* aRefCnt,
                                                int32_t* aDBRefCnt,
                                                int32_t* aSliceRefCnt,
                                                bool* aResult)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (NS_WARN_IF(!InTestingMode())) {
     return NS_ERROR_UNEXPECTED;
   }
 
   if (IsMainProcess()) {
     nsRefPtr<GetFileReferencesHelper> helper =
       new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName,
                                   aFileId);
@@ -843,16 +889,49 @@ IndexedDatabaseManager::BlockAndGetFileR
                                              aResult)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+IndexedDatabaseManager::FlushPendingFileDeletions()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!InTestingMode())) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (IsMainProcess()) {
+    nsresult rv = mDeleteTimer->Cancel();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = Notify(mDeleteTimer);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    ContentChild* contentChild = ContentChild::GetSingleton();
+    if (NS_WARN_IF(!contentChild)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (!contentChild->SendFlushPendingFileDeletions()) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  return NS_OK;
+}
+
 // static
 void
 IndexedDatabaseManager::LoggingModePrefChangedCallback(
                                                     const char* /* aPrefName */,
                                                     void* /* aClosure */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -883,17 +962,17 @@ IndexedDatabaseManager::LoggingModePrefC
                    Logging_ConciseProfilerMarks;
   } else {
     sLoggingMode = logDetails ? Logging_Detailed : Logging_Concise;
   }
 }
 
 NS_IMPL_ADDREF(IndexedDatabaseManager)
 NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy())
-NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsIObserver)
+NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsIObserver, nsITimerCallback)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::Observe(nsISupports* aSubject, const char* aTopic,
                                 const char16_t* aData)
 {
   NS_ASSERTION(IsMainProcess(), "Wrong process!");
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
@@ -912,17 +991,51 @@ IndexedDatabaseManager::Observe(nsISuppo
       NS_NOTREACHED("Unknown data value!");
     }
 
     return NS_OK;
   }
 
    NS_NOTREACHED("Unknown topic!");
    return NS_ERROR_UNEXPECTED;
- }
+}
+
+NS_IMETHODIMP
+IndexedDatabaseManager::Notify(nsITimer* aTimer)
+{
+  MOZ_ASSERT(IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static PLDHashOperator
+    CreateAndDispatchRunnables(FileManager* aFileManager,
+                               nsTArray<int64_t>* aValue,
+                               void* aClosure)
+    {
+      MOZ_ASSERT(!aValue->IsEmpty());
+
+      nsRefPtr<DeleteFilesRunnable> runnable =
+        new DeleteFilesRunnable(aFileManager, *aValue);
+
+      MOZ_ASSERT(aValue->IsEmpty());
+
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  mPendingDeleteInfos.EnumerateRead(Helper::CreateAndDispatchRunnables,
+                                    nullptr);
+  mPendingDeleteInfos.Clear();
+
+  return NS_OK;
+}
 
 already_AddRefed<FileManager>
 FileManagerInfo::GetFileManager(PersistenceType aPersistenceType,
                                 const nsAString& aName) const
 {
   AssertIsOnIOThread();
 
   const nsTArray<nsRefPtr<FileManager> >& managers =
@@ -1018,34 +1131,119 @@ FileManagerInfo::GetArray(PersistenceTyp
       return mDefaultStorageFileManagers;
 
     case PERSISTENCE_TYPE_INVALID:
     default:
       MOZ_CRASH("Bad storage type value!");
   }
 }
 
-AsyncDeleteFileRunnable::AsyncDeleteFileRunnable(FileManager* aFileManager,
-                                                 int64_t aFileId)
-: mFileManager(aFileManager), mFileId(aFileId)
+DeleteFilesRunnable::DeleteFilesRunnable(FileManager* aFileManager,
+                                         nsTArray<int64_t>& aFileIds)
+  : mFileManager(aFileManager)
+  , mState(State_Initial)
+{
+  mFileIds.SwapElements(aFileIds);
+}
+
+NS_IMPL_ISUPPORTS(DeleteFilesRunnable, nsIRunnable)
+
+NS_IMETHODIMP
+DeleteFilesRunnable::Run()
 {
+  nsresult rv;
+
+  switch (mState) {
+    case State_Initial:
+      rv = Open();
+      break;
+
+    case State_DatabaseWorkOpen:
+      rv = DoDatabaseWork();
+      break;
+
+    case State_UnblockingOpen:
+      UnblockOpen();
+      return NS_OK;
+
+    case State_DirectoryOpenPending:
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
+    Finish();
+  }
+
+  return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS(AsyncDeleteFileRunnable,
-                  nsIRunnable)
+void
+DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
 
-NS_IMETHODIMP
-AsyncDeleteFileRunnable::Run()
+  // Must set this before dispatching otherwise we will race with the IO thread
+  mState = State_DatabaseWorkOpen;
+
+  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Finish();
+    return;
+  }
+}
+
+void
+DeleteFilesRunnable::DirectoryLockFailed()
 {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  Finish();
+}
+
+nsresult
+DeleteFilesRunnable::Open()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_Initial);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (NS_WARN_IF(!quotaManager)) {
+    return NS_ERROR_FAILURE;
+  }
 
-  nsCOMPtr<nsIFile> directory = mFileManager->GetDirectory();
-  NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
+  mState = State_DirectoryOpenPending;
+
+  quotaManager->OpenDirectory(mFileManager->Type(),
+                              mFileManager->Group(),
+                              mFileManager->Origin(),
+                              mFileManager->IsApp(),
+                              Client::IDB,
+                              /* aExclusive */ false,
+                              this);
 
-  nsCOMPtr<nsIFile> file = mFileManager->GetFileForId(directory, mFileId);
+  return NS_OK;
+}
+
+nsresult
+DeleteFilesRunnable::DeleteFile(int64_t aFileId)
+{
+  MOZ_ASSERT(mDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  nsCOMPtr<nsIFile> file = mFileManager->GetFileForId(mDirectory, aFileId);
   NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
 
   nsresult rv;
   int64_t fileSize;
 
   if (mFileManager->EnforcingQuota()) {
     rv = file->GetFileSize(&fileSize);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
@@ -1058,29 +1256,78 @@ AsyncDeleteFileRunnable::Run()
     QuotaManager* quotaManager = QuotaManager::Get();
     NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
     quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
                                          mFileManager->Group(),
                                          mFileManager->Origin(), fileSize);
   }
 
-  directory = mFileManager->GetJournalDirectory();
-  NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
-
-  file = mFileManager->GetFileForId(directory, mFileId);
+  file = mFileManager->GetFileForId(mJournalDirectory, aFileId);
   NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
 
   rv = file->Remove(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
+DeleteFilesRunnable::DoDatabaseWork()
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(mState == State_DatabaseWorkOpen);
+
+  if (!mFileManager->Invalidated()) {
+    mDirectory = mFileManager->GetDirectory();
+    if (NS_WARN_IF(!mDirectory)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mJournalDirectory = mFileManager->GetJournalDirectory();
+    if (NS_WARN_IF(!mJournalDirectory)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    for (int64_t fileId : mFileIds) {
+      if (NS_FAILED(DeleteFile(fileId))) {
+        NS_WARNING("Failed to delete file!");
+      }
+    }
+  }
+
+  Finish();
+
+  return NS_OK;
+}
+
+void
+DeleteFilesRunnable::Finish()
+{
+  // Must set mState before dispatching otherwise we will race with the main
+  // thread.
+  mState = State_UnblockingOpen;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+DeleteFilesRunnable::UnblockOpen()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_UnblockingOpen);
+
+  if (mDirectoryLock) {
+    mDirectoryLock = nullptr;
+  }
+
+  mState = State_Completed;
+}
+
+nsresult
 GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
                                                          int32_t* aDBRefCnt,
                                                          int32_t* aSliceRefCnt,
                                                          bool* aResult)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   QuotaManager* quotaManager = QuotaManager::Get();
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -9,50 +9,53 @@
 
 #include "nsIObserver.h"
 
 #include "js/TypeDecls.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/Mutex.h"
 #include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
 #include "nsHashKeys.h"
+#include "nsITimer.h"
 
 struct PRLogModuleInfo;
 
 namespace mozilla {
 
 class EventChainPostVisitor;
 
 namespace dom {
 
-class TabContext;
-
 namespace indexedDB {
 
 class FileManager;
 class FileManagerInfo;
 class IDBFactory;
 
-class IndexedDatabaseManager final : public nsIObserver
+class IndexedDatabaseManager final
+  : public nsIObserver
+  , public nsITimerCallback
 {
   typedef mozilla::dom::quota::PersistenceType PersistenceType;
 
 public:
   enum LoggingMode
   {
     Logging_Disabled = 0,
     Logging_Concise,
     Logging_Detailed,
     Logging_ConciseProfilerMarks,
     Logging_DetailedProfilerMarks
   };
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager*
   GetOrCreate();
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager*
   Get();
@@ -147,52 +150,56 @@ public:
                             const nsACString& aOrigin,
                             const nsAString& aDatabaseName,
                             int64_t aFileId,
                             int32_t* aRefCnt,
                             int32_t* aDBRefCnt,
                             int32_t* aSliceRefCnt,
                             bool* aResult);
 
+  nsresult
+  FlushPendingFileDeletions();
+
   static mozilla::Mutex&
   FileMutex()
   {
     IndexedDatabaseManager* mgr = Get();
     NS_ASSERTION(mgr, "Must have a manager here!");
 
     return mgr->mFileMutex;
   }
 
   static nsresult
   CommonPostHandleEvent(EventChainPostVisitor& aVisitor, IDBFactory* aFactory);
 
   static bool
-  TabContextMayAccessOrigin(const mozilla::dom::TabContext& aContext,
-                            const nsACString& aOrigin);
-
-  static bool
   DefineIndexedDB(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
 
 private:
   IndexedDatabaseManager();
   ~IndexedDatabaseManager();
 
   nsresult
   Init();
 
   void
   Destroy();
 
   static void
   LoggingModePrefChangedCallback(const char* aPrefName, void* aClosure);
 
+  nsCOMPtr<nsITimer> mDeleteTimer;
+
   // Maintains a list of all file managers per origin. This list isn't
   // protected by any mutex but it is only ever touched on the IO thread.
   nsClassHashtable<nsCStringHashKey, FileManagerInfo> mFileManagerInfos;
 
+  nsClassHashtable<nsRefPtrHashKey<FileManager>,
+                   nsTArray<int64_t>> mPendingDeleteInfos;
+
   // Lock protecting FileManager.mFileInfos and BlobImplBase.mFileInfos
   // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt
   // and FileInfo.mSliceRefCnt
   mozilla::Mutex mFileMutex;
 
   static bool sIsMainProcess;
   static bool sFullSynchronousMode;
   static PRLogModuleInfo* sLoggingModule;
--- a/dom/indexedDB/test/file.js
+++ b/dom/indexedDB/test/file.js
@@ -215,8 +215,13 @@ function getFileRefCount(name, id)
 }
 
 function getFileDBRefCount(name, id)
 {
   let count = {};
   utils.getFileReferences(name, id, null, {}, count);
   return count.value;
 }
+
+function flushPendingFileDeletions()
+{
+  utils.flushPendingFileDeletions();
+}
--- a/dom/indexedDB/test/test_file_os_delete.html
+++ b/dom/indexedDB/test/test_file_os_delete.html
@@ -85,16 +85,19 @@
 
     // This isn't really necessary but in order to ensure that our files have
     // been deleted we need to round-trip with the PBackground thread...
     let request = indexedDB.deleteDatabase(name + "this can't exist");
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     yield undefined;
 
+    // and also flush pending file deletions before checking usage.
+    flushPendingFileDeletions();
+
     getUsage(grabFileUsageAndContinueHandler);
     let endUsage = yield undefined;
 
     is(endUsage, startUsage, "OS files deleted");
 
     finishTest();
     yield undefined;
   }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(42c529d8-54cf-4158-8b09-13ca89d7a88c)]
+[scriptable, uuid(e7b44320-8255-4ad1-bbe9-d78a8a1867c9)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1540,16 +1540,18 @@ interface nsIDOMWindowUtils : nsISupport
    */
   [implicit_jscontext]
   boolean getFileReferences(in AString aDatabaseName, in long long aId,
                             [optional] in jsval aOptions,
                             [optional] out long aRefCnt,
                             [optional] out long aDBRefCnt,
                             [optional] out long aSliceRefCnt);
 
+  void flushPendingFileDeletions();
+
   /**
    * Return whether incremental GC has been disabled due to a binary add-on.
    */
   [implicit_jscontext]
   boolean isIncrementalGCEnabled();
 
   /**
    * Begin opcode-level profiling of all JavaScript execution in the window's
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1688,17 +1688,17 @@ ContentParent::ShutDownProcess(ShutDownM
         // If call was not successful, the channel must have been broken
         // somehow, and we will clean up the error in ActorDestroy.
         return;
     }
 
     using mozilla::dom::quota::QuotaManager;
 
     if (QuotaManager* quotaManager = QuotaManager::Get()) {
-        quotaManager->AbortCloseStoragesForProcess(this);
+        quotaManager->AbortOperationsForProcess(mChildID);
     }
 
     // If Close() fails with an error, we'll end up back in this function, but
     // with aMethod = CLOSE_CHANNEL_WITH_ERROR.  It's important that we call
     // CloseWithError() in this case; see bug 895204.
 
     if (aMethod == CLOSE_CHANNEL && !mCalledClose) {
         // Close() can only be called once: It kicks off the destruction
@@ -4835,16 +4835,36 @@ ContentParent::RecvGetFileReferences(con
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return false;
     }
 
     return true;
 }
 
 bool
+ContentParent::RecvFlushPendingFileDeletions()
+{
+    nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
+    if (NS_WARN_IF(!mgr)) {
+        return false;
+    }
+
+    if (NS_WARN_IF(!mgr->IsMainProcess())) {
+        return false;
+    }
+
+    nsresult rv = mgr->FlushPendingFileDeletions();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+    }
+
+    return true;
+}
+
+bool
 ContentParent::IgnoreIPCPrincipal()
 {
   static bool sDidAddVarCache = false;
   static bool sIgnoreIPCPrincipal = false;
   if (!sDidAddVarCache) {
     sDidAddVarCache = true;
     Preferences::AddBoolVarCache(&sIgnoreIPCPrincipal,
                                  "dom.testing.ignore_ipc_principal", false);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -846,16 +846,19 @@ private:
                           const nsCString& aOrigin,
                           const nsString& aDatabaseName,
                           const int64_t& aFileId,
                           int32_t* aRefCnt,
                           int32_t* aDBRefCnt,
                           int32_t* aSliceRefCnt,
                           bool* aResult) override;
 
+    virtual bool
+    RecvFlushPendingFileDeletions() override;
+
     virtual PWebrtcGlobalParent* AllocPWebrtcGlobalParent() override;
     virtual bool DeallocPWebrtcGlobalParent(PWebrtcGlobalParent *aActor) override;
 
 
     virtual bool RecvUpdateDropEffect(const uint32_t& aDragAction,
                                       const uint32_t& aDropEffect) override;
 
     virtual bool RecvGetBrowserConfiguration(const nsCString& aURI, BrowserConfiguration* aConfig) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -965,16 +965,19 @@ parent:
     // Use only for testing!
     sync GetFileReferences(PersistenceType persistenceType,
                            nsCString origin,
                            nsString databaseName,
                            int64_t fileId)
       returns (int32_t refCnt, int32_t dBRefCnt, int32_t sliceRefCnt,
                bool result);
 
+    // Use only for testing!
+    FlushPendingFileDeletions();
+
     /**
      * Tell the chrome process there is an creation of PBrowser.
      * return a system-wise unique Id.
      */
     sync AllocateTabId(TabId openerTabId, IPCTabContext context, ContentParentId cpId)
         returns (TabId tabId);
     async DeallocateTabId(TabId tabId);
 
deleted file mode 100644
--- a/dom/quota/ArrayCluster.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_quota_arraycluster_h__
-#define mozilla_dom_quota_arraycluster_h__
-
-#include "mozilla/dom/quota/QuotaCommon.h"
-
-#include "Client.h"
-
-BEGIN_QUOTA_NAMESPACE
-
-template <class ValueType, uint32_t Length = Client::TYPE_MAX>
-class ArrayCluster
-{
-public:
-  ArrayCluster()
-  {
-    mArrays.AppendElements(Length);
-  }
-
-  nsTArray<ValueType>&
-  ArrayAt(uint32_t aIndex)
-  {
-    MOZ_ASSERT(aIndex < Length, "Bad index!");
-    return mArrays[aIndex];
-  }
-
-  nsTArray<ValueType>&
-  operator[](uint32_t aIndex)
-  {
-    return ArrayAt(aIndex);
-  }
-
-  bool
-  IsEmpty()
-  {
-    for (uint32_t index = 0; index < Length; index++) {
-      if (!mArrays[index].IsEmpty()) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  template <class T>
-  void
-  AppendElementsTo(uint32_t aIndex, nsTArray<T>& aArray)
-  {
-    NS_ASSERTION(aIndex < Length, "Bad index!");
-    aArray.AppendElements(mArrays[aIndex]);
-  }
-
-  template <class T>
-  void
-  AppendElementsTo(uint32_t aIndex, ArrayCluster<T, Length>& aArrayCluster)
-  {
-    NS_ASSERTION(aIndex < Length, "Bad index!");
-    aArrayCluster[aIndex].AppendElements(mArrays[aIndex]);
-  }
-
-  template <class T>
-  void
-  AppendElementsTo(nsTArray<T>& aArray)
-  {
-    for (uint32_t index = 0; index < Length; index++) {
-      aArray.AppendElements(mArrays[index]);
-    }
-  }
-
-  template<class T>
-  void
-  AppendElementsTo(ArrayCluster<T, Length>& aArrayCluster)
-  {
-    for (uint32_t index = 0; index < Length; index++) {
-      aArrayCluster[index].AppendElements(mArrays[index]);
-    }
-  }
-
-  template<class T>
-  void
-  SwapElements(ArrayCluster<T, Length>& aArrayCluster)
-  {
-    for (uint32_t index = 0; index < Length; index++) {
-      mArrays[index].SwapElements(aArrayCluster.mArrays[index]);
-    }
-  }
-
-  void
-  Clear()
-  {
-    for (uint32_t index = 0; index < Length; index++) {
-      mArrays[index].Clear();
-    }
-  }
-
-private:
-  nsAutoTArray<nsTArray<ValueType>, Length> mArrays;
-};
-
-END_QUOTA_NAMESPACE
-
-#endif // mozilla_dom_quota_arraycluster_h__
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -4,19 +4,20 @@
  * 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_quota_client_h__
 #define mozilla_dom_quota_client_h__
 
 #include "mozilla/dom/quota/QuotaCommon.h"
 
+#include "mozilla/dom/ipc/IdType.h"
+
 #include "PersistenceType.h"
 
-class nsIOfflineStorage;
 class nsIRunnable;
 
 #define IDB_DIRECTORY_NAME "idb"
 #define ASMJSCACHE_DIRECTORY_NAME "asmjs"
 #define DOMCACHE_DIRECTORY_NAME "cache"
 
 BEGIN_QUOTA_NAMESPACE
 
@@ -107,18 +108,20 @@ public:
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin) = 0;
 
   virtual void
   ReleaseIOThreadObjects() = 0;
 
   // Methods which are called on the main thred.
   virtual void
-  WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
-                            nsIRunnable* aCallback) = 0;
+  AbortOperations(const nsACString& aOrigin) = 0;
+
+  virtual void
+  AbortOperationsForProcess(ContentParentId aContentParentId) = 0;
 
   virtual void
   PerformIdleMaintenance() = 0;
 
   virtual void
   ShutdownWorkThreads() = 0;
 
 protected:
deleted file mode 100644
--- a/dom/quota/OriginCollection.h
+++ /dev/null
@@ -1,64 +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_quota_origincollection_h__
-#define mozilla_dom_quota_origincollection_h__
-
-#include "mozilla/dom/quota/QuotaCommon.h"
-
-#include "nsHashKeys.h"
-#include "nsTHashtable.h"
-
-#include "Utilities.h"
-
-BEGIN_QUOTA_NAMESPACE
-
-class OriginCollection
-{
-public:
-  bool
-  ContainsPattern(const nsACString& aPattern)
-  {
-    return mPatterns.Contains(aPattern);
-  }
-
-  void
-  AddPattern(const nsACString& aPattern)
-  {
-    MOZ_ASSERT(!mOrigins.Count());
-    if (!ContainsPattern(aPattern)) {
-      mPatterns.AppendElement(aPattern);
-    }
-  }
-
-  bool
-  ContainsOrigin(const nsACString& aOrigin)
-  {
-    for (uint32_t index = 0; index < mPatterns.Length(); index++) {
-      if (PatternMatchesOrigin(mPatterns[index], aOrigin)) {
-        return true;
-      }
-    }
-
-    return mOrigins.GetEntry(aOrigin);
-  }
-
-  void
-  AddOrigin(const nsACString& aOrigin)
-  {
-    if (!ContainsOrigin(aOrigin)) {
-      mOrigins.PutEntry(aOrigin);
-    }
-  }
-
-private:
-  nsTArray<nsCString> mPatterns;
-  nsTHashtable<nsCStringHashKey> mOrigins;
-};
-
-END_QUOTA_NAMESPACE
-
-#endif // mozilla_dom_quota_origincollection_h__
rename from dom/quota/OriginOrPatternString.h
rename to dom/quota/OriginScope.h
--- a/dom/quota/OriginOrPatternString.h
+++ b/dom/quota/OriginScope.h
@@ -6,68 +6,74 @@
 
 #ifndef mozilla_dom_quota_originorpatternstring_h__
 #define mozilla_dom_quota_originorpatternstring_h__
 
 #include "mozilla/dom/quota/QuotaCommon.h"
 
 BEGIN_QUOTA_NAMESPACE
 
-class OriginOrPatternString : public nsCString
+class OriginScope : public nsCString
 {
 public:
-  static OriginOrPatternString
+  enum Type
+  {
+    eOrigin,
+    ePattern,
+    eNull
+  };
+
+  static OriginScope
   FromOrigin(const nsACString& aOrigin)
   {
-    return OriginOrPatternString(aOrigin, true);
+    return OriginScope(aOrigin, eOrigin);
   }
 
-  static OriginOrPatternString
+  static OriginScope
   FromPattern(const nsACString& aPattern)
   {
-    return OriginOrPatternString(aPattern, false);
+    return OriginScope(aPattern, ePattern);
   }
 
-  static OriginOrPatternString
+  static OriginScope
   FromNull()
   {
-    return OriginOrPatternString();
+    return OriginScope(NullCString(), eNull);
   }
 
   bool
   IsOrigin() const
   {
-    return mIsOrigin;
+    return mType == eOrigin;
   }
 
   bool
   IsPattern() const
   {
-    return mIsPattern;
+    return mType == ePattern;
   }
 
   bool
   IsNull() const
   {
-    return mIsNull;
+    return mType == eNull;
+  }
+
+  Type
+  GetType() const
+  {
+    return mType;
   }
 
 private:
-  OriginOrPatternString(const nsACString& aOriginOrPattern, bool aIsOrigin)
-  : nsCString(aOriginOrPattern),
-    mIsOrigin(aIsOrigin), mIsPattern(!aIsOrigin), mIsNull(false)
-  { }
-
-  OriginOrPatternString()
-  : mIsOrigin(false), mIsPattern(false), mIsNull(true)
+  OriginScope(const nsACString& aString, Type aType)
+  : nsCString(aString), mType(aType)
   { }
 
   bool
-  operator==(const OriginOrPatternString& aOther) = delete;
+  operator==(const OriginScope& aOther) = delete;
 
-  bool mIsOrigin;
-  bool mIsPattern;
-  bool mIsNull;
+  const Type mType;
 };
 
 END_QUOTA_NAMESPACE
 
 #endif // mozilla_dom_quota_originorpatternstring_h__
--- a/dom/quota/PersistenceType.h
+++ b/dom/quota/PersistenceType.h
@@ -18,16 +18,22 @@ enum PersistenceType
   PERSISTENCE_TYPE_PERSISTENT = 0,
   PERSISTENCE_TYPE_TEMPORARY,
   PERSISTENCE_TYPE_DEFAULT,
 
   // Only needed for IPC serialization helper, should never be used in code.
   PERSISTENCE_TYPE_INVALID
 };
 
+static const PersistenceType kAllPersistenceTypes[] = {
+  PERSISTENCE_TYPE_PERSISTENT,
+  PERSISTENCE_TYPE_TEMPORARY,
+  PERSISTENCE_TYPE_DEFAULT
+};
+
 inline void
 PersistenceTypeToText(PersistenceType aPersistenceType, nsACString& aText)
 {
   switch (aPersistenceType) {
     case PERSISTENCE_TYPE_PERSISTENT:
       aText.AssignLiteral("persistent");
       return;
     case PERSISTENCE_TYPE_TEMPORARY:
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -7,36 +7,37 @@
 #include "QuotaManager.h"
 
 #include "mozIApplicationClearPrivateDataParams.h"
 #include "nsIBinaryInputStream.h"
 #include "nsIBinaryOutputStream.h"
 #include "nsIFile.h"
 #include "nsIIdleService.h"
 #include "nsIObserverService.h"
-#include "nsIOfflineStorage.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIQuotaRequest.h"
 #include "nsIRunnable.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsIUsageCallback.h"
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CondVar.h"
+#include "mozilla/dom/PContent.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/IntegerRange.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
 #include "nsAboutProtocolUtils.h"
 #include "nsCharSeparatedTokenizer.h"
@@ -46,20 +47,18 @@
 #include "nsEscape.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsScriptSecurityManager.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "xpcpublic.h"
 
-#include "OriginCollection.h"
-#include "OriginOrPatternString.h"
+#include "OriginScope.h"
 #include "QuotaObject.h"
-#include "StorageMatcher.h"
 #include "UsageInfo.h"
 #include "Utilities.h"
 
 // The amount of time, in milliseconds, that our IO thread will stay alive
 // after the last event it processes.
 #define DEFAULT_THREAD_TIMEOUT_MS 30000
 
 // The amount of time, in milliseconds, that we will wait for active storage
@@ -82,385 +81,532 @@
 #define METADATA_FILE_NAME ".metadata"
 
 #define PERMISSION_DEFAUT_PERSISTENT_STORAGE "default-persistent-storage"
 
 #define KB * 1024ULL
 #define MB * 1024ULL KB
 #define GB * 1024ULL MB
 
-USING_QUOTA_NAMESPACE
-using namespace mozilla;
-using namespace mozilla::dom;
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+using DirectoryLock = QuotaManager::DirectoryLock;
+
+namespace {
+
+/*******************************************************************************
+ * Constants
+ ******************************************************************************/
 
 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) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
   "Enum values should match.");
 
 static_assert(
   static_cast<uint32_t>(StorageType::Default) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT),
   "Enum values should match.");
 
-namespace {
-
 const char kChromeOrigin[] = "chrome";
 const char kAboutHomeOrigin[] = "moz-safe-about:home";
 const char kIndexedDBOriginPrefix[] = "indexeddb://";
 
 #define INDEXEDDB_DIRECTORY_NAME "indexedDB"
 #define STORAGE_DIRECTORY_NAME "storage"
 #define PERSISTENT_DIRECTORY_NAME "persistent"
 #define PERMANENT_DIRECTORY_NAME "permanent"
 #define TEMPORARY_DIRECTORY_NAME "temporary"
 #define DEFAULT_DIRECTORY_NAME "default"
 
 enum AppId {
   kNoAppId = nsIScriptSecurityManager::NO_APP_ID,
   kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID
 };
 
+/******************************************************************************
+ * Quota manager class declarations
+ ******************************************************************************/
+
 } // anonymous namespace
 
-BEGIN_QUOTA_NAMESPACE
-
-// A struct that contains the information corresponding to a pending or
-// running operation that requires synchronization (e.g. opening a db,
-// clearing dbs for an origin, etc).
-struct SynchronizedOp
+class QuotaManager::DirectoryLockImpl final
+  : public DirectoryLock
 {
-  SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
-                 Nullable<PersistenceType> aPersistenceType,
-                 const nsACString& aId);
-
-  ~SynchronizedOp();
-
-  // Test whether this SynchronizedOp needs to wait for the given op.
+  nsRefPtr<QuotaManager> mQuotaManager;
+
+  const Nullable<PersistenceType> mPersistenceType;
+  const nsCString mGroup;
+  const OriginScope mOriginScope;
+  const Nullable<bool> mIsApp;
+  const Nullable<Client::Type> mClientType;
+  nsRefPtr<OpenDirectoryListener> mOpenListener;
+
+  nsTArray<DirectoryLockImpl*> mBlocking;
+  nsTArray<DirectoryLockImpl*> mBlockedOn;
+
+  const bool mExclusive;
+
+  // Internal quota manager operations use this flag to prevent directory lock
+  // registraction/unregistration from updating origin access time, etc.
+  const bool mInternal;
+
+  bool mInvalidated;
+
+public:
+  DirectoryLockImpl(QuotaManager* aQuotaManager,
+                    Nullable<PersistenceType> aPersistenceType,
+                    const nsACString& aGroup,
+                    const OriginScope& aOriginScope,
+                    Nullable<bool> aIsApp,
+                    Nullable<Client::Type> aClientType,
+                    bool aExclusive,
+                    bool aInternal,
+                    OpenDirectoryListener* aOpenListener);
+
+  static bool
+  MatchOriginScopes(const OriginScope& aOriginScope1,
+                    const OriginScope& aOriginScope2);
+
+  const Nullable<PersistenceType>&
+  GetPersistenceType() const
+  {
+    return mPersistenceType;
+  }
+
+  const nsACString&
+  GetGroup() const
+  {
+    return mGroup;
+  }
+
+  const OriginScope&
+  GetOriginScope() const
+  {
+    return mOriginScope;
+  }
+
+  const Nullable<bool>&
+  GetIsApp() const
+  {
+    return mIsApp;
+  }
+
+  const Nullable<Client::Type>&
+  GetClientType() const
+  {
+    return mClientType;
+  }
+
   bool
-  MustWaitFor(const SynchronizedOp& aOp);
+  IsInternal() const
+  {
+    return mInternal;
+  }
+
+  bool
+  ShouldUpdateLockTable()
+  {
+    return !mInternal &&
+           mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT;
+  }
+
+  // Test whether this DirectoryLock needs to wait for the given lock.
+  bool
+  MustWaitFor(const DirectoryLockImpl& aLock);
 
   void
-  DelayRunnable(nsIRunnable* aRunnable);
+  AddBlockingLock(DirectoryLockImpl* aLock)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mBlocking.AppendElement(aLock);
+  }
+
+  const nsTArray<DirectoryLockImpl*>&
+  GetBlockedOnLocks()
+  {
+    return mBlockedOn;
+  }
+
+  void
+  AddBlockedOnLock(DirectoryLockImpl* aLock)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mBlockedOn.AppendElement(aLock);
+  }
+
+  void
+  MaybeUnblock(DirectoryLockImpl* aLock)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mBlockedOn.RemoveElement(aLock);
+    if (mBlockedOn.IsEmpty()) {
+      NotifyOpenListener();
+    }
+  }
+
+  void
+  NotifyOpenListener();
 
   void
-  DispatchDelayedRunnables();
-
-  const OriginOrPatternString mOriginOrPattern;
-  Nullable<PersistenceType> mPersistenceType;
-  nsCString mId;
-  nsCOMPtr<nsIRunnable> mRunnable;
-  nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
+  Invalidate()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mInvalidated = true;
+  }
+
+private:
+  ~DirectoryLockImpl();
+
+  NS_DECL_ISUPPORTS
 };
 
-class CollectOriginsHelper final : public nsRunnable
+namespace {
+
+/*******************************************************************************
+ * Local class declarations
+ ******************************************************************************/
+
+class CollectOriginsHelper final
+  : public nsRunnable
 {
+  uint64_t mMinSizeToBeFreed;
+
+  Mutex& mMutex;
+  CondVar mCondVar;
+
+  // The members below are protected by mMutex.
+  nsTArray<nsRefPtr<DirectoryLock>> mLocks;
+  uint64_t mSizeToBeFreed;
+  bool mWaiting;
+
 public:
-  CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
-
-  NS_IMETHOD
-  Run();
+  CollectOriginsHelper(mozilla::Mutex& aMutex,
+                       uint64_t aMinSizeToBeFreed);
 
   // Blocks the current thread until origins are collected on the main thread.
   // The returned value contains an aggregate size of those origins.
   int64_t
-  BlockAndReturnOriginsForEviction(nsTArray<OriginInfo*>& aOriginInfos);
+  BlockAndReturnOriginsForEviction(nsTArray<nsRefPtr<DirectoryLock>>& aLocks);
 
 private:
   ~CollectOriginsHelper()
   { }
 
-  uint64_t mMinSizeToBeFreed;
-
-  mozilla::Mutex& mMutex;
-  mozilla::CondVar mCondVar;
-
-  // The members below are protected by mMutex.
-  nsTArray<OriginInfo*> mOriginInfos;
-  uint64_t mSizeToBeFreed;
-  bool mWaiting;
+  NS_IMETHOD
+  Run();
 };
 
-// Responsible for clearing the storage files for a particular origin on the
-// IO thread. Created when nsIQuotaManager::ClearStoragesForURI is called.
-// Runs three times, first on the main thread, next on the IO thread, and then
-// finally again on the main thread. While on the IO thread the runnable will
-// actually remove the origin's storage files and the directory that contains
-// them before dispatching itself back to the main thread. When back on the main
-// thread the runnable will notify the QuotaManager that the job has been
-// completed.
-class OriginClearRunnable final : public nsRunnable
+class OriginOperationBase
+  : public nsRunnable
 {
-  enum CallbackState {
+protected:
+  enum State {
     // Not yet run.
-    Pending = 0,
-
-    // Running on the main thread in the callback for OpenAllowed.
-    OpenAllowed,
+    State_Initial,
+
+    // Running on the main thread in the listener for OpenDirectory.
+    State_DirectoryOpenPending,
 
     // Running on the IO thread.
-    IO,
+    State_DirectoryWorkOpen,
 
     // Running on the main thread after all work is done.
-    Complete
+    State_UnblockingOpen,
+
+    State_Complete
   };
 
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-
-  OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern,
-                      Nullable<PersistenceType> aPersistenceType)
-  : mOriginOrPattern(aOriginOrPattern),
-    mPersistenceType(aPersistenceType),
-    mCallbackState(Pending)
+  State mState;
+  nsresult mResultCode;
+
+protected:
+  OriginOperationBase()
+    : mState(State_Initial)
+    , mResultCode(NS_OK)
   { }
 
-  NS_IMETHOD
-  Run() override;
+  // Reference counted.
+  virtual ~OriginOperationBase()
+  {
+    MOZ_ASSERT(mState == State_Complete);
+  }
 
   void
   AdvanceState()
   {
-    switch (mCallbackState) {
-      case Pending:
-        mCallbackState = OpenAllowed;
+    switch (mState) {
+      case State_Initial:
+        mState = State_DirectoryOpenPending;
         return;
-      case OpenAllowed:
-        mCallbackState = IO;
+      case State_DirectoryOpenPending:
+        mState = State_DirectoryWorkOpen;
         return;
-      case IO:
-        mCallbackState = Complete;
+      case State_DirectoryWorkOpen:
+        mState = State_UnblockingOpen;
+        return;
+      case State_UnblockingOpen:
+        mState = State_Complete;
         return;
       default:
-        NS_NOTREACHED("Can't advance past Complete!");
+        MOZ_CRASH("Bad state!");
     }
   }
 
+  NS_IMETHOD
+  Run();
+
+  virtual nsresult
+  Open() = 0;
+
+  nsresult
+  DirectoryOpen();
+
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) = 0;
+
+  void
+  Finish(nsresult aResult);
+
+  virtual void
+  UnblockOpen() = 0;
+
+private:
+  nsresult
+  DirectoryWork();
+};
+
+class NormalOriginOperationBase
+  : public OriginOperationBase
+  , public OpenDirectoryListener
+{
+  nsRefPtr<DirectoryLock> mDirectoryLock;
+
+protected:
+  Nullable<PersistenceType> mPersistenceType;
+  OriginScope mOriginScope;
+  const bool mExclusive;
+
+public:
+  void
+  RunImmediately()
+  {
+    MOZ_ASSERT(mState == State_Initial);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(this->Run()));
+  }
+
+protected:
+  NormalOriginOperationBase(Nullable<PersistenceType> aPersistenceType,
+                            const OriginScope& aOriginScope,
+                            bool aExclusive)
+    : mPersistenceType(aPersistenceType)
+    , mOriginScope(aOriginScope)
+    , mExclusive(aExclusive)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  ~NormalOriginOperationBase()
+  { }
+
+private:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  virtual nsresult
+  Open() override;
+
+  virtual void
+  UnblockOpen() override;
+
+  // OpenDirectoryListener overrides.
+  virtual void
+  DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void
+  DirectoryLockFailed() override;
+
+  // Used to send results before unblocking open.
+  virtual void
+  SendResults() = 0;
+};
+
+class SaveOriginAccessTimeOp
+  : public NormalOriginOperationBase
+{
+  int64_t mTimestamp;
+
+public:
+  SaveOriginAccessTimeOp(PersistenceType aPersistenceType,
+                         const nsACString& aOrigin,
+                         int64_t aTimestamp)
+    : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType),
+                                OriginScope::FromOrigin(aOrigin),
+                                /* aExclusive */ false)
+    , mTimestamp(aTimestamp)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+private:
+  ~SaveOriginAccessTimeOp()
+  { }
+
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  virtual void
+  SendResults()
+  { }
+};
+
+class GetUsageOp
+  : public NormalOriginOperationBase
+  , public nsIQuotaRequest
+{
+  UsageInfo mUsageInfo;
+
+  const nsCString mGroup;
+  nsCOMPtr<nsIURI> mURI;
+  nsCOMPtr<nsIUsageCallback> mCallback;
+  const uint32_t mAppId;
+  const bool mIsApp;
+  const bool mInMozBrowserOnly;
+
+public:
+  GetUsageOp(const nsACString& aGroup,
+             const nsACString& aOrigin,
+             bool aIsApp,
+             nsIURI* aURI,
+             nsIUsageCallback* aCallback,
+             uint32_t aAppId,
+             bool aInMozBrowserOnly);
+
+private:
+  ~GetUsageOp()
+  { }
+
+  nsresult
+  AddToUsage(QuotaManager* aQuotaManager,
+             PersistenceType aPersistenceType);
+
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  virtual void
+  SendResults();
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIQUOTAREQUEST
+};
+
+class ResetOrClearOp
+  : public NormalOriginOperationBase
+{
+  bool mClear;
+
+public:
+  explicit ResetOrClearOp(bool aClear)
+    : NormalOriginOperationBase(Nullable<PersistenceType>(),
+                                OriginScope::FromNull(),
+                                /* aExclusive */ true)
+    , mClear(aClear)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+private:
+  ~ResetOrClearOp()
+  { }
+
+  void
+  DeleteFiles(QuotaManager* aQuotaManager);
+
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  virtual void
+  SendResults()
+  { }
+};
+
+class OriginClearOp
+  : public NormalOriginOperationBase
+{
+public:
+  OriginClearOp(Nullable<PersistenceType> aPersistenceType,
+                const OriginScope& aOriginScope)
+    : NormalOriginOperationBase(aPersistenceType,
+                                aOriginScope,
+                                /* aExclusive */ true)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+private:
+  ~OriginClearOp()
+  { }
+
   void
   DeleteFiles(QuotaManager* aQuotaManager,
               PersistenceType aPersistenceType);
 
-private:
-  ~OriginClearRunnable() {}
-
-  OriginOrPatternString mOriginOrPattern;
-  Nullable<PersistenceType> mPersistenceType;
-  CallbackState mCallbackState;
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  virtual void
+  SendResults()
+  { }
 };
 
-// Responsible for calculating the amount of space taken up by storages of a
-// certain origin. Created when nsIQuotaManager::GetUsageForURI is called.
-// May be canceled with nsIQuotaRequest::Cancel. Runs three times, first
-// on the main thread, next on the IO thread, and then finally again on the main
-// thread. While on the IO thread the runnable will calculate the size of all
-// files in the origin's directory before dispatching itself back to the main
-// thread. When on the main thread the runnable will call the callback and then
-// notify the QuotaManager that the job has been completed.
-class AsyncUsageRunnable final : public UsageInfo,
-                                 public nsRunnable,
-                                 public nsIQuotaRequest
+class FinalizeOriginEvictionOp
+  : public OriginOperationBase
 {
-  enum CallbackState {
-    // Not yet run.
-    Pending = 0,
-
-    // Running on the main thread in the callback for OpenAllowed.
-    OpenAllowed,
-
-    // Running on the IO thread.
-    IO,
-
-    // Running on the main thread after all work is done.
-    Complete,
-
-    // Running on the main thread after skipping the work
-    Shortcut
-  };
+  nsTArray<nsRefPtr<DirectoryLock>> mLocks;
 
 public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIQUOTAREQUEST
-
-  AsyncUsageRunnable(uint32_t aAppId,
-                     bool aInMozBrowserOnly,
-                     const nsACString& aGroup,
-                     const OriginOrPatternString& aOrigin,
-                     bool aIsApp,
-                     nsIURI* aURI,
-                     nsIUsageCallback* aCallback);
-
-  NS_IMETHOD
-  Run() override;
-
-  void
-  AdvanceState()
+  explicit FinalizeOriginEvictionOp(nsTArray<nsRefPtr<DirectoryLock>>& aLocks)
   {
-    switch (mCallbackState) {
-      case Pending:
-        mCallbackState = OpenAllowed;
-        return;
-      case OpenAllowed:
-        mCallbackState = IO;
-        return;
-      case IO:
-        mCallbackState = Complete;
-        return;
-      default:
-        NS_NOTREACHED("Can't advance past Complete!");
-    }
-  }
-
-  nsresult
-  TakeShortcut();
-
-private:
-  ~AsyncUsageRunnable() {}
-
-  // Run calls the RunInternal method and makes sure that we always dispatch
-  // to the main thread in case of an error.
-  inline nsresult
-  RunInternal();
-
-  nsresult
-  AddToUsage(QuotaManager* aQuotaManager,
-             PersistenceType aPersistenceType);
-
-  nsCOMPtr<nsIURI> mURI;
-  nsCOMPtr<nsIUsageCallback> mCallback;
-  uint32_t mAppId;
-  nsCString mGroup;
-  OriginOrPatternString mOrigin;
-  CallbackState mCallbackState;
-  bool mInMozBrowserOnly;
-  const bool mIsApp;
-};
-
-class ResetOrClearRunnable final : public nsRunnable
-{
-  enum CallbackState {
-    // Not yet run.
-    Pending = 0,
-
-    // Running on the main thread in the callback for OpenAllowed.
-    OpenAllowed,
-
-    // Running on the IO thread.
-    IO,
-
-    // Running on the main thread after all work is done.
-    Complete
-  };
-
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-
-  explicit ResetOrClearRunnable(bool aClear)
-  : mCallbackState(Pending),
-    mClear(aClear)
-  { }
-
-  NS_IMETHOD
-  Run() override;
-
-  void
-  AdvanceState()
-  {
-    switch (mCallbackState) {
-      case Pending:
-        mCallbackState = OpenAllowed;
-        return;
-      case OpenAllowed:
-        mCallbackState = IO;
-        return;
-      case IO:
-        mCallbackState = Complete;
-        return;
-      default:
-        NS_NOTREACHED("Can't advance past Complete!");
-    }
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mLocks.SwapElements(aLocks);
   }
 
   void
-  DeleteFiles(QuotaManager* aQuotaManager);
+  Dispatch();
+
+  void
+  RunOnIOThreadImmediately();
 
 private:
-  ~ResetOrClearRunnable() {}
-
-  CallbackState mCallbackState;
-  bool mClear;
+  ~FinalizeOriginEvictionOp()
+  { }
+
+  virtual nsresult
+  Open() override;
+
+  virtual nsresult
+  DoDirectoryWork(QuotaManager* aQuotaManager) override;
+
+  virtual void
+  UnblockOpen() override;
 };
 
-// Responsible for finalizing eviction of certian origins (storage files have
-// been already cleared, we just need to release IO thread only objects and
-// allow next synchronized ops for evicted origins). Created when
-// QuotaManager::FinalizeOriginEviction is called. Runs three times, first
-// on the main thread, next on the IO thread, and then finally again on the main
-// thread. While on the IO thread the runnable will release IO thread only
-// objects before dispatching itself back to the main thread. When back on the
-// main thread the runnable will call QuotaManager::AllowNextSynchronizedOp.
-// The runnable can also run in a shortened mode (runs only twice).
-class FinalizeOriginEvictionRunnable final : public nsRunnable
-{
-  enum CallbackState {
-    // Not yet run.
-    Pending = 0,
-
-    // Running on the main thread in the callback for OpenAllowed.
-    OpenAllowed,
-
-    // Running on the IO thread.
-    IO,
-
-    // Running on the main thread after IO work is done.
-    Complete
-  };
-
-public:
-  explicit FinalizeOriginEvictionRunnable(nsTArray<OriginParams>& aOrigins)
-  : mCallbackState(Pending)
-  {
-    mOrigins.SwapElements(aOrigins);
-  }
-
-  NS_IMETHOD
-  Run();
-
-  void
-  AdvanceState()
-  {
-    switch (mCallbackState) {
-      case Pending:
-        mCallbackState = OpenAllowed;
-        return;
-      case OpenAllowed:
-        mCallbackState = IO;
-        return;
-      case IO:
-        mCallbackState = Complete;
-        return;
-      default:
-        MOZ_ASSERT_UNREACHABLE("Can't advance past Complete!");
-    }
-  }
-
-  nsresult
-  Dispatch();
-
-  nsresult
-  RunImmediately();
-
-private:
-  CallbackState mCallbackState;
-  nsTArray<OriginParams> mOrigins;
-};
+} // anonymous namespace
 
 bool
 IsOnIOThread()
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "Must have a manager here!");
 
   bool currentThread;
@@ -496,78 +642,29 @@ ReportInternalError(const char* aFile, u
   }
 
   nsContentUtils::LogSimpleConsoleError(
     NS_ConvertUTF8toUTF16(nsPrintfCString(
                           "Quota %s: %s:%lu", aStr, aFile, aLine)),
     "quota");
 }
 
-END_QUOTA_NAMESPACE
-
 namespace {
 
 QuotaManager* gInstance = nullptr;
 mozilla::Atomic<bool> gShutdown(false);
 
 // Constants for temporary storage limit computing.
 static const int32_t kDefaultFixedLimitKB = -1;
 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
 int32_t gFixedLimitKB = kDefaultFixedLimitKB;
 uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
 
 bool gTestingEnabled = false;
 
-// A callback runnable used by the TransactionPool when it's safe to proceed
-// with a SetVersion/DeleteDatabase/etc.
-class WaitForTransactionsToFinishRunnable final : public nsRunnable
-{
-public:
-  explicit WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
-  : mOp(aOp), mCountdown(1)
-  {
-    NS_ASSERTION(mOp, "Why don't we have a runnable?");
-    NS_ASSERTION(mOp->mRunnable,
-                 "What are we supposed to do when we're done?");
-    NS_ASSERTION(mCountdown, "Wrong countdown!");
-  }
-
-  NS_IMETHOD
-  Run();
-
-  void
-  AddRun()
-  {
-    mCountdown++;
-  }
-
-private:
-  // The QuotaManager holds this alive.
-  SynchronizedOp* mOp;
-  uint32_t mCountdown;
-};
-
-class SaveOriginAccessTimeRunnable final : public nsRunnable
-{
-public:
-  SaveOriginAccessTimeRunnable(PersistenceType aPersistenceType,
-                               const nsACString& aOrigin,
-                               int64_t aTimestamp)
-  : mPersistenceType(aPersistenceType), mOrigin(aOrigin), mTimestamp(aTimestamp)
-  { }
-
-  NS_IMETHOD
-  Run();
-
-private:
-  PersistenceType mPersistenceType;
-  nsCString mOrigin;
-  int64_t mTimestamp;
-};
-
 class StorageDirectoryHelper final
   : public nsRunnable
 {
   struct OriginProps;
 
   nsTArray<OriginProps> mOriginProps;
 
   nsCOMPtr<nsIFile> mDirectory;
@@ -736,31 +833,16 @@ public:
             const nsACString& aOrigin)
   {
     PersistenceTypeToText(aPersistenceType, *this);
     Append(':');
     Append(aOrigin);
   }
 };
 
-struct MOZ_STACK_CLASS InactiveOriginsInfo
-{
-  InactiveOriginsInfo(OriginCollection& aPersistentCollection,
-                      OriginCollection& aTemporaryCollection,
-                      nsTArray<OriginInfo*>& aOrigins)
-  : persistentCollection(aPersistentCollection),
-    temporaryCollection(aTemporaryCollection),
-    origins(aOrigins)
-  { }
-
-  OriginCollection& persistentCollection;
-  OriginCollection& temporaryCollection;
-  nsTArray<OriginInfo*>& origins;
-};
-
 bool
 IsMainProcess()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default;
 }
 
 void
 SanitizeOriginString(nsCString& aOrigin)
@@ -1286,16 +1368,186 @@ GetTemporaryStorageLimit(nsIFile* aDirec
   uint64_t resultKB = availableKB * .50;
 
   *aLimit = resultKB * 1024;
   return NS_OK;
 }
 
 } // anonymous namespace
 
+/*******************************************************************************
+ * Directory lock
+ ******************************************************************************/
+
+const Nullable<PersistenceType>&
+QuotaManager::
+DirectoryLock::GetPersistenceType() const
+{
+  return static_cast<const DirectoryLockImpl*>(this)->GetPersistenceType();
+}
+
+const nsACString&
+QuotaManager::
+DirectoryLock::GetGroup() const
+{
+  return static_cast<const DirectoryLockImpl*>(this)->GetGroup();
+}
+
+const OriginScope&
+QuotaManager::
+DirectoryLock::GetOriginScope() const
+{
+  return static_cast<const DirectoryLockImpl*>(this)->GetOriginScope();
+}
+
+const Nullable<bool>&
+QuotaManager::
+DirectoryLock::GetIsApp() const
+{
+  return static_cast<const DirectoryLockImpl*>(this)->GetIsApp();
+}
+
+QuotaManager::
+DirectoryLockImpl::DirectoryLockImpl(QuotaManager* aQuotaManager,
+                                     Nullable<PersistenceType> aPersistenceType,
+                                     const nsACString& aGroup,
+                                     const OriginScope& aOriginScope,
+                                     Nullable<bool> aIsApp,
+                                     Nullable<Client::Type> aClientType,
+                                     bool aExclusive,
+                                     bool aInternal,
+                                     OpenDirectoryListener* aOpenListener)
+  : mQuotaManager(aQuotaManager)
+  , mPersistenceType(aPersistenceType)
+  , mGroup(aGroup)
+  , mOriginScope(aOriginScope)
+  , mIsApp(aIsApp)
+  , mClientType(aClientType)
+  , mOpenListener(aOpenListener)
+  , mExclusive(aExclusive)
+  , mInternal(aInternal)
+  , mInvalidated(false)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aQuotaManager);
+  MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
+  MOZ_ASSERT_IF(!aInternal,
+                aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
+  MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
+  MOZ_ASSERT_IF(aInternal, !aOriginScope.IsEmpty() || aOriginScope.IsNull());
+  MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
+  MOZ_ASSERT_IF(!aInternal, !aOriginScope.IsEmpty());
+  MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull());
+  MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
+  MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+  MOZ_ASSERT_IF(!aInternal, aOpenListener);
+}
+
+QuotaManager::
+DirectoryLockImpl::~DirectoryLockImpl()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mQuotaManager);
+
+  for (DirectoryLockImpl* blockingLock : mBlocking) {
+    blockingLock->MaybeUnblock(this);
+  }
+
+  mBlocking.Clear();
+
+  mQuotaManager->UnregisterDirectoryLock(this);
+}
+
+// static
+bool
+QuotaManager::
+DirectoryLockImpl::MatchOriginScopes(const OriginScope& aOriginScope1,
+                                     const OriginScope& aOriginScope2)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool match;
+
+  if (aOriginScope2.IsNull() || aOriginScope1.IsNull()) {
+    match = true;
+  } else if (aOriginScope2.IsOrigin()) {
+    if (aOriginScope1.IsOrigin()) {
+      match = aOriginScope2.Equals(aOriginScope1);
+    } else {
+      match = PatternMatchesOrigin(aOriginScope1, aOriginScope2);
+    }
+  } else if (aOriginScope1.IsOrigin()) {
+    match = PatternMatchesOrigin(aOriginScope2, aOriginScope1);
+  } else {
+    match = PatternMatchesOrigin(aOriginScope1, aOriginScope2) ||
+            PatternMatchesOrigin(aOriginScope2, aOriginScope1);
+  }
+
+  return match;
+}
+
+bool
+QuotaManager::
+DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aExistingLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Waiting is never required if the ops in comparison represent shared locks.
+  if (!aExistingLock.mExclusive && !mExclusive) {
+    return false;
+  }
+
+  // If the persistence types don't overlap, the op can proceed.
+  if (!aExistingLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
+      aExistingLock.mPersistenceType.Value() != mPersistenceType.Value()) {
+    return false;
+  }
+
+  // If the origin scopes don't overlap, the op can proceed.
+  bool match = MatchOriginScopes(mOriginScope, aExistingLock.mOriginScope);
+  if (!match) {
+    return false;
+  }
+
+  // If the client types don't overlap, the op can proceed.
+  if (!aExistingLock.mClientType.IsNull() && !mClientType.IsNull() &&
+      aExistingLock.mClientType.Value() != mClientType.Value()) {
+    return false;
+  }
+
+  // Otherwise, when all attributes overlap (persistence type, origin scope and
+  // client type) the op must wait.
+  return true;
+}
+
+void
+QuotaManager::
+DirectoryLockImpl::NotifyOpenListener()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mQuotaManager);
+  MOZ_ASSERT(mOpenListener);
+
+  if (mInvalidated) {
+    mOpenListener->DirectoryLockFailed();
+  } else {
+    mOpenListener->DirectoryLockAcquired(this);
+  }
+
+  mOpenListener = nullptr;
+
+  mQuotaManager->RemovePendingDirectoryLock(this);
+}
+
+NS_IMPL_ISUPPORTS0(QuotaManager::DirectoryLockImpl);
+
+/*******************************************************************************
+ * Quota manager
+ ******************************************************************************/
+
 QuotaManager::QuotaManager()
 : mQuotaMutex("QuotaManager.mQuotaMutex"),
   mTemporaryStorageLimit(0),
   mTemporaryStorageUsage(0),
   mTemporaryStorageInitialized(false),
   mStorageAreaInitialized(false)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -1361,16 +1613,346 @@ QuotaManager::FactoryCreate()
 
 // static
 bool
 QuotaManager::IsShuttingDown()
 {
   return gShutdown;
 }
 
+auto
+QuotaManager::CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType,
+                                  const nsACString& aGroup,
+                                  const OriginScope& aOriginScope,
+                                  Nullable<bool> aIsApp,
+                                  Nullable<Client::Type> aClientType,
+                                  bool aExclusive,
+                                  bool aInternal,
+                                  OpenDirectoryListener* aOpenListener)
+  -> already_AddRefed<DirectoryLockImpl>
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
+  MOZ_ASSERT_IF(!aInternal,
+                aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
+  MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
+  MOZ_ASSERT_IF(aInternal, !aOriginScope.IsEmpty() || aOriginScope.IsNull());
+  MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
+  MOZ_ASSERT_IF(!aInternal, !aOriginScope.IsEmpty());
+  MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull());
+  MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
+  MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+  MOZ_ASSERT_IF(!aInternal, aOpenListener);
+
+  nsRefPtr<DirectoryLockImpl> lock = new DirectoryLockImpl(this,
+                                                           aPersistenceType,
+                                                           aGroup,
+                                                           aOriginScope,
+                                                           aIsApp,
+                                                           aClientType,
+                                                           aExclusive,
+                                                           aInternal,
+                                                           aOpenListener);
+
+  mPendingDirectoryLocks.AppendElement(lock);
+
+  // See if this lock needs to wait.
+  bool blocked = false;
+  for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
+    DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
+    if (lock->MustWaitFor(*existingLock)) {
+      existingLock->AddBlockingLock(lock);
+      lock->AddBlockedOnLock(existingLock);
+      blocked = true;
+    }
+  }
+
+  RegisterDirectoryLock(lock);
+
+  // Otherwise, notify the open listener immediately.
+  if (!blocked) {
+    lock->NotifyOpenListener();
+  }
+
+  return lock.forget();
+}
+
+auto
+QuotaManager::CreateDirectoryLockForEviction(PersistenceType aPersistenceType,
+                                             const nsACString& aGroup,
+                                             const nsACString& aOrigin,
+                                             bool aIsApp)
+  -> already_AddRefed<DirectoryLockImpl>
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+
+  nsRefPtr<DirectoryLockImpl> lock =
+    new DirectoryLockImpl(this,
+                          Nullable<PersistenceType>(aPersistenceType),
+                          aGroup,
+                          OriginScope::FromOrigin(aOrigin),
+                          Nullable<bool>(aIsApp),
+                          Nullable<Client::Type>(),
+                          /* aExclusive */ true,
+                          /* aInternal */ true,
+                          nullptr);
+
+#ifdef DEBUG
+  for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) {
+    DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1];
+    MOZ_ASSERT(!lock->MustWaitFor(*existingLock));
+  }
+#endif
+
+  RegisterDirectoryLock(lock);
+
+  return lock.forget();
+}
+
+void
+QuotaManager::RegisterDirectoryLock(DirectoryLockImpl* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aLock);
+
+  mDirectoryLocks.AppendElement(aLock);
+
+  if (aLock->ShouldUpdateLockTable()) {
+    const Nullable<PersistenceType>& persistenceType =
+      aLock->GetPersistenceType();
+    const OriginScope& originScope = aLock->GetOriginScope();
+
+    MOZ_ASSERT(!persistenceType.IsNull());
+    MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
+    MOZ_ASSERT(originScope.IsOrigin());
+    MOZ_ASSERT(!originScope.IsEmpty());
+
+    DirectoryLockTable& directoryLockTable =
+      GetDirectoryLockTable(persistenceType.Value());
+
+    nsTArray<DirectoryLockImpl*>* array;
+    if (!directoryLockTable.Get(originScope, &array)) {
+      array = new nsTArray<DirectoryLockImpl*>();
+      directoryLockTable.Put(originScope, array);
+
+      if (!IsShuttingDown()) {
+        UpdateOriginAccessTime(persistenceType.Value(),
+                               aLock->GetGroup(),
+                               originScope);
+      }
+    }
+    array->AppendElement(aLock);
+  }
+}
+
+void
+QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(aLock));
+
+  if (aLock->ShouldUpdateLockTable()) {
+    const Nullable<PersistenceType>& persistenceType =
+      aLock->GetPersistenceType();
+    const OriginScope& originScope = aLock->GetOriginScope();
+
+    MOZ_ASSERT(!persistenceType.IsNull());
+    MOZ_ASSERT(!aLock->GetGroup().IsEmpty());
+    MOZ_ASSERT(originScope.IsOrigin());
+    MOZ_ASSERT(!originScope.IsEmpty());
+
+    DirectoryLockTable& directoryLockTable =
+      GetDirectoryLockTable(persistenceType.Value());
+
+    nsTArray<DirectoryLockImpl*>* array;
+    MOZ_ALWAYS_TRUE(directoryLockTable.Get(originScope, &array));
+
+    MOZ_ALWAYS_TRUE(array->RemoveElement(aLock));
+    if (array->IsEmpty()) {
+      directoryLockTable.Remove(originScope);
+
+      if (!IsShuttingDown()) {
+        UpdateOriginAccessTime(persistenceType.Value(),
+                               aLock->GetGroup(),
+                               originScope);
+      }
+    }
+  }
+}
+
+void
+QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aLock);
+
+  MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(aLock));
+}
+
+uint64_t
+QuotaManager::CollectOriginsForEviction(
+                                      uint64_t aMinSizeToBeFreed,
+                                      nsTArray<nsRefPtr<DirectoryLock>>& aLocks)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aLocks.IsEmpty());
+
+  class MOZ_STACK_CLASS Closure final
+  {
+    nsTArray<DirectoryLockImpl*>& mTemporaryStorageLocks;
+    nsTArray<DirectoryLockImpl*>& mDefaultStorageLocks;
+    nsTArray<OriginInfo*>& mInactiveOriginInfos;
+
+  public:
+    Closure(nsTArray<DirectoryLockImpl*>& aTemporaryStorageLocks,
+            nsTArray<DirectoryLockImpl*>& aDefaultStorageLocks,
+            nsTArray<OriginInfo*>& aInactiveOriginInfos)
+      : mTemporaryStorageLocks(aTemporaryStorageLocks)
+      , mDefaultStorageLocks(aDefaultStorageLocks)
+      , mInactiveOriginInfos(aInactiveOriginInfos)
+    { }
+
+    static PLDHashOperator
+    GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
+                                       GroupInfoPair* aValue,
+                                       void* aUserArg)
+    {
+      MOZ_ASSERT(!aKey.IsEmpty());
+      MOZ_ASSERT(aValue);
+      MOZ_ASSERT(aUserArg);
+
+      auto* closure = static_cast<Closure*>(aUserArg);
+
+      nsRefPtr<GroupInfo> groupInfo =
+        aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
+      if (groupInfo) {
+        GetInactiveOriginInfos(groupInfo->mOriginInfos,
+                               closure->mTemporaryStorageLocks,
+                               closure->mInactiveOriginInfos);
+      }
+
+      groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+      if (groupInfo) {
+        GetInactiveOriginInfos(groupInfo->mOriginInfos,
+                               closure->mDefaultStorageLocks,
+                               closure->mInactiveOriginInfos);
+      }
+
+      return PL_DHASH_NEXT;
+    }
+
+  private:
+    static void
+    GetInactiveOriginInfos(nsTArray<nsRefPtr<OriginInfo>>& aOriginInfos,
+                           nsTArray<DirectoryLockImpl*>& aLocks,
+                           nsTArray<OriginInfo*>& aInactiveOriginInfos)
+    {
+      for (OriginInfo* originInfo : aOriginInfos) {
+        MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType,
+                                        originInfo->mIsApp));
+
+        OriginScope originScope = OriginScope::FromOrigin(originInfo->mOrigin);
+
+        bool match = false;
+        for (uint32_t j = aLocks.Length(); j > 0; j--) {
+          DirectoryLockImpl* lock = aLocks[j - 1];
+          if (DirectoryLockImpl::MatchOriginScopes(originScope,
+                                                   lock->GetOriginScope())) {
+            match = true;
+            break;
+          }
+        }
+
+        if (!match) {
+          MOZ_ASSERT(!originInfo->mQuotaObjects.Count(),
+                     "Inactive origin shouldn't have open files!");
+          aInactiveOriginInfos.InsertElementSorted(originInfo,
+                                                   OriginInfoLRUComparator());
+        }
+      }
+    }
+  };
+
+  // Split locks into separate arrays and filter out locks for persistent
+  // storage, they can't block us.
+  nsTArray<DirectoryLockImpl*> temporaryStorageLocks;
+  nsTArray<DirectoryLockImpl*> defaultStorageLocks;
+  for (DirectoryLockImpl* lock : mDirectoryLocks) {
+    const Nullable<PersistenceType>& persistenceType =
+      lock->GetPersistenceType();
+
+    if (persistenceType.IsNull()) {
+      temporaryStorageLocks.AppendElement(lock);
+      defaultStorageLocks.AppendElement(lock);
+    } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
+      temporaryStorageLocks.AppendElement(lock);
+    } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
+      defaultStorageLocks.AppendElement(lock);
+    } else {
+      MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT);
+
+      // Do nothing here, persistent origins don't need to be collected ever.
+    }
+  }
+
+  nsTArray<OriginInfo*> inactiveOrigins;
+
+  Closure closure(temporaryStorageLocks, defaultStorageLocks, inactiveOrigins);
+
+  // Enumerate and process inactive origins. This must be protected by the
+  // mutex.
+  MutexAutoLock lock(mQuotaMutex);
+
+  mGroupInfoPairs.EnumerateRead(Closure::GetInactiveTemporaryStorageOrigins,
+                                &closure);
+
+#ifdef DEBUG
+  // Make sure the array is sorted correctly.
+  for (uint32_t index = inactiveOrigins.Length(); index > 1; index--) {
+    MOZ_ASSERT(inactiveOrigins[index - 1]->mAccessTime >=
+               inactiveOrigins[index - 2]->mAccessTime);
+  }
+#endif
+
+  // Create a list of inactive and the least recently used origins
+  // whose aggregate size is greater or equals the minimal size to be freed.
+  uint64_t sizeToBeFreed = 0;
+  for(uint32_t count = inactiveOrigins.Length(), index = 0;
+      index < count;
+      index++) {
+    if (sizeToBeFreed >= aMinSizeToBeFreed) {
+      inactiveOrigins.TruncateLength(index);
+      break;
+    }
+
+    sizeToBeFreed += inactiveOrigins[index]->mUsage;
+  }
+
+  if (sizeToBeFreed >= aMinSizeToBeFreed) {
+    // Success, add directory locks for these origins, so any other
+    // operations for them will be delayed (until origin eviction is finalized).
+
+    for (OriginInfo* originInfo : inactiveOrigins) {
+      nsRefPtr<DirectoryLockImpl> lock =
+        CreateDirectoryLockForEviction(originInfo->mGroupInfo->mPersistenceType,
+                                       originInfo->mGroupInfo->mGroup,
+                                       originInfo->mOrigin,
+                                       originInfo->mIsApp);
+      aLocks.AppendElement(lock.forget());
+    }
+
+    return sizeToBeFreed;
+  }
+
+  return 0;
+}
+
 nsresult
 QuotaManager::Init()
 {
   nsresult rv;
   if (IsMainProcess()) {
     nsCOMPtr<nsIFile> baseDir;
     rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
                                 getter_AddRefs(baseDir));
@@ -1525,17 +2107,20 @@ QuotaManager::UpdateOriginAccessTime(Per
 
   nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
   if (originInfo) {
     int64_t timestamp = PR_Now();
     originInfo->LockedUpdateAccessTime(timestamp);
 
     MutexAutoUnlock autoUnlock(mQuotaMutex);
 
-    SaveOriginAccessTime(aPersistenceType, aOrigin, timestamp);
+    nsRefPtr<SaveOriginAccessTimeOp> op =
+      new SaveOriginAccessTimeOp(aPersistenceType, aOrigin, timestamp);
+
+    op->RunImmediately();
   }
 }
 
 // static
 PLDHashOperator
 QuotaManager::RemoveQuotaCallback(const nsACString& aKey,
                                   nsAutoPtr<GroupInfoPair>& aValue,
                                   void* aUserArg)
@@ -1660,198 +2245,24 @@ QuotaManager::GetQuotaObject(Persistence
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   rv = file->InitWithPath(aPath);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file);
 }
 
-bool
-QuotaManager::RegisterStorage(nsIOfflineStorage* aStorage)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aStorage, "Null pointer!");
-
-  // Don't allow any new storages to be created after shutdown.
-  if (IsShuttingDown()) {
-    return false;
-  }
-
-  // Add this storage to its origin info if it exists, create it otherwise.
-  const nsACString& origin = aStorage->Origin();
-  ArrayCluster<nsIOfflineStorage*>* cluster;
-  if (!mLiveStorages.Get(origin, &cluster)) {
-    cluster = new ArrayCluster<nsIOfflineStorage*>();
-    mLiveStorages.Put(origin, cluster);
-  }
-  (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage);
-
-  if (aStorage->Type() != PERSISTENCE_TYPE_PERSISTENT) {
-    LiveStorageTable& liveStorageTable = GetLiveStorageTable(aStorage->Type());
-
-    nsTArray<nsIOfflineStorage*>* array;
-    if (!liveStorageTable.Get(origin, &array)) {
-      array = new nsTArray<nsIOfflineStorage*>();
-      liveStorageTable.Put(origin, array);
-
-      UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
-    }
-    array->AppendElement(aStorage);
-  }
-
-  return true;
-}
-
 void
-QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aStorage, "Null pointer!");
-
-  // Remove this storage from its origin array, maybe remove the array if it
-  // is then empty.
-  const nsACString& origin = aStorage->Origin();
-
-  ArrayCluster<nsIOfflineStorage*>* cluster;
-  MOZ_ALWAYS_TRUE(mLiveStorages.Get(origin, &cluster));
-
-  MOZ_ALWAYS_TRUE(
-    (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage));
-  if (cluster->IsEmpty()) {
-    mLiveStorages.Remove(origin);
-  }
-
-  if (aStorage->Type() != PERSISTENCE_TYPE_PERSISTENT) {
-    LiveStorageTable& liveStorageTable = GetLiveStorageTable(aStorage->Type());
-
-    nsTArray<nsIOfflineStorage*>* array;
-    MOZ_ALWAYS_TRUE(liveStorageTable.Get(origin, &array));
-
-    MOZ_ALWAYS_TRUE(array->RemoveElement(aStorage));
-    if (array->IsEmpty()) {
-      liveStorageTable.Remove(origin);
-
-      UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
-    }
-  }
-}
-
-void
-QuotaManager::AbortCloseStoragesForProcess(ContentParent* aContentParent)
+QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aContentParent);
-
-  // 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++) {
-    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!");
-        }
-      }
-    }
-  }
-}
-
-nsresult
-QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
-                                 Nullable<PersistenceType> aPersistenceType,
-                                 const nsACString& aId, nsIRunnable* aRunnable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
-               "Empty pattern!");
-  NS_ASSERTION(aRunnable, "Null pointer!");
-
-  nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
-                                                  aPersistenceType, aId));
-
-  // See if this runnable needs to wait.
-  bool delayed = false;
-  for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
-    nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
-    if (op->MustWaitFor(*existingOp)) {
-      existingOp->DelayRunnable(aRunnable);
-      delayed = true;
-      break;
-    }
-  }
-
-  // Otherwise, dispatch it immediately.
-  if (!delayed) {
-    nsresult rv = NS_DispatchToCurrentThread(aRunnable);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  // Adding this to the synchronized ops list will block any additional
-  // ops from proceeding until this one is done.
-  mSynchronizedOps.AppendElement(op.forget());
-
-  return NS_OK;
-}
-
-void
-QuotaManager::AddSynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
-                                Nullable<PersistenceType> aPersistenceType)
-{
-  nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
-                                                  aPersistenceType,
-                                                  EmptyCString()));
-
-#ifdef DEBUG
-  for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
-    nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
-    NS_ASSERTION(!op->MustWaitFor(*existingOp), "What?");
-  }
-#endif
-
-  mSynchronizedOps.AppendElement(op.forget());
-}
-
-void
-QuotaManager::AllowNextSynchronizedOp(
-                                  const OriginOrPatternString& aOriginOrPattern,
-                                  Nullable<PersistenceType> aPersistenceType,
-                                  const nsACString& aId)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
-               "Empty origin/pattern!");
-
-  uint32_t count = mSynchronizedOps.Length();
-  for (uint32_t index = 0; index < count; index++) {
-    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
-    if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() &&
-        op->mOriginOrPattern == aOriginOrPattern &&
-        op->mPersistenceType == aPersistenceType) {
-      if (op->mId == aId) {
-        op->DispatchDelayedRunnables();
-
-        mSynchronizedOps.RemoveElementAt(index);
-        return;
-      }
-
-      // If one or the other is for an origin clear, we should have matched
-      // solely on origin.
-      NS_ASSERTION(!op->mId.IsEmpty() && !aId.IsEmpty(),
-                   "Why didn't we match earlier?");
-    }
-  }
-
-  NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
+
+  for (nsRefPtr<Client>& client : mClients) {
+    client->AbortOperationsForProcess(aContentParentId);
+  }
 }
 
 nsresult
 QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType,
                                     const nsACString& aASCIIOrigin,
                                     nsIFile** aDirectory) const
 {
   nsresult rv;
@@ -2286,16 +2697,113 @@ QuotaManager::MaybeUpgradeStorageArea()
     return rv;
   }
 
   mStorageAreaInitialized = true;
 
   return NS_OK;
 }
 
+void
+QuotaManager::OpenDirectory(PersistenceType aPersistenceType,
+                            const nsACString& aGroup,
+                            const nsACString& aOrigin,
+                            bool aIsApp,
+                            Client::Type aClientType,
+                            bool aExclusive,
+                            OpenDirectoryListener* aOpenListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<DirectoryLockImpl> lock =
+    CreateDirectoryLock(Nullable<PersistenceType>(aPersistenceType),
+                        aGroup,
+                        OriginScope::FromOrigin(aOrigin),
+                        Nullable<bool>(aIsApp),
+                        Nullable<Client::Type>(aClientType),
+                        aExclusive,
+                        false,
+                        aOpenListener);
+  MOZ_ASSERT(lock);
+}
+
+void
+QuotaManager::OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType,
+                                    const OriginScope& aOriginScope,
+                                    bool aExclusive,
+                                    OpenDirectoryListener* aOpenListener)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  class MOZ_STACK_CLASS Helper final
+  {
+  public:
+    static PLDHashOperator
+    Enumerate(nsCStringHashKey* aKey, void* aClosure)
+    {
+      auto* client = static_cast<Client*>(aClosure);
+      MOZ_ASSERT(client);
+
+      client->AbortOperations(aKey->GetKey());
+
+      return PL_DHASH_NEXT;
+    }
+  };
+
+  nsRefPtr<DirectoryLockImpl> lock =
+    CreateDirectoryLock(aPersistenceType,
+                        EmptyCString(),
+                        aOriginScope,
+                        Nullable<bool>(),
+                        Nullable<Client::Type>(),
+                        aExclusive,
+                        true,
+                        aOpenListener);
+  MOZ_ASSERT(lock);
+
+  if (!aExclusive) {
+    return;
+  }
+
+  // All the locks that block this new exclusive lock need to be invalidated.
+  // We also need to notify clients to abort operations for them.
+  nsAutoTArray<nsAutoPtr<nsTHashtable<nsCStringHashKey>>,
+               Client::TYPE_MAX> origins;
+  origins.SetLength(Client::TYPE_MAX);
+
+  const nsTArray<DirectoryLockImpl*>& blockedOnLocks =
+    lock->GetBlockedOnLocks();
+
+  for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) {
+    blockedOnLock->Invalidate();
+
+    if (!blockedOnLock->IsInternal()) {
+      MOZ_ASSERT(!blockedOnLock->GetClientType().IsNull());
+      Client::Type clientType = blockedOnLock->GetClientType().Value();
+      MOZ_ASSERT(clientType < Client::TYPE_MAX);
+
+      const OriginScope& originScope = blockedOnLock->GetOriginScope();
+      MOZ_ASSERT(originScope.IsOrigin());
+      MOZ_ASSERT(!originScope.IsEmpty());
+
+      nsAutoPtr<nsTHashtable<nsCStringHashKey>>& origin = origins[clientType];
+      if (!origin) {
+        origin = new nsTHashtable<nsCStringHashKey>();
+      }
+      origin->PutEntry(originScope);
+    }
+  }
+
+  for (uint32_t index : MakeRange(uint32_t(Client::TYPE_MAX))) {
+    if (origins[index]) {
+      origins[index]->EnumerateEntries(Helper::Enumerate, mClients[index]);
+    }
+  }
+}
+
 nsresult
 QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType,
                                         const nsACString& aGroup,
                                         const nsACString& aOrigin,
                                         bool aIsApp,
                                         nsIFile** aDirectory)
 {
   AssertIsOnIOThread();
@@ -2425,21 +2933,23 @@ QuotaManager::ResetOrClearCompleted()
 
   mInitializedOrigins.Clear();
   mTemporaryStorageInitialized = false;
   mStorageAreaInitialized = false;
 
   ReleaseIOThreadObjects();
 }
 
-already_AddRefed<mozilla::dom::quota::Client>
+Client*
 QuotaManager::GetClient(Client::Type aClientType)
 {
-  nsRefPtr<Client> client = mClients.SafeElementAt(aClientType);
-  return client.forget();
+  MOZ_ASSERT(aClientType >= Client::IDB);
+  MOZ_ASSERT(aClientType < Client::TYPE_MAX);
+
+  return mClients.ElementAt(aClientType);
 }
 
 uint64_t
 QuotaManager::GetGroupLimit() const
 {
   MOZ_ASSERT(mTemporaryStorageInitialized);
 
   // To avoid one group evicting all the rest, limit the amount any one group
@@ -2453,27 +2963,24 @@ QuotaManager::GetGroupLimit() const
                             std::max<uint64_t>(x, 10 MB));
 }
 
 // static
 void
 QuotaManager::GetStorageId(PersistenceType aPersistenceType,
                            const nsACString& aOrigin,
                            Client::Type aClientType,
-                           const nsAString& aName,
                            nsACString& aDatabaseId)
 {
   nsAutoCString str;
   str.AppendInt(aPersistenceType);
   str.Append('*');
   str.Append(aOrigin);
   str.Append('*');
   str.AppendInt(aClientType);
-  str.Append('*');
-  AppendUTF16toUTF8(aName, str);
 
   aDatabaseId = str;
 }
 
 // static
 nsresult
 QuotaManager::GetInfoFromURI(nsIURI* aURI,
                              uint32_t aAppId,
@@ -2800,68 +3307,40 @@ QuotaManager::GetUsageForURI(nsIURI* aUR
   // Figure out which origin we're dealing with.
   nsCString group;
   nsCString origin;
   bool isApp;
   nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, &group, &origin,
                                &isApp);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin);
-
-  nsRefPtr<AsyncUsageRunnable> runnable =
-    new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, group, oops, isApp, aURI,
-                           aCallback);
-
-  // Put the computation runnable in the queue.
-  rv = WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
-                          runnable);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->AdvanceState();
-
-  runnable.forget(_retval);
+  nsRefPtr<GetUsageOp> op =
+    new GetUsageOp(group, origin, isApp, aURI, aCallback, aAppId,
+                   aInMozBrowserOnly);
+
+  op->RunImmediately();
+
+  op.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManager::Clear()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!gTestingEnabled) {
     NS_WARNING("Testing features are not enabled!");
     return NS_OK;
   }
 
-  OriginOrPatternString oops = OriginOrPatternString::FromNull();
-
-  nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(true);
-
-  // Put the clear runnable in the queue.
-  nsresult rv =
-    WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
-                       runnable);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->AdvanceState();
-
-  // Give the runnable some help by invalidating any storages in the way.
-  StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
-  matches.Find(mLiveStorages);
-
-  for (uint32_t index = 0; index < matches.Length(); index++) {
-    // We need to grab references to any live storages here to prevent them
-    // from dying while we invalidate them.
-    nsCOMPtr<nsIOfflineStorage> storage = matches[index];
-    storage->Invalidate();
-  }
-
-  // After everything has been invalidated the helper should be dispatched to
-  // the end of the event queue.
+  nsRefPtr<ResetOrClearOp> op = new ResetOrClearOp(/* aClear */ true);
+
+  op->RunImmediately();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManager::ClearStoragesForURI(nsIURI* aURI,
                                   uint32_t aAppId,
                                   bool aInMozBrowserOnly,
                                   const nsACString& aPersistenceType,
@@ -2889,87 +3368,38 @@ QuotaManager::ClearStoragesForURI(nsIURI
   nsCString origin;
   rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, nullptr, &origin,
                       nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString pattern;
   GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern);
 
-  // If there is a pending or running clear operation for this origin, return
-  // immediately.
-  if (IsClearOriginPending(pattern, persistenceType)) {
-    return NS_OK;
-  }
-
-  OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
-
-  // Queue up the origin clear runnable.
-  nsRefPtr<OriginClearRunnable> runnable =
-    new OriginClearRunnable(oops, persistenceType);
-
-  rv = WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->AdvanceState();
-
-  // Give the runnable some help by invalidating any storages in the way.
-  StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
-  matches.Find(mLiveStorages, pattern);
-
-  for (uint32_t index = 0; index < matches.Length(); index++) {
-    if (persistenceType.IsNull() ||
-        matches[index]->Type() == persistenceType.Value()) {
-      // We need to grab references to any live storages here to prevent them
-      // from dying while we invalidate them.
-      nsCOMPtr<nsIOfflineStorage> storage = matches[index];
-      storage->Invalidate();
-    }
-  }
-
-  // After everything has been invalidated the helper should be dispatched to
-  // the end of the event queue.
+  nsRefPtr<OriginClearOp> op =
+    new OriginClearOp(persistenceType, OriginScope::FromPattern(pattern));
+
+  op->RunImmediately();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManager::Reset()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!gTestingEnabled) {
     NS_WARNING("Testing features are not enabled!");
     return NS_OK;
   }
 
-  OriginOrPatternString oops = OriginOrPatternString::FromNull();
-
-  nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(false);
-
-  // Put the reset runnable in the queue.
-  nsresult rv =
-    WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
-                       runnable);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->AdvanceState();
-
-  // Give the runnable some help by invalidating any storages in the way.
-  StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
-  matches.Find(mLiveStorages);
-
-  for (uint32_t index = 0; index < matches.Length(); index++) {
-    // We need to grab references to any live storages here to prevent them
-    // from dying while we invalidate them.
-    nsCOMPtr<nsIOfflineStorage> storage = matches[index];
-    storage->Invalidate();
-  }
-
-  // After everything has been invalidated the helper should be dispatched to
-  // the end of the event queue.
+  nsRefPtr<ResetOrClearOp> op = new ResetOrClearOp(/* aClear */ false);
+
+  op->RunImmediately();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManager::Observe(nsISupports* aSubject,
                       const char* aTopic,
                       const char16_t* aData)
 {
@@ -3010,37 +3440,34 @@ QuotaManager::Observe(nsISupports* aSubj
       if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
         NS_WARNING("Failed to dispatch runnable!");
       }
 
       // Make sure to join with our IO thread.
       if (NS_FAILED(mIOThread->Shutdown())) {
         NS_WARNING("Failed to shutdown IO thread!");
       }
+
+      for (nsRefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
+        lock->Invalidate();
+      }
     }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
     NS_ASSERTION(IsMainProcess(), "Should only happen in the main process!");
 
     NS_WARNING("Some storage operations are taking longer than expected "
                "during shutdown and will be aborted!");
 
-    // Grab all live storages, for all origins.
-    StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 50> > liveStorages;
-    liveStorages.Find(mLiveStorages);
-
-    // Invalidate them all.
-    if (!liveStorages.IsEmpty()) {
-      uint32_t count = liveStorages.Length();
-      for (uint32_t index = 0; index < count; index++) {
-        liveStorages[index]->Invalidate();
-      }
+    // Abort all operations.
+    for (nsRefPtr<Client>& client : mClients) {
+      client->AbortOperations(NullCString());
     }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) {
     nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
       do_QueryInterface(aSubject);
@@ -3068,37 +3495,35 @@ QuotaManager::Observe(nsISupports* aSubj
   }
 
   NS_NOTREACHED("Unknown topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
 uint64_t
 QuotaManager::LockedCollectOriginsForEviction(
-                                            uint64_t aMinSizeToBeFreed,
-                                            nsTArray<OriginInfo*>& aOriginInfos)
+                                       uint64_t aMinSizeToBeFreed,
+                                       nsTArray<nsRefPtr<DirectoryLock>>& aLocks)
 {
   mQuotaMutex.AssertCurrentThreadOwns();
 
   nsRefPtr<CollectOriginsHelper> helper =
     new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
 
   // Unlock while calling out to XPCOM (code behind the dispatch method needs
   // to acquire its own lock which can potentially lead to a deadlock and it
   // also calls an observer that can do various stuff like IO, so it's better
   // to not hold our mutex while that happens).
   {
     MutexAutoUnlock autoUnlock(mQuotaMutex);
 
-    if (NS_FAILED(NS_DispatchToMainThread(helper))) {
-      NS_WARNING("Failed to dispatch to the main thread!");
-    }
-  }
-
-  return helper->BlockAndReturnOriginsForEviction(aOriginInfos);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(helper)));
+  }
+
+  return helper->BlockAndReturnOriginsForEviction(aLocks);
 }
 
 void
 QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
                                          const nsACString& aGroup,
                                          const nsACString& aOrigin)
 {
   mQuotaMutex.AssertCurrentThreadOwns();
@@ -3121,141 +3546,32 @@ QuotaManager::LockedRemoveQuotaForOrigin
       if (!pair->LockedHasGroupInfos()) {
         mGroupInfoPairs.Remove(aGroup);
       }
     }
   }
 }
 
 nsresult
-QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern,
-                                     Nullable<PersistenceType> aPersistenceType,
-                                     nsIRunnable* aRunnable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aRunnable, "Need a runnable!");
-
-  // Find the right SynchronizedOp.
-  SynchronizedOp* op =
-    FindSynchronizedOp(aPattern, aPersistenceType, EmptyCString());
-
-  NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
-  NS_ASSERTION(!op->mRunnable, "SynchronizedOp already has a runnable?!?");
-
-  ArrayCluster<nsIOfflineStorage*> liveStorages;
-
-  StorageMatcher<ArrayCluster<nsIOfflineStorage*> > matches;
-  if (aPattern.IsVoid()) {
-    matches.Find(mLiveStorages);
-  }
-  else {
-    matches.Find(mLiveStorages, aPattern);
-  }
-
-  // We want *all* storages that match the given persistence type, even those
-  // that are closed, when we're going to clear the origin.
-  if (!matches.IsEmpty()) {
-    for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
-      nsTArray<nsIOfflineStorage*>& storages = matches.ArrayAt(i);
-      for (uint32_t j = 0; j < storages.Length(); j++) {
-        nsIOfflineStorage* storage = storages[j];
-        if (aPersistenceType.IsNull() ||
-            aPersistenceType.Value() == storage->Type()) {
-          storage->Invalidate();
-          liveStorages[i].AppendElement(storage);
-        }
-      }
-    }
-  }
-
-  op->mRunnable = aRunnable;
-
-  nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
-    new WaitForTransactionsToFinishRunnable(op);
-
-  if (!liveStorages.IsEmpty()) {
-    // 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()) {
-        runnable->AddRun();
-
-        client->WaitForStoragesToComplete(liveStorages[index], runnable);
-      }
-    }
-  }
-
-  nsresult rv = runnable->Run();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-SynchronizedOp*
-QuotaManager::FindSynchronizedOp(const nsACString& aPattern,
-                                 Nullable<PersistenceType> aPersistenceType,
-                                 const nsACString& aId)
-{
-  for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) {
-    const nsAutoPtr<SynchronizedOp>& currentOp = mSynchronizedOps[index];
-    if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) &&
-        (currentOp->mPersistenceType.IsNull() ||
-         currentOp->mPersistenceType == aPersistenceType) &&
-        (currentOp->mId.IsEmpty() || currentOp->mId == aId)) {
-      return currentOp;
-    }
-  }
-
-  return nullptr;
-}
-
-nsresult
 QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aAppId != kUnknownAppId, "Bad appId!");
 
   // This only works from the main process.
   NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
 
   nsAutoCString pattern;
   GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern);
 
-  // Clear both temporary and persistent storages.
-  Nullable<PersistenceType> persistenceType;
-
-  // If there is a pending or running clear operation for this app, return
-  // immediately.
-  if (IsClearOriginPending(pattern, persistenceType)) {
-    return NS_OK;
-  }
-
-  OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
-
-  // Queue up the origin clear runnable.
-  nsRefPtr<OriginClearRunnable> runnable =
-    new OriginClearRunnable(oops, persistenceType);
-
-  nsresult rv =
-    WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->AdvanceState();
-
-  // Give the runnable some help by invalidating any storages in the way.
-  StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
-  matches.Find(mLiveStorages, pattern);
-
-  for (uint32_t index = 0; index < matches.Length(); index++) {
-    // We need to grab references here to prevent the storage from dying while
-    // we invalidate it.
-    nsCOMPtr<nsIOfflineStorage> storage = matches[index];
-    storage->Invalidate();
-  }
+  nsRefPtr<OriginClearOp> op =
+    new OriginClearOp(Nullable<PersistenceType>(),
+                      OriginScope::FromPattern(pattern));
+
+  op->RunImmediately();
 
   return NS_OK;
 }
 
 // static
 PLDHashOperator
 QuotaManager::GetOriginsExceedingGroupLimit(const nsACString& aKey,
                                             GroupInfoPair* aValue,
@@ -3405,210 +3721,21 @@ QuotaManager::CheckTemporaryStorageLimit
 #ifdef DEBUG
       doomedOriginInfos[index] = nullptr;
 #endif
 
       doomedOrigins.AppendElement(OriginParams(persistenceType, origin, isApp));
     }
   }
 
-  for (uint32_t index = 0; index < doomedOrigins.Length(); index++) {
-    const OriginParams& doomedOrigin = doomedOrigins[index];
-
-    OriginClearCompleted(
-                        doomedOrigin.mPersistenceType,
-                        OriginOrPatternString::FromOrigin(doomedOrigin.mOrigin),
-                        doomedOrigin.mIsApp);
-  }
-}
-
-// static
-PLDHashOperator
-QuotaManager::AddLiveStorageOrigins(const nsACString& aKey,
-                                    nsTArray<nsIOfflineStorage*>* aValue,
-                                    void* aUserArg)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
-  NS_ASSERTION(aValue, "Null pointer!");
-  NS_ASSERTION(aUserArg, "Null pointer!");
-
-  OriginCollection& collection = *static_cast<OriginCollection*>(aUserArg);
-
-  if (!collection.ContainsOrigin(aKey)) {
-    collection.AddOrigin(aKey);
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-// static
-PLDHashOperator
-QuotaManager::GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
-                                                 GroupInfoPair* aValue,
-                                                 void* aUserArg)
-{
-  NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
-  NS_ASSERTION(aValue, "Null pointer!");
-  NS_ASSERTION(aUserArg, "Null pointer!");
-
-  InactiveOriginsInfo* info = static_cast<InactiveOriginsInfo*>(aUserArg);
-
-  nsRefPtr<GroupInfo> groupInfo =
-    aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
-  if (groupInfo) {
-    nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
-
-    for (uint32_t i = 0; i < originInfos.Length(); i++) {
-      OriginInfo* originInfo = originInfos[i];
-
-      MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType,
-                                      originInfo->mIsApp));
-
-      if (!info->persistentCollection.ContainsOrigin(originInfo->mOrigin)) {
-        NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
-                     "Inactive origin shouldn't have open files!");
-        info->origins.AppendElement(originInfo);
-      }
-    }
-  }
-
-  groupInfo = aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
-  if (groupInfo) {
-    nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
-
-    for (uint32_t i = 0; i < originInfos.Length(); i++) {
-      OriginInfo* originInfo = originInfos[i];
-
-      MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType,
-                                      originInfo->mIsApp));
-
-      if (!info->temporaryCollection.ContainsOrigin(originInfo->mOrigin)) {
-        NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
-                     "Inactive origin shouldn't have open files!");
-        info->origins.AppendElement(originInfo);
-      }
-    }
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-uint64_t
-QuotaManager::CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
-                                        nsTArray<OriginInfo*>& aOriginInfos)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  // Collect active origins first.
-  OriginCollection temporaryOriginCollection;
-  OriginCollection defaultOriginCollection;
-
-  // Add patterns and origins that have running or pending synchronized ops.
-  // (add patterns first to reduce redundancy in the origin collection).
-  uint32_t index;
-  for (index = 0; index < mSynchronizedOps.Length(); index++) {
-    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
-
-    const OriginOrPatternString& originOrPattern = op->mOriginOrPattern;
-
-    if (!originOrPattern.IsPattern()) {
-      continue;
-    }
-
-    Nullable<PersistenceType>& persistenceType = op->mPersistenceType;
-
-    if (persistenceType.IsNull()) {
-      temporaryOriginCollection.AddPattern(originOrPattern);
-      defaultOriginCollection.AddPattern(originOrPattern);
-    } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
-      temporaryOriginCollection.AddPattern(originOrPattern);
-    } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
-      defaultOriginCollection.AddPattern(originOrPattern);
-    }
-  }
-
-  for (index = 0; index < mSynchronizedOps.Length(); index++) {
-    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
-
-    const OriginOrPatternString& originOrPattern = op->mOriginOrPattern;
-
-    if (!originOrPattern.IsOrigin()) {
-      continue;
-    }
-
-    Nullable<PersistenceType>& persistenceType = op->mPersistenceType;
-
-    if (persistenceType.IsNull()) {
-      temporaryOriginCollection.AddOrigin(originOrPattern);
-      defaultOriginCollection.AddOrigin(originOrPattern);
-    } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
-      temporaryOriginCollection.AddOrigin(originOrPattern);
-    } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) {
-      defaultOriginCollection.AddOrigin(originOrPattern);
-    }
-  }
-
-  // Add origins that have live temporary storages.
-  mTemporaryLiveStorageTable.EnumerateRead(AddLiveStorageOrigins,
-                                           &temporaryOriginCollection);
-
-  // Add origins that have live persistent storages.
-  mDefaultLiveStorageTable.EnumerateRead(AddLiveStorageOrigins,
-                                         &defaultOriginCollection);
-
-  // Enumerate inactive origins. This must be protected by the mutex.
-  nsTArray<OriginInfo*> inactiveOrigins;
-  {
-    InactiveOriginsInfo info(temporaryOriginCollection,
-                             defaultOriginCollection,
-                             inactiveOrigins);
-    MutexAutoLock lock(mQuotaMutex);
-    mGroupInfoPairs.EnumerateRead(GetInactiveTemporaryStorageOrigins, &info);
-  }
-
-  // We now have a list of all inactive origins. So it's safe to sort the list
-  // and calculate available size without holding the lock.
-
-  // Sort by the origin access time.
-  inactiveOrigins.Sort(OriginInfoLRUComparator());
-
-  // Create a list of inactive and the least recently used origins
-  // whose aggregate size is greater or equals the minimal size to be freed.
-  uint64_t sizeToBeFreed = 0;
-  for(index = 0; index < inactiveOrigins.Length(); index++) {
-    if (sizeToBeFreed >= aMinSizeToBeFreed) {
-      inactiveOrigins.TruncateLength(index);
-      break;
-    }
-
-    sizeToBeFreed += inactiveOrigins[index]->mUsage;
-  }
-
-  if (sizeToBeFreed >= aMinSizeToBeFreed) {
-    // Success, add synchronized ops for these origins, so any other
-    // operations for them will be delayed (until origin eviction is finalized).
-
-    for(index = 0; index < inactiveOrigins.Length(); index++) {
-      OriginInfo* inactiveOrigin = inactiveOrigins[index];
-
-      OriginOrPatternString oops =
-        OriginOrPatternString::FromOrigin(inactiveOrigin->mOrigin);
-
-      Nullable<PersistenceType> persistenceType =
-        Nullable<PersistenceType>(inactiveOrigin->mGroupInfo->mPersistenceType);
-
-      AddSynchronizedOp(oops, persistenceType);
-    }
-
-    inactiveOrigins.SwapElements(aOriginInfos);
-    return sizeToBeFreed;
-  }
-
-  return 0;
+  for (const OriginParams& doomedOrigin : doomedOrigins) {
+    OriginClearCompleted(doomedOrigin.mPersistenceType,
+                         doomedOrigin.mOrigin,
+                         doomedOrigin.mIsApp);
+  }
 }
 
 void
 QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType,
                                    const nsACString& aOrigin)
 {
   nsCOMPtr<nsIFile> directory;
   nsresult rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
@@ -3620,44 +3747,27 @@ QuotaManager::DeleteFilesForOrigin(Persi
       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
     // This should never fail if we've closed all storage connections
     // correctly...
     NS_ERROR("Failed to remove directory!");
   }
 }
 
 void
-QuotaManager::FinalizeOriginEviction(nsTArray<OriginParams>& aOrigins)
+QuotaManager::FinalizeOriginEviction(nsTArray<nsRefPtr<DirectoryLock>>& aLocks)
 {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-  nsRefPtr<FinalizeOriginEvictionRunnable> runnable =
-    new FinalizeOriginEvictionRunnable(aOrigins);
-
-  nsresult rv = IsOnIOThread() ? runnable->RunImmediately()
-                               : runnable->Dispatch();
-  NS_ENSURE_SUCCESS_VOID(rv);
-}
-
-void
-QuotaManager::SaveOriginAccessTime(PersistenceType aPersistenceType,
-                                   const nsACString& aOrigin,
-                                   int64_t aTimestamp)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (QuotaManager::IsShuttingDown()) {
-    return;
-  }
-
-  nsRefPtr<SaveOriginAccessTimeRunnable> runnable =
-    new SaveOriginAccessTimeRunnable(aPersistenceType, aOrigin, aTimestamp);
-
-  if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
-    NS_WARNING("Failed to dispatch runnable!");
+  nsRefPtr<FinalizeOriginEvictionOp> op =
+    new FinalizeOriginEvictionOp(aLocks);
+
+  if (IsOnIOThread()) {
+    op->RunOnIOThreadImmediately();
+  } else {
+    op->Dispatch();
   }
 }
 
 void
 QuotaManager::GetOriginPatternString(uint32_t aAppId,
                                      MozBrowserPatternFlag aBrowserFlag,
                                      const nsACString& aOrigin,
                                      nsAutoCString& _retval)
@@ -3694,225 +3804,564 @@ QuotaManager::GetOriginPatternString(uin
                  "Origin doesn't match parameters!");
   }
 #endif
 
   _retval = aOrigin;
 }
 
 auto
-QuotaManager::GetLiveStorageTable(PersistenceType aPersistenceType)
-  -> LiveStorageTable&
+QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType)
+  -> DirectoryLockTable&
 {
   switch (aPersistenceType) {
     case PERSISTENCE_TYPE_TEMPORARY:
-      return mTemporaryLiveStorageTable;
+      return mTemporaryDirectoryLockTable;
     case PERSISTENCE_TYPE_DEFAULT:
-      return mDefaultLiveStorageTable;
+      return mDefaultDirectoryLockTable;
 
     case PERSISTENCE_TYPE_PERSISTENT:
     case PERSISTENCE_TYPE_INVALID:
     default:
       MOZ_CRASH("Bad persistence type value!");
   }
 }
 
-SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
-                               Nullable<PersistenceType> aPersistenceType,
-                               const nsACString& aId)
-: mOriginOrPattern(aOriginOrPattern), mPersistenceType(aPersistenceType),
-  mId(aId)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  MOZ_COUNT_CTOR(SynchronizedOp);
-}
-
-SynchronizedOp::~SynchronizedOp()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  MOZ_COUNT_DTOR(SynchronizedOp);
-}
-
-bool
-SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  if (aExistingOp.mOriginOrPattern.IsNull() || mOriginOrPattern.IsNull()) {
-    return true;
-  }
-
-  bool match;
-
-  if (aExistingOp.mOriginOrPattern.IsOrigin()) {
-    if (mOriginOrPattern.IsOrigin()) {
-      match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern);
-    }
-    else {
-      match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern);
-    }
-  }
-  else if (mOriginOrPattern.IsOrigin()) {
-    match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
-  }
-  else {
-    match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) ||
-            PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
-  }
-
-  // If the origins don't match, the second can proceed.
-  if (!match) {
-    return false;
-  }
-
-  // If the origins match but the persistence types are different, the second
-  // can proceed.
-  if (!aExistingOp.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
-      aExistingOp.mPersistenceType.Value() != mPersistenceType.Value()) {
-    return false;
-  }
-
-  // If the origins and the ids match, the second must wait.
-  if (aExistingOp.mId == mId) {
-    return true;
-  }
-
-  // Waiting is required if either one corresponds to an origin clearing
-  // (an empty Id).
-  if (aExistingOp.mId.IsEmpty() || mId.IsEmpty()) {
-    return true;
-  }
-
-  // Otherwise, things for the same origin but different storages can proceed
-  // independently.
-  return false;
-}
-
-void
-SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(mDelayedRunnables.IsEmpty() || mId.IsEmpty(),
-               "Only ClearOrigin operations can delay multiple runnables!");
-
-  mDelayedRunnables.AppendElement(aRunnable);
-}
-
-void
-SynchronizedOp::DispatchDelayedRunnables()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!mRunnable, "Any runnable should be gone by now!");
-
-  uint32_t count = mDelayedRunnables.Length();
-  for (uint32_t index = 0; index < count; index++) {
-    NS_DispatchToCurrentThread(mDelayedRunnables[index]);
-  }
-
-  mDelayedRunnables.Clear();
-}
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
 
 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
                                            uint64_t aMinSizeToBeFreed)
 : mMinSizeToBeFreed(aMinSizeToBeFreed),
   mMutex(aMutex),
   mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
   mSizeToBeFreed(0),
   mWaiting(true)
 {
   MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
   mMutex.AssertCurrentThreadOwns();
 }
 
 int64_t
 CollectOriginsHelper::BlockAndReturnOriginsForEviction(
-                                            nsTArray<OriginInfo*>& aOriginInfos)
+                                      nsTArray<nsRefPtr<DirectoryLock>>& aLocks)
 {
   MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
   mMutex.AssertCurrentThreadOwns();
 
   while (mWaiting) {
     mCondVar.Wait();
   }
 
-  mOriginInfos.SwapElements(aOriginInfos);
+  mLocks.SwapElements(aLocks);
   return mSizeToBeFreed;
 }
 
 NS_IMETHODIMP
 CollectOriginsHelper::Run()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
   // We use extra stack vars here to avoid race detector warnings (the same
   // memory accessed with and without the lock held).
-  nsTArray<OriginInfo*> originInfos;
+  nsTArray<nsRefPtr<DirectoryLock>> locks;
   uint64_t sizeToBeFreed =
-    quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, originInfos);
+    quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks);
 
   MutexAutoLock lock(mMutex);
 
   NS_ASSERTION(mWaiting, "Huh?!");
 
-  mOriginInfos.SwapElements(originInfos);
+  mLocks.SwapElements(locks);
   mSizeToBeFreed = sizeToBeFreed;
   mWaiting = false;
   mCondVar.Notify();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+OriginOperationBase::Run()
+{
+  nsresult rv;
+
+  switch (mState) {
+    case State_Initial: {
+      rv = Open();
+      break;
+    }
+
+    case State_DirectoryOpenPending: {
+      rv = DirectoryOpen();
+      break;
+    }
+
+    case State_DirectoryWorkOpen: {
+      rv = DirectoryWork();
+      break;
+    }
+
+    case State_UnblockingOpen: {
+      UnblockOpen();
+      return NS_OK;
+    }
+
+    default:
+      MOZ_CRASH("Bad state!");
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
+    Finish(rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+OriginOperationBase::DirectoryOpen()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (NS_WARN_IF(!quotaManager)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Must set this before dispatching otherwise we will race with the IO thread.
+  AdvanceState();
+
+  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 void
-OriginClearRunnable::DeleteFiles(QuotaManager* aQuotaManager,
-                                 PersistenceType aPersistenceType)
+OriginOperationBase::Finish(nsresult aResult)
+{
+  if (NS_SUCCEEDED(mResultCode)) {
+    mResultCode = aResult;
+  }
+
+  // Must set mState before dispatching otherwise we will race with the main
+  // thread.
+  mState = State_UnblockingOpen;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+nsresult
+OriginOperationBase::DirectoryWork()
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(mState == State_DirectoryWorkOpen);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (NS_WARN_IF(!quotaManager)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = DoDirectoryWork(quotaManager);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Must set mState before dispatching otherwise we will race with the main
+  // thread.
+  AdvanceState();
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, nsRunnable)
+
+nsresult
+NormalOriginOperationBase::Open()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_Initial);
+
+  if (QuotaManager::IsShuttingDown()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (NS_WARN_IF(!quotaManager)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  AdvanceState();
+
+  quotaManager->OpenDirectoryInternal(mPersistenceType,
+                                      mOriginScope,
+                                      mExclusive,
+                                      this);
+
+  return NS_OK;
+}
+
+void
+NormalOriginOperationBase::UnblockOpen()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_UnblockingOpen);
+
+  SendResults();
+
+  mDirectoryLock = nullptr;
+
+  AdvanceState();
+}
+
+void
+NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aLock);
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  nsresult rv = DirectoryOpen();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Finish(rv);
+    return;
+  }
+}
+
+void
+NormalOriginOperationBase::DirectoryLockFailed()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  Finish(NS_ERROR_FAILURE);
+}
+
+nsresult
+SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(!mPersistenceType.IsNull());
+  MOZ_ASSERT(mOriginScope.IsOrigin());
+
+  PROFILER_LABEL("Quota", "SaveOriginAccessTimeOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv =
+    aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
+                                         mOriginScope,
+                                         getter_AddRefs(directory));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIBinaryOutputStream> stream;
+  rv = GetDirectoryMetadataOutputStream(directory, kUpdateFileFlag,
+                                        getter_AddRefs(stream));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The origin directory may not exist anymore.
+  if (stream) {
+    rv = stream->Write64(mTimestamp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+GetUsageOp::GetUsageOp(const nsACString& aGroup,
+                       const nsACString& aOrigin,
+                       bool aIsApp,
+                       nsIURI* aURI,
+                       nsIUsageCallback* aCallback,
+                       uint32_t aAppId,
+                       bool aInMozBrowserOnly)
+  : NormalOriginOperationBase(Nullable<PersistenceType>(),
+                              OriginScope::FromOrigin(aOrigin),
+                              /* aExclusive */ false)
+  , mGroup(aGroup)
+  , mURI(aURI)
+  , mCallback(aCallback)
+  , mAppId(aAppId)
+  , mIsApp(aIsApp)
+  , mInMozBrowserOnly(aInMozBrowserOnly)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aGroup.IsEmpty());
+  MOZ_ASSERT(!aOrigin.IsEmpty());
+  MOZ_ASSERT(aURI);
+  MOZ_ASSERT(aCallback);
+}
+
+nsresult
+GetUsageOp::AddToUsage(QuotaManager* aQuotaManager,
+                       PersistenceType aPersistenceType)
+{
+  AssertIsOnIOThread();
+
+  nsCOMPtr<nsIFile> directory;
+  nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType,
+                                                     mOriginScope,
+                                                     getter_AddRefs(directory));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool exists;
+  rv = directory->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If the directory exists then enumerate all the files inside, adding up
+  // the sizes to get the final usage statistic.
+  if (exists && !mUsageInfo.Canceled()) {
+    bool initialized;
+
+    if (IsTreatedAsPersistent(aPersistenceType, mIsApp)) {
+      nsCString originKey = OriginKey(aPersistenceType, mOriginScope);
+      initialized = aQuotaManager->IsOriginInitialized(originKey);
+    } else {
+      initialized = aQuotaManager->IsTemporaryStorageInitialized();
+    }
+
+    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT && !initialized) {
+      rv = MaybeUpgradeOriginDirectory(directory);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    nsCOMPtr<nsISimpleEnumerator> entries;
+    rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    bool hasMore;
+    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
+           hasMore && !mUsageInfo.Canceled()) {
+      nsCOMPtr<nsISupports> entry;
+      rv = entries->GetNext(getter_AddRefs(entry));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+      NS_ENSURE_TRUE(file, NS_NOINTERFACE);
+
+      nsString leafName;
+      rv = file->GetLeafName(leafName);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
+          leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
+        continue;
+      }
+
+      if (!initialized) {
+        bool isDirectory;
+        rv = file->IsDirectory(&isDirectory);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (!isDirectory) {
+          NS_WARNING("Unknown file found!");
+          return NS_ERROR_UNEXPECTED;
+        }
+      }
+
+      if (MaybeRemoveCorruptDirectory(leafName, file)) {
+        continue;
+      }
+
+      Client::Type clientType;
+      rv = Client::TypeFromText(leafName, clientType);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Unknown directory found!");
+        if (!initialized) {
+          return NS_ERROR_UNEXPECTED;
+        }
+        continue;
+      }
+
+      Client* client = aQuotaManager->GetClient(clientType);
+      MOZ_ASSERT(client);
+
+      if (initialized) {
+        rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOriginScope,
+                                       &mUsageInfo);
+      }
+      else {
+        rv = client->InitOrigin(aPersistenceType, mGroup, mOriginScope,
+                                &mUsageInfo);
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+
+  PROFILER_LABEL("Quota", "GetUsageOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  // Add all the persistent/temporary/default storage files we care about.
+  nsresult rv;
+  for (const PersistenceType type : kAllPersistenceTypes) {
+    rv = AddToUsage(aQuotaManager, type);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+GetUsageOp::SendResults()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Call the callback unless we were canceled.
+  if (!mUsageInfo.Canceled()) {
+    // XXX Implement better error reporting here, bug 1170019.
+    if (NS_FAILED(mResultCode)) {
+      mUsageInfo.ResetUsage();
+    }
+
+    mCallback->OnUsageResult(mURI, mUsageInfo.TotalUsage(), mUsageInfo.FileUsage(), mAppId,
+                             mInMozBrowserOnly);
+  }
+
+  // Clean up.
+  mURI = nullptr;
+  mCallback = nullptr;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(GetUsageOp,
+                            NormalOriginOperationBase,
+                            nsIQuotaRequest)
+
+NS_IMETHODIMP
+GetUsageOp::Cancel()
+{
+  return mUsageInfo.Cancel();
+}
+
+void
+ResetOrClearOp::DeleteFiles(QuotaManager* aQuotaManager)
 {
   AssertIsOnIOThread();
   NS_ASSERTION(aQuotaManager, "Don't pass me null!");
 
   nsresult rv;
 
   nsCOMPtr<nsIFile> directory =
     do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS_VOID(rv);
 
+  rv = directory->InitWithPath(aQuotaManager->GetStoragePath());
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  rv = directory->Remove(true);
+  if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+      rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
+    // This should never fail if we've closed all storage connections
+    // correctly...
+    NS_ERROR("Failed to remove directory!");
+  }
+}
+
+nsresult
+ResetOrClearOp::DoDirectoryWork(QuotaManager* aQuotaManager)
+{
+  AssertIsOnIOThread();
+
+  PROFILER_LABEL("Quota", "ResetOrClearOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  if (mClear) {
+    DeleteFiles(aQuotaManager);
+  }
+
+  aQuotaManager->RemoveQuota();
+
+  aQuotaManager->ResetOrClearCompleted();
+
+  return NS_OK;
+}
+
+void
+OriginClearOp::DeleteFiles(QuotaManager* aQuotaManager,
+                           PersistenceType aPersistenceType)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aQuotaManager);
+
+  nsresult rv;
+
+  nsCOMPtr<nsIFile> directory =
+    do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
   rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
-  NS_ENSURE_SUCCESS_VOID(rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   nsCOMPtr<nsISimpleEnumerator> entries;
-  if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) ||
-      !entries) {
+  if (NS_WARN_IF(NS_FAILED(
+        directory->GetDirectoryEntries(getter_AddRefs(entries)))) || !entries) {
     return;
   }
 
-  nsCString originSanitized(mOriginOrPattern);
+  nsCString originSanitized(mOriginScope);
   SanitizeOriginString(originSanitized);
 
   bool hasMore;
   while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
     nsCOMPtr<nsISupports> entry;
     rv = entries->GetNext(getter_AddRefs(entry));
-    NS_ENSURE_SUCCESS_VOID(rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
 
     nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
-    NS_ASSERTION(file, "Don't know what this is!");
+    MOZ_ASSERT(file);
+
+    bool isDirectory;
+    rv = file->IsDirectory(&isDirectory);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
 
     nsString leafName;
     rv = file->GetLeafName(leafName);
-    NS_ENSURE_SUCCESS_VOID(rv);
-
-    bool isDirectory;
-    rv = file->IsDirectory(&isDirectory);
-    NS_ENSURE_SUCCESS_VOID(rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
 
     if (!isDirectory) {
       if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
-        NS_WARNING("Something in the IndexedDB directory that doesn't belong!");
+        QM_WARNING("Something (%s) in the repository that doesn't belong!",
+                   NS_ConvertUTF16toUTF8(leafName).get());
       }
       continue;
     }
 
     // Skip storages for other apps.
     if (!PatternMatchesOrigin(originSanitized,
                               NS_ConvertUTF16toUTF8(leafName))) {
       continue;
@@ -3950,583 +4399,94 @@ OriginClearRunnable::DeleteFiles(QuotaMa
     }
 
     if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
       aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
     }
 
     aQuotaManager->OriginClearCompleted(aPersistenceType, origin, isApp);
   }
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(OriginClearRunnable, nsRunnable)
-
-NS_IMETHODIMP
-OriginClearRunnable::Run()
-{
-  PROFILER_LABEL("OriginClearRunnable", "Run",
-    js::ProfileEntry::Category::OTHER);
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  switch (mCallbackState) {
-    case Pending: {
-      NS_NOTREACHED("Should never get here without being dispatched!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    case OpenAllowed: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      AdvanceState();
-
-      // Now we have to wait until the thread pool is done with all of the
-      // storages we care about.
-      nsresult rv =
-        quotaManager->AcquireExclusiveAccess(mOriginOrPattern, mPersistenceType,
-                                             this);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      return NS_OK;
-    }
-
-    case IO: {
-      AssertIsOnIOThread();
-
-      AdvanceState();
-
-      if (mPersistenceType.IsNull()) {
-        DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
-        DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
-        DeleteFiles(quotaManager, PERSISTENCE_TYPE_DEFAULT);
-      } else {
-        DeleteFiles(quotaManager, mPersistenceType.Value());
-      }
-
-      // Now dispatch back to the main thread.
-      if (NS_FAILED(NS_DispatchToMainThread(this))) {
-        NS_WARNING("Failed to dispatch to main thread!");
-        return NS_ERROR_FAILURE;
-      }
-
-      return NS_OK;
-    }
-
-    case Complete: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      // Tell the QuotaManager that we're done.
-      quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, mPersistenceType,
-                                            EmptyCString());
-
-      return NS_OK;
-    }
-
-    default:
-      NS_ERROR("Unknown state value!");
-      return NS_ERROR_UNEXPECTED;
-  }
-
-  NS_NOTREACHED("Should never get here!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId,
-                                       bool aInMozBrowserOnly,
-                                       const nsACString& aGroup,
-                                       const OriginOrPatternString& aOrigin,
-                                       bool aIsApp,
-                                       nsIURI* aURI,
-                                       nsIUsageCallback* aCallback)
-: mURI(aURI),
-  mCallback(aCallback),
-  mAppId(aAppId),
-  mGroup(aGroup),
-  mOrigin(aOrigin),
-  mCallbackState(Pending),
-  mInMozBrowserOnly(aInMozBrowserOnly),
-  mIsApp(aIsApp)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aURI, "Null pointer!");
-  NS_ASSERTION(!aGroup.IsEmpty(), "Empty group!");
-  NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!");
-  NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
-  NS_ASSERTION(aCallback, "Null pointer!");
-}
-
-nsresult
-AsyncUsageRunnable::TakeShortcut()
-{
-  NS_ASSERTION(mCallbackState == Pending, "Huh?");
-
-  nsresult rv = NS_DispatchToCurrentThread(this);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mCallbackState = Shortcut;
-  return NS_OK;
+
 }
 
 nsresult
-AsyncUsageRunnable::RunInternal()
-{
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  nsresult rv;
-
-  switch (mCallbackState) {
-    case Pending: {
-      NS_NOTREACHED("Should never get here without being dispatched!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    case OpenAllowed: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      AdvanceState();
-
-      rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to dispatch to the IO thread!");
-      }
-
-      return NS_OK;
-    }
-
-    case IO: {
-      AssertIsOnIOThread();
-
-      AdvanceState();
-
-      // Add all the persistent storage files we care about.
-      rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Add all the temporary storage files we care about.
-      rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Add all the default storage files we care about.
-      rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_DEFAULT);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Run dispatches us back to the main thread.
-      return NS_OK;
-    }
-
-    case Complete: // Fall through
-    case Shortcut: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      // Call the callback unless we were canceled.
-      if (!mCanceled) {
-        mCallback->OnUsageResult(mURI, TotalUsage(), FileUsage(), mAppId,
-                                 mInMozBrowserOnly);
-      }
-
-      // Clean up.
-      mURI = nullptr;
-      mCallback = nullptr;
-
-      // And tell the QuotaManager that we're done.
-      if (mCallbackState == Complete) {
-        quotaManager->AllowNextSynchronizedOp(mOrigin,
-                                              Nullable<PersistenceType>(),
-                                              EmptyCString());
-      }
-
-      return NS_OK;
-    }
-
-    default:
-      NS_ERROR("Unknown state value!");
-      return NS_ERROR_UNEXPECTED;
-  }
-
-  NS_NOTREACHED("Should never get here!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-nsresult
-AsyncUsageRunnable::AddToUsage(QuotaManager* aQuotaManager,
-                               PersistenceType aPersistenceType)
+OriginClearOp::DoDirectoryWork(QuotaManager* aQuotaManager)
 {
   AssertIsOnIOThread();
 
-  nsCOMPtr<nsIFile> directory;
-  nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, mOrigin,
-                                                     getter_AddRefs(directory));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  bool exists;
-  rv = directory->Exists(&exists);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // If the directory exists then enumerate all the files inside, adding up
-  // the sizes to get the final usage statistic.
-  if (exists && !mCanceled) {
-    bool initialized;
-
-    if (IsTreatedAsPersistent(aPersistenceType, mIsApp)) {
-      nsCString originKey = OriginKey(aPersistenceType, mOrigin);
-      initialized = aQuotaManager->mInitializedOrigins.Contains(originKey);
-
-    } else {
-      initialized = aQuotaManager->mTemporaryStorageInitialized;
-    }
-
-    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT && !initialized) {
-      rv = MaybeUpgradeOriginDirectory(directory);
-      NS_ENSURE_SUCCESS(rv, rv);
+  PROFILER_LABEL("Quota", "OriginClearOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  if (mPersistenceType.IsNull()) {
+    for (const PersistenceType type : kAllPersistenceTypes) {
+      DeleteFiles(aQuotaManager, type);
     }
-
-    nsCOMPtr<nsISimpleEnumerator> entries;
-    rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    bool hasMore;
-    while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
-           hasMore && !mCanceled) {
-      nsCOMPtr<nsISupports> entry;
-      rv = entries->GetNext(getter_AddRefs(entry));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
-      NS_ENSURE_TRUE(file, NS_NOINTERFACE);
-
-      nsString leafName;
-      rv = file->GetLeafName(leafName);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
-          leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
-        continue;
-      }
-
-      if (!initialized) {
-        bool isDirectory;
-        rv = file->IsDirectory(&isDirectory);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (!isDirectory) {
-          NS_WARNING("Unknown file found!");
-          return NS_ERROR_UNEXPECTED;
-        }
-      }
-
-      if (MaybeRemoveCorruptDirectory(leafName, file)) {
-        continue;
-      }
-
-      Client::Type clientType;
-      rv = Client::TypeFromText(leafName, clientType);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Unknown directory found!");
-        if (!initialized) {
-          return NS_ERROR_UNEXPECTED;
-        }
-        continue;
-      }
-
-      nsRefPtr<Client>& client = aQuotaManager->mClients[clientType];
-
-      if (initialized) {
-        rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOrigin, this);
-      }
-      else {
-        rv = client->InitOrigin(aPersistenceType, mGroup, mOrigin, this);
-      }
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS_INHERITED(AsyncUsageRunnable, nsRunnable, nsIQuotaRequest)
-
-NS_IMETHODIMP
-AsyncUsageRunnable::Run()
-{
-  PROFILER_LABEL("Quota", "AsyncUsageRunnable::Run",
-    js::ProfileEntry::Category::OTHER);
-
-  nsresult rv = RunInternal();
-
-  if (!NS_IsMainThread()) {
-    if (NS_FAILED(rv)) {
-      ResetUsage();
-    }
-
-    if (NS_FAILED(NS_DispatchToMainThread(this))) {
-      NS_WARNING("Failed to dispatch to main thread!");
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-AsyncUsageRunnable::Cancel()
-{
-  if (mCanceled.exchange(true)) {
-    NS_WARNING("Canceled more than once?!");
-    return NS_ERROR_UNEXPECTED;
+  } else {
+    DeleteFiles(aQuotaManager, mPersistenceType.Value());
   }
 
   return NS_OK;
 }
 
 void
-ResetOrClearRunnable::DeleteFiles(QuotaManager* aQuotaManager)
+FinalizeOriginEvictionOp::Dispatch()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(mState == State_Initial);
+
+  mState = State_DirectoryOpenPending;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+FinalizeOriginEvictionOp::RunOnIOThreadImmediately()
 {
   AssertIsOnIOThread();
-  NS_ASSERTION(aQuotaManager, "Don't pass me null!");
-
-  nsresult rv;
-
-  nsCOMPtr<nsIFile> directory =
-    do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  rv = directory->InitWithPath(aQuotaManager->GetStoragePath());
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  rv = directory->Remove(true);
-  if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
-      rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
-    // This should never fail if we've closed all storage connections
-    // correctly...
-    NS_ERROR("Failed to remove directory!");
-  }
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(ResetOrClearRunnable, nsRunnable)
-
-NS_IMETHODIMP
-ResetOrClearRunnable::Run()
-{
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  switch (mCallbackState) {
-    case Pending: {
-      NS_NOTREACHED("Should never get here without being dispatched!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    case OpenAllowed: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      AdvanceState();
-
-      // Now we have to wait until the thread pool is done with all of the
-      // storages we care about.
-      nsresult rv =
-        quotaManager->AcquireExclusiveAccess(NullCString(),
-                                             Nullable<PersistenceType>(), this);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      return NS_OK;
-    }
-
-    case IO: {
-      AssertIsOnIOThread();
-
-      AdvanceState();
-
-      if (mClear) {
-        DeleteFiles(quotaManager);
-      }
-
-      quotaManager->RemoveQuota();
-      quotaManager->ResetOrClearCompleted();
-
-      // Now dispatch back to the main thread.
-      if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
-        NS_WARNING("Failed to dispatch to main thread!");
-        return NS_ERROR_FAILURE;
-      }
-
-      return NS_OK;
-    }
-
-    case Complete: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      // Tell the QuotaManager that we're done.
-      quotaManager->AllowNextSynchronizedOp(OriginOrPatternString::FromNull(),
-                                            Nullable<PersistenceType>(),
-                                            EmptyCString());
-
-      return NS_OK;
-    }
-
-    default:
-      NS_ERROR("Unknown state value!");
-      return NS_ERROR_UNEXPECTED;
-  }
-
-  NS_NOTREACHED("Should never get here!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-NS_IMETHODIMP
-FinalizeOriginEvictionRunnable::Run()
-{
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  nsresult rv;
-
-  switch (mCallbackState) {
-    case Pending: {
-      NS_NOTREACHED("Should never get here without being dispatched!");
-      return NS_ERROR_UNEXPECTED;
-    }
-
-    case OpenAllowed: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      AdvanceState();
-
-      rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to dispatch to the IO thread!");
-      }
-
-      return NS_OK;
-    }
-
-    case IO: {
-      AssertIsOnIOThread();
-
-      AdvanceState();
-
-      for (uint32_t index = 0; index < mOrigins.Length(); index++) {
-        const OriginParams& origin = mOrigins[index];
-
-        quotaManager->OriginClearCompleted(
-                              origin.mPersistenceType,
-                              OriginOrPatternString::FromOrigin(origin.mOrigin),
-                              origin.mIsApp);
-      }
-
-      if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
-        NS_WARNING("Failed to dispatch to main thread!");
-        return NS_ERROR_FAILURE;
-      }
-
-      return NS_OK;
-    }
-
-    case Complete: {
-      NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-      for (uint32_t index = 0; index < mOrigins.Length(); index++) {
-        const OriginParams& origin = mOrigins[index];
-
-        quotaManager->AllowNextSynchronizedOp(
-                             OriginOrPatternString::FromOrigin(origin.mOrigin),
-                             Nullable<PersistenceType>(origin.mPersistenceType),
-                             EmptyCString());
-      }
-
-      return NS_OK;
-    }
-
-    default:
-      NS_ERROR("Unknown state value!");
-      return NS_ERROR_UNEXPECTED;
-  }
-
-  NS_NOTREACHED("Should never get here!");
-  return NS_ERROR_UNEXPECTED;
+  MOZ_ASSERT(mState == State_Initial);
+
+  mState = State_DirectoryWorkOpen;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(this->Run()));
 }
 
 nsresult
-FinalizeOriginEvictionRunnable::Dispatch()
+FinalizeOriginEvictionOp::Open()
 {
-  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(mCallbackState == Pending, "Huh?");
-
-  mCallbackState = OpenAllowed;
-  return NS_DispatchToMainThread(this);
+  MOZ_CRASH("Shouldn't get here!");
 }
 
 nsresult
-FinalizeOriginEvictionRunnable::RunImmediately()
+FinalizeOriginEvictionOp::DoDirectoryWork(QuotaManager* aQuotaManager)
 {
   AssertIsOnIOThread();
-  NS_ASSERTION(mCallbackState == Pending, "Huh?");
-
-  mCallbackState = IO;
-  return this->Run();
-}
-
-NS_IMETHODIMP
-WaitForTransactionsToFinishRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(mOp, "Null op!");
-  NS_ASSERTION(mOp->mRunnable, "Nothing to run!");
-  NS_ASSERTION(mCountdown, "Wrong countdown!");
-
-  if (--mCountdown) {
-    return NS_OK;
-  }
-
-  // Don't hold the runnable alive longer than necessary.
-  nsCOMPtr<nsIRunnable> runnable;
-  runnable.swap(mOp->mRunnable);
-
-  mOp = nullptr;
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  nsresult rv =
-    quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // The listener is responsible for calling
-  // QuotaManager::AllowNextSynchronizedOp.
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-SaveOriginAccessTimeRunnable::Run()
-{
-  AssertIsOnIOThread();
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "This should never fail!");
-
-  nsCOMPtr<nsIFile> directory;
-  nsresult rv =
-    quotaManager->GetDirectoryForOrigin(mPersistenceType, mOrigin,
-                                        getter_AddRefs(directory));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<nsIBinaryOutputStream> stream;
-  rv = GetDirectoryMetadataOutputStream(directory, kUpdateFileFlag,
-                                        getter_AddRefs(stream));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // The origin directory may not exist anymore.
-  if (stream) {
-    rv = stream->Write64(mTimestamp);
-    NS_ENSURE_SUCCESS(rv, rv);
+
+  PROFILER_LABEL("Quota", "FinalizeOriginEvictionOp::DoDirectoryWork",
+                 js::ProfileEntry::Category::OTHER);
+
+  for (nsRefPtr<DirectoryLock>& lock : mLocks) {
+    aQuotaManager->OriginClearCompleted(lock->GetPersistenceType().Value(),
+                                        lock->GetOriginScope(),
+                                        lock->GetIsApp().Value());
   }
 
   return NS_OK;
 }
 
+void
+FinalizeOriginEvictionOp::UnblockOpen()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_UnblockingOpen);
+
+  mLocks.Clear();
+
+  AdvanceState();
+}
+
 nsresult
 StorageDirectoryHelper::CreateOrUpgradeMetadataFiles(bool aCreate)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT_IF(mPersistent, aCreate);
 
   mCreate = aCreate;
 
@@ -5232,8 +5192,12 @@ OriginParser::HandleTrailingSeparator()
 {
   MOZ_ASSERT(mState == eComplete);
   MOZ_ASSERT(mSchemaType == eFile);
 
   mPathnameComponents.AppendElement(EmptyCString());
 
   mState = eHandledTrailingSeparator;
 }
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -8,54 +8,48 @@
 #define mozilla_dom_quota_quotamanager_h__
 
 #include "QuotaCommon.h"
 
 #include "nsIObserver.h"
 #include "nsIQuotaManager.h"
 
 #include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/Mutex.h"
 
 #include "nsClassHashtable.h"
 #include "nsRefPtrHashtable.h"
 
-#include "ArrayCluster.h"
 #include "Client.h"
 #include "PersistenceType.h"
 
 #define QUOTA_MANAGER_CONTRACTID "@mozilla.org/dom/quota/manager;1"
 
-class nsIOfflineStorage;
 class nsIPrincipal;
 class nsIThread;
 class nsITimer;
 class nsIURI;
 class nsPIDOMWindow;
 class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
-class ContentParent;
+class OptionalContentId;
 }
 }
 
 BEGIN_QUOTA_NAMESPACE
 
-class AsyncUsageRunnable;
-class CollectOriginsHelper;
-class FinalizeOriginEvictionRunnable;
 class GroupInfo;
 class GroupInfoPair;
-class OriginClearRunnable;
+class OpenDirectoryListener;
 class OriginInfo;
-class OriginOrPatternString;
+class OriginScope;
 class QuotaObject;
-class ResetOrClearRunnable;
-struct SynchronizedOp;
 
 struct OriginParams
 {
   OriginParams(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                bool aIsApp)
   : mOrigin(aOrigin)
   , mPersistenceType(aPersistenceType)
@@ -65,36 +59,35 @@ struct OriginParams
   nsCString mOrigin;
   PersistenceType mPersistenceType;
   bool mIsApp;
 };
 
 class QuotaManager final : public nsIQuotaManager,
                            public nsIObserver
 {
-  friend class AsyncUsageRunnable;
-  friend class CollectOriginsHelper;
-  friend class FinalizeOriginEvictionRunnable;
+public:
+  class DirectoryLock;
+
+private:
   friend class GroupInfo;
-  friend class OriginClearRunnable;
   friend class OriginInfo;
   friend class QuotaObject;
-  friend class ResetOrClearRunnable;
-
-  typedef mozilla::dom::ContentParent ContentParent;
 
   enum MozBrowserPatternFlag
   {
     MozBrowser = 0,
     NotMozBrowser,
     IgnoreMozBrowser
   };
 
+  class DirectoryLockImpl;
+
   typedef nsClassHashtable<nsCStringHashKey,
-                           nsTArray<nsIOfflineStorage*>> LiveStorageTable;
+                           nsTArray<DirectoryLockImpl*>> DirectoryLockTable;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIQUOTAMANAGER
   NS_DECL_NSIOBSERVER
 
   // Returns a non-owning reference.
   static QuotaManager*
@@ -106,16 +99,32 @@ public:
 
   // Returns an owning reference! No one should call this but the factory.
   static QuotaManager*
   FactoryCreate();
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
+  bool
+  IsOriginInitialized(const nsACString& aOrigin) const
+  {
+    AssertIsOnIOThread();
+
+    return mInitializedOrigins.Contains(aOrigin);
+  }
+
+  bool
+  IsTemporaryStorageInitialized() const
+  {
+    AssertIsOnIOThread();
+
+    return mTemporaryStorageInitialized;
+  }
+
   void
   InitQuotaForOrigin(PersistenceType aPersistenceType,
                      const nsACString& aGroup,
                      const nsACString& aOrigin,
                      bool aIsApp,
                      uint64_t aUsageBytes,
                      int64_t aAccessTime);
 
@@ -149,53 +158,62 @@ public:
                  nsIFile* aFile);
 
   already_AddRefed<QuotaObject>
   GetQuotaObject(PersistenceType aPersistenceType,
                  const nsACString& aGroup,
                  const nsACString& aOrigin,
                  const nsAString& aPath);
 
-  // Called when a storage is created.
-  bool
-  RegisterStorage(nsIOfflineStorage* aStorage);
-
-  // Called when a storage is being unlinked or destroyed.
-  void
-  UnregisterStorage(nsIOfflineStorage* aStorage);
-
-  // Called when a process is being shot down. Forces any live storage objects
-  // to close themselves and aborts any running transactions.
+  // Called when a process is being shot down. Aborts any running operations
+  // for the given process.
   void
-  AbortCloseStoragesForProcess(ContentParent* aContentParent);
-
-  // Waits for storages to be cleared and for version change transactions to
-  // complete before dispatching the given runnable.
-  nsresult
-  WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
-                     Nullable<PersistenceType> aPersistenceType,
-                     const nsACString& aId, nsIRunnable* aRunnable);
-
-  void
-  AllowNextSynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
-                          Nullable<PersistenceType> aPersistenceType,
-                          const nsACString& aId);
-
-  bool
-  IsClearOriginPending(const nsACString& aPattern,
-                       Nullable<PersistenceType> aPersistenceType)
-  {
-    return !!FindSynchronizedOp(aPattern, aPersistenceType, EmptyCString());
-  }
+  AbortOperationsForProcess(ContentParentId aContentParentId);
 
   nsresult
   GetDirectoryForOrigin(PersistenceType aPersistenceType,
                         const nsACString& aASCIIOrigin,
                         nsIFile** aDirectory) const;
 
+  // This is the main entry point into the QuotaManager API.
+  // Any storage API implementation (quota client) that participates in
+  // centralized quota and storage handling should call this method to get
+  // a directory lock which will protect client's files from being deleted
+  // while they are still in use.
+  // After a lock is acquired, client is notified via the open listener's
+  // method DirectoryLockAcquired. If the lock couldn't be acquired, client
+  // gets DirectoryLockFailed notification.
+  // A lock is a reference counted object and at the time DirectoryLockAcquired
+  // is called, quota manager holds just one strong reference to it which is
+  // then immediatelly cleared by quota manager. So it's up to client to add
+  // a new reference in order to keep the lock alive.
+  // Unlocking is simply done by dropping all references to the lock object.
+  // In other words, protection which the lock represents dies with the lock
+  // object itself.
+  void
+  OpenDirectory(PersistenceType aPersistenceType,
+                const nsACString& aGroup,
+                const nsACString& aOrigin,
+                bool aIsApp,
+                Client::Type aClientType,
+                bool aExclusive,
+                OpenDirectoryListener* aOpenListener);
+
+  // XXX RemoveMe once bug 1170279 gets fixed.
+  void
+  OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType,
+                        const OriginScope& aOriginScope,
+                        bool aExclusive,
+                        OpenDirectoryListener* aOpenListener);
+
+  // Collect inactive and the least recently used origins.
+  uint64_t
+  CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
+                            nsTArray<nsRefPtr<DirectoryLock>>& aLocks);
+
   nsresult
   EnsureOriginIsInitialized(PersistenceType aPersistenceType,
                             const nsACString& aGroup,
                             const nsACString& aOrigin,
                             bool aIsApp,
                             nsIFile** aDirectory);
 
   void
@@ -214,17 +232,17 @@ public:
 
   nsIThread*
   IOThread()
   {
     NS_ASSERTION(mIOThread, "This should never be null!");
     return mIOThread;
   }
 
-  already_AddRefed<Client>
+  Client*
   GetClient(Client::Type aClientType);
 
   const nsString&
   GetStoragePath() const
   {
     return mStoragePath;
   }
 
@@ -246,17 +264,16 @@ public:
 
   uint64_t
   GetGroupLimit() const;
 
   static void
   GetStorageId(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                Client::Type aClientType,
-               const nsAString& aName,
                nsACString& aDatabaseId);
 
   static nsresult
   GetInfoFromURI(nsIURI* aURI,
                  uint32_t aAppId,
                  bool aInMozBrowser,
                  nsACString* aGroup,
                  nsACString* aOrigin,
@@ -323,40 +340,51 @@ public:
 private:
   QuotaManager();
 
   virtual ~QuotaManager();
 
   nsresult
   Init();
 
+  already_AddRefed<DirectoryLockImpl>
+  CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType,
+                      const nsACString& aGroup,
+                      const OriginScope& aOriginScope,
+                      Nullable<bool> aIsApp,
+                      Nullable<Client::Type> aClientType,
+                      bool aExclusive,
+                      bool aInternal,
+                      OpenDirectoryListener* aOpenListener);
+
+  already_AddRefed<DirectoryLockImpl>
+  CreateDirectoryLockForEviction(PersistenceType aPersistenceType,
+                                 const nsACString& aGroup,
+                                 const nsACString& aOrigin,
+                                 bool aIsApp);
+
+  void
+  RegisterDirectoryLock(DirectoryLockImpl* aLock);
+
+  void
+  UnregisterDirectoryLock(DirectoryLockImpl* aLock);
+
+  void
+  RemovePendingDirectoryLock(DirectoryLockImpl* aLock);
+
   uint64_t
   LockedCollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
-                                  nsTArray<OriginInfo*>& aOriginInfos);
+                                  nsTArray<nsRefPtr<DirectoryLock>>& aLocks);
 
   void
   LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
                              const nsACString& aGroup,
                              const nsACString& aOrigin);
 
   nsresult
-  AcquireExclusiveAccess(const nsACString& aOrigin,
-                         Nullable<PersistenceType> aPersistenceType,
-                         nsIRunnable* aRunnable);
-
-  void
-  AddSynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
-                    Nullable<PersistenceType> aPersistenceType);
-
-  SynchronizedOp*
-  FindSynchronizedOp(const nsACString& aPattern,
-                     Nullable<PersistenceType> aPersistenceType,
-                     const nsACString& aId);
-
-  nsresult
   MaybeUpgradeIndexedDBDirectory();
 
   nsresult
   MaybeUpgradePersistentStorageDirectory();
 
   nsresult
   MaybeUpgradeStorageArea();
 
@@ -372,45 +400,35 @@ private:
                    nsIFile* aDirectory);
 
   nsresult
   ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly);
 
   void
   CheckTemporaryStorageLimits();
 
-  // Collect inactive and the least recently used origins.
-  uint64_t
-  CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
-                            nsTArray<OriginInfo*>& aOriginInfos);
-
   void
   DeleteFilesForOrigin(PersistenceType aPersistenceType,
                        const nsACString& aOrigin);
 
   void
-  FinalizeOriginEviction(nsTArray<OriginParams>& aOrigins);
-
-  void
-  SaveOriginAccessTime(PersistenceType aPersistenceType,
-                       const nsACString& aOrigin,
-                       int64_t aTimestamp);
+  FinalizeOriginEviction(nsTArray<nsRefPtr<DirectoryLock>>& aLocks);
 
   void
   ReleaseIOThreadObjects()
   {
     AssertIsOnIOThread();
 
     for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
       mClients[index]->ReleaseIOThreadObjects();
     }
   }
 
-  LiveStorageTable&
-  GetLiveStorageTable(PersistenceType aPersistenceType);
+  DirectoryLockTable&
+  GetDirectoryLockTable(PersistenceType aPersistenceType);
 
   static void
   GetOriginPatternString(uint32_t aAppId,
                          MozBrowserPatternFlag aBrowserFlag,
                          const nsACString& aOrigin,
                          nsAutoCString& _retval);
 
   static PLDHashOperator
@@ -423,39 +441,29 @@ private:
                                 GroupInfoPair* aValue,
                                 void* aUserArg);
 
   static PLDHashOperator
   GetAllTemporaryStorageOrigins(const nsACString& aKey,
                                 GroupInfoPair* aValue,
                                 void* aUserArg);
 
-  static PLDHashOperator
-  AddLiveStorageOrigins(const nsACString& aKey,
-                        nsTArray<nsIOfflineStorage*>* aValue,
-                        void* aUserArg);
-
-  static PLDHashOperator
-  GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
-                                     GroupInfoPair* aValue,
-                                     void* aUserArg);
-
   mozilla::Mutex mQuotaMutex;
 
   nsClassHashtable<nsCStringHashKey, GroupInfoPair> mGroupInfoPairs;
 
-  // Maintains a list of live storages per origin.
-  nsClassHashtable<nsCStringHashKey,
-                   ArrayCluster<nsIOfflineStorage*> > mLiveStorages;
+  // Maintains a list of directory locks that are queued.
+  nsTArray<nsRefPtr<DirectoryLockImpl>> mPendingDirectoryLocks;
 
-  LiveStorageTable mTemporaryLiveStorageTable;
-  LiveStorageTable mDefaultLiveStorageTable;
+  // Maintains a list of directory locks that are acquired or queued.
+  nsTArray<DirectoryLockImpl*> mDirectoryLocks;
 
-  // Maintains a list of synchronized operatons that are in progress or queued.
-  nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
+  // Directory lock tables that are used to update origin access time.
+  DirectoryLockTable mTemporaryDirectoryLockTable;
+  DirectoryLockTable mDefaultDirectoryLockTable;
 
   // Thread on which IO is performed.
   nsCOMPtr<nsIThread> mIOThread;
 
   // A timer that gets activated at shutdown to ensure we close all storages.
   nsCOMPtr<nsITimer> mShutdownTimer;
 
   // A list of all successfully initialized origins. This list isn't protected
@@ -472,11 +480,66 @@ private:
 
   uint64_t mTemporaryStorageLimit;
   uint64_t mTemporaryStorageUsage;
   bool mTemporaryStorageInitialized;
 
   bool mStorageAreaInitialized;
 };
 
+class NS_NO_VTABLE RefCountedObject
+{
+public:
+  NS_IMETHOD_(MozExternalRefCountType)
+  AddRef() = 0;
+
+  NS_IMETHOD_(MozExternalRefCountType)
+  Release() = 0;
+};
+
+// nsISupports is needed for nsMainThreadPtrHandle<DirectoryLock>
+// XXX RemoveMe once bug 1164581 gets fixed.
+class QuotaManager::DirectoryLock
+  : public nsISupports
+{
+  friend class DirectoryLockImpl;
+
+public:
+  // These four methods can go away once QuotaObject is merged with QuotaManager
+  // XXX RemoveMe once bug 1170021 gets fixed.
+  const Nullable<PersistenceType>&
+  GetPersistenceType() const;
+
+  const nsACString&
+  GetGroup() const;
+
+  const OriginScope&
+  GetOriginScope() const;
+
+  const Nullable<bool>&
+  GetIsApp() const;
+
+private:
+  DirectoryLock()
+  { }
+
+  ~DirectoryLock()
+  { }
+};
+
+class NS_NO_VTABLE OpenDirectoryListener
+  : public RefCountedObject
+{
+public:
+  virtual void
+  DirectoryLockAcquired(QuotaManager::DirectoryLock* aLock) = 0;
+
+  virtual void
+  DirectoryLockFailed() = 0;
+
+protected:
+  virtual ~OpenDirectoryListener()
+  { }
+};
+
 END_QUOTA_NAMESPACE
 
 #endif /* mozilla_dom_quota_quotamanager_h__ */
--- a/dom/quota/QuotaObject.cpp
+++ b/dom/quota/QuotaObject.cpp
@@ -175,60 +175,56 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
 
   AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
   uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage +
                                       delta;
 
   if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) {
     // This will block the thread without holding the lock while waitting.
 
-    nsAutoTArray<OriginInfo*, 10> originInfos;
+    nsAutoTArray<nsRefPtr<DirectoryLock>, 10> locks;
+
     uint64_t sizeToBeFreed =
-      quotaManager->LockedCollectOriginsForEviction(delta, originInfos);
+      quotaManager->LockedCollectOriginsForEviction(delta, locks);
 
     if (!sizeToBeFreed) {
       return false;
     }
 
     NS_ASSERTION(sizeToBeFreed >= delta, "Huh?");
 
     {
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
-      for (uint32_t i = 0; i < originInfos.Length(); i++) {
-        OriginInfo* originInfo = originInfos[i];
+      for (nsRefPtr<DirectoryLock>& lock : locks) {
+        MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
+        MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
+        MOZ_ASSERT(!lock->GetOriginScope().IsEmpty());
 
-        quotaManager->DeleteFilesForOrigin(
-                                       originInfo->mGroupInfo->mPersistenceType,
-                                       originInfo->mOrigin);
+        quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType().Value(),
+                                           lock->GetOriginScope());
       }
     }
 
     // Relocked.
 
     NS_ASSERTION(mOriginInfo, "How come?!");
 
-    nsTArray<OriginParams> origins;
-    for (uint32_t i = 0; i < originInfos.Length(); i++) {
-      OriginInfo* originInfo = originInfos[i];
-
-      NS_ASSERTION(originInfo != mOriginInfo, "Deleted itself!");
+    for (DirectoryLock* lock : locks) {
+      MOZ_ASSERT(!lock->GetPersistenceType().IsNull());
+      MOZ_ASSERT(!lock->GetGroup().IsEmpty());
+      MOZ_ASSERT(lock->GetOriginScope().IsOrigin());
+      MOZ_ASSERT(!lock->GetOriginScope().IsEmpty());
+      MOZ_ASSERT(lock->GetOriginScope() != mOriginInfo->mOrigin,
+                 "Deleted itself!");
 
-      PersistenceType persistenceType =
-        originInfo->mGroupInfo->mPersistenceType;
-      nsCString group = originInfo->mGroupInfo->mGroup;
-      nsCString origin = originInfo->mOrigin;
-      bool isApp = originInfo->mIsApp;
-      quotaManager->LockedRemoveQuotaForOrigin(persistenceType, group, origin);
-
-#ifdef DEBUG
-      originInfos[i] = nullptr;
-#endif
-
-      origins.AppendElement(OriginParams(persistenceType, origin, isApp));
+      quotaManager->LockedRemoveQuotaForOrigin(
+                                             lock->GetPersistenceType().Value(),
+                                             lock->GetGroup(),
+                                             lock->GetOriginScope());
     }
 
     // We unlocked and relocked several times so we need to recompute all the
     // essential variables and recheck the group limit.
 
     AssertNoUnderflow(aSize, mSize);
     delta = aSize - mSize;
 
@@ -247,17 +243,17 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
     AssertNoOverflow(groupUsage, delta);
     if (groupUsage + delta > quotaManager->GetGroupLimit()) {
       // Unfortunately some other thread increased the group usage in the
       // meantime and we are not below the group limit anymore.
 
       // However, the origin eviction must be finalized in this case too.
       MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
-      quotaManager->FinalizeOriginEviction(origins);
+      quotaManager->FinalizeOriginEviction(locks);
 
       return false;
     }
 
     AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta);
     newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta;
 
     NS_ASSERTION(newTemporaryStorageUsage <=
@@ -273,17 +269,17 @@ QuotaObject::MaybeUpdateSize(int64_t aSi
     // than this one.
     MOZ_ASSERT(mSize < aSize);
     mSize = aSize;
 
     // Finally, release IO thread only objects and allow next synchronized
     // ops for the evicted origins.
     MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex);
 
-    quotaManager->FinalizeOriginEviction(origins);
+    quotaManager->FinalizeOriginEviction(locks);
 
     return true;
   }
 
   mOriginInfo->mUsage = newUsage;
   groupInfo->mUsage = newGroupUsage;
   quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;
 
@@ -310,19 +306,17 @@ OriginInfo::LockedDecreaseUsage(int64_t 
   quotaManager->mTemporaryStorageUsage -= aSize;
 }
 
 already_AddRefed<OriginInfo>
 GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin)
 {
   AssertCurrentThreadOwnsQuotaMutex();
 
-  for (uint32_t index = 0; index < mOriginInfos.Length(); index++) {
-    nsRefPtr<OriginInfo>& originInfo = mOriginInfos[index];
-
+  for (nsRefPtr<OriginInfo>& originInfo : mOriginInfos) {
     if (originInfo->mOrigin == aOrigin) {
       nsRefPtr<OriginInfo> result = originInfo;
       return result.forget();
     }
   }
 
   return nullptr;
 }
deleted file mode 100644
--- a/dom/quota/StorageMatcher.h
+++ /dev/null
@@ -1,191 +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_quota_patternmatcher_h__
-#define mozilla_dom_quota_patternmatcher_h__
-
-#include "mozilla/dom/quota/QuotaCommon.h"
-
-#include "ArrayCluster.h"
-#include "Utilities.h"
-
-BEGIN_QUOTA_NAMESPACE
-
-template <class ValueType, class BaseType = ArrayCluster<nsIOfflineStorage*> >
-class StorageMatcher : public ValueType
-{
-  typedef StorageMatcher<ValueType, BaseType> SelfType;
-
-  struct Closure
-  {
-    explicit Closure(SelfType& aSelf)
-    : mSelf(aSelf), mPattern(EmptyCString()), mIndexes(nullptr)
-    { }
-
-    Closure(SelfType& aSelf, const nsACString& aPattern)
-    : mSelf(aSelf), mPattern(aPattern), mIndexes(nullptr)
-    { }
-
-    Closure(SelfType& aSelf, const nsTArray<uint32_t>* aIndexes)
-    : mSelf(aSelf), mPattern(EmptyCString()), mIndexes(aIndexes)
-    { }
-
-    Closure(SelfType& aSelf, const nsACString& aPattern,
-            const nsTArray<uint32_t>* aIndexes)
-    : mSelf(aSelf), mPattern(aPattern), mIndexes(aIndexes)
-    { }
-
-    SelfType& mSelf;
-    const nsACString& mPattern;
-    const nsTArray<uint32_t>* mIndexes;
-  };
-
-public:
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable,
-       const nsACString& aPattern)
-  {
-    SelfType::Clear();
-
-    Closure closure(*this, aPattern);
-    aHashtable.EnumerateRead(SelfType::MatchPattern, &closure);
-  }
-
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable,
-       const nsTArray<uint32_t>* aIndexes)
-  {
-    SelfType::Clear();
-
-    Closure closure(*this, aIndexes);
-    aHashtable.EnumerateRead(SelfType::MatchIndexes, &closure);
-  }
-
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable,
-       uint32_t aIndex)
-  {
-    nsAutoTArray<uint32_t, 1> indexes;
-    indexes.AppendElement(aIndex);
-
-    Find(aHashtable, &indexes);
-  }
-
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable,
-       const nsACString& aPattern,
-       const nsTArray<uint32_t>* aIndexes)
-  {
-    SelfType::Clear();
-
-    Closure closure(*this, aPattern, aIndexes);
-    aHashtable.EnumerateRead(SelfType::MatchPatternAndIndexes, &closure);
-  }
-
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable,
-       const nsACString& aPattern,
-       uint32_t aIndex)
-  {
-    nsAutoTArray<uint32_t, 1> indexes;
-    indexes.AppendElement(aIndex);
-
-    Find(aHashtable, aPattern, &indexes);
-  }
-
-  template <class T, class U, class V>
-  void
-  Find(const nsBaseHashtable<T, U, V>& aHashtable)
-  {
-    SelfType::Clear();
-
-    Closure closure(*this);
-    aHashtable.EnumerateRead(SelfType::MatchAll, &closure);
-  }
-
-private:
-  static PLDHashOperator
-  MatchPattern(const nsACString& aKey,
-               BaseType* aValue,
-               void* aUserArg)
-  {
-    MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!");
-    MOZ_ASSERT(aValue, "Null pointer!");
-    MOZ_ASSERT(aUserArg, "Null pointer!");
-
-    Closure* closure = static_cast<Closure*>(aUserArg);
-
-    if (PatternMatchesOrigin(closure->mPattern, aKey)) {
-      aValue->AppendElementsTo(closure->mSelf);
-    }
-
-    return PL_DHASH_NEXT;
-  }
-
-  static PLDHashOperator
-  MatchIndexes(const nsACString& aKey,
-               BaseType* aValue,
-               void* aUserArg)
-  {
-    MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!");
-    MOZ_ASSERT(aValue, "Null pointer!");
-    MOZ_ASSERT(aUserArg, "Null pointer!");
-
-    Closure* closure = static_cast<Closure*>(aUserArg);
-
-    for (uint32_t index = 0; index < closure->mIndexes->Length(); index++) {
-      aValue->AppendElementsTo(closure->mIndexes->ElementAt(index),
-                               closure->mSelf);
-    }
-
-    return PL_DHASH_NEXT;
-  }
-
-  static PLDHashOperator
-  MatchPatternAndIndexes(const nsACString& aKey,
-                         BaseType* aValue,
-                         void* aUserArg)
-  {
-    MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!");
-    MOZ_ASSERT(aValue, "Null pointer!");
-    MOZ_ASSERT(aUserArg, "Null pointer!");
-
-    Closure* closure = static_cast<Closure*>(aUserArg);
-
-    if (PatternMatchesOrigin(closure->mPattern, aKey)) {
-      for (uint32_t index = 0; index < closure->mIndexes->Length(); index++) {
-        aValue->AppendElementsTo(closure->mIndexes->ElementAt(index),
-                                 closure->mSelf);
-      }
-    }
-
-    return PL_DHASH_NEXT;
-  }
-
-  static PLDHashOperator
-  MatchAll(const nsACString& aKey,
-           BaseType* aValue,
-           void* aUserArg)
-  {
-    MOZ_ASSERT(!aKey.IsEmpty(), "Empty key!");
-    MOZ_ASSERT(aValue, "Null pointer!");
-    MOZ_ASSERT(aUserArg, "Null pointer!");
-
-    Closure* closure = static_cast<Closure*>(aUserArg);
-    aValue->AppendElementsTo(closure->mSelf);
-
-    return PL_DHASH_NEXT;
-  }
-};
-
-END_QUOTA_NAMESPACE
-
-#endif // mozilla_dom_quota_patternmatcher_h__
--- a/dom/quota/UsageInfo.h
+++ b/dom/quota/UsageInfo.h
@@ -25,16 +25,26 @@ public:
   { }
 
   bool
   Canceled()
   {
     return mCanceled;
   }
 
+  nsresult
+  Cancel()
+  {
+    if (mCanceled.exchange(true)) {
+      NS_WARNING("Canceled more than once?!");
+      return NS_ERROR_UNEXPECTED;
+    }
+    return NS_OK;
+  }
+
   void
   AppendToDatabaseUsage(uint64_t aUsage)
   {
     IncrementUsage(&mDatabaseUsage, aUsage);
   }
 
   void
   AppendToFileUsage(uint64_t aUsage)
--- a/dom/quota/moz.build
+++ b/dom/quota/moz.build
@@ -7,25 +7,19 @@
 XPIDL_SOURCES += [
     'nsIQuotaManager.idl',
     'nsIQuotaRequest.idl',
     'nsIUsageCallback.idl',
 ]
 
 XPIDL_MODULE = 'dom_quota'
 
-EXPORTS += [
-    'nsIOfflineStorage.h',
-]
-
 EXPORTS.mozilla.dom.quota += [
-    'ArrayCluster.h',
     'Client.h',
     'FileStreams.h',
-    'OriginOrPatternString.h',
     'PersistenceType.h',
     'QuotaCommon.h',
     'QuotaManager.h',
     'QuotaObject.h',
     'UsageInfo.h',
     'Utilities.h',
 ]
 
deleted file mode 100644
--- a/dom/quota/nsIOfflineStorage.h
+++ /dev/null
@@ -1,104 +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 nsIOfflineStorage_h__
-#define nsIOfflineStorage_h__
-
-#include "mozilla/dom/quota/PersistenceType.h"
-
-#define NS_OFFLINESTORAGE_IID \
-  {0x91c57bf2, 0x0eda, 0x4db6, {0x9f, 0xf6, 0xcb, 0x38, 0x26, 0x8d, 0xb3, 0x01}}
-
-namespace mozilla {
-namespace dom {
-
-class ContentParent;
-
-namespace quota {
-
-class Client;
-
-}
-}
-}
-
-class nsIOfflineStorage : public nsISupports
-{
-public:
-  typedef mozilla::dom::ContentParent ContentParent;
-  typedef mozilla::dom::quota::Client Client;
-  typedef mozilla::dom::quota::PersistenceType PersistenceType;
-
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_OFFLINESTORAGE_IID)
-
-  NS_IMETHOD_(const nsACString&)
-  Id() = 0;
-
-  NS_IMETHOD_(Client*)
-  GetClient() = 0;
-
-  NS_IMETHOD_(bool)
-  IsOwnedByProcess(ContentParent* aOwner) = 0;
-
-  NS_IMETHOD_(PersistenceType)
-  Type()
-  {
-    return mPersistenceType;
-  }
-
-  NS_IMETHOD_(const nsACString&)
-  Group()
-  {
-    return mGroup;
-  }
-
-  NS_IMETHOD_(const nsACString&)
-  Origin() = 0;
-
-  // Implementation of this method should close the storage (without aborting
-  // running operations nor discarding pending operations).
-  NS_IMETHOD_(nsresult)
-  Close() = 0;
-
-  // Implementation of this method should close the storage, all running
-  // operations should be aborted and pending operations should be discarded.
-  NS_IMETHOD_(void)
-  Invalidate() = 0;
-
-protected:
-  nsIOfflineStorage()
-  : mPersistenceType(mozilla::dom::quota::PERSISTENCE_TYPE_INVALID)
-  { }
-
-  virtual ~nsIOfflineStorage()
-  { }
-
-  PersistenceType mPersistenceType;
-  nsCString mGroup;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIOfflineStorage, NS_OFFLINESTORAGE_IID)
-
-#define NS_DECL_NSIOFFLINESTORAGE                                              \
-  NS_IMETHOD_(const nsACString&)                                               \
-  Id() override;                                                               \
-                                                                               \
-  NS_IMETHOD_(Client*)                                                         \
-  GetClient() override;                                                        \
-                                                                               \
-  NS_IMETHOD_(bool)                                                            \
-  IsOwnedByProcess(ContentParent* aOwner) override;                            \
-                                                                               \
-  NS_IMETHOD_(const nsACString&)                                               \
-  Origin() override;                                                           \
-                                                                               \
-  NS_IMETHOD_(nsresult)                                                        \
-  Close() override;                                                            \
-                                                                               \
-  NS_IMETHOD_(void)                                                            \
-  Invalidate() override;
-
-#endif // nsIOfflineStorage_h__
--- a/storage/moz.build
+++ b/storage/moz.build
@@ -76,16 +76,18 @@ UNIFIED_SOURCES += [
 ]
 
 # These files need to be built separately because they #include variantToSQLiteT_impl.h.
 SOURCES += [
     'mozStorageBindingParams.cpp',
     'mozStorageConnection.cpp',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 
 # Don't use the jemalloc allocator on Android, because we can't guarantee
 # that Gecko will configure sqlite before it is first used (bug 730495).
 #
 # Don't use the jemalloc allocator when using system sqlite. Linked in libraries