Bug 1286798 - Part 4: Basic integration with QuotaManager; r=asuth
authorJan Varga <jan.varga@gmail.com>
Thu, 29 Nov 2018 21:47:24 +0100
changeset 508002 2aaa8d6bbaf1a08d7825e7b2827a398955974e0d
parent 508001 2a6e7e64cec1e3b8184a432c6f4c2351a87f9f43
child 508003 25c5ff3cec8f573b05d2435859ba68e4ad53a4a0
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1286798
milestone65.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 1286798 - Part 4: Basic integration with QuotaManager; r=asuth This adds a new quota client implementation, but only implements ShutdownWorkThreads. At shutdown we wait for all running operations to finish including database actors which are closed by using an extra IPC message which avoids races between the parent and child. Databases are dropped on the child side as soon as they are not used (e.g. after unlinking by the cycle collector).
dom/localstorage/ActorsChild.cpp
dom/localstorage/ActorsChild.h
dom/localstorage/ActorsParent.cpp
dom/localstorage/ActorsParent.h
dom/localstorage/LSDatabase.cpp
dom/localstorage/LSDatabase.h
dom/localstorage/LSObject.cpp
dom/localstorage/LSObject.h
dom/localstorage/LocalStorageCommon.cpp
dom/localstorage/LocalStorageCommon.h
dom/localstorage/PBackgroundLSDatabase.ipdl
dom/quota/ActorsParent.cpp
dom/quota/Client.h
dom/quota/QuotaManager.h
dom/storage/Storage.cpp
dom/storage/Storage.h
--- a/dom/localstorage/ActorsChild.cpp
+++ b/dom/localstorage/ActorsChild.cpp
@@ -52,16 +52,28 @@ LSDatabaseChild::ActorDestroy(ActorDestr
   if (mDatabase) {
     mDatabase->ClearActor();
 #ifdef DEBUG
     mDatabase = nullptr;
 #endif
   }
 }
 
+mozilla::ipc::IPCResult
+LSDatabaseChild::RecvRequestAllowToClose()
+{
+  AssertIsOnOwningThread();
+
+  if (mDatabase) {
+    mDatabase->AllowToClose();
+  }
+
+  return IPC_OK();
+}
+
 /*******************************************************************************
  * LocalStorageRequestChild
  ******************************************************************************/
 
 LSRequestChild::LSRequestChild(LSRequestChildCallback* aCallback)
   : mCallback(aCallback)
   , mFinishing(false)
 {
--- a/dom/localstorage/ActorsChild.h
+++ b/dom/localstorage/ActorsChild.h
@@ -50,16 +50,19 @@ private:
   ~LSDatabaseChild();
 
   void
   SendDeleteMeInternal();
 
   // IPDL methods are only called by IPDL.
   void
   ActorDestroy(ActorDestroyReason aWhy) override;
+
+  mozilla::ipc::IPCResult
+  RecvRequestAllowToClose() override;
 };
 
 class LSRequestChild final
   : public PBackgroundLSRequestChild
 {
   friend class LSObject;
   friend class LSObjectChild;
 
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -10,18 +10,18 @@
 #include "mozilla/Unused.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
-#include "nsRefPtrHashtable.h"
 
 #define DISABLE_ASSERTS_FOR_FUZZING 0
 
 #if DISABLE_ASSERTS_FOR_FUZZING
 #define ASSERT_UNLESS_FUZZING(...) do { } while (0)
 #else
 #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
 #endif
@@ -166,51 +166,84 @@ public:
 
   NS_INLINE_DECL_REFCOUNTING(Datastore)
 
 private:
   // Reference counted.
   ~Datastore();
 };
 
+class PreparedDatastore
+{
+  RefPtr<Datastore> mDatastore;
+
+public:
+  explicit PreparedDatastore(Datastore* aDatastore)
+    : mDatastore(aDatastore)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(aDatastore);
+  }
+
+  already_AddRefed<Datastore>
+  ForgetDatastore()
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(mDatastore);
+
+    return mDatastore.forget();
+  }
+};
+
 /*******************************************************************************
  * Actor class declarations
  ******************************************************************************/
 
 class Database final
   : public PBackgroundLSDatabaseParent
 {
   RefPtr<Datastore> mDatastore;
 
+  bool mAllowedToClose;
+  bool mActorDestroyed;
+  bool mRequestedAllowToClose;
 #ifdef DEBUG
-  bool mActorDestroyed;
   bool mActorWasAlive;
 #endif
 
 public:
   // Created in AllocPBackgroundLSDatabaseParent.
   Database();
 
   void
   SetActorAlive(already_AddRefed<Datastore>&& aDatastore);
 
+  void
+  RequestAllowToClose();
+
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Database)
 
 private:
   // Reference counted.
   ~Database();
 
+  void
+  AllowToClose();
+
   // IPDL methods are only called by IPDL.
   void
   ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult
   RecvDeleteMe() override;
 
   mozilla::ipc::IPCResult
+  RecvAllowToClose() override;
+
+  mozilla::ipc::IPCResult
   RecvGetLength(uint32_t* aLength) override;
 
   mozilla::ipc::IPCResult
   RecvGetKey(const uint32_t& aIndex, nsString* aKey) override;
 
   mozilla::ipc::IPCResult
   RecvGetItem(const nsString& aKey, nsString* aValue) override;
 
@@ -299,97 +332,197 @@ private:
   OpenOnOwningThread();
 
   void
   SendReadyMessage();
 
   void
   SendResults();
 
