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 250705 c9b3704bd0ed90f0e737a25f889c25a425075455
parent 250704 8c0027994ca49a93bf00d12d356f00dda4aa26d7
child 250706 c1bc9e9c1c92d51907af9a772ed4358bcfc3cc57
push id28968
push userkwierso@gmail.com
push dateTue, 30 Jun 2015 23:40:44 +0000
treeherdermozilla-central@e5ef71b73fec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs1130775
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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