Bug 1563023 - Part 4: Get rid of custom usage tracking in LS by using client usage tracked by QM; r=asuth
authorJan Varga <jan.varga@gmail.com>
Fri, 23 Aug 2019 04:46:14 +0000
changeset 553290 5df00af5913e90ba7c60411e1e6cb78c2f7c46e0
parent 553289 54d0977adb446888899d91be9ab9a5276ccbd012
child 553291 e21bc14e05be8c5f0ae2674fe50526bfbd4bae15
child 553323 435296776a6e8c59f69fc5b3ca5a7c16d56e1f79
push id2165
push userffxbld-merge
push dateMon, 14 Oct 2019 16:30:58 +0000
treeherdermozilla-release@0eae18af659f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1563023
milestone70.0a1
first release with
nightly linux32
5df00af5913e / 70.0a1 / 20190823094538 / files
nightly linux64
5df00af5913e / 70.0a1 / 20190823094538 / files
nightly mac
5df00af5913e / 70.0a1 / 20190823094538 / files
nightly win32
5df00af5913e / 70.0a1 / 20190823094538 / files
nightly win64
5df00af5913e / 70.0a1 / 20190823094538 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1563023 - Part 4: Get rid of custom usage tracking in LS by using client usage tracked by QM; r=asuth This patch gets rid of gUsages in LSNG. This provides better consistency and makes it easier to cache quota info on disk. The patch also fixes some edge cases when usage was not adjusted correctly after a failed file or database operation. Differential Revision: https://phabricator.services.mozilla.com/D38229
dom/localstorage/ActorsParent.cpp
dom/localstorage/test/unit/head.js
dom/localstorage/test/unit/test_corruptedDatabase.js
dom/localstorage/test/unit/test_originInit.js
dom/localstorage/test/unit/test_usageAfterMigration.js
dom/localstorage/test/unit/usageAfterMigration_profile.zip
dom/localstorage/test/unit/xpcshell.ini
dom/quota/ActorsParent.cpp
dom/quota/Client.h
dom/quota/QuotaManager.h
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -1138,19 +1138,32 @@ nsresult GetUsageJournalFile(const nsASt
 
 nsresult UpdateUsageFile(nsIFile* aUsageFile, nsIFile* aUsageJournalFile,
                          int64_t aUsage) {
   MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aUsageFile);
   MOZ_ASSERT(aUsageJournalFile);
   MOZ_ASSERT(aUsage >= 0);
 
-  nsresult rv = aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+  bool isDirectory;
+  nsresult rv = aUsageJournalFile->IsDirectory(&isDirectory);
+  if (rv != NS_ERROR_FILE_NOT_FOUND &&
+      rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (NS_WARN_IF(isDirectory)) {
+      return NS_ERROR_FAILURE;
+    }
+  } else {
+    rv = aUsageJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   nsCOMPtr<nsIOutputStream> stream;
   rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aUsageFile);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -1409,20 +1422,22 @@ class Connection final {
   /**
    * Propagated from PrepareDatastoreOp. PrepareDatastoreOp may defer the
    * creation of the localstorage client directory and database on the
    * QuotaManager IO thread in its DatabaseWork method to
    * Connection::EnsureStorageConnection, in which case the method needs to know
    * it is responsible for taking those actions (without redundantly performing
    * the existence checks).
    */
-  const bool mDatabaseNotAvailable;
+  const bool mDatabaseWasNotAvailable;
+  bool mHasCreatedDatabase;
   bool mFlushScheduled;
 #ifdef DEBUG
   bool mInUpdateBatch;
+  bool mFinished;
 #endif
 
  public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
   void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); }
 
   QuotaClient* GetQuotaClient() const {
@@ -1434,16 +1449,25 @@ class Connection final {
   ArchivedOriginScope* GetArchivedOriginScope() const {
     return mArchivedOriginScope;
   }
 
   const nsCString& Origin() const { return mOrigin; }
 
   const nsString& DirectoryPath() const { return mDirectoryPath; }
 
+  void GetFinishInfo(bool& aDatabaseWasNotAvailable,
+                     bool& aHasCreatedDatabase) const {
+    AssertIsOnOwningThread();
+    MOZ_ASSERT(mFinished);
+
+    aDatabaseWasNotAvailable = mDatabaseWasNotAvailable;
+    aHasCreatedDatabase = mHasCreatedDatabase;
+  }
+
   //////////////////////////////////////////////////////////////////////////////
   // Methods which can only be called on the owning thread.
 
   // This method is used to asynchronously execute a connection datastore
   // operation on the connection thread.
   void Dispatch(ConnectionDatastoreOperationBase* aOp);
 
   // This method is used to asynchronously close the storage connection on the
@@ -1483,17 +1507,17 @@ class Connection final {
 
   nsresult RollbackWriteTransaction();
 
  private:
   // Only created by ConnectionThread.
   Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix,
              const nsACString& aGroup, const nsACString& aOrigin,
              nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
-             bool aDatabaseNotAvailable);
+             bool aDatabaseWasNotAvailable);
 
   ~Connection();
 
   void ScheduleFlush();
 
   void Flush();
 
   static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
@@ -1519,24 +1543,24 @@ class Connection::CachedStatement final 
               already_AddRefed<mozIStorageStatement> aStatement);
 
   // No funny business allowed.
   CachedStatement(const CachedStatement&) = delete;
   CachedStatement& operator=(const CachedStatement&) = delete;
 };
 
 /**
- * Helper to invoke EnsureOriginIsInitialized and InitUsageForOrigin on the
- * QuotaManager IO thread from the LocalStorage connection thread when creating
- * a database connection on demand. This is necessary because we attempt to
- * defer the creation of the origin directory and the database until absolutely
- * needed, but the directory creation and origin initialization must happen on
- * the QM IO thread for invariant reasons. (We can't just use a mutex because
- * there could be logic on the IO thread that also wants to deal with the same
- * origin, so we need to queue a runnable and wait our turn.)
+ * Helper to invoke EnsureOriginIsInitialized on the QuotaManager IO thread from
+ * the LocalStorage connection thread when creating a database connection on
+ * demand. This is necessary because we attempt to defer the creation of the
+ * origin directory and the database until absolutely needed, but the directory
+ * creation and origin initialization must happen on the QM IO thread for
+ * invariant reasons. (We can't just use a mutex because there could be logic on
+ * the IO thread that also wants to deal with the same origin, so we need to
+ * queue a runnable and wait our turn.)
  */
 class Connection::InitOriginHelper final : public Runnable {
   mozilla::Monitor mMonitor;
   const nsCString mSuffix;
   const nsCString mGroup;
   const nsCString mOrigin;
   nsString mOriginDirectoryPath;
   nsresult mIOThreadResultCode;
@@ -1609,17 +1633,17 @@ class ConnectionThread final {
   bool IsOnConnectionThread();
 
   void AssertIsOnConnectionThread();
 
   already_AddRefed<Connection> CreateConnection(
       const nsACString& aSuffix, const nsACString& aGroup,
       const nsACString& aOrigin,
       nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
-      bool aDatabaseNotAvailable);
+      bool aDatabaseWasNotAvailable);
 
   void Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
  private:
   ~ConnectionThread();
 };