+  void
+  Cleanup();
+
   NS_IMETHOD
   Run() override;
 
   // IPDL overrides.
   mozilla::ipc::IPCResult
   RecvFinish() override;
 };
 
 /*******************************************************************************
+ * Other class declarations
+ ******************************************************************************/
+
+class QuotaClient final
+  : public mozilla::dom::quota::Client
+{
+  static QuotaClient* sInstance;
+
+  bool mShutdownRequested;
+
+public:
+  QuotaClient();
+
+  static bool
+  IsShuttingDownOnBackgroundThread()
+  {
+    AssertIsOnBackgroundThread();
+
+    if (sInstance) {
+      return sInstance->IsShuttingDown();
+    }
+
+    return QuotaManager::IsShuttingDown();
+  }
+
+  static bool
+  IsShuttingDownOnNonBackgroundThread()
+  {
+    MOZ_ASSERT(!IsOnBackgroundThread());
+
+    return QuotaManager::IsShuttingDown();
+  }
+
+  bool
+  IsShuttingDown() const
+  {
+    AssertIsOnBackgroundThread();
+
+    return mShutdownRequested;
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::QuotaClient, override)
+
+  Type
+  GetType() override;
+
+  nsresult
+  InitOrigin(PersistenceType aPersistenceType,
+             const nsACString& aGroup,
+             const nsACString& aOrigin,
+             const AtomicBool& aCanceled,
+             UsageInfo* aUsageInfo) override;
+
+  nsresult
+  GetUsageForOrigin(PersistenceType aPersistenceType,
+                    const nsACString& aGroup,
+                    const nsACString& aOrigin,
+                    const AtomicBool& aCanceled,
+                    UsageInfo* aUsageInfo) override;
+
+  void
+  OnOriginClearCompleted(PersistenceType aPersistenceType,
+                         const nsACString& aOrigin)
+                         override;
+
+  void
+  ReleaseIOThreadObjects() override;
+
+  void
+  AbortOperations(const nsACString& aOrigin) override;
+
+  void
+  AbortOperationsForProcess(ContentParentId aContentParentId) override;
+
+  void
+  StartIdleMaintenance() override;
+
+  void
+  StopIdleMaintenance() override;
+
+  void
+  ShutdownWorkThreads() override;
+
+private:
+  ~QuotaClient() override;
+};
+
+/*******************************************************************************
  * Globals
  ******************************************************************************/
 
+typedef nsTArray<PrepareDatastoreOp*> PrepareDatastoreOpArray;
+
+StaticAutoPtr<PrepareDatastoreOpArray> gPrepareDatastoreOps;
+
 typedef nsDataHashtable<nsCStringHashKey, Datastore*> DatastoreHashtable;
 
 StaticAutoPtr<DatastoreHashtable> gDatastores;
 
 uint64_t gLastDatastoreId = 0;
 
-typedef nsRefPtrHashtable<nsUint64HashKey, Datastore>
-  TemporaryStrongDatastoreHashtable;
+typedef nsClassHashtable<nsUint64HashKey, PreparedDatastore>
+  PreparedDatastoreHashtable;
 
