Bug 1548788 - LSNG: Datastore preparation doesn't have to create new origin directories if they don't exist; r=asuth
authorJan Varga <jan.varga@gmail.com>
Fri, 03 May 2019 15:03:54 +0200
changeset 531652 f3bff9237a3016bbf02ea5bb37601ba3e13f84b3
parent 531651 248d2f08f8bd4524658e8840c0e496d82102a15e
child 531653 a52b0d5b0f9403317c9fcc3a4bd19e7c6142152b
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1548788
milestone68.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 1548788 - LSNG: Datastore preparation doesn't have to create new origin directories if they don't exist; r=asuth Differential Revision: https://phabricator.services.mozilla.com/D29822
dom/localstorage/ActorsParent.cpp
dom/quota/ActorsParent.cpp
dom/quota/QuotaManager.h
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -1481,29 +1481,33 @@ class ConnectionDatastoreOperationBase :
 
 class Connection final {
   friend class ConnectionThread;
 
  public:
   class CachedStatement;
 
  private:
+  class InitOriginHelper;
+
   class FlushOp;
   class CloseOp;
 
   RefPtr<ConnectionThread> mConnectionThread;
   RefPtr<QuotaClient> mQuotaClient;
   nsCOMPtr<nsITimer> mFlushTimer;
   nsCOMPtr<mozIStorageConnection> mStorageConnection;
   nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
       mCachedStatements;
   WriteOptimizer mWriteOptimizer;
+  const nsCString mSuffix;
+  const nsCString mGroup;
   const nsCString mOrigin;
-  const nsString mDirectoryPath;
+  nsString mDirectoryPath;
   /**
    * 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).
    */
@@ -1562,18 +1566,18 @@ class Connection final {
 
   void CloseStorageConnection();
 
   nsresult GetCachedStatement(const nsACString& aQuery,
                               CachedStatement* aCachedStatement);
 
  private:
   // Only created by ConnectionThread.
-  Connection(ConnectionThread* aConnectionThread, const nsACString& aOrigin,
-             const nsAString& aDirectoryPath,
+  Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix,
+             const nsACString& aGroup, const nsACString& aOrigin,
              nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
              bool aDatabaseNotAvailable);
 
   ~Connection();
 
   void ScheduleFlush();
 
   void Flush();
@@ -1600,16 +1604,58 @@ class Connection::CachedStatement final 
   void Assign(Connection* aConnection,
               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.)
+ */
+class Connection::InitOriginHelper final : public Runnable {
+  mozilla::Monitor mMonitor;
+  const nsCString mSuffix;
+  const nsCString mGroup;
+  const nsCString mOrigin;
+  nsString mOriginDirectoryPath;
+  nsresult mIOThreadResultCode;
+  bool mWaiting;
+
+ public:
+  InitOriginHelper(const nsACString& aSuffix, const nsACString& aGroup,
+                   const nsACString& aOrigin)
+      : Runnable("dom::localstorage::Connection::InitOriginHelper"),
+        mMonitor("InitOriginHelper::mMonitor"),
+        mSuffix(aSuffix),
+        mGroup(aGroup),
+        mOrigin(aOrigin),
+        mIOThreadResultCode(NS_OK),
+        mWaiting(true) {
+    AssertIsOnConnectionThread();
+  }
+
+  nsresult BlockAndReturnOriginDirectoryPath(nsAString& aOriginDirectoryPath);
+
+ private:
+  ~InitOriginHelper() {}
+
+  nsresult RunOnIOThread();
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class Connection::FlushOp final : public ConnectionDatastoreOperationBase {
   WriteOptimizer mWriteOptimizer;
   bool mShadowWrites;
 
  public:
   FlushOp(Connection* aConnection, WriteOptimizer&& aWriteOptimizer);
 
  private:
@@ -1644,17 +1690,18 @@ class ConnectionThread final {
     NS_ASSERT_OWNINGTHREAD(ConnectionThread);
   }
 
   bool IsOnConnectionThread();
 
   void AssertIsOnConnectionThread();
 
   already_AddRefed<Connection> CreateConnection(
-      const nsACString& aOrigin, const nsAString& aDirectoryPath,
+      const nsACString& aSuffix, const nsACString& aGroup,
+      const nsACString& aOrigin,
       nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
       bool aDatabaseNotAvailable);
 
   void Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
  private:
@@ -2356,17 +2403,16 @@ class PrepareDatastoreOp
   nsDataHashtable<nsStringHashKey, LSValue> mValues;
   nsTArray<LSItemInfo> mOrderedItems;
   const LSRequestCommonParams mParams;
   Maybe<ContentParentId> mContentParentId;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
-  nsString mDirectoryPath;
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   int64_t mSizeOfKeys;
   int64_t mSizeOfItems;
   uint64_t mDatastoreId;
   NestedState mNestedState;
   const bool mForPreload;
@@ -4124,35 +4170,36 @@ ConnectionDatastoreOperationBase::Run() 
   return NS_OK;
 }
 
 /*******************************************************************************
  * Connection implementation
  ******************************************************************************/
 
 Connection::Connection(ConnectionThread* aConnectionThread,
+                       const nsACString& aSuffix, const nsACString& aGroup,
                        const nsACString& aOrigin,
-                       const nsAString& aDirectoryPath,
                        nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
                        bool aDatabaseNotAvailable)
     : mConnectionThread(aConnectionThread),
       mQuotaClient(QuotaClient::GetInstance()),
       mArchivedOriginScope(std::move(aArchivedOriginScope)),
+      mSuffix(aSuffix),
+      mGroup(aGroup),
       mOrigin(aOrigin),
-      mDirectoryPath(aDirectoryPath),
       mDatabaseNotAvailable(aDatabaseNotAvailable),
       mFlushScheduled(false)
 #ifdef DEBUG
       ,
       mInUpdateBatch(false)
 #endif
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(!aGroup.IsEmpty());
   MOZ_ASSERT(!aOrigin.IsEmpty());
-  MOZ_ASSERT(!aDirectoryPath.IsEmpty());
 }
 
 Connection::~Connection() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mStorageConnection);
   MOZ_ASSERT(!mCachedStatements.Count());
   MOZ_ASSERT(!mInUpdateBatch);
   MOZ_ASSERT(!mFlushScheduled);
@@ -4234,104 +4281,145 @@ void Connection::EndUpdateBatch() {
 #ifdef DEBUG
   mInUpdateBatch = false;
 #endif
 }
 
 nsresult Connection::EnsureStorageConnection() {
   AssertIsOnConnectionThread();
 
-  if (!mStorageConnection) {
-    nsCOMPtr<nsIFile> file;
-    nsresult rv = NS_NewLocalFile(mDirectoryPath, false, getter_AddRefs(file));
+  if (mStorageConnection) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  if (!mDatabaseNotAvailable) {
+    nsCOMPtr<nsIFile> directoryEntry;
+    rv = quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_DEFAULT, mOrigin,
+                                             getter_AddRefs(directoryEntry));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    if (mDatabaseNotAvailable) {
-      bool exists;
-      rv = file->Exists(&exists);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      if (!exists) {
-        rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-      }
-    }
-
-    rv = file->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
+    rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = directoryEntry->GetPath(mDirectoryPath);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    nsString databaseFilePath;
+    rv = directoryEntry->GetPath(databaseFilePath);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     nsCOMPtr<mozIStorageConnection> storageConnection;
-
-    if (mDatabaseNotAvailable) {
-      QuotaManager* quotaManager = QuotaManager::Get();
-      MOZ_ASSERT(quotaManager);
-
-      RefPtr<Runnable> runnable =
-          NS_NewRunnableFunction("dom::localstorage::InitUsageRunnable",
-                                 [origin = mOrigin]() {
-                                   InitUsageForOrigin(origin, 0);
-                                 });
-
-      MOZ_ALWAYS_SUCCEEDS(
-          quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
-
-      nsCOMPtr<nsIFile> usageFile;
-      rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      bool removedUsageFile;
-      rv = CreateStorageConnection(file, usageFile, mOrigin,
-                                   getter_AddRefs(storageConnection),
-                                   &removedUsageFile);
-
-      MOZ_ASSERT(!removedUsageFile);
-
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      MOZ_ASSERT(mQuotaClient);
-
-      MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
-
-      nsCOMPtr<mozIStorageConnection> shadowConnection;
-      if (!gInitializedShadowStorage) {
-        rv = CreateShadowStorageConnection(quotaManager->GetBasePath(),
-                                           getter_AddRefs(shadowConnection));
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        gInitializedShadowStorage = true;
-      }
-    } else {
-      nsString filePath;
-      rv = file->GetPath(filePath);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-
-      rv = GetStorageConnection(filePath, getter_AddRefs(storageConnection));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+    rv = GetStorageConnection(databaseFilePath,
+                              getter_AddRefs(storageConnection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
 
     mStorageConnection = storageConnection;
-  }
+
+    return NS_OK;
+  }
+
+  RefPtr<InitOriginHelper> helper =
+      new InitOriginHelper(mSuffix, mGroup, mOrigin);
+
+  nsString originDirectoryPath;
+  rv = helper->BlockAndReturnOriginDirectoryPath(originDirectoryPath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFile> directoryEntry;
+  rv = NS_NewLocalFile(originDirectoryPath, false,
+                       getter_AddRefs(directoryEntry));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = directoryEntry->GetPath(mDirectoryPath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool exists;
+  rv = directoryEntry->Exists(&exists);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (!exists) {
+    rv = directoryEntry->Create(nsIFile::DIRECTORY_TYPE, 0755);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  rv = directoryEntry->Append(NS_LITERAL_STRING(DATA_FILE_NAME));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIFile> usageFile;
+  rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCOMPtr<mozIStorageConnection> storageConnection;
+  bool removedUsageFile;
+
+  rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin,
+                               getter_AddRefs(storageConnection),
+                               &removedUsageFile);
+
+  MOZ_ASSERT(!removedUsageFile);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(mQuotaClient);
+
+  MutexAutoLock shadowDatabaseLock(mQuotaClient->ShadowDatabaseMutex());
+
+  nsCOMPtr<mozIStorageConnection> shadowConnection;
+  if (!gInitializedShadowStorage) {
+    rv = CreateShadowStorageConnection(quotaManager->GetBasePath(),
+                                       getter_AddRefs(shadowConnection));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    gInitializedShadowStorage = true;
+  }
+
+  mStorageConnection = storageConnection;
 
   return NS_OK;
 }
 
 void Connection::CloseStorageConnection() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mStorageConnection);
 
@@ -4450,16 +4538,81 @@ void Connection::CachedStatement::Assign
 
   mStatement = aStatement;
 
   if (mStatement) {
     mScoper.emplace(mStatement);
   }
 }
 
+nsresult Connection::InitOriginHelper::BlockAndReturnOriginDirectoryPath(
+    nsAString& aOriginDirectoryPath) {
+  AssertIsOnConnectionThread();
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  MOZ_ALWAYS_SUCCEEDS(
+      quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL));
+
+  mozilla::MonitorAutoLock lock(mMonitor);
+  while (mWaiting) {
+    lock.Wait();
+  }
+
+  if (NS_WARN_IF(NS_FAILED(mIOThreadResultCode))) {
+    return mIOThreadResultCode;
+  }
+
+  aOriginDirectoryPath = mOriginDirectoryPath;
+  return NS_OK;
+}
+
+nsresult Connection::InitOriginHelper::RunOnIOThread() {
+  AssertIsOnIOThread();
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsCOMPtr<nsIFile> directoryEntry;
+  nsresult rv = quotaManager->EnsureOriginIsInitialized(
+      PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
+      getter_AddRefs(directoryEntry));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    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();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    mIOThreadResultCode = rv;
+  }
+
+  mozilla::MonitorAutoLock lock(mMonitor);
+  MOZ_ASSERT(mWaiting);
+
+  mWaiting = false;
+  lock.Notify();
+
+  return NS_OK;
+}
+
 Connection::FlushOp::FlushOp(Connection* aConnection,
                              WriteOptimizer&& aWriteOptimizer)
     : ConnectionDatastoreOperationBase(aConnection),
       mWriteOptimizer(std::move(aWriteOptimizer)),
       mShadowWrites(gShadowWrites) {}
 
 nsresult Connection::FlushOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
@@ -4608,25 +4761,26 @@ bool ConnectionThread::IsOnConnectionThr
   return NS_SUCCEEDED(mThread->IsOnCurrentThread(&current)) && current;
 }
 
 void ConnectionThread::AssertIsOnConnectionThread() {
   MOZ_ASSERT(IsOnConnectionThread());
 }
 
 already_AddRefed<Connection> ConnectionThread::CreateConnection(
-    const nsACString& aOrigin, const nsAString& aDirectoryPath,
+    const nsACString& aSuffix, const nsACString& aGroup,
+    const nsACString& aOrigin,
     nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
     bool aDatabaseNotAvailable) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!aOrigin.IsEmpty());
   MOZ_ASSERT(!mConnections.GetWeak(aOrigin));
 
   RefPtr<Connection> connection =