@@ -1670,29 +1694,31 @@ class Datastore final
   nsDataHashtable<nsStringHashKey, LSValue> mValues;
   /**
    * The authoritative ordered state of the Datastore; mValue also exists as an
    * unordered hashtable for efficient lookup.
    */
   nsTArray<LSItemInfo> mOrderedItems;
   nsTArray<int64_t> mPendingUsageDeltas;
   DatastoreWriteOptimizer mWriteOptimizer;
+  const nsCString mGroup;
   const nsCString mOrigin;
   const uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   int64_t mUpdateBatchUsage;
   int64_t mSizeOfKeys;
   int64_t mSizeOfItems;
   bool mClosed;
   bool mInUpdateBatch;
 
  public:
   // Created by PrepareDatastoreOp.
-  Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId,
-            int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
+  Datastore(const nsACString& aGroup, const nsACString& aOrigin,
+            uint32_t aPrivateBrowsingId, int64_t aUsage, int64_t aSizeOfKeys,
+            int64_t aSizeOfItems,
             already_AddRefed<DirectoryLock>&& aDirectoryLock,
             already_AddRefed<Connection>&& aConnection,
             already_AddRefed<QuotaObject>&& aQuotaObject,
             nsDataHashtable<nsStringHashKey, LSValue>& aValues,
             nsTArray<LSItemInfo>& aOrderedItems);
 
   const nsCString& Origin() const { return mOrigin; }
 
@@ -2765,18 +2791,16 @@ class QuotaClient final : public mozilla
       const Nullable<PersistenceType>& aPersistenceType,
       const OriginScope& aOriginScope) override;
 
   void OnOriginClearCompleted(PersistenceType aPersistenceType,
                               const nsACString& aOrigin) override;
 
   void ReleaseIOThreadObjects() override;
 
-  void OnStorageInitFailed() override;
-
   void AbortOperations(const nsACString& aOrigin) override;
 
   void AbortOperationsForProcess(ContentParentId aContentParentId) override;
 
   void StartIdleMaintenance() override;
 
   void StopIdleMaintenance() override;
 
@@ -2910,19 +2934,16 @@ Atomic<bool> gNextGen(kDefaultNextGen);
 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
 Atomic<int32_t, Relaxed> gSnapshotGradualPrefill(
     kDefaultSnapshotGradualPrefill);
 Atomic<bool> gClientValidation(kDefaultClientValidation);
 
 typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
 
-// Can only be touched on the Quota Manager I/O thread.
-StaticAutoPtr<UsageHashtable> gUsages;
-
 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
 
 // Can only be touched on the Quota Manager I/O thread.
 bool gInitializedShadowStorage = false;
 
 bool IsOnConnectionThread() {
   MOZ_ASSERT(gConnectionThread);
   return gConnectionThread->IsOnConnectionThread();
@@ -2944,51 +2965,16 @@ already_AddRefed<Datastore> GetDatastore
       RefPtr<Datastore> result(datastore);
       return result.forget();
     }
   }
 
   return nullptr;
 }
 
-void InitUsageForOrigin(const nsACString& aOrigin, int64_t aUsage) {
-  AssertIsOnIOThread();
-
-  if (!gUsages) {
-    gUsages = new UsageHashtable();
-  }
-
-  MOZ_ASSERT(!gUsages->Contains(aOrigin));
-  gUsages->Put(aOrigin, aUsage);
-}
-
-bool GetUsageForOrigin(const nsACString& aOrigin, int64_t& aUsage) {
-  AssertIsOnIOThread();
-
-  if (gUsages) {
-    int64_t usage;
-    if (gUsages->Get(aOrigin, &usage)) {
-      MOZ_ASSERT(usage >= 0);
-
-      aUsage = usage;
-      return true;
-    }
-  }
-
-  return false;
-}
-
-void UpdateUsageForOrigin(const nsACString& aOrigin, int64_t aUsage) {
-  AssertIsOnIOThread();
-  MOZ_ASSERT(gUsages);
-  MOZ_ASSERT(gUsages->Contains(aOrigin));
-
-  gUsages->Put(aOrigin, aUsage);
-}
-
 nsresult LoadArchivedOrigins() {
   AssertIsOnIOThread();
   MOZ_ASSERT(!gArchivedOrigins);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   // Ensure that the webappsstore.sqlite is moved to new place.
@@ -4079,41 +4065,44 @@ ConnectionDatastoreOperationBase::Run() 
 /*******************************************************************************
  * Connection implementation
  ******************************************************************************/
 
 Connection::Connection(ConnectionThread* aConnectionThread,
                        const nsACString& aSuffix, const nsACString& aGroup,
                        const nsACString& aOrigin,
                        nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
-                       bool aDatabaseNotAvailable)
+                       bool aDatabaseWasNotAvailable)
     : mConnectionThread(aConnectionThread),
       mQuotaClient(QuotaClient::GetInstance()),
       mArchivedOriginScope(std::move(aArchivedOriginScope)),
       mSuffix(aSuffix),
       mGroup(aGroup),
       mOrigin(aOrigin),