-StaticAutoPtr<TemporaryStrongDatastoreHashtable> gTemporaryStrongDatastores;
+StaticAutoPtr<PreparedDatastoreHashtable> gPreparedDatastores;
 
 typedef nsTArray<Database*> LiveDatabaseArray;
 
 StaticAutoPtr<LiveDatabaseArray> gLiveDatabases;
 
 } // namespace
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
 PBackgroundLSDatabaseParent*
 AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId)
 {
   AssertIsOnBackgroundThread();
 
-  if (NS_WARN_IF(!gTemporaryStrongDatastores)) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
+  if (NS_WARN_IF(!gPreparedDatastores)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
-  Datastore* datastore = gTemporaryStrongDatastores->GetWeak(aDatastoreId);
-  if (NS_WARN_IF(!datastore)) {
+  PreparedDatastore* preparedDatastore = gPreparedDatastores->Get(aDatastoreId);
+  if (NS_WARN_IF(!preparedDatastore)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
   // If we ever decide to return null from this point on, we need to make sure
-  // that the prepared datastore is removed from the gTemporaryStrongDatastores
+  // that the prepared datastore is removed from the gPreparedDatastores
   // hashtable.
   // We also assume that IPDL must call RecvPBackgroundLSDatabaseConstructor
   // once we return a valid actor in this method.
 
   RefPtr<Database> database = new Database();
 
   // Transfer ownership to IPDL.
   return database.forget().take();
 }
 
 bool
 RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
                                      const uint64_t& aDatastoreId)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
-  MOZ_ASSERT(gTemporaryStrongDatastores);
-  MOZ_ASSERT(gTemporaryStrongDatastores->GetWeak(aDatastoreId));
+  MOZ_ASSERT(gPreparedDatastores);
+  MOZ_ASSERT(gPreparedDatastores->Get(aDatastoreId));
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 
   // The actor is now completely built (it has a manager, channel and it's
   // registered as a subprotocol).
   // ActorDestroy will be called if we fail here.
 
-  RefPtr<Datastore> datastore;
-  gTemporaryStrongDatastores->Remove(aDatastoreId, datastore.StartAssignment());
-  MOZ_ASSERT(datastore);
+  nsAutoPtr<PreparedDatastore> preparedDatastore;
+  gPreparedDatastores->Remove(aDatastoreId, &preparedDatastore);
+  MOZ_ASSERT(preparedDatastore);
 
   auto* database = static_cast<Database*>(aActor);
 
-  database->SetActorAlive(datastore.forget());
+  database->SetActorAlive(preparedDatastore->ForgetDatastore());
 
   return true;
 }
 
 bool
 DeallocPBackgroundLSDatabaseParent(PBackgroundLSDatabaseParent* aActor)
 {
   AssertIsOnBackgroundThread();
@@ -402,16 +535,20 @@ DeallocPBackgroundLSDatabaseParent(PBack
 }
 
 PBackgroundLSRequestParent*
 AllocPBackgroundLSRequestParent(PBackgroundParent* aBackgroundActor,
                                 const LSRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
 
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
+    return nullptr;
+  }
+
   RefPtr<LSRequestBase> actor;
 
   switch (aParams.type()) {
     case LSRequestParams::TLSRequestPrepareDatastoreParams: {
       bool isOtherProcess =
         BackgroundParent::IsOtherProcessActor(aBackgroundActor);
 
       const LSRequestPrepareDatastoreParams& params =
@@ -425,17 +562,26 @@ AllocPBackgroundLSRequestParent(PBackgro
         (isOtherProcess && infoType == PrincipalOrQuotaInfo::TPrincipalInfo) ||
         (!isOtherProcess && infoType == PrincipalOrQuotaInfo::TQuotaInfo);
 
       if (NS_WARN_IF(!paramsOk)) {
         ASSERT_UNLESS_FUZZING();
         return nullptr;
       }
 
-      actor = new PrepareDatastoreOp(aParams);
+      RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
+        new PrepareDatastoreOp(aParams);
+
+      if (!gPrepareDatastoreOps) {
+        gPrepareDatastoreOps = new PrepareDatastoreOpArray();
+      }
+      gPrepareDatastoreOps->AppendElement(prepareDatastoreOp);
+
+      actor = std::move(prepareDatastoreOp);
+
       break;
     }
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   // Transfer ownership to IPDL.
@@ -444,16 +590,17 @@ AllocPBackgroundLSRequestParent(PBackgro
 
 bool
 RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
                                     const LSRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 
   // The actor is now completely built.
 
   auto* op = static_cast<LSRequestBase*>(aActor);
 
   op->Dispatch();
 
   return true;
@@ -466,16 +613,30 @@ DeallocPBackgroundLSRequestParent(PBackg
 
   // Transfer ownership back from IPDL.
   RefPtr<LSRequestBase> actor =
     dont_AddRef(static_cast<LSRequestBase*>(aActor));
 
   return true;
 }
 
+namespace localstorage {
+
+already_AddRefed<mozilla::dom::quota::Client>
+CreateQuotaClient()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(CachedNextGenLocalStorageEnabled());
+
+  RefPtr<QuotaClient> client = new QuotaClient();
+  return client.forget();
+}
+
+} // namespace localstorage
+
 /*******************************************************************************
  * DatastoreOperationBase
  ******************************************************************************/
 
 /*******************************************************************************
  * Datastore
  ******************************************************************************/
 
@@ -565,26 +726,29 @@ Datastore::GetKeys(nsTArray<nsString>& a
   }
 }
 
 /*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database()
+  : mAllowedToClose(false)
+  , mActorDestroyed(false)
+  , mRequestedAllowToClose(false)
 #ifdef DEBUG
-  : mActorDestroyed(false)
   , mActorWasAlive(false)
 #endif
 {
   AssertIsOnBackgroundThread();
 }
 
 Database::~Database()
 {
+  MOZ_ASSERT_IF(mActorWasAlive, mAllowedToClose);
   MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
 }
 
 void
 Database::SetActorAlive(already_AddRefed<Datastore>&& aDatastore)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorWasAlive);
@@ -599,125 +763,206 @@ Database::SetActorAlive(already_AddRefed
   if (!gLiveDatabases) {
     gLiveDatabases = new LiveDatabaseArray();
   }
 
   gLiveDatabases->AppendElement(this);
 }
 
 void
-Database::ActorDestroy(ActorDestroyReason aWhy)
+Database::RequestAllowToClose()
 {
   AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!mActorDestroyed);
+
+  if (mRequestedAllowToClose) {
+    return;
+  }
+
+  mRequestedAllowToClose = true;
+
+  // Send the RequestAllowToClose message to the child to avoid racing with the
+  // child actor. Except the case when the actor was already destroyed.
+  if (mActorDestroyed) {
+    MOZ_ASSERT(mAllowedToClose);
+  } else {
+    Unused << SendRequestAllowToClose();
+  }
+}
+
+void
+Database::AllowToClose()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mAllowedToClose);
   MOZ_ASSERT(mDatastore);
 
-#ifdef DEBUG
-  mActorDestroyed = true;
-#endif
+  mAllowedToClose = true;
 
   mDatastore = nullptr;
 
   MOZ_ASSERT(gLiveDatabases);
   gLiveDatabases->RemoveElement(this);
 
   if (gLiveDatabases->IsEmpty()) {
     gLiveDatabases = nullptr;
   }
 }
 
+void
+Database::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  mActorDestroyed = true;
+
+  if (!mAllowedToClose) {
+    AllowToClose();
+  }
+}
+
 mozilla::ipc::IPCResult
 Database::RecvDeleteMe()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorDestroyed);
 
   IProtocol* mgr = Manager();
   if (!PBackgroundLSDatabaseParent::Send__delete__(this)) {
     return IPC_FAIL_NO_REASON(mgr);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+Database::RecvAllowToClose()
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  AllowToClose();
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 Database::RecvGetLength(uint32_t* aLength)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aLength);
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   *aLength = mDatastore->GetLength();
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvGetKey(const uint32_t& aIndex, nsString* aKey)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aKey);
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->GetKey(aIndex, *aKey);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvGetItem(const nsString& aKey, nsString* aValue)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aValue);
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->GetItem(aKey, *aValue);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvSetItem(const nsString& aKey, const nsString& aValue)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->SetItem(aKey, aValue);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvRemoveItem(const nsString& aKey)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->RemoveItem(aKey);
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvClear()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->Clear();
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 Database::RecvGetKeys(nsTArray<nsString>* aKeys)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aKeys);
   MOZ_ASSERT(mDatastore);
 
+  if (NS_WARN_IF(mAllowedToClose)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
   mDatastore->GetKeys(*aKeys);
 
   return IPC_OK();
 }
 
 /*******************************************************************************
  * LSRequestBase
  ******************************************************************************/
@@ -780,17 +1025,18 @@ PrepareDatastoreOp::Dispatch()
 }
 
 nsresult
 PrepareDatastoreOp::OpenOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == State::OpeningOnMainThread);
 
-  if (!MayProceedOnNonOwningThread()) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+      !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalOrQuotaInfo& info = mParams.info();
 
   MOZ_ASSERT(info.type() == PrincipalOrQuotaInfo::TPrincipalInfo);
 
   const PrincipalInfo& principalInfo = info.get_PrincipalInfo();
@@ -823,17 +1069,18 @@ PrepareDatastoreOp::OpenOnMainThread()
 }
 
 nsresult
 PrepareDatastoreOp::OpenOnOwningThread()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::OpeningOnOwningThread);
 