-      new Connection(this, aOrigin, aDirectoryPath,
+      new Connection(this, aSuffix, aGroup, aOrigin,
                      std::move(aArchivedOriginScope), aDatabaseNotAvailable);
   mConnections.Put(aOrigin, connection);
 
   return connection.forget();
 }
 
 void ConnectionThread::Shutdown() {
   AssertIsOnOwningThread();
@@ -6738,33 +6892,47 @@ nsresult PrepareDatastoreOp::DatabaseWor
   // 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) {
     return DatabaseNotAvailable();
   }
 
-  // Initialize the origin even when the origin directory doesn't exist and we
-  // don't have data for migration. GetQuotaObject in GetResponse would fail
-  // otherwise.
+  // 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.
   nsCOMPtr<nsIFile> directoryEntry;
-  rv = quotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
-                                               mSuffix, mGroup, mOrigin,
-                                               getter_AddRefs(directoryEntry));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+  if (hasDataForMigration) {
+    rv = quotaManager->EnsureOriginIsInitialized(
+        PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
+        getter_AddRefs(directoryEntry));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    rv = quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_DEFAULT, mOrigin,
+                                             getter_AddRefs(directoryEntry));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    quotaManager->EnsureQuotaForOrigin(PERSISTENCE_TYPE_DEFAULT, mGroup,
+                                       mOrigin);
   }
 
   rv = directoryEntry->Append(NS_LITERAL_STRING(LS_DIRECTORY_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = directoryEntry->GetPath(mDirectoryPath);
+  nsString directoryPath;
+  rv = directoryEntry->GetPath(directoryPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // The ls 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.
   rv = EnsureDirectoryEntry(directoryEntry,
@@ -6810,17 +6978,17 @@ nsresult PrepareDatastoreOp::DatabaseWor
       return DatabaseNotAvailable();
     }
 
     MOZ_ASSERT(mUsage == 0);
     InitUsageForOrigin(mOrigin, mUsage);
   }
 
   nsCOMPtr<nsIFile> usageFile;
-  rv = GetUsageFile(mDirectoryPath, getter_AddRefs(usageFile));
+  rv = GetUsageFile(directoryPath, getter_AddRefs(usageFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<mozIStorageConnection> connection;
   bool removedUsageFile;
 
   rv = CreateStorageConnection(directoryEntry, usageFile, mOrigin,
@@ -7099,17 +7267,17 @@ nsresult PrepareDatastoreOp::BeginLoadDa
     return NS_ERROR_FAILURE;
   }
 
   if (!gConnectionThread) {
     gConnectionThread = new ConnectionThread();
   }
 
   mConnection = gConnectionThread->CreateConnection(
-      mOrigin, mDirectoryPath, std::move(mArchivedOriginScope),
+      mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
       /* aDatabaseNotAvailable */ 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
@@ -7224,17 +7392,17 @@ 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(
-            mOrigin, mDirectoryPath, std::move(mArchivedOriginScope),
+            mSuffix, mGroup, mOrigin, std::move(mArchivedOriginScope),
             /* aDatabaseNotAvailable */ true);
         MOZ_ASSERT(mConnection);
       }
 
       quotaObject = GetQuotaObject();
       MOZ_ASSERT(quotaObject);
     }
 
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -608,20 +608,30 @@ namespace {
 
 class OriginInfo final {
   friend class GroupInfo;
   friend class QuotaManager;
   friend class QuotaObject;
 
  public:
   OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, uint64_t aUsage,
-             int64_t aAccessTime, bool aPersisted);
+             int64_t aAccessTime, bool aPersisted, bool aDirectoryExists);
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo)
 
+  GroupInfo* GetGroupInfo() const { return mGroupInfo; }
+
+  const nsCString& Origin() const { return mOrigin; }
+
+  int64_t LockedUsage() const {
+    AssertCurrentThreadOwnsQuotaMutex();
+
+    return mUsage;
+  }
+
   int64_t LockedAccessTime() const {
     AssertCurrentThreadOwnsQuotaMutex();
 
     return mAccessTime;
   }
 
   bool LockedPersisted() const {
     AssertCurrentThreadOwnsQuotaMutex();
@@ -649,16 +659,29 @@ class OriginInfo final {
 
   nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects;
 
   GroupInfo* mGroupInfo;
   const nsCString mOrigin;
   uint64_t mUsage;
   int64_t mAccessTime;
   bool mPersisted;
+  /**
+   * In some special cases like the LocalStorage client where it's possible to
+   * create a Quota-using representation but not actually write any data, we
+   * want to be able to track quota for an origin without creating its origin
+   * directory or the per-client files until they are actually needed to store
+   * data. In those cases, the OriginInfo will be created by
+   * EnsureQuotaForOrigin and the resulting mDirectoryExists will be false until
+   * the origin actually needs to be created. It is possible for mUsage to be
+   * greater than zero while mDirectoryExists is false, representing a state
+   * where a client like LocalStorage has reserved quota for disk writes, but
+   * has not yet flushed the data to disk.
+   */
+  bool mDirectoryExists;
 };
 
 class OriginInfoLRUComparator {
  public:
   bool Equals(const OriginInfo* a, const OriginInfo* b) const {
     return a && b ? a->LockedAccessTime() == b->LockedAccessTime()
                   : !a && !b ? true : false;
   }
@@ -684,16 +707,18 @@ class GroupInfo final {
         mUsage(0) {
     MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
 
     MOZ_COUNT_CTOR(GroupInfo);
   }
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo)
 
+  PersistenceType GetPersistenceType() const { return mPersistenceType; }
+
  private:
   // Private destructor, to discourage deletion outside of Release():
   ~GroupInfo() { MOZ_COUNT_DTOR(GroupInfo); }
 
   already_AddRefed<OriginInfo> LockedGetOriginInfo(const nsACString& aOrigin);
 
   void LockedAddOriginInfo(OriginInfo* aOriginInfo);
 
@@ -1119,16 +1144,22 @@ class GetUsageOp final : public QuotaUsa
   bool mGetAll;
 
  public:
   explicit GetUsageOp(const UsageRequestParams& aParams);
 
  private:
   ~GetUsageOp() {}
 
+  void ProcessOriginInternal(QuotaManager* aQuotaManager,
+                             const PersistenceType aPersistenceType,
+                             const nsACString& aOrigin,
+                             const int64_t aTimestamp, const bool aPersisted,
+                             const uint64_t aUsage);
+
   nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
 
   bool IsCanceled() override;
 
   nsresult ProcessOrigin(QuotaManager* aQuotaManager, nsIFile* aOriginDir,
                          const bool aPersistent,
                          const PersistenceType aPersistenceType) override;
 
@@ -2219,36 +2250,31 @@ nsresult CreateDirectoryMetadata2(nsIFil
 
   return NS_OK;
 }
 
 nsresult CreateDirectoryMetadataFiles(nsIFile* aDirectory, bool aPersisted,
                                       const nsACString& aSuffix,
                                       const nsACString& aGroup,
                                       const nsACString& aOrigin,
-                                      int64_t* aTimestamp) {
-  AssertIsOnIOThread();
-
-  int64_t timestamp = PR_Now();
+                                      int64_t aTimestamp) {
+  AssertIsOnIOThread();
 
   nsresult rv =
-      CreateDirectoryMetadata(aDirectory, timestamp, aSuffix, aGroup, aOrigin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = CreateDirectoryMetadata2(aDirectory, timestamp, aPersisted, aSuffix,
+      CreateDirectoryMetadata(aDirectory, aTimestamp, aSuffix, aGroup, aOrigin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CreateDirectoryMetadata2(aDirectory, aTimestamp, aPersisted, aSuffix,
                                 aGroup, aOrigin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (aTimestamp) {
-    *aTimestamp = timestamp;
-  }
   return NS_OK;
 }
 
 nsresult GetBinaryInputStream(nsIFile* aDirectory, const nsAString& aFilename,
                               nsIBinaryInputStream** aStream) {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aStream);
@@ -3380,16 +3406,38 @@ uint64_t QuotaManager::CollectOriginsFor
     }
 
     return sizeToBeFreed;
   }
 
   return 0;
 }
 
+template <typename P>
+void QuotaManager::CollectPendingOriginsForListing(P aPredicate) {
+  MutexAutoLock lock(mQuotaMutex);
+
+  for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
+    GroupInfoPair* pair = iter.UserData();
+
+    MOZ_ASSERT(!iter.Key().IsEmpty());
+    MOZ_ASSERT(pair);
+
+    RefPtr<GroupInfo> groupInfo =
+        pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT);
+    if (groupInfo) {
+      for (RefPtr<OriginInfo>& originInfo : groupInfo->mOriginInfos) {
+        if (!originInfo->mDirectoryExists) {
+          aPredicate(originInfo);
+        }
+      }
+    }
+  }
+}
+
 nsresult QuotaManager::Init(const nsAString& aBasePath) {
   mBasePath = aBasePath;
 
   nsCOMPtr<nsIFile> baseDir;
   nsresult rv = NS_NewLocalFile(aBasePath, false, getter_AddRefs(baseDir));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3516,34 +3564,76 @@ void QuotaManager::InitQuotaForOrigin(Pe
                                       const nsACString& aOrigin,
                                       uint64_t aUsageBytes, int64_t aAccessTime,
                                       bool aPersisted) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
 
   MutexAutoLock lock(mQuotaMutex);
 
-  GroupInfoPair* pair;
-  if (!mGroupInfoPairs.Get(aGroup, &pair)) {
-    pair = new GroupInfoPair();
-    mGroupInfoPairs.Put(aGroup, pair);
-    // The hashtable is now responsible to delete the GroupInfoPair.
-  }
-
-  RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
-  if (!groupInfo) {
-    groupInfo = new GroupInfo(pair, aPersistenceType, aGroup);
-    pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
-  }
+  RefPtr<GroupInfo> groupInfo =
+      LockedGetOrCreateGroupInfo(aPersistenceType, aGroup);
 
   RefPtr<OriginInfo> originInfo =
-      new OriginInfo(groupInfo, aOrigin, aUsageBytes, aAccessTime, aPersisted);
+      new OriginInfo(groupInfo, aOrigin, aUsageBytes, aAccessTime, aPersisted,
+                     /* aDirectoryExists */ true);
   groupInfo->LockedAddOriginInfo(originInfo);
 }
 
+void QuotaManager::EnsureQuotaForOrigin(PersistenceType aPersistenceType,
+                                        const nsACString& aGroup,
+                                        const nsACString& aOrigin) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+  MutexAutoLock lock(mQuotaMutex);
+
+  RefPtr<GroupInfo> groupInfo =
+      LockedGetOrCreateGroupInfo(aPersistenceType, aGroup);
+
+  RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
+  if (!originInfo) {
+    originInfo = new OriginInfo(
+        groupInfo, aOrigin, /* aUsageBytes */ 0, /* aAccessTime */ PR_Now(),
+        /* aPersisted */ false, /* aDirectoryExists */ false);
+    groupInfo->LockedAddOriginInfo(originInfo);
+  }
+}
+
+void QuotaManager::NoteOriginDirectoryCreated(PersistenceType aPersistenceType,
+                                              const nsACString& aGroup,
+                                              const nsACString& aOrigin,
+                                              bool aPersisted,
+                                              int64_t& aTimestamp) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+  int64_t timestamp;
+
+  MutexAutoLock lock(mQuotaMutex);
+
+  RefPtr<GroupInfo> groupInfo =
+      LockedGetOrCreateGroupInfo(aPersistenceType, aGroup);
+
+  RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
+  if (originInfo) {
+    originInfo->mPersisted = aPersisted;
+    originInfo->mDirectoryExists = true;
+    timestamp = originInfo->LockedAccessTime();
+  } else {
+    timestamp = PR_Now();
+    RefPtr<OriginInfo> originInfo = new OriginInfo(
+        groupInfo, aOrigin, /* aUsageBytes */ 0, /* aAccessTime */ timestamp,
+        aPersisted, /* aDirectoryExists */ true);
+    groupInfo->LockedAddOriginInfo(originInfo);
+  }
+
+  aTimestamp = timestamp;
+}
+
 void QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
                                           const nsACString& aGroup,
                                           const nsACString& aOrigin,
                                           int64_t aSize) {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
 
   MutexAutoLock lock(mQuotaMutex);
@@ -5680,19 +5770,21 @@ nsresult QuotaManager::EnsureOriginIsIni
   rv = EnsureOriginDirectory(directory, &created);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   int64_t timestamp;
   if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
     if (created) {
+      timestamp = PR_Now();
+
       rv = CreateDirectoryMetadataFiles(directory,
                                         /* aPersisted */ true, aSuffix, aGroup,
-                                        aOrigin, &timestamp);
+                                        aOrigin, timestamp);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
       rv = GetDirectoryMetadata2WithRestore(directory,
                                             /* aPersistent */ true, &timestamp,
                                             /* aPersisted */ nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -5703,27 +5795,25 @@ nsresult QuotaManager::EnsureOriginIsIni
     }
 
     rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, timestamp,
                           /* aPersisted */ true, directory);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mInitializedOrigins.AppendElement(aOrigin);
   } else if (created) {
+    NoteOriginDirectoryCreated(aPersistenceType, aGroup, aOrigin,
+                               /* aPersisted */ false, timestamp);
+
     rv = CreateDirectoryMetadataFiles(directory,
                                       /* aPersisted */ false, aSuffix, aGroup,
-                                      aOrigin, &timestamp);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    // Don't need to traverse the directory, since it's empty.
-    InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin,
-                       /* aUsageBytes */ 0, timestamp,
-                       /* aPersisted */ false);
+                                      aOrigin, timestamp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   directory.forget(aDirectory);
   *aCreated = created;
   return NS_OK;
 }
 
 nsresult QuotaManager::EnsureTemporaryStorageIsInitialized() {
@@ -6251,16 +6341,37 @@ void QuotaManager::LockedRemoveQuotaForO
 
       if (!pair->LockedHasGroupInfos()) {
         mGroupInfoPairs.Remove(aGroup);
       }
     }
   }
 }
 