-      mDatabaseNotAvailable(aDatabaseNotAvailable),
+      mDatabaseWasNotAvailable(aDatabaseWasNotAvailable),
+      mHasCreatedDatabase(false),
       mFlushScheduled(false)
 #ifdef DEBUG
       ,
-      mInUpdateBatch(false)
+      mInUpdateBatch(false),
+      mFinished(false)
 #endif
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aGroup.IsEmpty());
   MOZ_ASSERT(!aOrigin.IsEmpty());
 }
 
 Connection::~Connection() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mStorageConnection);
   MOZ_ASSERT(!mCachedStatements.Count());
+  MOZ_ASSERT(!mFlushScheduled);
   MOZ_ASSERT(!mInUpdateBatch);
-  MOZ_ASSERT(!mFlushScheduled);
+  MOZ_ASSERT(mFinished);
 }
 
 void Connection::Dispatch(ConnectionDatastoreOperationBase* aOp) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mConnectionThread);
 
   MOZ_ALWAYS_SUCCEEDS(
       mConnectionThread->mThread->Dispatch(aOp, NS_DISPATCH_NORMAL));
@@ -4192,17 +4181,17 @@ nsresult Connection::EnsureStorageConnec
     return NS_OK;
   }
 
   nsresult rv;
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
-  if (!mDatabaseNotAvailable) {
+  if (!mDatabaseWasNotAvailable || mHasCreatedDatabase) {
     nsCOMPtr<nsIFile> directoryEntry;
     rv = quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_DEFAULT, mOrigin,
                                              getter_AddRefs(directoryEntry));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
@@ -4277,25 +4266,45 @@ nsresult Connection::EnsureStorageConnec
     }
   }
 
   rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+#ifdef DEBUG
+  rv = directoryEntry->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  MOZ_ASSERT(!exists);
+#endif
+
   nsCOMPtr<nsIFile> usageFile;
   rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> storageConnection;
   bool removedUsageFile;
 
+  auto autoRemove = MakeScopeExit([&] {
+    if (storageConnection) {
+      MOZ_ALWAYS_SUCCEEDS(storageConnection->Close());
+    }
+
+    nsresult rv = directoryEntry->Remove(false);
+    if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+        rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
+      NS_WARNING("Failed to remove database file!");
+    }
+  });
+
   rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin,
                                getter_AddRefs(storageConnection),
                                &removedUsageFile);
 
   MOZ_ASSERT(!removedUsageFile);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -4311,16 +4320,22 @@ nsresult Connection::EnsureStorageConnec
                                        getter_AddRefs(shadowConnection));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     gInitializedShadowStorage = true;
   }
 
+  autoRemove.release();
+
+  if (!mHasCreatedDatabase) {
+    mHasCreatedDatabase = true;
+  }
+
   mStorageConnection = storageConnection;
 
   return NS_OK;
 }
 
 void Connection::CloseStorageConnection() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mStorageConnection);
@@ -4536,18 +4551,16 @@ nsresult Connection::InitOriginHelper::R
     return rv;
   }
 
   rv = directoryEntry->GetPath(mOriginDirectoryPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  InitUsageForOrigin(mOrigin, 0);
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::InitOriginHelper::Run() {
   AssertIsOnIOThread();
 
   nsresult rv = RunOnIOThread();
@@ -4610,28 +4623,16 @@ nsresult Connection::FlushOp::DoDatastor
     return rv;
   }
 
   rv = usageJournalFile->Remove(false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  RefPtr<Runnable> runnable =
-      NS_NewRunnableFunction("dom::localstorage::UpdateUsageRunnable",
-                             [origin = mConnection->Origin(), usage]() {
-                               UpdateUsageForOrigin(origin, usage);
-                             });
-
-  MOZ_ALWAYS_SUCCEEDS(
-      quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
-
   return NS_OK;
 }
 
 void Connection::FlushOp::Cleanup() {
   AssertIsOnOwningThread();
 
   mWriteOptimizer.Reset();
 
@@ -4652,16 +4653,21 @@ nsresult Connection::CloseOp::DoDatastor
 }
 
 void Connection::CloseOp::Cleanup() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mConnection);
 
   mConnection->mConnectionThread->mConnections.Remove(mConnection->mOrigin);
 
+#ifdef DEBUG
+  MOZ_ASSERT(!mConnection->mFinished);
+  mConnection->mFinished = true;
+#endif
+
   nsCOMPtr<nsIRunnable> callback;
   mCallback.swap(callback);
 
   callback->Run();
 
   ConnectionDatastoreOperationBase::Cleanup();
 }
 
@@ -4691,50 +4697,52 @@ bool ConnectionThread::IsOnConnectionThr
 void ConnectionThread::AssertIsOnConnectionThread() {
   MOZ_ASSERT(IsOnConnectionThread());
 }
 
 already_AddRefed<Connection> ConnectionThread::CreateConnection(
     const nsACString& aSuffix, const nsACString& aGroup,
     const nsACString& aOrigin,
     nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
-    bool aDatabaseNotAvailable) {
+    bool aDatabaseWasNotAvailable) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
   MOZ_ASSERT(!mConnections.GetWeak(aOrigin));
 
   RefPtr<Connection> connection =
       new Connection(this, aSuffix, aGroup, aOrigin,
-                     std::move(aArchivedOriginScope), aDatabaseNotAvailable);
+                     std::move(aArchivedOriginScope), aDatabaseWasNotAvailable);
   mConnections.Put(aOrigin, connection);
 
   return connection.forget();
 }
 
 void ConnectionThread::Shutdown() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mThread);
 
   mThread->Shutdown();
 }
 
 /*******************************************************************************
  * Datastore
  ******************************************************************************/
 