-  if (!MayProceed()) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
+      !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalOrQuotaInfo& info = mParams.info();
 
   MOZ_ASSERT(info.type() == PrincipalOrQuotaInfo::TQuotaInfo);
 
   const QuotaInfo& quotaInfo = info.get_QuotaInfo();
@@ -852,16 +1099,18 @@ void
 PrepareDatastoreOp::SendReadyMessage()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingReadyMessage);
 
   if (!MayProceed()) {
     MaybeSetFailureCode(NS_ERROR_FAILURE);
 
+    Cleanup();
+
     mState = State::Completed;
   } else {
     Unused << SendReady();
 
     mState = State::WaitingForFinish;
   }
 }
 
@@ -890,36 +1139,54 @@ PrepareDatastoreOp::SendResults()
           gDatastores = new DatastoreHashtable();
         }
 
         gDatastores->Put(mOrigin, datastore);
       }
 
       uint64_t datastoreId = ++gLastDatastoreId;
 
-      if (!gTemporaryStrongDatastores) {
-        gTemporaryStrongDatastores = new TemporaryStrongDatastoreHashtable();
+      nsAutoPtr<PreparedDatastore> preparedDatastore(
+        new PreparedDatastore(datastore));
+
+      if (!gPreparedDatastores) {
+        gPreparedDatastores = new PreparedDatastoreHashtable();
       }
-      gTemporaryStrongDatastores->Put(datastoreId, datastore);
+      gPreparedDatastores->Put(datastoreId, preparedDatastore.forget());
 
       LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
       prepareDatastoreResponse.datastoreId() = datastoreId;
 
       response = prepareDatastoreResponse;
     } else {
       response = ResultCode();
     }
 
     Unused <<
       PBackgroundLSRequestParent::Send__delete__(this, response);
   }
 
+  Cleanup();
+
   mState = State::Completed;
 }
 