+already_AddRefed<GroupInfo> QuotaManager::LockedGetOrCreateGroupInfo(
+    PersistenceType aPersistenceType, const nsACString& aGroup) {
+  mQuotaMutex.AssertCurrentThreadOwns();
+  MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
+
+  GroupInfoPair* pair;
+  if (!mGroupInfoPairs.Get(aGroup, &pair)) {
+    pair = new GroupInfoPair();
+    mGroupInfoPairs.Put(aGroup, pair);
+    // The hashtable is now responsible to delete the GroupInfoPair.
+  }
+
+  RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
+  if (!groupInfo) {
+    groupInfo = new GroupInfo(pair, aPersistenceType, aGroup);
+    pair->LockedSetGroupInfo(aPersistenceType, groupInfo);
+  }
+
+  return groupInfo.forget();
+}
+
 already_AddRefed<OriginInfo> QuotaManager::LockedGetOriginInfo(
     PersistenceType aPersistenceType, const nsACString& aGroup,
     const nsACString& aOrigin) {
   mQuotaMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT);
 
   GroupInfoPair* pair;
   if (mGroupInfoPairs.Get(aGroup, &pair)) {
@@ -6506,22 +6617,24 @@ bool QuotaManager::IsSanitizedOriginVali
   return valid;
 }
 
 /*******************************************************************************
  * Local class implementations
  ******************************************************************************/
 
 OriginInfo::OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