-Datastore::Datastore(const nsACString& aOrigin, uint32_t aPrivateBrowsingId,
-                     int64_t aUsage, int64_t aSizeOfKeys, int64_t aSizeOfItems,
+Datastore::Datastore(const nsACString& aGroup, const nsACString& aOrigin,
+                     uint32_t aPrivateBrowsingId, int64_t aUsage,
+                     int64_t aSizeOfKeys, int64_t aSizeOfItems,
                      already_AddRefed<DirectoryLock>&& aDirectoryLock,
                      already_AddRefed<Connection>&& aConnection,
                      already_AddRefed<QuotaObject>&& aQuotaObject,
                      nsDataHashtable<nsStringHashKey, LSValue>& aValues,
                      nsTArray<LSItemInfo>& aOrderedItems)
     : mDirectoryLock(std::move(aDirectoryLock)),
       mConnection(std::move(aConnection)),
       mQuotaObject(std::move(aQuotaObject)),
+      mGroup(aGroup),
       mOrigin(aOrigin),
       mPrivateBrowsingId(aPrivateBrowsingId),
       mUsage(aUsage),
       mUpdateBatchUsage(-1),
       mSizeOfKeys(aSizeOfKeys),
       mSizeOfItems(aSizeOfItems),
       mClosed(false),
       mInUpdateBatch(false) {
@@ -5435,21 +5443,36 @@ void Datastore::ConnectionClosedCallback
   MOZ_ASSERT(mDirectoryLock);
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(mQuotaObject);
   MOZ_ASSERT(mClosed);
 
   // Release the quota object first.
   mQuotaObject = nullptr;
 
+  bool databaseWasNotAvailable;
+  bool hasCreatedDatabase;
+  mConnection->GetFinishInfo(databaseWasNotAvailable, hasCreatedDatabase);
+
+  if (databaseWasNotAvailable && !hasCreatedDatabase) {
+    MOZ_ASSERT(mUsage == 0);
+
+    QuotaManager* quotaManager = QuotaManager::Get();
+    MOZ_ASSERT(quotaManager);
+
+    quotaManager->ResetUsageForClient(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin,
+                                      mozilla::dom::quota::Client::LS);
+  }
+
+  mConnection = nullptr;
+
   // Now it's safe to release the directory lock and unregister itself from
   // the hashtable.
 
   mDirectoryLock = nullptr;
-  mConnection = nullptr;
 
   CleanupMetadata();
 
   if (mCompleteCallback) {
     MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
   }
 }
 
@@ -7025,16 +7048,17 @@ void PrepareDatastoreOp::SendToIOThread(
 
   MOZ_ALWAYS_SUCCEEDS(
       quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
 }
 
 nsresult PrepareDatastoreOp::DatabaseWork() {
   AssertIsOnIOThread();
   MOZ_ASSERT(mArchivedOriginScope);
+  MOZ_ASSERT(mUsage == 0);
   MOZ_ASSERT(mState == State::Nesting);
   MOZ_ASSERT(mNestedState == NestedState::DatabaseWorkOpen);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
@@ -7042,40 +7066,43 @@ nsresult PrepareDatastoreOp::DatabaseWor
   MOZ_ASSERT(quotaManager);
 
   // This must be called before EnsureTemporaryStorageIsInitialized.
   nsresult rv = quotaManager->EnsureStorageIsInitialized();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // This ensures that gUsages gets populated with usages for existings origin
-  // directories.
+  // This ensures that usages for existings origin directories are cached in
+  // memory.
   rv = quotaManager->EnsureTemporaryStorageIsInitialized();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  uint64_t usage;
+  bool hasUsage =
+      quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, mGroup, mOrigin,
+                                      mozilla::dom::quota::Client::LS, usage);
+
   if (!gArchivedOrigins) {
     rv = LoadArchivedOrigins();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(gArchivedOrigins);
   }
 
   bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
 
   // If there's nothing to preload (except the case when we want to migrate data
   // during preloading), then we can finish the operation without creating a
   // datastore in GetResponse (GetResponse won't create a datastore if
   // mDatatabaseNotAvailable and mForPreload are both true).
-  int64_t usage;
-  if (mForPreload && !GetUsageForOrigin(mOrigin, usage) &&
-      !hasDataForMigration) {
+  if (mForPreload && !hasUsage && !hasDataForMigration) {
     return DatabaseNotAvailable();
   }
 
   // The origin directory doesn't need to be created when we don't have data for
   // migration. It will be created on the connection thread in
   // Connection::EnsureStorageConnection.
   // However, origin quota must be initialized, GetQuotaObject in GetResponse
   // would fail otherwise.
@@ -7136,51 +7163,65 @@ nsresult PrepareDatastoreOp::DatabaseWor
   rv = EnsureDirectoryEntry(directoryEntry,
                             /* aCreateIfNotExists */ hasDataForMigration,
                             /* aIsDirectory */ false, &alreadyExisted);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (alreadyExisted) {
-    MOZ_ASSERT(gUsages);
-    DebugOnly<bool> hasUsage = gUsages->Get(mOrigin, &mUsage);
+    // The database does exist.
     MOZ_ASSERT(hasUsage);
+    mUsage = usage;
   } else {
     // The database doesn't exist.
+    MOZ_ASSERT(!hasUsage);
 
     if (!hasDataForMigration) {
       // The database doesn't exist and we don't have data for migration.
       // Finish the operation, but create an empty datastore in GetResponse
       // (GetResponse will create an empty datastore if mDatabaseNotAvailable
       // is true and mForPreload is false).
       return DatabaseNotAvailable();
     }
-
-    MOZ_ASSERT(mUsage == 0);
-    InitUsageForOrigin(mOrigin, mUsage);
-  }
+  }
+
+  // We initialized mDatabaseFilePath and mUsage, GetQuotaObject can be called
+  // from now on.
+  RefPtr<QuotaObject> quotaObject;
 
   nsCOMPtr<nsIFile> usageFile;
   rv = GetUsageFile(directoryPath, getter_AddRefs(usageFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  nsCOMPtr<nsIFile> usageJournalFile;
+  rv = GetUsageJournalFile(directoryPath, getter_AddRefs(usageJournalFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   nsCOMPtr<mozIStorageConnection> connection;
   bool removedUsageFile;
 
   rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin,
                                getter_AddRefs(connection), &removedUsageFile);
 
   // removedUsageFile must be checked before rv since we may need to reset usage
   // even when CreateStorageConnection failed.
   if (removedUsageFile) {
+    if (!quotaObject) {
+      quotaObject = GetQuotaObject();
+      MOZ_ASSERT(quotaObject);
+    }
+
+    MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
+
     mUsage = 0;
-    UpdateUsageForOrigin(mOrigin, mUsage);
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = VerifyDatabaseInformation(connection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -7196,23 +7237,29 @@ nsresult PrepareDatastoreOp::DatabaseWor
     }
 
     int64_t newUsage;
     rv = GetUsage(connection, mArchivedOriginScope, &newUsage);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    RefPtr<QuotaObject> quotaObject = GetQuotaObject();
-    MOZ_ASSERT(quotaObject);
+    if (!quotaObject) {
+      quotaObject = GetQuotaObject();
+      MOZ_ASSERT(quotaObject);
+    }
 
     if (!quotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
       return NS_ERROR_FILE_NO_DEVICE_SPACE;
     }
 
+    auto autoUpdateSize = MakeScopeExit([&quotaObject] {
+      MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
+    });
+
     mozStorageTransaction transaction(
         connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     nsCOMPtr<mozIStorageFunction> function = new CompressFunction();
 
     rv =
         connection->CreateFunction(NS_LITERAL_CSTRING("compress"), 1, function);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -7294,35 +7341,43 @@ nsresult PrepareDatastoreOp::DatabaseWor
       return rv;
     }
 
     rv = stmt->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    rv = UpdateUsageFile(usageFile, usageJournalFile, newUsage);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     rv = transaction.Commit();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    autoUpdateSize.release();
+
+    rv = usageJournalFile->Remove(false);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     rv = DetachArchiveDatabase(connection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     MOZ_ASSERT(gArchivedOrigins);
     MOZ_ASSERT(mArchivedOriginScope->HasMatches(gArchivedOrigins));
     mArchivedOriginScope->RemoveMatches(gArchivedOrigins);
 
     mUsage = newUsage;
-
-    MOZ_ASSERT(gUsages);
-    MOZ_ASSERT(gUsages->Contains(mOrigin));
-    gUsages->Put(mOrigin, newUsage);
   }
 
   nsCOMPtr<mozIStorageConnection> shadowConnection;
   if (!gInitializedShadowStorage) {
     rv = CreateShadowStorageConnection(quotaManager->GetBasePath(),
                                        getter_AddRefs(shadowConnection));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -7474,17 +7529,17 @@ nsresult PrepareDatastoreOp::BeginLoadDa
   }
 
   if (!gConnectionThread) {
     gConnectionThread = new ConnectionThread();
   }
 
   mConnection = gConnectionThread->CreateConnection(
       mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
-      /* aDatabaseNotAvailable */ false);
+      /* aDatabaseWasNotAvailable */ false);
   MOZ_ASSERT(mConnection);
 
   // Must set this before dispatching otherwise we will race with the
   // connection thread.
   mNestedState = NestedState::DatabaseWorkLoadData;
 
   // Can't assign to mLoadDataOp directly since that's a weak reference and
   // LoadDataOp is reference counted.
@@ -7601,28 +7656,28 @@ void PrepareDatastoreOp::GetResponse(LSR
         // Even though there's no database file, we need to create a connection
         // and pass it to datastore.
         if (!gConnectionThread) {
           gConnectionThread = new ConnectionThread();
         }
 
         mConnection = gConnectionThread->CreateConnection(
             mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
-            /* aDatabaseNotAvailable */ true);
+            /* aDatabaseWasNotAvailable */ true);
         MOZ_ASSERT(mConnection);
       }
 
       quotaObject = GetQuotaObject();
       MOZ_ASSERT(quotaObject);
     }
 
-    mDatastore = new Datastore(mOrigin, mPrivateBrowsingId, mUsage, mSizeOfKeys,
-                               mSizeOfItems, mDirectoryLock.forget(),
-                               mConnection.forget(), quotaObject.forget(),
-                               mValues, mOrderedItems);
+    mDatastore = new Datastore(mGroup, mOrigin, mPrivateBrowsingId, mUsage,
+                               mSizeOfKeys, mSizeOfItems,
+                               mDirectoryLock.forget(), mConnection.forget(),
+                               quotaObject.forget(), mValues, mOrderedItems);
 
     mDatastore->NoteLivePrepareDatastoreOp(this);
 
     if (!gDatastores) {
       gDatastores = new DatastoreHashtable();
     }
 
     MOZ_ASSERT(!gDatastores->Get(mOrigin));
@@ -8678,20 +8733,16 @@ nsresult QuotaClient::InitOrigin(Persist
       if (NS_WARN_IF(NS_FAILED(rv))) {
         REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove3);
         return rv;
       }
     }
 
     MOZ_ASSERT(usage >= 0);
 
-    if (!aForGetUsage) {
-      InitUsageForOrigin(aOrigin, usage);
-    }
-
     aUsageInfo->AppendToDatabaseUsage(Some(uint64_t(usage)));
   } else if (usageFileExists) {
     rv = usageFile->Remove(false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       REPORT_TELEMETRY_INIT_ERR(kQuotaExternalError, LS_Remove4);
       return rv;
     }
   }
@@ -8769,19 +8820,22 @@ nsresult QuotaClient::GetUsageForOrigin(
                                         UsageInfo* aUsageInfo) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT);
   MOZ_ASSERT(aUsageInfo);
 
   // We can't open the database at this point, since it can be already used
   // by the connection thread. Use the cached value instead.
 