+void
+PrepareDatastoreOp::Cleanup()
+{
+  AssertIsOnOwningThread();
+
+  MOZ_ASSERT(gPrepareDatastoreOps);
+  gPrepareDatastoreOps->RemoveElement(this);
+
+  if (gPrepareDatastoreOps->IsEmpty()) {
+    gPrepareDatastoreOps = nullptr;
+  }
+}
+
 NS_IMETHODIMP
 PrepareDatastoreOp::Run()
 {
   nsresult rv;
 
   switch (mState) {
     case State::OpeningOnMainThread:
       rv = OpenOnMainThread();
@@ -966,10 +1233,145 @@ PrepareDatastoreOp::RecvFinish()
   MOZ_ASSERT(mState == State::WaitingForFinish);
 
   mState = State::SendingResults;
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
 
   return IPC_OK();
 }
 
+/*******************************************************************************
+ * QuotaClient
+ ******************************************************************************/
+
+QuotaClient* QuotaClient::sInstance = nullptr;
+
+QuotaClient::QuotaClient()
+  : mShutdownRequested(false)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
+
+  sInstance = this;
+}
+
+QuotaClient::~QuotaClient()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
+
+  sInstance = nullptr;
+}
+
+mozilla::dom::quota::Client::Type
+QuotaClient::GetType()
+{
+  return QuotaClient::LS;
+}
+
+nsresult
+QuotaClient::InitOrigin(PersistenceType aPersistenceType,
+                        const nsACString& aGroup,
+                        const nsACString& aOrigin,
+                        const AtomicBool& aCanceled,
+                        UsageInfo* aUsageInfo)
+{
+  AssertIsOnIOThread();
+
+  if (!aUsageInfo) {
+    return NS_OK;
+  }
+
+  return GetUsageForOrigin(aPersistenceType,
+                           aGroup,
+                           aOrigin,
+                           aCanceled,
+                           aUsageInfo);
+}
+
+nsresult
+QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
+                               const nsACString& aGroup,
+                               const nsACString& aOrigin,
+                               const AtomicBool& aCanceled,
+                               UsageInfo* aUsageInfo)
+{
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aUsageInfo);
+
+  return NS_OK;
+}
+
+void
+QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
+                                    const nsACString& aOrigin)
+{
+  AssertIsOnIOThread();
+}
+
+void
+QuotaClient::ReleaseIOThreadObjects()
+{
+  AssertIsOnIOThread();
+}
+
+void
+QuotaClient::AbortOperations(const nsACString& aOrigin)
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::StartIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::StopIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
+}
+
+void
+QuotaClient::ShutdownWorkThreads()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mShutdownRequested);
+
+  mShutdownRequested = true;
+
+  // gPrepareDatastoreOps are short lived objects running a state machine.
+  // The shutdown flag is checked between states, so we don't have to notify
+  // all the objects here.
+  // Allocation of a new PrepareDatastoreOp object is prevented once the
+  // shutdown flag is set.
+  // When the last PrepareDatastoreOp finishes, the gPrepareDatastoreOps array
+  // is destroyed.
+
+  // If database actors haven't been created yet, don't do anything special.
+  // We are shutting down and we can release prepared datastores immediatelly
+  // since database actors will never be created for them.
+  if (gPreparedDatastores) {
+    gPreparedDatastores->Clear();
+    gPreparedDatastores = nullptr;
+  }
+
+  if (gLiveDatabases) {
+    for (Database* database : *gLiveDatabases) {
+      database->RequestAllowToClose();
+    }
+  }
+
+  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
+    // Don't have to check gPreparedDatastores since we nulled it out above.
+    return !gPrepareDatastoreOps && !gDatastores && !gLiveDatabases;
+  }));
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/ActorsParent.h
+++ b/dom/localstorage/ActorsParent.h
@@ -16,16 +16,22 @@ class PBackgroundParent;
 } // namespace ipc
 
 namespace dom {
 
 class LSRequestParams;
 class PBackgroundLSDatabaseParent;
 class PBackgroundLSRequestParent;
 
+namespace quota {
+
+class Client;
+
+} // namespace quota
+
 PBackgroundLSDatabaseParent*
 AllocPBackgroundLSDatabaseParent(const uint64_t& aDatastoreId);
 
 bool
 RecvPBackgroundLSDatabaseConstructor(PBackgroundLSDatabaseParent* aActor,
                                      const uint64_t& aDatastoreId);
 
 bool
@@ -38,12 +44,19 @@ AllocPBackgroundLSRequestParent(
 
 bool
 RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
                                     const LSRequestParams& aParams);
 
 bool
 DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor);
 