-                       uint64_t aUsage, int64_t aAccessTime, bool aPersisted)
+                       uint64_t aUsage, int64_t aAccessTime, bool aPersisted,
+                       bool aDirectoryExists)
     : mGroupInfo(aGroupInfo),
       mOrigin(aOrigin),
       mUsage(aUsage),
       mAccessTime(aAccessTime),
-      mPersisted(aPersisted) {
+      mPersisted(aPersisted),
+      mDirectoryExists(aDirectoryExists) {
   MOZ_ASSERT(aGroupInfo);
   MOZ_ASSERT_IF(aPersisted,
                 aGroupInfo->mPersistenceType == PERSISTENCE_TYPE_DEFAULT);
 
   MOZ_COUNT_CTOR(OriginInfo);
 }
 
 void OriginInfo::LockedDecreaseUsage(int64_t aSize) {
@@ -7614,16 +7727,57 @@ nsresult TraverseRepositoryHelper::Trave
 }
 
 GetUsageOp::GetUsageOp(const UsageRequestParams& aParams)
     : mGetAll(aParams.get_AllUsageParams().getAll()) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams);
 }
 
+void GetUsageOp::ProcessOriginInternal(QuotaManager* aQuotaManager,
+                                       const PersistenceType aPersistenceType,
+                                       const nsACString& aOrigin,
+                                       const int64_t aTimestamp,
+                                       const bool aPersisted,
+                                       const uint64_t aUsage) {
+  if (!mGetAll && aQuotaManager->IsOriginInternal(aOrigin)) {
+    return;
+  }
+
+  OriginUsage* originUsage;
+
+  // We can't store pointers to OriginUsage objects in the hashtable
+  // since AppendElement() reallocates its internal array buffer as number
+  // of elements grows.
+  uint32_t index;
+  if (mOriginUsagesIndex.Get(aOrigin, &index)) {
+    originUsage = &mOriginUsages[index];
+  } else {
+    index = mOriginUsages.Length();
+
+    originUsage = mOriginUsages.AppendElement();
+
+    originUsage->origin() = aOrigin;
+    originUsage->persisted() = false;
+    originUsage->usage() = 0;
+    originUsage->lastAccessed() = 0;
+
+    mOriginUsagesIndex.Put(aOrigin, index);
+  }
+
+  if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
+    originUsage->persisted() = aPersisted;
+  }
+
+  originUsage->usage() = originUsage->usage() + aUsage;
+
+  originUsage->lastAccessed() =
+      std::max<int64_t>(originUsage->lastAccessed(), aTimestamp);
+}
+
 bool GetUsageOp::IsCanceled() {
   AssertIsOnIOThread();
 
   return mCanceled;
 }
 
 nsresult GetUsageOp::ProcessOrigin(QuotaManager* aQuotaManager,
                                    nsIFile* aOriginDir, const bool aPersistent,
@@ -7638,54 +7792,25 @@ nsresult GetUsageOp::ProcessOrigin(Quota
   nsCString group;
   nsCString origin;
   nsresult rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
       aOriginDir, aPersistent, &timestamp, &persisted, suffix, group, origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (!mGetAll && aQuotaManager->IsOriginInternal(origin)) {
-    return NS_OK;
-  }
-
-  OriginUsage* originUsage;
-
-  // We can't store pointers to OriginUsage objects in the hashtable
-  // since AppendElement() reallocates its internal array buffer as number
-  // of elements grows.
-  uint32_t index;
-  if (mOriginUsagesIndex.Get(origin, &index)) {
-    originUsage = &mOriginUsages[index];
-  } else {
-    index = mOriginUsages.Length();
-
-    originUsage = mOriginUsages.AppendElement();
-
-    originUsage->origin() = origin;
-    originUsage->persisted() = false;
-    originUsage->usage() = 0;
-
-    mOriginUsagesIndex.Put(origin, index);
-  }
-
-  if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) {
-    originUsage->persisted() = persisted;
-  }
-
-  originUsage->lastAccessed() = timestamp;
-
   UsageInfo usageInfo;
   rv = GetUsageForOrigin(aQuotaManager, aPersistenceType, group, origin,
                          &usageInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  originUsage->usage() = originUsage->usage() + usageInfo.TotalUsage();
+  ProcessOriginInternal(aQuotaManager, aPersistenceType, origin, timestamp,
+                        persisted, usageInfo.TotalUsage());
 
   return NS_OK;
 }
 
 nsresult GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
   AssertIsOnIOThread();
 
   AUTO_PROFILER_LABEL("GetUsageOp::DoDirectoryWork", OTHER);
@@ -7694,16 +7819,28 @@ nsresult GetUsageOp::DoDirectoryWork(Quo
 
   for (const PersistenceType type : kAllPersistenceTypes) {
     rv = TraverseRepository(aQuotaManager, type);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
+  // TraverseRepository above only consulted the filesystem. We also need to
+  // consider origins which may have pending quota usage, such as buffered
+  // LocalStorage writes for an origin which didn't previously have any
+  // LocalStorage data.
+
+  aQuotaManager->CollectPendingOriginsForListing([&](OriginInfo* aOriginInfo) {
+    ProcessOriginInternal(
+        aQuotaManager, aOriginInfo->GetGroupInfo()->GetPersistenceType(),
+        aOriginInfo->Origin(), aOriginInfo->LockedAccessTime(),
+        aOriginInfo->LockedPersisted(), aOriginInfo->LockedUsage());
+  });
+
   return NS_OK;
 }
 
 void GetUsageOp::GetResponse(UsageRequestResponse& aResponse) {
   AssertIsOnOwningThread();
 
   aResponse = AllUsageResponse();
 
@@ -8429,30 +8566,32 @@ nsresult PersistOp::DoDirectoryWork(Quot
   bool created;
   rv = aQuotaManager->EnsureOriginDirectory(directory, &created);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (created) {
     int64_t timestamp;
+
+    // Origin directory has been successfully created.
+    // Create OriginInfo too if temporary storage was already initialized.
+    if (aQuotaManager->IsTemporaryStorageInitialized()) {
+      aQuotaManager->NoteOriginDirectoryCreated(
+          mPersistenceType.Value(), mGroup, mOriginScope.GetOrigin(),
+          /* aPersisted */ true, timestamp);
+    } else {
+      timestamp = PR_Now();
+    }
+
     rv = CreateDirectoryMetadataFiles(directory,
                                       /* aPersisted */ true, mSuffix, mGroup,
-                                      mOriginScope.GetOrigin(), &timestamp);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    // Directory metadata has been successfully created.
-    // Create OriginInfo too if temporary storage was already initialized.
-    if (aQuotaManager->IsTemporaryStorageInitialized()) {
-      aQuotaManager->InitQuotaForOrigin(mPersistenceType.Value(), mGroup,
-                                        mOriginScope.GetOrigin(),
-                                        /* aUsageBytes */ 0, timestamp,
-                                        /* aPersisted */ true);
+                                      mOriginScope.GetOrigin(), timestamp);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
   } else {
     // Get the persisted flag (restore the metadata file if necessary).
     bool persisted;
     rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
         directory,
         /* aPersistent */ false,
         /* aTimestamp */ nullptr, &persisted);
@@ -8537,16 +8676,24 @@ nsresult ListInitializedOriginsOp::DoDir
 
   for (const PersistenceType type : kAllPersistenceTypes) {
     rv = TraverseRepository(aQuotaManager, type);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
+  // TraverseRepository above only consulted the file-system to get a list of
+  // known origins, but we also need to include origins that have pending quota
+  // usage.
+
+  aQuotaManager->CollectPendingOriginsForListing([&](OriginInfo* aOriginInfo) {
+    mOrigins.AppendElement(aOriginInfo->Origin());
+  });
+
   return NS_OK;
 }
 
 bool ListInitializedOriginsOp::IsCanceled() {
   AssertIsOnIOThread();
 
   return mCanceled;
 }
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -140,21 +140,51 @@ class QuotaManager final : public Backgr
   }
 
   bool IsTemporaryStorageInitialized() const {
     AssertIsOnIOThread();
 
     return mTemporaryStorageInitialized;
   }
 
+  /**
+   * For initialization of an origin where the directory already exists. This is
+   * used by EnsureTemporaryStorageIsInitialized/InitializeRepository once it
+   * has tallied origin usage by calling each of the QuotaClient InitOrigin
+   * methods.
+   */
   void InitQuotaForOrigin(PersistenceType aPersistenceType,
                           const nsACString& aGroup, const nsACString& aOrigin,
                           uint64_t aUsageBytes, int64_t aAccessTime,
                           bool aPersisted);
 
+  /**
+   * For use in special-cases like LSNG where we need to be able to know that
+   * there is no data stored for an origin. LSNG knows that there is 0 usage for
+   * its storage of an origin and wants to make sure there is a QuotaObject
+   * tracking this. This method will create a non-persisted, 0-usage,
+   * mDirectoryExists=false OriginInfo if there isn't already an OriginInfo. If
+   * an OriginInfo already exists, it will be left as-is, because that implies a
+   * different client has usages for the origin (and there's no need to add
+   * LSNG's 0 usage to the QuotaObject).
+   */
+  void EnsureQuotaForOrigin(PersistenceType aPersistenceType,
+                            const nsACString& aGroup,
+                            const nsACString& aOrigin);
+
+  /**
+   * For use when creating an origin directory. It's possible that origin usage
+   * is already being tracked due to a call to EnsureQuotaForOrigin, and in that
+   * case we need to update the existing OriginInfo rather than create a new one.
+   */
+  void NoteOriginDirectoryCreated(PersistenceType aPersistenceType,
+                                  const nsACString& aGroup,
+                                  const nsACString& aOrigin, bool aPersisted,
+                                  int64_t& aTimestamp);
+
   void DecreaseUsageForOrigin(PersistenceType aPersistenceType,
                               const nsACString& aGroup,
                               const nsACString& aOrigin, int64_t aSize);
 
   void UpdateOriginAccessTime(PersistenceType aPersistenceType,
                               const nsACString& aGroup,
                               const nsACString& aOrigin);
 
@@ -243,16 +273,29 @@ class QuotaManager final : public Backgr
                              const Nullable<Client::Type>& aClientType,
                              bool aExclusive,
                              OpenDirectoryListener* aOpenListener);
 
   // Collect inactive and the least recently used origins.
   uint64_t CollectOriginsForEviction(
       uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
 
+  /**
+   * Helper method to invoke the provided predicate on all "pending" OriginInfo
+   * instances. These are origins for which the origin directory has not yet
+   * been created but for which quota is already being tracked. This happens,
+   * for example, for the LocalStorage client where an origin that previously
+   * was not using LocalStorage can start issuing writes which it buffers until
+   * eventually flushing them. We defer creating the origin directory for as
+   * long as possible in that case, so the directory won't exist. Logic that
+   * would otherwise only consult the filesystem also needs to use this method.
+   */
+  template <typename P>
+  void CollectPendingOriginsForListing(P aPredicate);
+
   void AssertStorageIsInitialized() const
 #ifdef DEBUG
       ;
 #else
   {
   }
 #endif
 
@@ -394,16 +437,19 @@ class QuotaManager final : public Backgr
 
   uint64_t LockedCollectOriginsForEviction(
       uint64_t aMinSizeToBeFreed, nsTArray<RefPtr<DirectoryLockImpl>>& aLocks);
 
   void LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
                                   const nsACString& aGroup,
                                   const nsACString& aOrigin);
 
+  already_AddRefed<GroupInfo> LockedGetOrCreateGroupInfo(
+      PersistenceType aPersistenceType, const nsACString& aGroup);
+
   already_AddRefed<OriginInfo> LockedGetOriginInfo(
       PersistenceType aPersistenceType, const nsACString& aGroup,
       const nsACString& aOrigin);
 
   nsresult MaybeUpgradeIndexedDBDirectory();
 
   nsresult MaybeUpgradePersistentStorageDirectory();