-  int64_t usage;
-  if (mozilla::dom::GetUsageForOrigin(aOrigin, usage)) {
-    MOZ_ASSERT(usage >= 0);
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  uint64_t usage;
+  if (quotaManager->GetUsageForClient(PERSISTENCE_TYPE_DEFAULT, aGroup, aOrigin,
+                                      Client::LS, usage)) {
     aUsageInfo->AppendToDatabaseUsage(Some(usage));
   }
 
   return NS_OK;
 }
 
 nsresult QuotaClient::AboutToClearOrigins(
     const Nullable<PersistenceType>& aPersistenceType,
@@ -8946,45 +9000,27 @@ nsresult QuotaClient::AboutToClearOrigin
   }
 
   return NS_OK;
 }
 
 void QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
                                          const nsACString& aOrigin) {
   AssertIsOnIOThread();
-
-  if (aPersistenceType != PERSISTENCE_TYPE_DEFAULT) {
-    return;
-  }
-
-  if (gUsages) {
-    gUsages->Remove(aOrigin);
-  }
 }
 
 void QuotaClient::ReleaseIOThreadObjects() {
   AssertIsOnIOThread();
 
-  gUsages = nullptr;
-
   // Delete archived origins hashtable since QuotaManager clears the whole
   // storage directory including ls-archive.sqlite.
 
   gArchivedOrigins = nullptr;
 }
 