+namespace localstorage {
+
+already_AddRefed<mozilla::dom::quota::Client>
+CreateQuotaClient();
+
+} // namespace localstorage
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_localstorage_ActorsParent_h
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -6,134 +6,159 @@
 
 #include "LSDatabase.h"
 
 namespace mozilla {
 namespace dom {
 
 LSDatabase::LSDatabase()
   : mActor(nullptr)
+  , mAllowedToClose(false)
 {
   AssertIsOnOwningThread();
 }
 
 LSDatabase::~LSDatabase()
 {
   AssertIsOnOwningThread();
 
+  if (!mAllowedToClose) {
+    AllowToClose();
+  }
+
   if (mActor) {
     mActor->SendDeleteMeInternal();
     MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
   }
 }
 
 void
 LSDatabase::SetActor(LSDatabaseChild* aActor)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(!mActor);
 
   mActor = aActor;
 }
 
+void
+LSDatabase::AllowToClose()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!mAllowedToClose);
+
+  mAllowedToClose = true;
+
+  if (mActor) {
+    mActor->SendAllowToClose();
+  }
+}
+
 nsresult
 LSDatabase::GetLength(uint32_t* aResult)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   uint32_t result;
   if (NS_WARN_IF(!mActor->SendGetLength(&result))) {
     return NS_ERROR_FAILURE;
   }
 
   *aResult = result;
   return NS_OK;
 }
 
 nsresult
 LSDatabase::GetKey(uint32_t aIndex,
                    nsAString& aResult)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   nsString result;
   if (NS_WARN_IF(!mActor->SendGetKey(aIndex, &result))) {
     return NS_ERROR_FAILURE;
   }
 
   aResult = result;
   return NS_OK;
 }
 
 nsresult
 LSDatabase::GetItem(const nsAString& aKey,
                     nsAString& aResult)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   nsString result;
   if (NS_WARN_IF(!mActor->SendGetItem(nsString(aKey), &result))) {
     return NS_ERROR_FAILURE;
   }
 
   aResult = result;
   return NS_OK;
 }
 
 nsresult
 LSDatabase::GetKeys(nsTArray<nsString>& aKeys)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   nsTArray<nsString> result;
   if (NS_WARN_IF(!mActor->SendGetKeys(&result))) {
     return NS_ERROR_FAILURE;
   }
 
   aKeys.SwapElements(result);
   return NS_OK;
 }
 
 nsresult
 LSDatabase::SetItem(const nsAString& aKey,
                     const nsAString& aValue)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   if (NS_WARN_IF(!mActor->SendSetItem(nsString(aKey), nsString(aValue)))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
 LSDatabase::RemoveItem(const nsAString& aKey)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   if (NS_WARN_IF(!mActor->SendRemoveItem(nsString(aKey)))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
 LSDatabase::Clear()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mActor);
+  MOZ_ASSERT(!mAllowedToClose);
 
   if (NS_WARN_IF(!mActor->SendClear())) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
--- a/dom/localstorage/LSDatabase.h
+++ b/dom/localstorage/LSDatabase.h
@@ -11,16 +11,18 @@ namespace mozilla {
 namespace dom {
 
 class LSDatabaseChild;
 
 class LSDatabase final
 {
   LSDatabaseChild* mActor;
 
+  bool mAllowedToClose;
+
 public:
   LSDatabase();
 
   NS_INLINE_DECL_REFCOUNTING(LSDatabase)
 
   void
   AssertIsOnOwningThread() const
   {
@@ -34,16 +36,27 @@ public:
   ClearActor()
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mActor);
 
     mActor = nullptr;
   }
 
+  void
+  AllowToClose();
+
+  bool
+  IsAllowedToClose() const
+  {
+    AssertIsOnOwningThread();
+
+    return mAllowedToClose;
+  }
+
   nsresult
   GetLength(uint32_t* aResult);
 
   nsresult
   GetKey(uint32_t aIndex,
          nsAString& aResult);
 
   nsresult
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -387,25 +387,44 @@ LSObject::Clear(nsIPrincipal& aSubjectPr
 
   rv = mDatabase->Clear();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aError.Throw(rv);
     return;
   }
 }
 
+NS_IMPL_ADDREF_INHERITED(LSObject, Storage)
+NS_IMPL_RELEASE_INHERITED(LSObject, Storage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LSObject)
+NS_INTERFACE_MAP_END_INHERITING(Storage)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(LSObject)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(LSObject, Storage)
+  tmp->AssertIsOnOwningThread();
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(LSObject, Storage)
+  tmp->AssertIsOnOwningThread();
+  tmp->DropDatabase();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
 nsresult
 LSObject::EnsureDatabase()
 {
   AssertIsOnOwningThread();
 
-  if (mDatabase) {
+  if (mDatabase && !mDatabase->IsAllowedToClose()) {
     return NS_OK;
   }
 
+  mDatabase = nullptr;
+
   // We don't need this yet, but once the request successfully finishes, it's
   // too late to initialize PBackground child on the owning thread, because
   // it can fail and parent would keep an extra strong ref to the datastore.
   PBackgroundChild* backgroundActor =
     BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!backgroundActor)) {
     return NS_ERROR_FAILURE;
   }
@@ -452,16 +471,37 @@ LSObject::EnsureDatabase()
 
   database->SetActor(actor);
 
   mDatabase = std::move(database);
 
   return NS_OK;
 }
 
+void
+LSObject::DropDatabase()
+{
+  AssertIsOnOwningThread();
+
+  if (mDatabase) {
+    if (!mDatabase->IsAllowedToClose()) {
+      mDatabase->AllowToClose();
+    }
+    mDatabase = nullptr;
+  }
+}
+
+void
+LSObject::LastRelease()
+{
+  AssertIsOnOwningThread();
+
+  DropDatabase();
+}
+
 nsresult
 RequestHelper::StartAndReturnResponse(LSRequestResponse& aResponse)
 {
   AssertIsOnOwningThread();
 
   // Normally, we would use the standard way of blocking the thread using
   // a monitor.
   // The problem is that BackgroundChild::GetOrCreateForCurrentThread()
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -89,22 +89,32 @@ public:
   RemoveItem(const nsAString& aKey,
              nsIPrincipal& aSubjectPrincipal,
              ErrorResult& aError) override;
 
   void
   Clear(nsIPrincipal& aSubjectPrincipal,
         ErrorResult& aError) override;
 
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(LSObject, Storage)
+
 private:
   LSObject(nsPIDOMWindowInner* aWindow,
            nsIPrincipal* aPrincipal);
 
   ~LSObject();
 
   nsresult
   EnsureDatabase();
+
+  void
+  DropDatabase();
+
+  // Storage overrides.
+  void
+  LastRelease() override;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_localstorage_LSObject_h
--- a/dom/localstorage/LocalStorageCommon.cpp
+++ b/dom/localstorage/LocalStorageCommon.cpp
@@ -6,27 +6,35 @@
 
 #include "LocalStorageCommon.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-int32_t gNextGenLocalStorageEnabled = -1;
+Atomic<int32_t> gNextGenLocalStorageEnabled(-1);
 
 } // namespace
 
 bool
 NextGenLocalStorageEnabled()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gNextGenLocalStorageEnabled == -1) {
     bool enabled = Preferences::GetBool("dom.storage.next_gen", false);
     gNextGenLocalStorageEnabled = enabled ? 1 : 0;
   }
 
   return !!gNextGenLocalStorageEnabled;
 }
 
+bool
+CachedNextGenLocalStorageEnabled()
+{
+  MOZ_ASSERT(gNextGenLocalStorageEnabled != -1);
+
+  return !!gNextGenLocalStorageEnabled;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/LocalStorageCommon.h
+++ b/dom/localstorage/LocalStorageCommon.h
@@ -182,12 +182,15 @@
  */
 
 namespace mozilla {
 namespace dom {
 
 bool
 NextGenLocalStorageEnabled();
 
+bool
+CachedNextGenLocalStorageEnabled();
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_localstorage_LocalStorageCommon_h
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -18,16 +18,18 @@ parent:
   // However, that would destroy the child actor immediatelly and the parent
   // could be sending a message to the child at the same time resulting in a
   // routing error since the child actor wouldn't exist anymore. A routing
   // error typically causes a crash. The race can be prevented by doing the
   // teardown in two steps. First, we send the DeleteMe message to the parent
   // and the parent then sends the __delete__ message to the child.
   async DeleteMe();
 
+  async AllowToClose();
+
   sync GetLength()
     returns (uint32_t length);
 
   sync GetKey(uint32_t index)
     returns (nsString key);
 
   sync GetItem(nsString key)
     returns (nsString value);
@@ -38,12 +40,14 @@ parent:
   sync SetItem(nsString key, nsString value);
 
   sync RemoveItem(nsString key);
 
   sync Clear();
 
 child:
   async __delete__();
+
+  async RequestAllowToClose();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -29,16 +29,17 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Telemetry.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/dom/localstorage/ActorsParent.h"
 #include "mozilla/dom/quota/PQuotaParent.h"
 #include "mozilla/dom/quota/PQuotaRequestParent.h"
 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
 #include "mozilla/dom/simpledb/ActorsParent.h"
 #include "mozilla/dom/StorageActivityService.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/IntegerRange.h"
@@ -2556,17 +2557,17 @@ DirectoryLockImpl::DirectoryLockImpl(Quo
   MOZ_ASSERT(aQuotaManager);
   MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
   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.IsOrigin());
   MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
-  MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+  MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
   MOZ_ASSERT_IF(!aInternal, aOpenListener);
 }
 
 DirectoryLockImpl::~DirectoryLockImpl()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mQuotaManager);
 
@@ -2661,16 +2662,18 @@ CreateRunnable::Init()
     return rv;
   }
 
   rv = baseDir->GetPath(mBaseDirPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  Unused << NextGenLocalStorageEnabled();
+
   return NS_OK;
 }
 
 nsresult
 QuotaManager::
 CreateRunnable::CreateManager()
 {
   AssertIsOnOwningThread();
@@ -3294,17 +3297,17 @@ QuotaManager::CreateDirectoryLock(const 
   AssertIsOnOwningThread();
   MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
   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.IsOrigin());
   MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
-  MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX);
+  MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
   MOZ_ASSERT_IF(!aInternal, aOpenListener);
 
   RefPtr<DirectoryLockImpl> lock = new DirectoryLockImpl(this,
                                                            aPersistenceType,
                                                            aGroup,
                                                            aOriginScope,
                                                            aClientType,
                                                            aExclusive,
@@ -3642,27 +3645,33 @@ QuotaManager::Init(const nsAString& aBas
   if (NS_WARN_IF(!mShutdownTimer)) {
     return NS_ERROR_FAILURE;
   }
 
   static_assert(Client::IDB == 0 &&
                 Client::ASMJS == 1 &&
                 Client::DOMCACHE == 2 &&
                 Client::SDB == 3 &&
-                Client::TYPE_MAX == 4,
+                Client::LS == 4 &&
+                Client::TYPE_MAX == 5,
                 "Fix the registration!");
 
   MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
              "Should be using an auto array with correct capacity!");
 
   // Register clients.
   mClients.AppendElement(indexedDB::CreateQuotaClient());
   mClients.AppendElement(asmjscache::CreateClient());
   mClients.AppendElement(cache::CreateQuotaClient());
   mClients.AppendElement(simpledb::CreateQuotaClient());
+  if (CachedNextGenLocalStorageEnabled()) {
+    mClients.AppendElement(localstorage::CreateQuotaClient());
+  } else {
+    mClients.SetLength(Client::TypeMax());
+  }
 
   return NS_OK;
 }
 
 void
 QuotaManager::Shutdown()
 {
   AssertIsOnOwningThread();
@@ -3680,17 +3689,17 @@ QuotaManager::Shutdown()
     mShutdownTimer->InitWithNamedFuncCallback(&ShutdownTimerCallback,
                                               this,
                                               DEFAULT_SHUTDOWN_TIMER_MS,
                                               nsITimer::TYPE_ONE_SHOT,
                                               "QuotaManager::ShutdownTimerCallback"));
 
   // Each client will spin the event loop while we wait on all the threads
   // to close. Our timer may fire during that loop.
-  for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+  for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
     mClients[index]->ShutdownWorkThreads();
   }
 
   // Cancel the timer regardless of whether it actually fired.
   if (NS_FAILED(mShutdownTimer->Cancel())) {
     NS_WARNING("Failed to cancel shutdown timer!");
   }
 
@@ -5217,42 +5226,42 @@ QuotaManager::OpenDirectoryInternal(cons
   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.
   AutoTArray<nsAutoPtr<nsTHashtable<nsCStringHashKey>>,
                Client::TYPE_MAX> origins;
-  origins.SetLength(Client::TYPE_MAX);
+  origins.SetLength(Client::TypeMax());
 
   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);
+      MOZ_ASSERT(clientType < Client::TypeMax());
 
       const OriginScope& originScope = blockedOnLock->GetOriginScope();
       MOZ_ASSERT(originScope.IsOrigin());
       MOZ_ASSERT(!originScope.GetOrigin().IsEmpty());
 
       nsAutoPtr<nsTHashtable<nsCStringHashKey>>& origin = origins[clientType];
       if (!origin) {
         origin = new nsTHashtable<nsCStringHashKey>();
       }
       origin->PutEntry(originScope.GetOrigin());
     }
   }
 