-void QuotaClient::OnStorageInitFailed() {
-  AssertIsOnIOThread();
-  MOZ_DIAGNOSTIC_ASSERT(QuotaManager::Get());
-  MOZ_DIAGNOSTIC_ASSERT(!QuotaManager::Get()->IsTemporaryStorageInitialized());
-
-  gUsages = nullptr;
-}
-
 void QuotaClient::AbortOperations(const nsACString& aOrigin) {
   AssertIsOnBackgroundThread();
 
   // A PrepareDatastoreOp object could already acquire a directory lock for
   // the given origin. Its last step is creation of a Datastore object (which
   // will take ownership of the directory lock) and a PreparedDatastore object
   // which keeps the Datastore alive until a database actor is created.
   // We need to invalidate the PreparedDatastore object when it's created,
--- a/dom/localstorage/test/unit/head.js
+++ b/dom/localstorage/test/unit/head.js
@@ -114,18 +114,22 @@ function init() {
 }
 
 function initOrigin(principal, persistence) {
   let request = Services.qms.initStoragesForPrincipal(principal, persistence);
 
   return request;
 }
 
-function getOriginUsage(principal) {
-  let request = Services.qms.getUsageForPrincipal(principal, function() {});
+function getOriginUsage(principal, fromMemory = false) {
+  let request = Services.qms.getUsageForPrincipal(
+    principal,
+    function() {},
+    fromMemory
+  );
 
   return request;
 }
 
 function clear() {
   let request = Services.qms.clear();
 
   return request;
@@ -297,8 +301,24 @@ function requestFinished(request) {
   });
 }
 
 function loadSubscript(path) {
   let file = do_get_file(path, false);
   let uri = Services.io.newFileURI(file);
   Services.scriptloader.loadSubScript(uri.spec);
 }
+
+async function readUsageFromUsageFile(usageFile) {
+  let file = await File.createFromNsIFile(usageFile);
+
+  let buffer = await new Promise(resolve => {
+    let reader = new FileReader();
+    reader.onloadend = () => resolve(reader.result);
+    reader.readAsArrayBuffer(file);
+  });
+
+  // Manually getting the lower 32-bits because of the lack of support for
+  // 64-bit values currently from DataView/JS (other than the BigInt support
+  // that's currently behind a flag).
+  let view = new DataView(buffer, 8, 4);
+  return view.getUint32();
+}
--- a/dom/localstorage/test/unit/test_corruptedDatabase.js
+++ b/dom/localstorage/test/unit/test_corruptedDatabase.js
@@ -33,9 +33,21 @@ async function testSteps() {
   // 2. Remove the folder "storage/temporary".
   installPackage("corruptedDatabase_profile");
 
   let storage = getLocalStorage(principal);
 
   let length = storage.length;
 
   ok(length === 0, "Correct length");
+
+  info("Resetting origin");
+
+  request = resetOrigin(principal);
+  await requestFinished(request);
+
+  info("Getting usage");
+
+  request = getOriginUsage(principal);
+  await requestFinished(request);
+
+  is(request.result.usage, 0, "Correct usage");
 }
--- a/dom/localstorage/test/unit/test_originInit.js
+++ b/dom/localstorage/test/unit/test_originInit.js
@@ -51,32 +51,16 @@ async function testSteps() {
     let bstream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
       Ci.nsIBinaryOutputStream
     );
     bstream.setOutputStream(ostream);
 
     return bstream;
   }
 
-  async function readUsageFromUsageFile() {
-    let file = await File.createFromNsIFile(usageFile);
-
-    let buffer = await new Promise(resolve => {
-      let reader = new FileReader();
-      reader.onloadend = () => resolve(reader.result);
-      reader.readAsArrayBuffer(file);
-    });
-
-    // Manually getting the lower 32-bits because of the lack of support for
-    // 64-bit values currently from DataView/JS (other than the BigInt support
-    // that's currently behind a flag).
-    let view = new DataView(buffer, 8, 4);
-    return view.getUint32();
-  }
-
   async function initTestOrigin() {
     let request = initOrigin(principal, "default");
     await requestFinished(request);
   }
 
   async function checkFiles(wantData, wantUsage) {
     let exists = dataFile.exists();
     if (wantData) {
@@ -91,17 +75,17 @@ async function testSteps() {
     exists = usageFile.exists();
     if (wantUsage) {
       ok(exists, "Usage file does exist");
     } else {
       ok(!exists, "Usage file doesn't exist");
       return;
     }
 
-    let usage = await readUsageFromUsageFile();
+    let usage = await readUsageFromUsageFile(usageFile);
     ok(usage == data.usage, "Correct usage");
   }
 
   async function clearTestOrigin() {
     let request = clearOrigin(principal, "default");
     await requestFinished(request);
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_usageAfterMigration.js
@@ -0,0 +1,150 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+  const principal = getPrincipal("http://example.com");
+
+  const dataFile = getRelativeFile(
+    "storage/default/http+++example.com/ls/data.sqlite"
+  );
+
+  const usageJournalFile = getRelativeFile(
+    "storage/default/http+++example.com/ls/usage-journal"
+  );
+
+  const usageFile = getRelativeFile(
+    "storage/default/http+++example.com/ls/usage"
+  );
+
+  const data = {};
+  data.key = "foo";
+  data.value = "bar";
+  data.usage = data.key.length + data.value.length;
+
+  async function createStorageForMigration(createUsageDir) {
+    info("Clearing");
+
+    let request = clear();
+    await requestFinished(request);
+
+    info("Installing package");
+
+    // The profile contains storage.sqlite and webappsstore.sqlite. The file
+    // create_db.js in the package was run locally, specifically it was
+    // temporarily added to xpcshell.ini and then executed:
+    // mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+    installPackage("usageAfterMigration_profile");
+
+    if (createUsageDir) {
+      info("Initializing origin");
+
+      // Origin must be initialized before the usage dir is created.
+      request = initOrigin(principal, "default");
+      await requestFinished(request);
+
+      info("Creating usage as a directory");
+
+      // This will cause a failure during migration.
+      usageFile.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+    }
+  }
+
+  function verifyData() {
+    ok(dataFile.exists(), "Data file does exist");
+  }
+
+  async function verifyUsage(success) {
+    info("Verifying usage in memory");
+
+    let request = getOriginUsage(principal, /* fromMemory */ true);
+    await requestFinished(request);
+
+    if (success) {
+      is(request.result.usage, data.usage, "Correct usage");
+    } else {
+      is(request.result.usage, 0, "Zero usage");
+    }
+
+    info("Verifying usage on disk");
+
+    if (success) {
+      ok(!usageJournalFile.exists(), "Usage journal file doesn't exist");
+      ok(usageFile.exists(), "Usage file does exist");
+      let usage = await readUsageFromUsageFile(usageFile);
+      is(usage, data.usage, "Correct usage");
+    } else {
+      ok(usageJournalFile.exists(), "Usage journal file does exist");
+      ok(usageFile.exists(), "Usage file does exist");
+    }
+  }
+
+  info("Setting prefs");
+
+  Services.prefs.setBoolPref("dom.storage.next_gen", true);
+
+  info("Stage 1 - Testing usage after successful data migration");
+
+  await createStorageForMigration(/* createUsageDir */ false);
+
+  info("Getting storage");
+
+  let storage = getLocalStorage(principal);
+
+  info("Opening");
+
+  storage.open();
+
+  verifyData();
+
+  await verifyUsage(/* success */ true);
+
+  info("Stage 2 - Testing usage after unsuccessful data migration");
+
+  await createStorageForMigration(/* createUsageDir */ true);
+
+  info("Getting storage");
+
+  storage = getLocalStorage(principal);
+
+  info("Opening");
+
+  try {
+    storage.open();
+    ok(false, "Should have thrown");
+  } catch (ex) {
+    ok(true, "Did throw");
+  }
+
+  verifyData();
+
+  await verifyUsage(/* success */ false);
+
+  info("Stage 3 - Testing usage after unsuccessful/successful data migration");
+
+  await createStorageForMigration(/* createUsageDir */ true);
+
+  info("Getting storage");
+
+  storage = getLocalStorage(principal);
+
+  info("Opening");
+
+  try {
+    storage.open();
+    ok(false, "Should have thrown");
+  } catch (ex) {
+    ok(true, "Did throw");
+  }
+
+  usageFile.remove(true);
+
+  info("Opening");
+
+  storage.open();
+
+  verifyData();
+
+  await verifyUsage(/* success */ true);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..30a73292c3ff8ee3b5881da2af34a0829083eeeb
GIT binary patch
literal 1227
zc$^FHW@Zs#U|`^2h&6rd7jcK1X+0y5+rY>m%pk*1o|=?cP*7Z4l3$dnS6rBrS&|wW
z!pXoKn5Y?h1c*y3xEUB(z5;cC4S9RP(O=k6g6+ZGMM9>wg%2Jj=6N*n26}kxjVR@J
zX>#;F)SFW{p>5jTE#1?)Pu%~UU-8|1hsWZO-gM`A>c+Ew?t3#k-zdL)+pkcb_M;gy
z!XJ8mvdlD@l&+}ad1=M>m`|ruYhOpr^{f7W$s;OzVP)lyFS?fUSLGS-gMZB8naOn@
zF9ub8+*1EZ&#x=+f9?O2{}%J!{$F8#EG}-^<HFFMM~`=k=Wp^e=iYsLw)O7F8+Ynv
zzHPlP{P1hp3{TrVzZM6FF0OmOrR(w3%x9rC(VV4uKOfau?cWh{KXLo1>Sl|{^E9O{
zbC+13-@fUl=EHu&g|pw5u0K2L+T-R$J(oX!JpZn}rl9I-Y~Ho?ljhZXzG5ZdWb*@2
zRm*qo+qc0$hcBuyfbX+s&er}_t8((!{99WdA+oooWWC?A=kb%P81b8P!1vpOvf|o@
z?>1Ou9CfrjqrUN~-Ktfa_HL|-5V_A70FV3Sz_>RsS$g6(kXHjVlLr|0$wjG&C8_Z#
zNqSku;7Gp(jPMVbk?z}fFzc`ZPuu%Cu7|-vU3+7#%QQreCA2%uKd^P`kuw{c)=o)X
z{NC>P>0^DrzP`M?zMScSg!l#%-IL#$o6kSEP?;?uYrx3Q@%*RMo>t-BWA34TY|maU
z*y@{c<Nk$97C~pag4QoTwX5ruzt0l+tIcB8x-BQYw_G@Tano{+o|*bR!Ff?1LzisR
zka#BYK=R2qM*A}stRYXEYlP}280T<pjbq&NxQOAV;K|y#`D$k(Caw0|#PYrBd9r(N
zjboa8^$DMoUo>v-;y$t8bK<uX`6<3T{;2rQJb%*twYu=EOY3s~bM~KGyKn1xwc9RQ
z%KMG_&h83Y`e>iis%hF!Vr!TKP%;mTW^KG7DAbu482Eqz2+B2y>BQt4ZoV%kbv#2o
zPV1lY)d@66OGrvcNht^jNC1MCq&p3q93Pq8*ldMa<2r5y8gMW&1b8zt$uZ-~w-WI9
zU(yI7vF2h{h-=VXf^0BmqJS6-wx{tAkU~u&tdJyv$3)CfLUyVxGXqAL;c_J_8%Pf`
M5dH_!+n7N-06o00O8@`>
--- a/dom/localstorage/test/unit/xpcshell.ini
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -7,16 +7,17 @@ head = head.js
 support-files =
   archive_profile.zip
   corruptedDatabase_profile.zip
   groupMismatch_profile.zip
   migration_profile.zip
   schema3upgrade_profile.zip
   stringLength2_profile.zip
   stringLength_profile.zip
+  usageAfterMigration_profile.zip
 
 [test_archive.js]
 [test_clientValidation.js]
 [test_corruptedDatabase.js]
 [test_databaseShadowing1.js]
 run-sequentially = test_databaseShadowing2.js depends on a file produced by this test
 [test_databaseShadowing2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing1.js
@@ -42,8 +43,9 @@ run-sequentially = this test depends on 
 [test_originInit.js]
 [test_schema3upgrade.js]
 [test_snapshotting.js]
 requesttimeoutfactor = 4
 [test_stringLength.js]
 [test_stringLength2.js]
 [test_uri_encoding_edge_cases.js]
 [test_usage.js]
+[test_usageAfterMigration.js]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -660,16 +660,18 @@ class OriginInfo final {
 
     MOZ_ASSERT(!mQuotaObjects.Count());
   }
 
   void LockedDecreaseUsage(Client::Type aClientType, int64_t aSize);
 
   void LockedResetUsageForClient(Client::Type aClientType);
 
+  bool LockedGetUsageForClient(Client::Type aClientType, uint64_t& aUsage);
+
   void LockedUpdateAccessTime(int64_t aAccessTime) {
     AssertCurrentThreadOwnsQuotaMutex();
 
     mAccessTime = aAccessTime;
   }
 
   void LockedPersist();
 
@@ -3679,16 +3681,44 @@ void QuotaManager::ResetUsageForClient(P
   }
 
   RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
   if (originInfo) {
     originInfo->LockedResetUsageForClient(aClientType);
   }
 }
 
+bool QuotaManager::GetUsageForClient(PersistenceType aPersistenceType,
+                                     const nsACString& aGroup,
+                                     const nsACString& aOrigin,
+                                     Client::Type aClientType,
+                                     uint64_t& aUsage) {
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+  MutexAutoLock lock(mQuotaMutex);
+
+  GroupInfoPair* pair;
+  if (!mGroupInfoPairs.Get(aGroup, &pair)) {
+    return false;
+  }
+
+  RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+  if (!groupInfo) {
+    return false;
+  }
+
+  RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
+  if (!originInfo) {
+    return false;
+  }
+
+  return originInfo->LockedGetUsageForClient(aClientType, aUsage);
+}
+
 void QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType,
                                           const nsACString& aGroup,
                                           const nsACString& aOrigin) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
 
   MutexAutoLock lock(mQuotaMutex);
 
@@ -5890,23 +5920,16 @@ nsresult QuotaManager::EnsureTemporarySt
   }
 
   TimeStamp startTime = TimeStamp::Now();
 
   // A keeper to defer the return only in Nightly, so that the telemetry data
   // for whole profile can be collected
   nsresult statusKeeper = NS_OK;
 
-  AutoTArray<RefPtr<Client>, Client::TYPE_MAX>& clients = mClients;
-  auto autoNotifier = MakeScopeExit([&clients] {
-    for (RefPtr<Client>& client : clients) {
-      client->OnStorageInitFailed();
-    }
-  });
-
   if (NS_WARN_IF(IsShuttingDown())) {
     RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_FAILURE);
   }
 
   nsresult rv = InitializeRepository(PERSISTENCE_TYPE_DEFAULT);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     RECORD_IN_NIGHTLY(statusKeeper, rv);
 
@@ -5960,18 +5983,16 @@ nsresult QuotaManager::EnsureTemporarySt
       return rv;
     }
   }
 
   mTemporaryStorageInitialized = true;
 
   CheckTemporaryStorageLimits();
 
-  autoNotifier.release();
-
   return rv;
 }
 
 nsresult QuotaManager::EnsureOriginDirectory(nsIFile* aDirectory,
                                              bool* aCreated) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aCreated);
@@ -6798,16 +6819,30 @@ void OriginInfo::LockedResetUsageForClie
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, size);
   quotaManager->mTemporaryStorageUsage -= size;
 }
 
+bool OriginInfo::LockedGetUsageForClient(Client::Type aClientType,
+                                         uint64_t& aUsage) {
+  AssertCurrentThreadOwnsQuotaMutex();
+
+  Maybe<uint64_t>& clientUsage = mClientUsages[aClientType];
+
+  if (clientUsage.isNothing()) {
+    return false;
+  }
+
+  aUsage = clientUsage.value();
+  return true;
+}
+
 void OriginInfo::LockedPersist() {
   AssertCurrentThreadOwnsQuotaMutex();
   MOZ_ASSERT(mGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
   MOZ_ASSERT(!mPersisted);
 
   mPersisted = true;
 
   // Remove Usage from GroupInfo
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -207,18 +207,16 @@ class Client {
     return NS_OK;
   }
 
   virtual void OnOriginClearCompleted(PersistenceType aPersistenceType,
                                       const nsACString& aOrigin) = 0;
 
   virtual void ReleaseIOThreadObjects() = 0;
 
-  virtual void OnStorageInitFailed(){};
-
   // Methods which are called on the background thread.
   virtual void AbortOperations(const nsACString& aOrigin) = 0;
 
   virtual void AbortOperationsForProcess(ContentParentId aContentParentId) = 0;
 
   virtual void StartIdleMaintenance() = 0;
 
   virtual void StopIdleMaintenance() = 0;
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -188,16 +188,20 @@ class QuotaManager final : public Backgr
                               const nsACString& aGroup,
                               const nsACString& aOrigin,
                               Client::Type aClientType, int64_t aSize);
 
   void ResetUsageForClient(PersistenceType aPersistenceType,
                            const nsACString& aGroup, const nsACString& aOrigin,
                            Client::Type aClientType);
 
+  bool GetUsageForClient(PersistenceType aPersistenceType,
+                         const nsACString& aGroup, const nsACString& aOrigin,
+                         Client::Type aClientType, uint64_t& aUsage);
+
   void UpdateOriginAccessTime(PersistenceType aPersistenceType,
                               const nsACString& aGroup,
                               const nsACString& aOrigin);
 
   void RemoveQuota();
 
   void RemoveQuotaForOrigin(PersistenceType aPersistenceType,
                             const nsACString& aGroup,