-  for (uint32_t index : IntegerRange(uint32_t(Client::TYPE_MAX))) {
+  for (uint32_t index : IntegerRange(uint32_t(Client::TypeMax()))) {
     if (origins[index]) {
       for (auto iter = origins[index]->Iter(); !iter.Done(); iter.Next()) {
         MOZ_ASSERT(mClients[index]);
 
         mClients[index]->AbortOperations(iter.Get()->GetKey());
       }
     }
   }
@@ -5481,17 +5490,17 @@ QuotaManager::OriginClearCompleted(Persi
                                    const nsACString& aOrigin)
 {
   AssertIsOnIOThread();
 
   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
     mInitializedOrigins.RemoveElement(aOrigin);
   }
 
-  for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+  for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
     mClients[index]->OnOriginClearCompleted(aPersistenceType, aOrigin);
   }
 }
 
 void
 QuotaManager::ResetOrClearCompleted()
 {
   AssertIsOnIOThread();
@@ -5502,17 +5511,17 @@ QuotaManager::ResetOrClearCompleted()
 
   ReleaseIOThreadObjects();
 }
 
 Client*
 QuotaManager::GetClient(Client::Type aClientType)
 {
   MOZ_ASSERT(aClientType >= Client::IDB);
-  MOZ_ASSERT(aClientType < Client::TYPE_MAX);
+  MOZ_ASSERT(aClientType < Client::TypeMax());
 
   return mClients.ElementAt(aClientType);
 }
 
 uint64_t
 QuotaManager::GetGroupLimit() const
 {
   MOZ_ASSERT(mTemporaryStorageInitialized);
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -4,16 +4,17 @@
  * 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/LocalStorageCommon.h"
 #include "mozilla/dom/ipc/IdType.h"
 
 #include "PersistenceType.h"
 
 class nsIFile;
 class nsIRunnable;
 
 #define IDB_DIRECTORY_NAME "idb"
@@ -34,24 +35,33 @@ class Client
 {
 public:
   typedef mozilla::Atomic<bool> AtomicBool;
 
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   enum Type {
     IDB = 0,
-    //LS,
     //APPCACHE,
     ASMJS,
     DOMCACHE,
     SDB,
+    LS,
     TYPE_MAX
   };
 
+  static Type
+  TypeMax()
+  {
+    if (CachedNextGenLocalStorageEnabled()) {
+      return TYPE_MAX;
+    }
+    return LS;
+  }
+
   virtual Type
   GetType() = 0;
 
   static nsresult
   TypeToText(Type aType, nsAString& aText)
   {
     switch (aType) {
       case IDB:
@@ -65,16 +75,23 @@ public:
       case DOMCACHE:
         aText.AssignLiteral(DOMCACHE_DIRECTORY_NAME);
         break;
 
       case SDB:
         aText.AssignLiteral(SDB_DIRECTORY_NAME);
         break;
 
+      case LS:
+        if (CachedNextGenLocalStorageEnabled()) {
+          aText.AssignLiteral(LS_DIRECTORY_NAME);
+          break;
+        }
+        MOZ_FALLTHROUGH;
+
       case TYPE_MAX:
       default:
         MOZ_ASSERT_UNREACHABLE("Bad id value!");
         return NS_ERROR_UNEXPECTED;
     }
 
     return NS_OK;
   }
@@ -89,16 +106,20 @@ public:
       aType = ASMJS;
     }
     else if (aText.EqualsLiteral(DOMCACHE_DIRECTORY_NAME)) {
       aType = DOMCACHE;
     }
     else if (aText.EqualsLiteral(SDB_DIRECTORY_NAME)) {
       aType = SDB;
     }
+    else if (CachedNextGenLocalStorageEnabled() &&
+             aText.EqualsLiteral(LS_DIRECTORY_NAME)) {
+      aType = LS;
+    }
     else {
       return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
   }
 
   // Methods which are called on the IO thread.
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -519,17 +519,17 @@ private:
   void
   FinalizeOriginEviction(nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
 
   void
   ReleaseIOThreadObjects()
   {
     AssertIsOnIOThread();
 
-    for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
+    for (uint32_t index = 0; index < uint32_t(Client::TypeMax()); index++) {
       mClients[index]->ReleaseIOThreadObjects();
     }
   }
 
   DirectoryLockTable&
   GetDirectoryLockTable(PersistenceType aPersistenceType);
 
   bool
--- a/dom/storage/Storage.cpp
+++ b/dom/storage/Storage.cpp
@@ -14,17 +14,17 @@
 namespace mozilla {
 namespace dom {
 
 static const char kStorageEnabled[] = "dom.storage.enabled";
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mWindow, mPrincipal)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Storage)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(Storage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Storage, LastRelease())
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Storage)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 Storage::Storage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal)
   : mWindow(aWindow)
--- a/dom/storage/Storage.h
+++ b/dom/storage/Storage.h
@@ -133,16 +133,20 @@ protected:
   // CanUseStorage is called before any DOM initiated operation
   // on a storage is about to happen and ensures that the storage's
   // session-only flag is properly set according the current settings.
   // It is an optimization since the privileges check and session only
   // state determination are complex and share the code (comes hand in
   // hand together).
   bool CanUseStorage(nsIPrincipal& aSubjectPrincipal);
 
+  virtual void
+  LastRelease()
+  { }
+
 private:
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   // Whether storage is set to persist data only per session, may change
   // dynamically and is set by CanUseStorage function that is called
   // before any operation on the storage.
   bool mIsSessionOnly : 1;