Merge mozilla-central to autoland on a CLOSED TREE
authorMihai Alexandru Michis <malexandru@mozilla.com>
Mon, 06 May 2019 12:55:43 +0300
changeset 531487 1c299539ae52612e97589699a089b0aa208d053f
parent 531486 f92e2f3f7fa9b920780230a3db4695ecb15d55eb (current diff)
parent 531475 c6d806b496845985516cc04342c04988aa1817dd (diff)
child 531488 fdc2af8b1f733114a7301804e98f9917dfeb0e34
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)
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
Merge mozilla-central to autoland on a CLOSED TREE
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -384,18 +384,17 @@ Context::QuotaInitRunnable::Run() {
         resolver->Resolve(NS_ERROR_ABORT);
         break;
       }
 
       QuotaManager* qm = QuotaManager::Get();
       MOZ_DIAGNOSTIC_ASSERT(qm);
       nsresult rv = qm->EnsureOriginIsInitialized(
           PERSISTENCE_TYPE_DEFAULT, mQuotaInfo.mSuffix, mQuotaInfo.mGroup,
-          mQuotaInfo.mOrigin,
-          /* aCreateIfNotExists */ true, getter_AddRefs(mQuotaInfo.mDir));
+          mQuotaInfo.mOrigin, getter_AddRefs(mQuotaInfo.mDir));
       if (NS_FAILED(rv)) {
         resolver->Resolve(rv);
         break;
       }
 
       mState = STATE_RUN_ON_TARGET;
 
       MOZ_ALWAYS_SUCCEEDS(mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -17154,18 +17154,17 @@ nsresult Maintenance::DirectoryWork() {
             persistenceType, group, origin, std::move(databasePaths)));
 
         nsCOMPtr<nsIFile> directory;
 
         // Idle maintenance may occur before origin is initailized.
         // Ensure origin is initialized first. It will initialize all origins
         // for temporary storage including IDB origins.
         rv = quotaManager->EnsureOriginIsInitialized(
-            persistenceType, suffix, group, origin,
-            /* aCreateIfNotExists */ true, getter_AddRefs(directory));
+            persistenceType, suffix, group, origin, getter_AddRefs(directory));
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       }
     }
   }
 
@@ -19864,18 +19863,17 @@ nsresult OpenDatabaseOp::DoDatabaseWork(
   PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   nsCOMPtr<nsIFile> dbDirectory;
 
   nsresult rv = quotaManager->EnsureOriginIsInitialized(
-      persistenceType, mSuffix, mGroup, mOrigin,
-      /* aCreateIfNotExists */ true, getter_AddRefs(dbDirectory));
+      persistenceType, mSuffix, mGroup, mOrigin, getter_AddRefs(dbDirectory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/dom/localstorage/ActorsChild.cpp
+++ b/dom/localstorage/ActorsChild.cpp
@@ -65,19 +65,19 @@ mozilla::ipc::IPCResult LSDatabaseChild:
     //       datastore right here, but asynchronously.
     //       However, we probably shouldn't do that if we are shutting down.
   }
 
   return IPC_OK();
 }
 
 PBackgroundLSSnapshotChild* LSDatabaseChild::AllocPBackgroundLSSnapshotChild(
-    const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-    const int64_t& aRequestedSize, const int64_t& aMinSize,
-    LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aDocumentURI, const nsString& aKey,
+    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
   MOZ_CRASH("PBackgroundLSSnapshotChild actor should be manually constructed!");
 }
 
 bool LSDatabaseChild::DeallocPBackgroundLSSnapshotChild(
     PBackgroundLSSnapshotChild* aActor) {
   MOZ_ASSERT(aActor);
 
   delete aActor;
@@ -121,32 +121,32 @@ void LSObserverChild::ActorDestroy(Actor
     mObserver = nullptr;
 #endif
   }
 }
 
 mozilla::ipc::IPCResult LSObserverChild::RecvObserve(
     const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
     const nsString& aDocumentURI, const nsString& aKey,
-    const nsString& aOldValue, const nsString& aNewValue) {
+    const LSValue& aOldValue, const LSValue& aNewValue) {
   AssertIsOnOwningThread();
 
   if (!mObserver) {
     return IPC_OK();
   }
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
       PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey, aOldValue,
-                        aNewValue,
+  Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey,
+                        aOldValue.AsString(), aNewValue.AsString(),
                         /* aStorageType */ kLocalStorageType, aDocumentURI,
                         /* aIsPrivate */ !!aPrivateBrowsingId,
                         /* aImmediateDispatch */ true);
 
   return IPC_OK();
 }
 
 /*******************************************************************************
--- a/dom/localstorage/ActorsChild.h
+++ b/dom/localstorage/ActorsChild.h
@@ -73,19 +73,19 @@ class LSDatabaseChild final : public PBa
   void SendDeleteMeInternal();
 
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvRequestAllowToClose() override;
 
   PBackgroundLSSnapshotChild* AllocPBackgroundLSSnapshotChild(
-      const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-      const int64_t& aRequestedSize, const int64_t& aMinSize,
-      LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aDocumentURI, const nsString& aKey,
+      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
 
   bool DeallocPBackgroundLSSnapshotChild(
       PBackgroundLSSnapshotChild* aActor) override;
 };
 
 /**
  * Minimal IPC-managed (new/delete) actor that exists to receive and relay
  * "storage" events from changes to LocalStorage that take place in other
@@ -119,18 +119,18 @@ class LSObserverChild final : public PBa
 
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvObserve(const PrincipalInfo& aPrinciplaInfo,
                                       const uint32_t& aPrivateBrowsingId,
                                       const nsString& aDocumentURI,
                                       const nsString& aKey,
-                                      const nsString& aOldValue,
-                                      const nsString& aNewValue) override;
+                                      const LSValue& aOldValue,
+                                      const LSValue& aNewValue) override;
 };
 
 /**
  * Minimal glue IPC-managed (new/delete) actor that is used by LSObject and its
  * RequestHelper to perform synchronous requests on top of an asynchronous
  * protocol.
  *
  * Takes an `LSReuestChildCallback` to be invoked when a response is received
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -88,17 +88,17 @@ class Snapshot;
 typedef nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>
     ArchivedOriginHashtable;
 
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 2;
+const uint32_t kMajorSchemaVersion = 4;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -295,17 +295,17 @@ int32_t MakeSchemaVersion(uint32_t aMajo
 }
 
 nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
                                    const nsACString& aOriginNoSuffix) {
   return aOriginSuffix + NS_LITERAL_CSTRING(":") + aOriginNoSuffix;
 }
 
 nsresult CreateTables(mozIStorageConnection* aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aConnection);
 
   // Table `database`
   nsresult rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE TABLE database"
                          "( origin TEXT NOT NULL"
                          ", usage INTEGER NOT NULL DEFAULT 0"
                          ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
@@ -316,16 +316,17 @@ nsresult CreateTables(mozIStorageConnect
     return rv;
   }
 
   // Table `data`
   rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("CREATE TABLE data"
                          "( key TEXT PRIMARY KEY"
                          ", value TEXT NOT NULL"
+                         ", utf16Length INTEGER NOT NULL DEFAULT 0"
                          ", compressed INTEGER NOT NULL DEFAULT 0"
                          ", lastAccessTime INTEGER NOT NULL DEFAULT 0"
                          ");"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
@@ -357,16 +358,54 @@ nsresult UpgradeSchemaFrom1_0To2_0(mozIS
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+nsresult UpgradeSchemaFrom2_0To3_0(mozIStorageConnection* aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE data ADD COLUMN utf16Length INTEGER NOT NULL DEFAULT 0;"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("UPDATE data "
+                         "SET utf16Length = utf16Length(value) "
+                         "FROM data);"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(3, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom3_0To4_0(mozIStorageConnection* aConnection) {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(aConnection);
+
+  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(4, 0));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConnection);
 
   nsresult rv = aConnection->ExecuteSimpleSQL(
       NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -386,17 +425,17 @@ nsresult SetDefaultPragmas(mozIStorageCo
 
   return NS_OK;
 }
 
 nsresult CreateStorageConnection(nsIFile* aDBFile, nsIFile* aUsageFile,
                                  const nsACString& aOrigin,
                                  mozIStorageConnection** aConnection,
                                  bool* aRemovedUsageFile) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aDBFile);
   MOZ_ASSERT(aUsageFile);
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(aRemovedUsageFile);
 
   // aRemovedUsageFile has to be initialized even when this method fails.
   *aRemovedUsageFile = false;
 
@@ -506,22 +545,26 @@ nsresult CreateStorageConnection(nsIFile
       }
 
       rv = stmt->Execute();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     } else {
       // This logic needs to change next time we change the schema!
-      static_assert(kSQLiteSchemaVersion == int32_t((2 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((4 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == MakeSchemaVersion(1, 0)) {
           rv = UpgradeSchemaFrom1_0To2_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(2, 0)) {
+          rv = UpgradeSchemaFrom2_0To3_0(connection);
+        } else if (schemaVersion == MakeSchemaVersion(3, 0)) {
+          rv = UpgradeSchemaFrom3_0To4_0(connection);
         } else {
           LS_WARNING(
               "Unable to open LocalStorage database, no upgrade path is "
               "available!");
           return NS_ERROR_FAILURE;
         }
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -796,17 +839,17 @@ nsresult GetShadowFile(const nsAString& 
     return rv;
   }
 
   archiveFile.forget(aArchiveFile);
   return NS_OK;
 }
 
 nsresult SetShadowJournalMode(mozIStorageConnection* aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(aConnection);
 
   // Try enabling WAL mode. This can fail in various circumstances so we have to
   // check the results here.
   NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
   NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");
 
   nsCOMPtr<mozIStorageStatement> stmt;
@@ -884,17 +927,17 @@ nsresult SetShadowJournalMode(mozIStorag
     }
   }
 
   return NS_OK;
 }
 
 nsresult CreateShadowStorageConnection(const nsAString& aBasePath,
                                        mozIStorageConnection** aConnection) {
-  AssertIsOnIOThread();
+  MOZ_ASSERT(IsOnIOThread() || IsOnConnectionThread());
   MOZ_ASSERT(!aBasePath.IsEmpty());
   MOZ_ASSERT(aConnection);
 
   nsCOMPtr<nsIFile> shadowFile;
   nsresult rv = GetShadowFile(aBasePath, getter_AddRefs(shadowFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -1222,20 +1265,19 @@ class WriteOptimizer final {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(&aWriteOptimizer != this);
 
     mWriteInfos.SwapElements(aWriteOptimizer.mWriteInfos);
     mTotalDelta = aWriteOptimizer.mTotalDelta;
     aWriteOptimizer.mTotalDelta = 0;
   }
 
-  void AddItem(const nsString& aKey, const nsString& aValue,
-               int64_t aDelta = 0);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue,
+  void AddItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta = 0);
+
+  void UpdateItem(const nsString& aKey, const LSValue& aValue,
                   int64_t aDelta = 0);
 
   void RemoveItem(const nsString& aKey, int64_t aDelta = 0);
 
   void Clear(int64_t aDelta = 0);
 
   bool HasWrites() const {
     AssertIsOnBackgroundThread();
@@ -1266,38 +1308,38 @@ class WriteOptimizer::WriteInfo {
   virtual ~WriteInfo() = default;
 };
 
 /**
  * SetItem mutation where the key did not previously exist.
  */
 class WriteOptimizer::AddItemInfo : public WriteInfo {
   nsString mKey;
-  nsString mValue;
+  LSValue mValue;
 
  public:
-  AddItemInfo(const nsAString& aKey, const nsAString& aValue)
+  AddItemInfo(const nsAString& aKey, const LSValue& aValue)
       : mKey(aKey), mValue(aValue) {}
 
   const nsAString& GetKey() const { return mKey; }
 
-  const nsAString& GetValue() const { return mValue; }
+  const LSValue& GetValue() const { return mValue; }
 
  private:
   Type GetType() override { return AddItem; }
 
   nsresult Perform(Connection* aConnection, bool aShadowWrites) override;
 };
 
 /**
  * SetItem mutation where the key already existed.
  */
 class WriteOptimizer::UpdateItemInfo final : public AddItemInfo {
  public:
-  UpdateItemInfo(const nsAString& aKey, const nsAString& aValue)
+  UpdateItemInfo(const nsAString& aKey, const LSValue& aValue)
       : AddItemInfo(aKey, aValue) {}
 
  private:
   Type GetType() override { return UpdateItem; }
 };
 
 class WriteOptimizer::RemoveItemInfo final : public WriteInfo {
   nsString mKey;
@@ -1397,25 +1439,30 @@ class DatastoreOperationBase : public Ru
         mMayProceed(true) {}
 
   ~DatastoreOperationBase() override { MOZ_ASSERT(!mMayProceed); }
 };
 
 class ConnectionDatastoreOperationBase : public DatastoreOperationBase {
  protected:
   RefPtr<Connection> mConnection;
+  /**
+   * This boolean flag is used by the CloseOp to avoid creating empty databases.
+   */
+  const bool mEnsureStorageConnection;
 
  public:
   // This callback will be called on the background thread before releasing the
   // final reference to this request object. Subclasses may perform any
   // additional cleanup here but must always call the base class implementation.
   virtual void Cleanup();
 
  protected:
-  ConnectionDatastoreOperationBase(Connection* aConnection);
+  ConnectionDatastoreOperationBase(Connection* aConnection,
+                                   bool aEnsureStorageConnection = true);
 
   ~ConnectionDatastoreOperationBase();
 
   // Must be overridden in subclasses. Called on the target thread to allow the
   // subclass to perform necessary datastore operations. A successful return
   // value will trigger an OnSuccess callback on the background thread while
   // while a failure value will trigger an OnFailure callback.
   virtual nsresult DoDatastoreWork() = 0;
@@ -1436,28 +1483,42 @@ 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).
+   */
+  const bool mDatabaseNotAvailable;
   bool mFlushScheduled;
 #ifdef DEBUG
   bool mInUpdateBatch;
 #endif
 
  public:
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
@@ -1477,50 +1538,50 @@ class Connection final {
   // 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
   // connection thread.
   void Close(nsIRunnable* aCallback);
 
-  void AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue, int64_t aDelta);
+  void AddItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta);
+
+  void UpdateItem(const nsString& aKey, const LSValue& aValue, int64_t aDelta);
 
   void RemoveItem(const nsString& aKey, int64_t aDelta);
 
   void Clear(int64_t aDelta);
 
   void BeginUpdateBatch();
 
   void EndUpdateBatch();
 
   //////////////////////////////////////////////////////////////////////////////
   // Methods which can only be called on the connection thread.
 
   nsresult EnsureStorageConnection();
 
   mozIStorageConnection* StorageConnection() const {
     AssertIsOnConnectionThread();
-    MOZ_ASSERT(mStorageConnection);
 
     return mStorageConnection;
   }
 
   void CloseStorageConnection();
 
   nsresult GetCachedStatement(const nsACString& aQuery,
                               CachedStatement* aCachedStatement);
 
  private:
   // Only created by ConnectionThread.
-  Connection(ConnectionThread* aConnectionThread, const nsACString& aOrigin,
-             const nsAString& aDirectoryPath,
-             nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
+  Connection(ConnectionThread* aConnectionThread, const nsACString& aSuffix,
+             const nsACString& aGroup, const nsACString& aOrigin,
+             nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+             bool aDatabaseNotAvailable);
 
   ~Connection();
 
   void ScheduleFlush();
 
   void Flush();
 
   static void FlushTimerCallback(nsITimer* aTimer, void* aClosure);
@@ -1545,34 +1606,77 @@ 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 {
-  RefPtr<QuotaClient> mQuotaClient;
   WriteOptimizer mWriteOptimizer;
   bool mShadowWrites;
 
  public:
   FlushOp(Connection* aConnection, WriteOptimizer&& aWriteOptimizer);
 
  private:
   nsresult DoDatastoreWork() override;
 };
 
 class Connection::CloseOp final : public ConnectionDatastoreOperationBase {
   nsCOMPtr<nsIRunnable> mCallback;
 
  public:
   CloseOp(Connection* aConnection, nsIRunnable* aCallback)
-      : ConnectionDatastoreOperationBase(aConnection), mCallback(aCallback) {}
+      : ConnectionDatastoreOperationBase(aConnection,
+                                         /* aEnsureStorageConnection */ false),
+        mCallback(aCallback) {}
 
  private:
   nsresult DoDatastoreWork() override;
 
   void Cleanup() override;
 };
 
 class ConnectionThread final {
@@ -1588,18 +1692,20 @@ class ConnectionThread final {
     NS_ASSERT_OWNINGTHREAD(ConnectionThread);
   }
 
   bool IsOnConnectionThread();
 
   void AssertIsOnConnectionThread();
 
   already_AddRefed<Connection> CreateConnection(
-      const nsACString& aOrigin, const nsAString& aDirectoryPath,
-      nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope);
+      const nsACString& aSuffix, const nsACString& aGroup,
+      const nsACString& aOrigin,
+      nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope,
+      bool aDatabaseNotAvailable);
 
   void Shutdown();
 
   NS_INLINE_DECL_REFCOUNTING(ConnectionThread)
 
  private:
   ~ConnectionThread();
 };
@@ -1642,17 +1748,17 @@ class Datastore final
    * are any active databases final deltas can't be calculated and
    * `UpdateUsage()` can't be invoked.
    */
   nsTHashtable<nsPtrHashKey<Database>> mActiveDatabases;
   /**
    * Non-authoritative hashtable representation of mOrderedItems for efficient
    * lookup.
    */
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  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;
   WriteOptimizer mWriteOptimizer;
   const nsCString mOrigin;
@@ -1668,17 +1774,17 @@ class Datastore final
 
  public:
   // Created by PrepareDatastoreOp.
   Datastore(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, nsString>& aValues,
+            nsDataHashtable<nsStringHashKey, LSValue>& aValues,
             nsTArray<LSItemInfo>& aOrderedItems);
 
   const nsCString& Origin() const { return mOrigin; }
 
   uint32_t PrivateBrowsingId() const { return mPrivateBrowsingId; }
 
   bool IsPersistent() const {
     // Private-browsing is forbidden from touching disk, but
@@ -1708,43 +1814,44 @@ class Datastore final
   void NoteLiveDatabase(Database* aDatabase);
 
   void NoteFinishedDatabase(Database* aDatabase);
 
   void NoteActiveDatabase(Database* aDatabase);
 
   void NoteInactiveDatabase(Database* aDatabase);
 
-  void GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
+  void GetSnapshotInitInfo(const nsString& aKey, bool& aAddKeyToUnknownItems,
+                           nsTHashtable<nsStringHashKey>& aLoadedItems,
                            nsTArray<LSItemInfo>& aItemInfos,
                            uint32_t& aNextLoadIndex, uint32_t& aTotalLength,
                            int64_t& aInitialUsage, int64_t& aPeakUsage,
                            LSSnapshot::LoadState& aLoadState);
 
   const nsTArray<LSItemInfo>& GetOrderedItems() const { return mOrderedItems; }
 
-  void GetItem(const nsString& aKey, nsString& aValue) const;
+  void GetItem(const nsString& aKey, LSValue& aValue) const;
 
   void GetKeys(nsTArray<nsString>& aKeys) const;
 
   //////////////////////////////////////////////////////////////////////////////
   // Mutation Methods
   //
   // These are only called during Snapshot::RecvCheckpoint
 
   /**
    * Used by Snapshot::RecvCheckpoint to set a key/value pair as part of a an
    * explicit batch.
    */
   void SetItem(Database* aDatabase, const nsString& aDocumentURI,
-               const nsString& aKey, const nsString& aOldValue,
-               const nsString& aValue);
+               const nsString& aKey, const LSValue& aOldValue,
+               const LSValue& aValue);
 
   void RemoveItem(Database* aDatabase, const nsString& aDocumentURI,
-                  const nsString& aKey, const nsString& aOldValue);
+                  const nsString& aKey, const LSValue& aOldValue);
 
   void Clear(Database* aDatabase, const nsString& aDocumentURI);
 
   void PrivateBrowsingClear();
 
   void BeginUpdateBatch(int64_t aSnapshotInitialUsage);
 
   int64_t EndUpdateBatch(int64_t aSnapshotPeakUsage);
@@ -1761,23 +1868,23 @@ class Datastore final
 
   void MaybeClose();
 
   void ConnectionClosedCallback();
 
   void CleanupMetadata();
 
   void NotifySnapshots(Database* aDatabase, const nsAString& aKey,
-                       const nsAString& aOldValue, bool aAffectsOrder);
+                       const LSValue& aOldValue, bool aAffectsOrder);
 
   void MarkSnapshotsDirty();
 
   void NotifyObservers(Database* aDatabase, const nsString& aDocumentURI,
-                       const nsString& aKey, const nsString& aOldValue,
-                       const nsString& aNewValue);
+                       const nsString& aKey, const LSValue& aOldValue,
+                       const LSValue& aNewValue);
 };
 
 class PreparedDatastore {
   RefPtr<Datastore> mDatastore;
   nsCOMPtr<nsITimer> mTimer;
   const Maybe<ContentParentId> mContentParentId;
   // Strings share buffers if possible, so it's not a problem to duplicate the
   // origin here.
@@ -1924,24 +2031,25 @@ class Database final
   // IPDL methods are only called by IPDL.
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvDeleteMe() override;
 
   mozilla::ipc::IPCResult RecvAllowToClose() override;
 
   PBackgroundLSSnapshotParent* AllocPBackgroundLSSnapshotParent(
-      const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-      const int64_t& aRequestedSize, const int64_t& aMinSize,
-      LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aDocumentURI, const nsString& aKey,
+      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
 
   mozilla::ipc::IPCResult RecvPBackgroundLSSnapshotConstructor(
       PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
-      const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
-      const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) override;
+      const nsString& aKey, const bool& aIncreasePeakUsage,
+      const int64_t& aRequestedSize, const int64_t& aMinSize,
+      LSSnapshotInitInfo* aInitInfo) override;
 
   bool DeallocPBackgroundLSSnapshotParent(
       PBackgroundLSSnapshotParent* aActor) override;
 };
 
 /**
  * Attempts to capture the state of the underlying Datastore at the time of its
  * creation so run-to-completion semantics can be honored.
@@ -1989,17 +2097,17 @@ class Snapshot final : public PBackgroun
    * notifications that are not yet known to the child LSSnapshot.
    *
    * The naive way to snapshot the state of mDatastore would be to duplicate its
    * internal mValues at the time of our creation, but that is wasteful if few
    * changes are made to the Datastore's state.  So we only track values that
    * are changed/evicted from the Datastore as they happen, as reported to us by
    * SaveItem notifications.
    */
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  nsDataHashtable<nsStringHashKey, LSValue> mValues;
   /**
    * Latched state of mDatastore's keys during a SaveItem notification with
    * aAffectsOrder=true.  The ordered keys needed to be saved off so that a
    * consistent ordering could be presented to the child LSSnapshot when it asks
    * for them via RecvLoadKeys.
    */
   nsTArray<nsString> mKeys;
   nsString mDocumentURI;
@@ -2046,50 +2154,54 @@ class Snapshot final : public PBackgroun
   bool mLoadKeysReceived;
   bool mSentMarkDirty;
 
  public:
   // Created in AllocPBackgroundLSSnapshotParent.
   Snapshot(Database* aDatabase, const nsAString& aDocumentURI);
 
   void Init(nsTHashtable<nsStringHashKey>& aLoadedItems,
+            nsTHashtable<nsStringHashKey>& aUnknownItems,
             uint32_t aNextLoadIndex, uint32_t aTotalLength,
             int64_t aInitialUsage, int64_t aPeakUsage,
             LSSnapshot::LoadState aLoadState) {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aInitialUsage >= 0);
     MOZ_ASSERT(aPeakUsage >= aInitialUsage);
     MOZ_ASSERT_IF(aLoadState != LSSnapshot::LoadState::AllOrderedItems,
                   aNextLoadIndex < aTotalLength);
     MOZ_ASSERT(mTotalLength == 0);
     MOZ_ASSERT(mUsage == -1);
     MOZ_ASSERT(mPeakUsage == -1);
 
     mLoadedItems.SwapElements(aLoadedItems);
+    mUnknownItems.SwapElements(aUnknownItems);
     mNextLoadIndex = aNextLoadIndex;
     mTotalLength = aTotalLength;
     mUsage = aInitialUsage;
     mPeakUsage = aPeakUsage;
     if (aLoadState == LSSnapshot::LoadState::AllOrderedKeys) {
+      MOZ_ASSERT(mUnknownItems.Count() == 0);
       mLoadKeysReceived = true;
     } else if (aLoadState == LSSnapshot::LoadState::AllOrderedItems) {
       MOZ_ASSERT(mLoadedItems.Count() == 0);
+      MOZ_ASSERT(mUnknownItems.Count() == 0);
       MOZ_ASSERT(mNextLoadIndex == mTotalLength);
       mLoadedReceived = true;
       mLoadedAllItems = true;
       mLoadKeysReceived = true;
     }
   }
 
   /**
    * Called via NotifySnapshots by Datastore whenever it is updating its
    * internal state so that snapshots can save off the state of a value at the
    * time of their creation.
    */
-  void SaveItem(const nsAString& aKey, const nsAString& aOldValue,
+  void SaveItem(const nsAString& aKey, const LSValue& aOldValue,
                 bool aAffectsOrder);
 
   void MarkDirty();
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Snapshot)
 
  private:
   // Reference counted.
@@ -2105,17 +2217,17 @@ class Snapshot final : public PBackgroun
   mozilla::ipc::IPCResult RecvCheckpoint(
       nsTArray<LSWriteInfo>&& aWriteInfos) override;
 
   mozilla::ipc::IPCResult RecvFinish() override;
 
   mozilla::ipc::IPCResult RecvLoaded() override;
 
   mozilla::ipc::IPCResult RecvLoadValueAndMoreItems(
-      const nsString& aKey, nsString* aValue,
+      const nsString& aKey, LSValue* aValue,
       nsTArray<LSItemInfo>* aItemInfos) override;
 
   mozilla::ipc::IPCResult RecvLoadKeys(nsTArray<nsString>* aKeys) override;
 
   mozilla::ipc::IPCResult RecvIncreasePeakUsage(const int64_t& aRequestedSize,
                                                 const int64_t& aMinSize,
                                                 int64_t* aSize) override;
 
@@ -2128,18 +2240,18 @@ class Observer final : public PBackgroun
 
  public:
   // Created in AllocPBackgroundLSObserverParent.
   explicit Observer(const nsACString& aOrigin);
 
   const nsCString& Origin() const { return mOrigin; }
 
   void Observe(Database* aDatabase, const nsString& aDocumentURI,
-               const nsString& aKey, const nsString& aOldValue,
-               const nsString& aNewValue);
+               const nsString& aKey, const LSValue& aOldValue,
+               const LSValue& aNewValue);
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Observer)
 
  private:
   // Reference counted.
   ~Observer();
 
   // IPDL methods are only called by IPDL.
@@ -2285,33 +2397,32 @@ class PrepareDatastoreOp
   nsCOMPtr<nsIEventTarget> mMainEventTarget;
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mPendingDirectoryLock;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<Datastore> mDatastore;
   nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   LoadDataOp* mLoadDataOp;
-  nsDataHashtable<nsStringHashKey, nsString> mValues;
+  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 mCreateIfNotExists;
+  const bool mForPreload;
   bool mDatabaseNotAvailable;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 #ifdef DEBUG
   int64_t mDEBUGUsage;
 #endif
 
@@ -2832,16 +2943,32 @@ void InitUsageForOrigin(const nsACString
   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);
 }
 
@@ -3567,34 +3694,34 @@ already_AddRefed<mozilla::dom::quota::Cl
 }
 
 }  // namespace localstorage
 
 /*******************************************************************************
  * WriteOptimizer
  ******************************************************************************/
 
-void WriteOptimizer::AddItem(const nsString& aKey, const nsString& aValue,
+void WriteOptimizer::AddItem(const nsString& aKey, const LSValue& aValue,
                              int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   nsAutoPtr<WriteInfo> newWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::RemoveItem) {
     newWriteInfo = new UpdateItemInfo(aKey, aValue);
   } else {
     newWriteInfo = new AddItemInfo(aKey, aValue);
   }
   mWriteInfos.Put(aKey, newWriteInfo.forget());
 
   mTotalDelta += aDelta;
 }
 
-void WriteOptimizer::UpdateItem(const nsString& aKey, const nsString& aValue,
+void WriteOptimizer::UpdateItem(const nsString& aKey, const LSValue& aValue,
                                 int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   nsAutoPtr<WriteInfo> newWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::AddItem) {
     newWriteInfo = new AddItemInfo(aKey, aValue);
@@ -3753,29 +3880,42 @@ nsresult WriteOptimizer::PerformWrites(C
 
 nsresult WriteOptimizer::AddItemInfo::Perform(Connection* aConnection,
                                               bool aShadowWrites) {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection);
 
   Connection::CachedStatement stmt;
   nsresult rv = aConnection->GetCachedStatement(
-      NS_LITERAL_CSTRING("INSERT OR REPLACE INTO data (key, value) "
-                         "VALUES(:key, :value)"),
+      NS_LITERAL_CSTRING(
+          "INSERT OR REPLACE INTO data (key, value, utf16Length, compressed) "
+          "VALUES(:key, :value, :utf16Length, :compressed)"),
       &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), mValue);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("utf16Length"),
+                             mValue.UTF16Length());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("compressed"),
+                             mValue.IsCompressed());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3810,17 +3950,25 @@ nsresult WriteOptimizer::AddItemInfo::Pe
     return rv;
   }
 
   rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"), mKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"), mValue);
+  if (mValue.IsCompressed()) {
+    nsCString value;
+    if (NS_WARN_IF(!SnappyUncompress(mValue, value))) {
+      return NS_ERROR_FAILURE;
+    }
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), value);
+  } else {
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), mValue);
+  }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3931,18 +4079,19 @@ nsresult WriteOptimizer::ClearInfo::Perf
  * DatastoreOperationBase
  ******************************************************************************/
 
 /*******************************************************************************
  * ConnectionDatastoreOperationBase
  ******************************************************************************/
 
 ConnectionDatastoreOperationBase::ConnectionDatastoreOperationBase(
-    Connection* aConnection)
-    : mConnection(aConnection) {
+    Connection* aConnection, bool aEnsureStorageConnection)
+    : mConnection(aConnection),
+      mEnsureStorageConnection(aEnsureStorageConnection) {
   MOZ_ASSERT(aConnection);
 }
 
 ConnectionDatastoreOperationBase::~ConnectionDatastoreOperationBase() {
   MOZ_ASSERT(!mConnection,
              "ConnectionDatabaseOperationBase::Cleanup() was not called by a "
              "subclass!");
 }
@@ -3966,22 +4115,30 @@ void ConnectionDatastoreOperationBase::O
 void ConnectionDatastoreOperationBase::RunOnConnectionThread() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
 
   if (!MayProceedOnNonOwningThread()) {
     SetFailureCode(NS_ERROR_FAILURE);
   } else {
-    nsresult rv = mConnection->EnsureStorageConnection();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SetFailureCode(rv);
-    } else {
-      MOZ_ASSERT(mConnection->StorageConnection());
-
+    nsresult rv = NS_OK;
+
+    // The boolean flag is only used by the CloseOp to avoid creating empty
+    // databases.
+    if (mEnsureStorageConnection) {
+      rv = mConnection->EnsureStorageConnection();
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        SetFailureCode(rv);
+      } else {
+        MOZ_ASSERT(mConnection->StorageConnection());
+      }
+    }
+
+    if (NS_SUCCEEDED(rv)) {
       rv = DoDatastoreWork();
       if (NS_FAILED(rv)) {
         SetFailureCode(rv);
       }
     }
   }
 
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
@@ -4015,32 +4172,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)
+                       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);
@@ -4067,25 +4228,25 @@ void Connection::Close(nsIRunnable* aCal
     mFlushTimer = nullptr;
   }
 
   RefPtr<CloseOp> op = new CloseOp(this, aCallback);
 
   Dispatch(op);
 }
 
-void Connection::AddItem(const nsString& aKey, const nsString& aValue,
+void Connection::AddItem(const nsString& aKey, const LSValue& aValue,
                          int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
   mWriteOptimizer.AddItem(aKey, aValue, aDelta);
 }
 
-void Connection::UpdateItem(const nsString& aKey, const nsString& aValue,
+void Connection::UpdateItem(const nsString& aKey, const LSValue& aValue,
                             int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
   mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
 }
 
 void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
@@ -4122,42 +4283,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;
     }
 
-    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;
     }
 
-    nsString filePath;
-    rv = file->GetPath(filePath);
+    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;
-    rv = GetStorageConnection(filePath, getter_AddRefs(storageConnection));
+    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);
 
@@ -4276,24 +4540,86 @@ 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),
-      mQuotaClient(QuotaClient::GetInstance()),
       mWriteOptimizer(std::move(aWriteOptimizer)),
-      mShadowWrites(gShadowWrites) {
-  MOZ_ASSERT(mQuotaClient);
-}
+      mShadowWrites(gShadowWrites) {}
 
 nsresult Connection::FlushOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
@@ -4301,19 +4627,20 @@ nsresult Connection::FlushOp::DoDatastor
       mConnection->StorageConnection();
   MOZ_ASSERT(storageConnection);
 
   nsresult rv;
 
   Maybe<MutexAutoLock> shadowDatabaseLock;
 
   if (mShadowWrites) {
-    MOZ_ASSERT(mQuotaClient);
-
-    shadowDatabaseLock.emplace(mQuotaClient->ShadowDatabaseMutex());
+    MOZ_ASSERT(mConnection->mQuotaClient);
+
+    shadowDatabaseLock.emplace(
+        mConnection->mQuotaClient->ShadowDatabaseMutex());
 
     rv = AttachShadowDatabase(quotaManager->GetBasePath(), storageConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   CachedStatement stmt;
@@ -4387,17 +4714,19 @@ nsresult Connection::FlushOp::DoDatastor
 
   return NS_OK;
 }
 
 nsresult Connection::CloseOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
-  mConnection->CloseStorageConnection();
+  if (mConnection->StorageConnection()) {
+    mConnection->CloseStorageConnection();
+  }
 
   return NS_OK;
 }
 
 void Connection::CloseOp::Cleanup() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mConnection);
 
@@ -4434,24 +4763,27 @@ 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,
-    nsAutoPtr<ArchivedOriginScope>&& aArchivedOriginScope) {
+    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, std::move(aArchivedOriginScope));
+  RefPtr<Connection> connection =
+      new Connection(this, aSuffix, aGroup, aOrigin,
+                     std::move(aArchivedOriginScope), aDatabaseNotAvailable);
   mConnections.Put(aOrigin, connection);
 
   return connection.forget();
 }
 
 void ConnectionThread::Shutdown() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mThread);
@@ -4463,17 +4795,17 @@ void ConnectionThread::Shutdown() {
  * Datastore
  ******************************************************************************/
 
 Datastore::Datastore(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, nsString>& aValues,
+                     nsDataHashtable<nsStringHashKey, LSValue>& aValues,
                      nsTArray<LSItemInfo>& aOrderedItems)
     : mDirectoryLock(std::move(aDirectoryLock)),
       mConnection(std::move(aConnection)),
       mQuotaObject(std::move(aQuotaObject)),
       mOrigin(aOrigin),
       mPrivateBrowsingId(aPrivateBrowsingId),
       mUsage(aUsage),
       mUpdateBatchUsage(-1),
@@ -4641,17 +4973,19 @@ void Datastore::NoteInactiveDatabase(Dat
       DebugOnly<bool> ok = UpdateUsage(finalDelta);
       MOZ_ASSERT(ok);
     }
 
     mPendingUsageDeltas.Clear();
   }
 }
 
-void Datastore::GetSnapshotInitInfo(nsTHashtable<nsStringHashKey>& aLoadedItems,
+void Datastore::GetSnapshotInitInfo(const nsString& aKey,
+                                    bool& aAddKeyToUnknownItems,
+                                    nsTHashtable<nsStringHashKey>& aLoadedItems,
                                     nsTArray<LSItemInfo>& aItemInfos,
                                     uint32_t& aNextLoadIndex,
                                     uint32_t& aTotalLength,
                                     int64_t& aInitialUsage, int64_t& aPeakUsage,
                                     LSSnapshot::LoadState& aLoadState) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
@@ -4663,90 +4997,200 @@ void Datastore::GetSnapshotInitInfo(nsTH
     int64_t sizeOfKey = static_cast<int64_t>(item.key().Length());
     sizeOfKeys += sizeOfKey;
     sizeOfItems += sizeOfKey + static_cast<int64_t>(item.value().Length());
   }
   MOZ_ASSERT(mSizeOfKeys == sizeOfKeys);
   MOZ_ASSERT(mSizeOfItems == sizeOfItems);
 #endif
 
-  if (mSizeOfKeys <= gSnapshotPrefill) {
-    if (mSizeOfItems <= gSnapshotPrefill) {
+  // Computes load state optimized for current size of keys and items.
+  // Zero key length and value can be passed to do a quick initial estimation.
+  // If computed load state is already AllOrderedItems then excluded key length
+  // and value length can't make it any better.
+  auto GetLoadState = [&](auto aKeyLength, auto aValueLength) {
+    if (mSizeOfKeys - aKeyLength <= gSnapshotPrefill) {
+      if (mSizeOfItems - aKeyLength - aValueLength <= gSnapshotPrefill) {
+        return LSSnapshot::LoadState::AllOrderedItems;
+      }
+
+      return LSSnapshot::LoadState::AllOrderedKeys;
+    }
+
+    return LSSnapshot::LoadState::Partial;
+  };
+
+  // Value for given aKey if aKey is not void (can be void too if value doesn't
+  // exist for given aKey).
+  LSValue value;
+  // If aKey and value are not void, checkKey will be set to true. Once we find
+  // an item for given aKey in one of the loops below, checkKey is set to false
+  // to prevent additional comparison of strings (string implementation compares
+  // string lengths first to avoid char by char comparison if possible).
+  bool checkKey = false;
+
+  // Avoid additional hash lookup if all ordered items fit into initial prefill
+  // already.
+  LSSnapshot::LoadState loadState = GetLoadState(/* aKeyLength */ 0,
+                                                 /* aValueLength */ 0);
+  if (loadState != LSSnapshot::LoadState::AllOrderedItems && !aKey.IsVoid()) {
+    GetItem(aKey, value);
+    if (!value.IsVoid()) {
+      // Ok, we have a non void aKey and value.
+
+      // We have to watch for aKey during one of the loops below to exclude it
+      // from the size computation. The super fast mode (AllOrderedItems)
+      // doesn't have to do that though.
+      checkKey = true;
+
+      // We have to compute load state again because aKey length and value
+      // length is excluded from the size in this case.
+      loadState = GetLoadState(aKey.Length(), value.Length());
+    }
+  }
+
+  switch (loadState) {
+    case LSSnapshot::LoadState::AllOrderedItems: {
+      // We're sending all ordered items, we don't need to check keys because
+      // mOrderedItems must contain a value for aKey if checkKey is true.
+
       aItemInfos.AppendElements(mOrderedItems);
 
       MOZ_ASSERT(aItemInfos.Length() == mValues.Count());
       aNextLoadIndex = mValues.Count();
 
-      aLoadState = LSSnapshot::LoadState::AllOrderedItems;
-    } else {
+      aAddKeyToUnknownItems = false;
+
+      break;
+    }
+
+    case LSSnapshot::LoadState::AllOrderedKeys: {
+      // We don't have enough snapshot budget to send all items, but we do have
+      // enough to send all of the keys and to make a best effort to populate as
+      // many values as possible. We send void string values once we run out of
+      // budget. A complicating factor is that we want to make sure that we send
+      // the value for aKey which is a localStorage read that's triggering this
+      // request. Since that key can happen anywhere in the list of items, we
+      // need to handle it specially.
+      //
+      // The loop is effectively doing 2 things in parallel:
+      //
+      //   1. Looking for the `aKey` to send. This is tracked by `checkKey`
+      //      which is true if there was an `aKey` specified and until we
+      //      populate its value, and false thereafter.
+      //   2. Sending values until we run out of `size` budget and switch to
+      //      sending void values. `doneSendingValues` tracks when we've run out
+      //      of size budget, with `setVoidValue` tracking whether a value
+      //      should be sent for each turn of the event loop but can be
+      //      overridden when `aKey` is found.
+
       int64_t size = mSizeOfKeys;
-      nsString value;
+      bool setVoidValue = false;
+      bool doneSendingValues = false;
       for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
         const LSItemInfo& item = mOrderedItems[index];
 
         const nsString& key = item.key();
-
-        if (!value.IsVoid()) {
-          value = item.value();
-
-          size += static_cast<int64_t>(value.Length());
-
-          if (size <= gSnapshotPrefill) {
-            aLoadedItems.PutEntry(key);
+        const LSValue& value = item.value();
+
+        if (checkKey && key == aKey) {
+          checkKey = false;
+          setVoidValue = false;
+        } else if (!setVoidValue) {
+          if (doneSendingValues) {
+            setVoidValue = true;
           } else {
-            value.SetIsVoid(true);
-
-            // We set value to void so that will guard against entering the
-            // parent branch during next iterations. So aNextLoadIndex is set
-            // only once.
-            aNextLoadIndex = index;
+            size += static_cast<int64_t>(value.Length());
+
+            if (size > gSnapshotPrefill) {
+              setVoidValue = true;
+              doneSendingValues = true;
+
+              // We set doneSendingValues to true and that will guard against
+              // entering this branch during next iterations. So aNextLoadIndex
+              // is set only once.
+              aNextLoadIndex = index;
+            }
           }
         }
 
         LSItemInfo* itemInfo = aItemInfos.AppendElement();
         itemInfo->key() = key;
+        if (setVoidValue) {
+          itemInfo->value().SetIsVoid(true);
+        } else {
+          aLoadedItems.PutEntry(key);
+          itemInfo->value() = value;
+        }
+      }
+
+      aAddKeyToUnknownItems = false;
+
+      break;
+    }
+
+    case LSSnapshot::LoadState::Partial: {
+      int64_t size = 0;
+      for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
+        const LSItemInfo& item = mOrderedItems[index];
+
+        const nsString& key = item.key();
+        const LSValue& value = item.value();
+
+        if (checkKey && key == aKey) {
+          checkKey = false;
+        } else {
+          size += static_cast<int64_t>(key.Length()) +
+                  static_cast<int64_t>(value.Length());
+
+          if (size > gSnapshotPrefill) {
+            aNextLoadIndex = index;
+            break;
+          }
+        }
+
+        aLoadedItems.PutEntry(key);
+
+        LSItemInfo* itemInfo = aItemInfos.AppendElement();
+        itemInfo->key() = key;
         itemInfo->value() = value;
       }
 
-      aLoadState = LSSnapshot::LoadState::AllOrderedKeys;
-    }
-  } else {
-    int64_t size = 0;
-    for (uint32_t index = 0; index < mOrderedItems.Length(); index++) {
-      const LSItemInfo& item = mOrderedItems[index];
-
-      const nsString& key = item.key();
-      const nsString& value = item.value();
-
-      size += static_cast<int64_t>(key.Length()) +
-              static_cast<int64_t>(value.Length());
-
-      if (size > gSnapshotPrefill) {
-        aNextLoadIndex = index;
-        break;
+      aAddKeyToUnknownItems = false;
+
+      if (!aKey.IsVoid()) {
+        if (value.IsVoid()) {
+          aAddKeyToUnknownItems = true;
+        } else if (checkKey) {
+          // The item wasn't added in the loop above, add it here.
+
+          LSItemInfo* itemInfo = aItemInfos.AppendElement();
+          itemInfo->key() = aKey;
+          itemInfo->value() = value;
+        }
       }
 
-      aLoadedItems.PutEntry(key);
-
-      LSItemInfo* itemInfo = aItemInfos.AppendElement();
-      itemInfo->key() = key;
-      itemInfo->value() = value;
-    }
-
-    MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
-    aLoadState = LSSnapshot::LoadState::Partial;
+      MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length());
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Bad load state value!");
   }
 
   aTotalLength = mValues.Count();
 
   aInitialUsage = mUsage;
   aPeakUsage = aInitialUsage;
-}
-
-void Datastore::GetItem(const nsString& aKey, nsString& aValue) const {
+
+  aLoadState = loadState;
+}
+
+void Datastore::GetItem(const nsString& aKey, LSValue& aValue) const {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mClosed);
 
   if (!mValues.Get(aKey, &aValue)) {
     aValue.SetIsVoid(true);
   }
 }
 
@@ -4755,135 +5199,139 @@ void Datastore::GetKeys(nsTArray<nsStrin
   MOZ_ASSERT(!mClosed);
 
   for (auto item : mOrderedItems) {
     aKeys.AppendElement(item.key());
   }
 }
 
 void Datastore::SetItem(Database* aDatabase, const nsString& aDocumentURI,
-                        const nsString& aKey, const nsString& aOldValue,
-                        const nsString& aValue) {
+                        const nsString& aKey, const LSValue& aOldValue,
+                        const LSValue& aValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
-  nsString oldValue;
+  LSValue oldValue;
   GetItem(aKey, oldValue);
 
-  if (oldValue != aValue || oldValue.IsVoid() != aValue.IsVoid()) {
+  if (oldValue != aValue) {
     bool isNewItem = oldValue.IsVoid();
 
     NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
 
     mValues.Put(aKey, aValue);
 
-    int64_t sizeOfItem;
+    int64_t delta;
 
     if (isNewItem) {
       mWriteOptimizer.AddItem(aKey, aValue);
 
       int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
-      sizeOfItem = sizeOfKey + static_cast<int64_t>(aValue.Length());
-
-      mUpdateBatchUsage += sizeOfItem;
+
+      delta = sizeOfKey + static_cast<int64_t>(aValue.UTF16Length());
+
+      mUpdateBatchUsage += delta;
 
       mSizeOfKeys += sizeOfKey;
-      mSizeOfItems += sizeOfItem;
+      mSizeOfItems += sizeOfKey + static_cast<int64_t>(aValue.Length());
+      ;
     } else {
       mWriteOptimizer.UpdateItem(aKey, aValue);
 
-      sizeOfItem = static_cast<int64_t>(aValue.Length()) -
-                   static_cast<int64_t>(oldValue.Length());
-
-      mUpdateBatchUsage += sizeOfItem;
-
-      mSizeOfItems += sizeOfItem;
+      delta = static_cast<int64_t>(aValue.UTF16Length()) -
+              static_cast<int64_t>(oldValue.UTF16Length());
+
+      mUpdateBatchUsage += delta;
+
+      mSizeOfItems += static_cast<int64_t>(aValue.Length()) -
+                      static_cast<int64_t>(oldValue.Length());
     }
 
     if (IsPersistent()) {
       if (oldValue.IsVoid()) {
-        mConnection->AddItem(aKey, aValue, sizeOfItem);
+        mConnection->AddItem(aKey, aValue, delta);
       } else {
-        mConnection->UpdateItem(aKey, aValue, sizeOfItem);
+        mConnection->UpdateItem(aKey, aValue, delta);
       }
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue);
 }
 
 void Datastore::RemoveItem(Database* aDatabase, const nsString& aDocumentURI,
-                           const nsString& aKey, const nsString& aOldValue) {
+                           const nsString& aKey, const LSValue& aOldValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
-  nsString oldValue;
+  LSValue oldValue;
   GetItem(aKey, oldValue);
 
   if (!oldValue.IsVoid()) {
     NotifySnapshots(aDatabase, aKey, oldValue, /* aAffectsOrder */ true);
 
     mValues.Remove(aKey);
 
     mWriteOptimizer.RemoveItem(aKey);
 
     int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
-    int64_t sizeOfItem = sizeOfKey + static_cast<int64_t>(oldValue.Length());
-
-    mUpdateBatchUsage -= sizeOfItem;
+
+    int64_t delta = -sizeOfKey - static_cast<int64_t>(oldValue.UTF16Length());
+
+    mUpdateBatchUsage += delta;
 
     mSizeOfKeys -= sizeOfKey;
-    mSizeOfItems -= sizeOfItem;
+    mSizeOfItems -= sizeOfKey + static_cast<int64_t>(oldValue.Length());
 
     if (IsPersistent()) {
-      mConnection->RemoveItem(aKey, -sizeOfItem);
-    }
-  }
-
-  NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
+      mConnection->RemoveItem(aKey, delta);
+    }
+  }
+
+  NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidLSValue());
 }
 
 void Datastore::Clear(Database* aDatabase, const nsString& aDocumentURI) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   if (mValues.Count()) {
-    int64_t sizeOfItems = 0;
+    int64_t delta = 0;
     for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
       const nsAString& key = iter.Key();
-      const nsAString& value = iter.Data();
-
-      sizeOfItems += (static_cast<int64_t>(key.Length()) +
-                      static_cast<int64_t>(value.Length()));
+      const LSValue& value = iter.Data();
+
+      delta += -static_cast<int64_t>(key.Length()) -
+               static_cast<int64_t>(value.UTF16Length());
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
 
     mWriteOptimizer.Clear();
 
-    mUpdateBatchUsage -= sizeOfItems;
+    mUpdateBatchUsage += delta;
 
     mSizeOfKeys = 0;
     mSizeOfItems = 0;
 
     if (IsPersistent()) {
-      mConnection->Clear(-sizeOfItems);
-    }
-  }
-
-  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidString(),
-                  VoidString());
+      mConnection->Clear(delta);
+    }
+  }
+
+  NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidLSValue(),
+                  VoidLSValue());
 }
 
 void Datastore::PrivateBrowsingClear() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mPrivateBrowsingId);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(!mInUpdateBatch);
 
@@ -5044,18 +5492,17 @@ void Datastore::CleanupMetadata() {
   gDatastores->Remove(mOrigin);
 
   if (!gDatastores->Count()) {
     gDatastores = nullptr;
   }
 }
 
 void Datastore::NotifySnapshots(Database* aDatabase, const nsAString& aKey,
-                                const nsAString& aOldValue,
-                                bool aAffectsOrder) {
+                                const LSValue& aOldValue, bool aAffectsOrder) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   for (auto iter = mDatabases.ConstIter(); !iter.Done(); iter.Next()) {
     Database* database = iter.Get()->GetKey();
     if (database == aDatabase) {
       continue;
     }
@@ -5077,18 +5524,18 @@ void Datastore::MarkSnapshotsDirty() {
     if (snapshot) {
       snapshot->MarkDirty();
     }
   }
 }
 
 void Datastore::NotifyObservers(Database* aDatabase,
                                 const nsString& aDocumentURI,
-                                const nsString& aKey, const nsString& aOldValue,
-                                const nsString& aNewValue) {
+                                const nsString& aKey, const LSValue& aOldValue,
+                                const LSValue& aNewValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   if (!gObservers) {
     return;
   }
 
   nsTArray<Observer*>* array;
@@ -5278,19 +5725,19 @@ mozilla::ipc::IPCResult Database::RecvAl
   }
 
   AllowToClose();
 
   return IPC_OK();
 }
 
 PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent(
-    const nsString& aDocumentURI, const bool& aIncreasePeakUsage,
-    const int64_t& aRequestedSize, const int64_t& aMinSize,
-    LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aDocumentURI, const nsString& aKey,
+    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
+    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(aIncreasePeakUsage && aRequestedSize <= 0)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
   if (NS_WARN_IF(aIncreasePeakUsage && aMinSize <= 0)) {
@@ -5306,49 +5753,55 @@ PBackgroundLSSnapshotParent* Database::A
   RefPtr<Snapshot> snapshot = new Snapshot(this, aDocumentURI);
 
   // Transfer ownership to IPDL.
   return snapshot.forget().take();
 }
 
 mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor(
     PBackgroundLSSnapshotParent* aActor, const nsString& aDocumentURI,
-    const bool& aIncreasePeakUsage, const int64_t& aRequestedSize,
-    const int64_t& aMinSize, LSSnapshotInitInfo* aInitInfo) {
+    const nsString& aKey, const bool& aIncreasePeakUsage,
+    const int64_t& aRequestedSize, const int64_t& aMinSize,
+    LSSnapshotInitInfo* aInitInfo) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT_IF(aIncreasePeakUsage, aRequestedSize > 0);
   MOZ_ASSERT_IF(aIncreasePeakUsage, aMinSize > 0);
   MOZ_ASSERT(aInitInfo);
   MOZ_ASSERT(!mAllowedToClose);
 
   auto* snapshot = static_cast<Snapshot*>(aActor);
 
-  // TODO: This can be optimized depending on which operation triggers snapshot
-  //       creation. For example clear() doesn't need to receive items at all.
+  bool addKeyToUnknownItems;
   nsTHashtable<nsStringHashKey> loadedItems;
   nsTArray<LSItemInfo> itemInfos;
   uint32_t nextLoadIndex;
   uint32_t totalLength;
   int64_t initialUsage;
   int64_t peakUsage;
   LSSnapshot::LoadState loadState;
-  mDatastore->GetSnapshotInitInfo(loadedItems, itemInfos, nextLoadIndex,
-                                  totalLength, initialUsage, peakUsage,
-                                  loadState);
+  mDatastore->GetSnapshotInitInfo(aKey, addKeyToUnknownItems, loadedItems,
+                                  itemInfos, nextLoadIndex, totalLength,
+                                  initialUsage, peakUsage, loadState);
+
+  nsTHashtable<nsStringHashKey> unknownItems;
+  if (addKeyToUnknownItems) {
+    unknownItems.PutEntry(aKey);
+  }
 
   if (aIncreasePeakUsage) {
     int64_t size = mDatastore->RequestUpdateUsage(aRequestedSize, aMinSize);
     peakUsage += size;
   }
 
-  snapshot->Init(loadedItems, nextLoadIndex, totalLength, initialUsage,
-                 peakUsage, loadState);
+  snapshot->Init(loadedItems, unknownItems, nextLoadIndex, totalLength,
+                 initialUsage, peakUsage, loadState);
 
   RegisterSnapshot(snapshot);
 
+  aInitInfo->addKeyToUnknownItems() = addKeyToUnknownItems;
   aInitInfo->itemInfos() = std::move(itemInfos);
   aInitInfo->totalLength() = totalLength;
   aInitInfo->initialUsage() = initialUsage;
   aInitInfo->peakUsage() = peakUsage;
   aInitInfo->loadState() = loadState;
 
   return IPC_OK();
 }
@@ -5386,28 +5839,28 @@ Snapshot::Snapshot(Database* aDatabase, 
   MOZ_ASSERT(aDatabase);
 }
 
 Snapshot::~Snapshot() {
   MOZ_ASSERT(mActorDestroyed);
   MOZ_ASSERT(mFinishReceived);
 }
 
-void Snapshot::SaveItem(const nsAString& aKey, const nsAString& aOldValue,
+void Snapshot::SaveItem(const nsAString& aKey, const LSValue& aOldValue,
                         bool aAffectsOrder) {
   AssertIsOnBackgroundThread();
 
   MarkDirty();
 
   if (mLoadedAllItems) {
     return;
   }
 
   if (!mLoadedItems.GetEntry(aKey) && !mUnknownItems.GetEntry(aKey)) {
-    nsString oldValue(aOldValue);
+    LSValue oldValue(aOldValue);
     mValues.LookupForAdd(aKey).OrInsert([oldValue]() { return oldValue; });
   }
 
   if (aAffectsOrder && !mSavedKeys) {
     mDatastore->GetKeys(mKeys);
     mSavedKeys = true;
   }
 }
@@ -5552,17 +6005,17 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
   mKeys.Clear();
   mLoadedAllItems = true;
   mLoadKeysReceived = true;
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult Snapshot::RecvLoadValueAndMoreItems(
-    const nsString& aKey, nsString* aValue, nsTArray<LSItemInfo>* aItemInfos) {
+    const nsString& aKey, LSValue* aValue, nsTArray<LSItemInfo>* aItemInfos) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aValue);
   MOZ_ASSERT(aItemInfos);
   MOZ_ASSERT(mDatastore);
 
   if (NS_WARN_IF(mFinishReceived)) {
     ASSERT_UNLESS_FUZZING();
     return IPC_FAIL_NO_REASON(this);
@@ -5641,17 +6094,17 @@ mozilla::ipc::IPCResult Snapshot::RecvLo
       if (countBeforePut != mLoadedItems.Count()) {
         // Check mValues first since that contains values as they existed when
         // our snapshot was created, but have since been changed/removed in the
         // datastore. If it's not there, then the datastore has the
         // still-current value. However, if the datastore's key ordering has
         // changed, we need to do a hash lookup rather than being able to do an
         // optimized direct access to the index.
 
-        nsString value;
+        LSValue value;
         auto valueEntry = mValues.Lookup(key);
         if (valueEntry) {
           value = valueEntry.Data();
         } else if (mSavedKeys) {
           mDatastore->GetItem(nsString(key), value);
         } else {
           value = orderedItems[mNextLoadIndex].value();
         }
@@ -5774,18 +6227,18 @@ mozilla::ipc::IPCResult Snapshot::RecvPi
 Observer::Observer(const nsACString& aOrigin)
     : mOrigin(aOrigin), mActorDestroyed(false) {
   AssertIsOnBackgroundThread();
 }
 
 Observer::~Observer() { MOZ_ASSERT(mActorDestroyed); }
 
 void Observer::Observe(Database* aDatabase, const nsString& aDocumentURI,
-                       const nsString& aKey, const nsString& aOldValue,
-                       const nsString& aNewValue) {
+                       const nsString& aKey, const LSValue& aOldValue,
+                       const LSValue& aNewValue) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 
   Unused << SendObserve(aDatabase->GetPrincipalInfo(),
                         aDatabase->PrivateBrowsingId(), aDocumentURI, aKey,
                         aOldValue, aNewValue);
 }
 
@@ -6095,18 +6548,18 @@ PrepareDatastoreOp::PrepareDatastoreOp(
               : aParams.get_LSRequestPrepareDatastoreParams().commonParams()),
       mContentParentId(aContentParentId),
       mPrivateBrowsingId(0),
       mUsage(0),
       mSizeOfKeys(0),
       mSizeOfItems(0),
       mDatastoreId(0),
       mNestedState(NestedState::BeforeNesting),
-      mCreateIfNotExists(aParams.type() ==
-                         LSRequestParams::TLSRequestPrepareDatastoreParams),
+      mForPreload(aParams.type() ==
+                  LSRequestParams::TLSRequestPreloadDatastoreParams),
       mDatabaseNotAvailable(false),
       mRequestedDirectoryLock(false),
       mInvalidated(false)
 #ifdef DEBUG
       ,
       mDEBUGUsage(0)
 #endif
 {
@@ -6408,91 +6861,136 @@ nsresult PrepareDatastoreOp::DatabaseWor
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
-  nsresult rv;
+  // 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.
+  rv = quotaManager->EnsureTemporaryStorageIsInitialized();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   if (!gArchivedOrigins) {
     rv = LoadArchivedOrigins();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(gArchivedOrigins);
   }
 
   bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
 
-  bool createIfNotExists = mCreateIfNotExists || hasDataForMigration;
-
+  // 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) {
+    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.
   nsCOMPtr<nsIFile> directoryEntry;
-  rv = quotaManager->EnsureOriginIsInitialized(
-      PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin, createIfNotExists,
-      getter_AddRefs(directoryEntry));
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return DatabaseNotAvailable();
-  }
-  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 = EnsureDirectoryEntry(directoryEntry, createIfNotExists,
+  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,
+                            /* aCreateIfNotExists */ hasDataForMigration,
                             /* aIsDirectory */ true);
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return DatabaseNotAvailable();
-  }
-  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;
   }
 
+  rv = directoryEntry->GetPath(mDatabaseFilePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // The database 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.
   bool alreadyExisted;
-  rv = EnsureDirectoryEntry(directoryEntry, createIfNotExists,
+  rv = EnsureDirectoryEntry(directoryEntry,
+                            /* aCreateIfNotExists */ hasDataForMigration,
                             /* aIsDirectory */ false, &alreadyExisted);
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return DatabaseNotAvailable();
-  }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (alreadyExisted) {
     MOZ_ASSERT(gUsages);
     DebugOnly<bool> hasUsage = gUsages->Get(mOrigin, &mUsage);
     MOZ_ASSERT(hasUsage);
   } else {
+    // The database doesn't exist.
+
+    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);
   }
 
-  rv = directoryEntry->GetPath(mDatabaseFilePath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   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,
@@ -6535,18 +7033,18 @@ nsresult PrepareDatastoreOp::DatabaseWor
       return NS_ERROR_FILE_NO_DEVICE_SPACE;
     }
 
     mozStorageTransaction transaction(
         connection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     nsCOMPtr<mozIStorageStatement> stmt;
     rv = connection->CreateStatement(
-        NS_LITERAL_CSTRING("INSERT INTO data (key, value) "
-                           "SELECT key, value "
+        NS_LITERAL_CSTRING("INSERT INTO data (key, value, utf16Length) "
+                           "SELECT key, value, utf16Length(value) "
                            "FROM webappsstore2 "
                            "WHERE originKey = :originKey "
                            "AND originAttributes = :originAttributes;"
 
                            ),
         getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -6675,17 +7173,20 @@ nsresult PrepareDatastoreOp::EnsureDirec
   bool exists;
   nsresult rv = aEntry->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!exists) {
     if (!aCreateIfNotExists) {
-      return NS_ERROR_NOT_AVAILABLE;
+      if (aAlreadyExisted) {
+        *aAlreadyExisted = false;
+      }
+      return NS_OK;
     }
 
     if (aIsDirectory) {
       rv = aEntry->Create(nsIFile::DIRECTORY_TYPE, 0755);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
@@ -6768,17 +7269,18 @@ 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
   // LoadDataOp is reference counted.
@@ -6865,32 +7367,48 @@ nsresult PrepareDatastoreOp::NestedRun()
   return NS_OK;
 }
 
 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
   MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
 
-  if (mDatabaseNotAvailable) {
-    MOZ_ASSERT(!mCreateIfNotExists);
-
+  // A datastore is not created when we are just trying to preload data and
+  // there's no database file.
+  if (mDatabaseNotAvailable && mForPreload) {
     LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
 
     aResponse = preloadDatastoreResponse;
 
     return;
   }
 
   if (!mDatastore) {
     MOZ_ASSERT(mUsage == mDEBUGUsage);
 
     RefPtr<QuotaObject> quotaObject;
 
     if (mPrivateBrowsingId == 0) {
+      if (!mConnection) {
+        // This can happen when there's no database file.
+        MOZ_ASSERT(mDatabaseNotAvailable);
+
+        // 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);
+        MOZ_ASSERT(mConnection);
+      }
+
       quotaObject = GetQuotaObject();
       MOZ_ASSERT(quotaObject);
     }
 
     mDatastore = new Datastore(mOrigin, mPrivateBrowsingId, mUsage, mSizeOfKeys,
                                mSizeOfItems, mDirectoryLock.forget(),
                                mConnection.forget(), quotaObject.forget(),
                                mValues, mOrderedItems);
@@ -6904,38 +7422,38 @@ void PrepareDatastoreOp::GetResponse(LSR
     MOZ_ASSERT(!gDatastores->Get(mOrigin));
     gDatastores->Put(mOrigin, mDatastore);
   }
 
   mDatastoreId = ++gLastDatastoreId;
 
   nsAutoPtr<PreparedDatastore> preparedDatastore(
       new PreparedDatastore(mDatastore, mContentParentId, mOrigin, mDatastoreId,
-                            /* aForPreload */ !mCreateIfNotExists));
+                            /* aForPreload */ mForPreload));
 
   if (!gPreparedDatastores) {
     gPreparedDatastores = new PreparedDatastoreHashtable();
   }
   gPreparedDatastores->Put(mDatastoreId, preparedDatastore);
 
   if (mInvalidated) {
     preparedDatastore->Invalidate();
   }
 
   preparedDatastore.forget();
 
-  if (mCreateIfNotExists) {
+  if (mForPreload) {
+    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
+
+    aResponse = preloadDatastoreResponse;
+  } else {
     LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
     prepareDatastoreResponse.datastoreId() = mDatastoreId;
 
     aResponse = prepareDatastoreResponse;
-  } else {
-    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
-
-    aResponse = preloadDatastoreResponse;
   }
 }
 
 void PrepareDatastoreOp::Cleanup() {
   AssertIsOnOwningThread();
 
   if (mDatastore) {
     MOZ_ASSERT(mDatastoreId > 0);
@@ -7157,46 +7675,60 @@ nsresult PrepareDatastoreOp::LoadDataOp:
              NestedState::DatabaseWorkLoadData);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   Connection::CachedStatement stmt;
-  nsresult rv =
-      mConnection->GetCachedStatement(NS_LITERAL_CSTRING("SELECT key, value "
-                                                         "FROM data;"),
-                                      &stmt);
+  nsresult rv = mConnection->GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT key, value, utf16Length, compressed "
+                         "FROM data;"),
+      &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   bool hasResult;
   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasResult)) && hasResult) {
     nsString key;
     rv = stmt->GetString(0, key);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    nsString value;
-    rv = stmt->GetString(1, value);
+    nsCString buffer;
+    rv = stmt->GetUTF8String(1, buffer);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    int32_t utf16Length;
+    rv = stmt->GetInt32(2, &utf16Length);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
+    int32_t compressed;
+    rv = stmt->GetInt32(3, &compressed);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    LSValue value(buffer, utf16Length, compressed);
+
     mPrepareDatastoreOp->mValues.Put(key, value);
     auto item = mPrepareDatastoreOp->mOrderedItems.AppendElement();
     item->key() = key;
     item->value() = value;
     mPrepareDatastoreOp->mSizeOfKeys += key.Length();
     mPrepareDatastoreOp->mSizeOfItems += key.Length() + value.Length();
 #ifdef DEBUG
-    mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.Length();
+    mPrepareDatastoreOp->mDEBUGUsage += key.Length() + value.UTF16Length();
 #endif
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
@@ -7937,22 +8469,20 @@ 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.
 
-  if (gUsages) {
-    int64_t usage;
-    if (gUsages->Get(aOrigin, &usage)) {
-      MOZ_ASSERT(usage >= 0);
-      aUsageInfo->AppendToDatabaseUsage(usage);
-    }
+  int64_t usage;
+  if (mozilla::dom::GetUsageForOrigin(aOrigin, usage)) {
+    MOZ_ASSERT(usage >= 0);
+    aUsageInfo->AppendToDatabaseUsage(usage);
   }
 
   return NS_OK;
 }
 
 nsresult QuotaClient::AboutToClearOrigins(
     const Nullable<PersistenceType>& aPersistenceType,
     const OriginScope& aOriginScope) {
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -115,17 +115,17 @@ void LSDatabase::NoteFinishedSnapshot(LS
 }
 
 nsresult LSDatabase::GetLength(LSObject* aObject, uint32_t* aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetLength(aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -135,17 +135,17 @@ nsresult LSDatabase::GetLength(LSObject*
 
 nsresult LSDatabase::GetKey(LSObject* aObject, uint32_t aIndex,
                             nsAString& aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetKey(aIndex, aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -155,17 +155,17 @@ nsresult LSDatabase::GetKey(LSObject* aO
 
 nsresult LSDatabase::GetItem(LSObject* aObject, const nsAString& aKey,
                              nsAString& aResult) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetItem(aKey, aResult);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -174,17 +174,17 @@ nsresult LSDatabase::GetItem(LSObject* a
 }
 
 nsresult LSDatabase::GetKeys(LSObject* aObject, nsTArray<nsString>& aKeys) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->GetKeys(aKeys);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -195,17 +195,17 @@ nsresult LSDatabase::GetKeys(LSObject* a
 nsresult LSDatabase::SetItem(LSObject* aObject, const nsAString& aKey,
                              const nsAString& aValue,
                              LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->SetItem(aKey, aValue, aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -215,17 +215,17 @@ nsresult LSDatabase::SetItem(LSObject* a
 
 nsresult LSDatabase::RemoveItem(LSObject* aObject, const nsAString& aKey,
                                 LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, aKey);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->RemoveItem(aKey, aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -234,17 +234,17 @@ nsresult LSDatabase::RemoveItem(LSObject
 }
 
 nsresult LSDatabase::Clear(LSObject* aObject, LSNotifyInfo& aNotifyInfo) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
-  nsresult rv = EnsureSnapshot(aObject);
+  nsresult rv = EnsureSnapshot(aObject, VoidString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mSnapshot->Clear(aNotifyInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -257,17 +257,17 @@ nsresult LSDatabase::BeginExplicitSnapsh
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(!mAllowedToClose);
 
   if (mSnapshot) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
 
-  nsresult rv = EnsureSnapshot(aObject, /* aExplicit */ true);
+  nsresult rv = EnsureSnapshot(aObject, VoidString(), /* aExplicit */ true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 nsresult LSDatabase::EndExplicitSnapshot(LSObject* aObject) {
@@ -285,44 +285,45 @@ nsresult LSDatabase::EndExplicitSnapshot
   nsresult rv = mSnapshot->End();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, bool aExplicit) {
+nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, const nsAString& aKey,
+                                    bool aExplicit) {
   MOZ_ASSERT(aObject);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT_IF(mSnapshot, !aExplicit);
   MOZ_ASSERT(!mAllowedToClose);
 
   if (mSnapshot) {
     return NS_OK;
   }
 
   RefPtr<LSSnapshot> snapshot = new LSSnapshot(this);
 
   LSSnapshotChild* actor = new LSSnapshotChild(snapshot);
 
   LSSnapshotInitInfo initInfo;
   bool ok = mActor->SendPBackgroundLSSnapshotConstructor(
-      actor, aObject->DocumentURI(),
+      actor, aObject->DocumentURI(), nsString(aKey),
       /* increasePeakUsage */ true,
       /* requestedSize */ 131072,
       /* minSize */ 4096, &initInfo);
   if (NS_WARN_IF(!ok)) {
     return NS_ERROR_FAILURE;
   }
 
   snapshot->SetActor(actor);
 
   // This add refs snapshot.
-  nsresult rv = snapshot->Init(initInfo, aExplicit);
+  nsresult rv = snapshot->Init(aKey, initInfo, aExplicit);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // This is cleared in LSSnapshot::Run() before the snapshot is destroyed.
   mSnapshot = snapshot;
 
   return NS_OK;
--- a/dom/localstorage/LSDatabase.h
+++ b/dom/localstorage/LSDatabase.h
@@ -74,17 +74,18 @@ class LSDatabase final {
 
   nsresult BeginExplicitSnapshot(LSObject* aObject);
 
   nsresult EndExplicitSnapshot(LSObject* aObject);
 
  private:
   ~LSDatabase();
 
-  nsresult EnsureSnapshot(LSObject* aObject, bool aExplicit = false);
+  nsresult EnsureSnapshot(LSObject* aObject, const nsAString& aKey,
+                          bool aExplicit = false);
 
   void AllowToClose();
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_localstorage_LSDatabase_h
--- a/dom/localstorage/LSSnapshot.cpp
+++ b/dom/localstorage/LSSnapshot.cpp
@@ -54,42 +54,46 @@ LSSnapshot::~LSSnapshot() {
 void LSSnapshot::SetActor(LSSnapshotChild* aActor) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(!mActor);
 
   mActor = aActor;
 }
 
-nsresult LSSnapshot::Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
+nsresult LSSnapshot::Init(const nsAString& aKey,
+                          const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mSelfRef);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(mLoadState == LoadState::Initial);
   MOZ_ASSERT(!mInitialized);
   MOZ_ASSERT(!mSentFinish);
 
   mSelfRef = this;
 
   LoadState loadState = aInitInfo.loadState();
 
   const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
   for (uint32_t i = 0; i < itemInfos.Length(); i++) {
     const LSItemInfo& itemInfo = itemInfos[i];
 
-    const nsString& value = itemInfo.value();
+    const LSValue& value = itemInfo.value();
 
     if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
       mLoadedItems.PutEntry(itemInfo.key());
     }
 
-    mValues.Put(itemInfo.key(), value);
+    mValues.Put(itemInfo.key(), value.AsString());
   }
 
   if (loadState == LoadState::Partial) {
+    if (aInitInfo.addKeyToUnknownItems()) {
+      mUnknownItems.PutEntry(aKey);
+    }
     mInitLength = aInitInfo.totalLength();
     mLength = mInitLength;
   } else if (loadState == LoadState::AllOrderedKeys) {
     mInitLength = aInitInfo.totalLength();
   } else {
     MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
   }
 
@@ -234,18 +238,18 @@ nsresult LSSnapshot::SetItem(const nsASt
     }
 
     if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
       mLength++;
     }
 
     LSSetItemInfo setItemInfo;
     setItemInfo.key() = aKey;
-    setItemInfo.oldValue() = oldValue;
-    setItemInfo.value() = aValue;
+    setItemInfo.oldValue() = LSValue(oldValue);
+    setItemInfo.value() = LSValue(aValue);
 
     mWriteInfos.AppendElement(std::move(setItemInfo));
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
@@ -280,17 +284,17 @@ nsresult LSSnapshot::RemoveItem(const ns
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     if (mLoadState == LoadState::Partial) {
       mLength--;
     }
 
     LSRemoveItemInfo removeItemInfo;
     removeItemInfo.key() = aKey;
-    removeItemInfo.oldValue() = oldValue;
+    removeItemInfo.oldValue() = LSValue(oldValue);
 
     mWriteInfos.AppendElement(std::move(removeItemInfo));
   }
 
   aNotifyInfo.changed() = changed;
   aNotifyInfo.oldValue() = oldValue;
 
   return NS_OK;
@@ -427,36 +431,39 @@ nsresult LSSnapshot::GetItemInternal(con
 
   switch (mLoadState) {
     case LoadState::Partial: {
       if (mValues.Get(aKey, &result)) {
         MOZ_ASSERT(!result.IsVoid());
       } else if (mLoadedItems.GetEntry(aKey) || mUnknownItems.GetEntry(aKey)) {
         result.SetIsVoid(true);
       } else {
+        LSValue value;
         nsTArray<LSItemInfo> itemInfos;
         if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
-                nsString(aKey), &result, &itemInfos))) {
+                nsString(aKey), &value, &itemInfos))) {
           return NS_ERROR_FAILURE;
         }
 
+        result = value.AsString();
+
         if (result.IsVoid()) {
           mUnknownItems.PutEntry(aKey);
         } else {
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
           // mLoadedItems.Count()==mInitLength is checked below.
         }
 
         for (uint32_t i = 0; i < itemInfos.Length(); i++) {
           const LSItemInfo& itemInfo = itemInfos[i];
 
           mLoadedItems.PutEntry(itemInfo.key());
-          mValues.Put(itemInfo.key(), itemInfo.value());
+          mValues.Put(itemInfo.key(), itemInfo.value().AsString());
         }
 
         if (mLoadedItems.Count() == mInitLength) {
           mLoadedItems.Clear();
           mUnknownItems.Clear();
           mLength = 0;
           mLoadState = LoadState::AllUnorderedItems;
         }
@@ -472,34 +479,37 @@ nsresult LSSnapshot::GetItemInternal(con
       }
 
       break;
     }
 
     case LoadState::AllOrderedKeys: {
       if (mValues.Get(aKey, &result)) {
         if (result.IsVoid()) {
+          LSValue value;
           nsTArray<LSItemInfo> itemInfos;
           if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
-                  nsString(aKey), &result, &itemInfos))) {
+                  nsString(aKey), &value, &itemInfos))) {
             return NS_ERROR_FAILURE;
           }
 
+          result = value.AsString();
+
           MOZ_ASSERT(!result.IsVoid());
 
           mLoadedItems.PutEntry(aKey);
           mValues.Put(aKey, result);
 
           // mLoadedItems.Count()==mInitLength is checked below.
 
           for (uint32_t i = 0; i < itemInfos.Length(); i++) {
             const LSItemInfo& itemInfo = itemInfos[i];
 
             mLoadedItems.PutEntry(itemInfo.key());
-            mValues.Put(itemInfo.key(), itemInfo.value());
+            mValues.Put(itemInfo.key(), itemInfo.value().AsString());
           }
 
           if (mLoadedItems.Count() == mInitLength) {
             mLoadedItems.Clear();
             MOZ_ASSERT(mLength == 0);
             mLoadState = LoadState::AllOrderedItems;
           }
         }
--- a/dom/localstorage/LSSnapshot.h
+++ b/dom/localstorage/LSSnapshot.h
@@ -2,16 +2,18 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_localstorage_LSSnapshot_h
 #define mozilla_dom_localstorage_LSSnapshot_h
 
+#include "LSValue.h"
+
 namespace mozilla {
 namespace dom {
 
 class LSDatabase;
 class LSNotifyInfo;
 class LSSnapshotChild;
 class LSSnapshotInitInfo;
 class LSWriteInfo;
@@ -107,17 +109,18 @@ class LSSnapshot final : public nsIRunna
     AssertIsOnOwningThread();
     MOZ_ASSERT(mActor);
 
     mActor = nullptr;
   }
 
   bool Explicit() const { return mExplicit; }
 
-  nsresult Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit);
+  nsresult Init(const nsAString& aKey, const LSSnapshotInitInfo& aInitInfo,
+                bool aExplicit);
 
   nsresult GetLength(uint32_t* aResult);
 
   nsresult GetKey(uint32_t aIndex, nsAString& aResult);
 
   nsresult GetItem(const nsAString& aKey, nsAString& aResult);
 
   nsresult GetKeys(nsTArray<nsString>& aKeys);
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSValue.cpp
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LSValue.h"
+
+namespace mozilla {
+namespace dom {
+
+const LSValue& VoidLSValue() {
+  static const LSValue sVoidLSValue(VoidCString(), 0, false);
+
+  return sVoidLSValue;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/LSValue.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_localstorage_LSValue_h
+#define mozilla_dom_localstorage_LSValue_h
+
+#include "SnappyUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Represents a LocalStorage value. From content's perspective, values (if
+ * present) are always DOMStrings. This is also true from a quota-tracking
+ * perspective. However, for memory and disk efficiency it's preferable to store
+ * the value in alternate compressed or utf-8 encoding representations. The
+ * LSValue type exists to support these alternate representations, dynamically
+ * decompressing/re-encoding to utf-16 while still tracking value size on a
+ * utf-16 basis for quota purposes.
+ */
+class LSValue final {
+  friend struct IPC::ParamTraits<LSValue>;
+
+  nsCString mBuffer;
+  uint32_t mUTF16Length;
+  bool mCompressed;
+
+ public:
+  LSValue() : mUTF16Length(0), mCompressed(false) {}
+
+  explicit LSValue(const nsACString& aBuffer, uint32_t aUTF16Length,
+                   bool aCompressed)
+      : mBuffer(aBuffer),
+        mUTF16Length(aUTF16Length),
+        mCompressed(aCompressed) {}
+
+  explicit LSValue(const nsAString& aBuffer) : mUTF16Length(aBuffer.Length()) {
+    if (aBuffer.IsVoid()) {
+      mBuffer.SetIsVoid(true);
+      mCompressed = false;
+    } else {
+      CopyUTF16toUTF8(aBuffer, mBuffer);
+      nsCString buffer;
+      if ((mCompressed = SnappyCompress(mBuffer, buffer))) {
+        mBuffer = buffer;
+      }
+    }
+  }
+
+  bool IsVoid() const { return mBuffer.IsVoid(); }
+
+  void SetIsVoid(bool aVal) { mBuffer.SetIsVoid(aVal); }
+
+  /**
+   * This represents the "physical" length that the parent process uses for
+   * the size of value/item computation. This can also be used to see how much
+   * memory the value is using at rest or what the cost is for sending the value
+   * over IPC.
+   */
+  uint32_t Length() const { return mBuffer.Length(); }
+
+  /*
+   * This represents the "logical" length that content sees and that is also
+   * used for quota management purposes.
+   */
+  uint32_t UTF16Length() const { return mUTF16Length; }
+
+  bool IsCompressed() const { return mCompressed; }
+
+  bool Equals(const LSValue& aOther) const {
+    return mBuffer == aOther.mBuffer &&
+           mBuffer.IsVoid() == aOther.mBuffer.IsVoid() &&
+           mUTF16Length == aOther.mUTF16Length &&
+           mCompressed == aOther.mCompressed;
+  }
+
+  bool operator==(const LSValue& aOther) const { return Equals(aOther); }
+
+  bool operator!=(const LSValue& aOther) const { return !Equals(aOther); }
+
+  operator const nsCString&() const { return mBuffer; }
+
+  operator Span<const char>() const { return mBuffer; }
+
+  class Converter {
+    nsString mBuffer;
+
+   public:
+    explicit Converter(const LSValue& aValue) {
+      if (aValue.mBuffer.IsVoid()) {
+        mBuffer.SetIsVoid(true);
+      } else if (aValue.mCompressed) {
+        nsCString buffer;
+        MOZ_ALWAYS_TRUE(SnappyUncompress(aValue.mBuffer, buffer));
+        CopyUTF8toUTF16(buffer, mBuffer);
+      } else {
+        CopyUTF8toUTF16(aValue.mBuffer, mBuffer);
+      }
+    }
+    Converter(Converter&& aOther) : mBuffer(aOther.mBuffer) {}
+    ~Converter() {}
+
+    operator const nsString&() const { return mBuffer; }
+
+   private:
+    Converter() = delete;
+    Converter(const Converter&) = delete;
+    Converter& operator=(const Converter&) = delete;
+    Converter& operator=(const Converter&&) = delete;
+  };
+
+  Converter AsString() const { return Converter(const_cast<LSValue&>(*this)); }
+};
+
+const LSValue& VoidLSValue();
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_localstorage_LSValue_h
--- a/dom/localstorage/PBackgroundLSDatabase.ipdl
+++ b/dom/localstorage/PBackgroundLSDatabase.ipdl
@@ -18,16 +18,24 @@ namespace dom {
 /**
  * Initial LSSnapshot state as produced by Datastore::GetSnapshotInitInfo.  See
  * `LSSnapshot::LoadState` for more details about the possible states and a
  * high level overview.
  */
 struct LSSnapshotInitInfo
 {
   /**
+   * Boolean indicating whether the `key` provided as an argument to the
+   * PBackgroundLSSnapshot constructor did not exist in the Datastore and should
+   * be treated as an unknown and therefore undefined value. Note that `key` may
+   * have been provided as a void string, in which case this value is forced to
+   * be false.
+   */
+  bool addKeyToUnknownItems;
+  /**
    * As many key/value or key/void pairs as the snapshot prefill byte budget
    * allowed.
    */
   LSItemInfo[] itemInfos;
   /**
    * The total number of key/value pairs in LocalStorage for this origin at the
    * time the snapshot was created.  (And the point of the snapshot is to
    * conceptually freeze the state of the Datastore in time, so this value does
@@ -94,21 +102,30 @@ parent:
    * Datastore state in the parent.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Datastore in the PBackground parent already
    * has the answers to this request immediately available without needing to
    * consult any other threads or perform any I/O.  Additionally, the response
    * is explicitly bounded in size by the tunable snapshot prefill byte limit.
    *
+   * @param key
+   *   If key is non-void, then the snapshot is being triggered by a direct
+   *   access to a localStorage key (get, set, or removal, with set/removal
+   *   requiring the old value in order to properly populate the "storage"
+   *   event), the key being requested. It's possible the key is not present in
+   *   localStorage, in which case LSSnapshotInitInfo::addKeyToUnknownItems will
+   *   be true indicating that there is no such key/value pair, otherwise it
+   *   will be false.
    * @param increasePeakUsage
    *   Whether the parent should attempt to pre-allocate some amount of quota
    *   usage to the Snapshot.
    */
   sync PBackgroundLSSnapshot(nsString documentURI,
+                             nsString key,
                              bool increasePeakUsage,
                              int64_t requestedSize,
                              int64_t minSize)
     returns (LSSnapshotInitInfo initInfo);
 
 child:
   /**
    * Only sent by the parent in response to the child's DeleteMe request.
--- a/dom/localstorage/PBackgroundLSObserver.ipdl
+++ b/dom/localstorage/PBackgroundLSObserver.ipdl
@@ -1,16 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 
 include PBackgroundSharedTypes;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSValue
+  from "mozilla/dom/LSValue.h";
+
 namespace mozilla {
 namespace dom {
 
 /**
  * The observer protocol sends "storage" event notifications for changes to
  * LocalStorage that take place in other processes as their Snapshots are
  * Checkpointed to the canonical Datastore in the parent process.  Same-process
  * notifications are generated as mutations happen.
@@ -44,14 +49,14 @@ child:
    * Checkpointed, applying their mutations.  The child actor currently directly
    * shunts these to Storage::NotifyChange to generate "storage" events for
    * immediate dispatch.
    */
   async Observe(PrincipalInfo principalInfo,
                 uint32_t privateBrowsingId,
                 nsString documentURI,
                 nsString key,
-                nsString oldValue,
-                nsString newValue);
+                LSValue oldValue,
+                LSValue newValue);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -1,15 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include PBackgroundSharedTypes;
 include ProtocolTypes;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSValue
+  from "mozilla/dom/LSValue.h";
+
 namespace mozilla {
 namespace dom {
 
 struct LSRequestCommonParams
 {
   PrincipalInfo principalInfo;
   nsCString originKey;
 };
@@ -52,13 +57,13 @@ union LSSimpleRequestParams
  * LocalStorage key/value pair wire representations.  `value` may be void in
  * cases where there is a value but it is not being sent for memory/bandwidth
  * conservation purposes.  (It's not possible to have a null/undefined `value`
  * as Storage is defined explicitly as a String store.)
  */
 struct LSItemInfo
 {
   nsString key;
-  nsString value;
+  LSValue value;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/localstorage/PBackgroundLSSnapshot.ipdl
+++ b/dom/localstorage/PBackgroundLSSnapshot.ipdl
@@ -2,30 +2,35 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBackground;
 include protocol PBackgroundLSDatabase;
 
 include PBackgroundLSSharedTypes;
 
+include "mozilla/dom/localstorage/SerializationHelpers.h";
+
+using mozilla::dom::LSValue
+  from "mozilla/dom/LSValue.h";
+
 namespace mozilla {
 namespace dom {
 
 struct LSSetItemInfo
 {
   nsString key;
-  nsString oldValue;
-  nsString value;
+  LSValue oldValue;
+  LSValue value;
 };
 
 struct LSRemoveItemInfo
 {
   nsString key;
-  nsString oldValue;
+  LSValue oldValue;
 };
 
 struct LSClearInfo
 {
 };
 
 /**
  * Union of LocalStorage mutation types.
@@ -56,17 +61,17 @@ parent:
    * the need to use this synchronous message again.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
    * consult any other threads or perform any I/O.
    */
   sync LoadValueAndMoreItems(nsString key)
-    returns (nsString value, LSItemInfo[] itemInfos);
+    returns (LSValue value, LSItemInfo[] itemInfos);
 
   /**
    * Invoked on demand to load all keys in in their canonical order if they
    * didn't fit into the initial snapshot prefill.
    *
    * This needs to be synchronous because LocalStorage's semantics are
    * synchronous.  Note that the Snapshot in the PBackground parent already
    * has the answers to this request immediately available without needing to
--- a/dom/localstorage/SerializationHelpers.h
+++ b/dom/localstorage/SerializationHelpers.h
@@ -5,21 +5,46 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_localstorage_SerializationHelpers_h
 #define mozilla_dom_localstorage_SerializationHelpers_h
 
 #include "ipc/IPCMessageUtils.h"
 
 #include "mozilla/dom/LSSnapshot.h"
+#include "mozilla/dom/LSValue.h"
 
 namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::dom::LSSnapshot::LoadState>
     : public ContiguousEnumSerializer<
           mozilla::dom::LSSnapshot::LoadState,
           mozilla::dom::LSSnapshot::LoadState::Initial,
           mozilla::dom::LSSnapshot::LoadState::EndGuard> {};
 
+template <>
+struct ParamTraits<mozilla::dom::LSValue> {
+  typedef mozilla::dom::LSValue paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.mBuffer);
+    WriteParam(aMsg, aParam.mUTF16Length);
+    WriteParam(aMsg, aParam.mCompressed);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(aMsg, aIter, &aResult->mBuffer) &&
+           ReadParam(aMsg, aIter, &aResult->mUTF16Length) &&
+           ReadParam(aMsg, aIter, &aResult->mCompressed);
+  }
+
+  static void Log(const paramType& aParam, std::wstring* aLog) {
+    LogParam(aParam.mBuffer, aLog);
+    LogParam(aParam.mUTF16Length, aLog);
+    LogParam(aParam.mCompressed, aLog);
+  }
+};
+
 }  // namespace IPC
 
 #endif  // mozilla_dom_localstorage_SerializationHelpers_h
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/SnappyUtils.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SnappyUtils.h"
+
+#include "snappy/snappy.h"
+
+namespace mozilla {
+namespace dom {
+
+bool SnappyCompress(const nsACString& aSource, nsACString& aDest) {
+  MOZ_ASSERT(!aSource.IsVoid());
+
+  size_t uncompressedLength = aSource.Length();
+
+  if (uncompressedLength <= 16) {
+    return false;
+  }
+
+  size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
+
+  aDest.SetLength(compressedLength);
+
+  snappy::RawCompress(aSource.BeginReading(), uncompressedLength,
+                      aDest.BeginWriting(), &compressedLength);
+
+  if (compressedLength >= uncompressedLength) {
+    return false;
+  }
+
+  aDest.SetLength(compressedLength);
+
+  return true;
+}
+
+bool SnappyUncompress(const nsACString& aSource, nsACString& aDest) {
+  MOZ_ASSERT(!aSource.IsVoid());
+
+  const char* compressed = aSource.BeginReading();
+
+  size_t compressedLength = aSource.Length();
+
+  size_t uncompressedLength;
+  if (!snappy::GetUncompressedLength(compressed, compressedLength,
+                                     &uncompressedLength)) {
+    return false;
+  }
+
+  aDest.SetLength(uncompressedLength);
+
+  if (!snappy::RawUncompress(compressed, compressedLength,
+                             aDest.BeginWriting())) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/SnappyUtils.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_localstorage_SnappyUtils_h
+#define mozilla_dom_localstorage_SnappyUtils_h
+
+namespace mozilla {
+namespace dom {
+
+bool SnappyCompress(const nsACString& aSource, nsACString& aDest);
+
+bool SnappyUncompress(const nsACString& aSource, nsACString& aDest);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_localstorage_SnappyUtils_h
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -29,28 +29,32 @@ EXPORTS.mozilla.dom.localstorage += [
 ]
 
 EXPORTS.mozilla.dom += [
     'LocalStorageCommon.h',
     'LocalStorageManager2.h',
     'LSObject.h',
     'LSObserver.h',
     'LSSnapshot.h',
+    'LSValue.h',
+    'SnappyUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'LocalStorageCommon.cpp',
     'LocalStorageManager2.cpp',
     'LSDatabase.cpp',
     'LSObject.cpp',
     'LSObserver.cpp',
     'LSSnapshot.cpp',
+    'LSValue.cpp',
     'ReportInternalError.cpp',
+    'SnappyUtils.cpp',
 ]
 
 IPDL_SOURCES += [
     'PBackgroundLSDatabase.ipdl',
     'PBackgroundLSObserver.ipdl',
     'PBackgroundLSRequest.ipdl',
     'PBackgroundLSSharedTypes.ipdlh',
     'PBackgroundLSSimpleRequest.ipdl',
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -213,17 +213,17 @@ const char kResourceOriginPrefix[] = "re
 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
 #define METADATA_V2_FILE_NAME ".metadata-v2"
 #define METADATA_V2_TMP_FILE_NAME ".metadata-v2-tmp"
 
 #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite"
 #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite"
 #define LS_ARCHIVE_TMP_FILE_NAME "ls-archive-tmp.sqlite"
 
-const uint32_t kLocalStorageArchiveVersion = 1;
+const uint32_t kLocalStorageArchiveVersion = 3;
 
 const char kProfileDoChangeTopic[] = "profile-do-change";
 
 /******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
@@ -441,17 +441,16 @@ nsresult LoadLocalStorageArchiveVersion(
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   aVersion = version;
   return NS_OK;
 }
 
-/*
 nsresult SaveLocalStorageArchiveVersion(mozIStorageConnection* aConnection,
                                         uint32_t aVersion) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = aConnection->CreateStatement(
       NS_LITERAL_CSTRING("UPDATE database SET version = :version;"),
@@ -467,17 +466,16 @@ nsresult SaveLocalStorageArchiveVersion(
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
-*/
 
 /******************************************************************************
  * Quota manager class declarations
  ******************************************************************************/
 
 }  // namespace
 
 class DirectoryLockImpl final : public DirectoryLock {
@@ -610,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();
@@ -651,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;
   }
@@ -686,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);
 
@@ -1121,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;
 
@@ -2221,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);
@@ -3382,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;
   }
@@ -3518,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);
@@ -5256,27 +5344,35 @@ nsresult QuotaManager::UpgradeLocalStora
   rv = InitializeLocalStorageArchive(aConnection, 1);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-/*
 nsresult QuotaManager::UpgradeLocalStorageArchiveFrom1To2(
     nsCOMPtr<mozIStorageConnection>& aConnection) {
   nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 2);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
-*/
+
+nsresult QuotaManager::UpgradeLocalStorageArchiveFrom2To3(
+    nsCOMPtr<mozIStorageConnection>& aConnection) {
+  nsresult rv = SaveLocalStorageArchiveVersion(aConnection, 3);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
 
 #ifdef DEBUG
 
 void QuotaManager::AssertStorageIsInitialized() const {
   AssertIsOnIOThread();
   MOZ_ASSERT(mStorageInitialized);
 }
 
@@ -5490,27 +5586,28 @@ nsresult QuotaManager::EnsureStorageIsIn
         MOZ_ASSERT(version == 0);
 
         rv = InitializeLocalStorageArchive(connection,
                                            kLocalStorageArchiveVersion);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
       } else {
-        static_assert(kLocalStorageArchiveVersion == 1,
+        static_assert(kLocalStorageArchiveVersion == 3,
                       "Upgrade function needed due to LocalStorage archive "
                       "version increase.");
 
         while (version != kLocalStorageArchiveVersion) {
           if (version == 0) {
             rv = UpgradeLocalStorageArchiveFrom0To1(connection);
-          } /* else if (version == 1) {
+          } else if (version == 1) {
             rv = UpgradeLocalStorageArchiveFrom1To2(connection);
-          } */
-          else {
+          } else if (version == 2) {
+            rv = UpgradeLocalStorageArchiveFrom2To3(connection);
+          } else {
             QM_WARNING(
                 "Unable to initialize LocalStorage archive, no upgrade path is "
                 "available!");
             return NS_ERROR_FAILURE;
           }
 
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
@@ -5617,41 +5714,37 @@ void QuotaManager::OpenDirectoryInternal
         mClients[index]->AbortOperations(iter.Get()->GetKey());
       }
     }
   }
 }
 
 nsresult QuotaManager::EnsureOriginIsInitialized(
     PersistenceType aPersistenceType, const nsACString& aSuffix,
-    const nsACString& aGroup, const nsACString& aOrigin,
-    bool aCreateIfNotExists, nsIFile** aDirectory) {
+    const nsACString& aGroup, const nsACString& aOrigin, nsIFile** aDirectory) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
 
   nsCOMPtr<nsIFile> directory;
   bool created;
   nsresult rv = EnsureOriginIsInitializedInternal(
-      aPersistenceType, aSuffix, aGroup, aOrigin, aCreateIfNotExists,
-      getter_AddRefs(directory), &created);
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return rv;
-  }
+      aPersistenceType, aSuffix, aGroup, aOrigin, getter_AddRefs(directory),
+      &created);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   directory.forget(aDirectory);
   return NS_OK;
 }
 
 nsresult QuotaManager::EnsureOriginIsInitializedInternal(
     PersistenceType aPersistenceType, const nsACString& aSuffix,
-    const nsACString& aGroup, const nsACString& aOrigin,
-    bool aCreateIfNotExists, nsIFile** aDirectory, bool* aCreated) {
+    const nsACString& aGroup, const nsACString& aOrigin, nsIFile** aDirectory,
+    bool* aCreated) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aCreated);
 
   nsresult rv = EnsureStorageIsInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Get directory for this origin and persistence type.
@@ -5669,30 +5762,29 @@ nsresult QuotaManager::EnsureOriginIsIni
   } else {
     rv = EnsureTemporaryStorageIsInitialized();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   bool created;
-  rv = EnsureOriginDirectory(directory, aCreateIfNotExists, &created);
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    return rv;
-  }
+  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() {
@@ -5791,33 +5881,28 @@ nsresult QuotaManager::EnsureTemporarySt
   mTemporaryStorageInitialized = true;
 
   CheckTemporaryStorageLimits();
 
   return rv;
 }
 
 nsresult QuotaManager::EnsureOriginDirectory(nsIFile* aDirectory,
-                                             bool aCreateIfNotExists,
                                              bool* aCreated) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(aCreated);
 
   bool exists;
   nsresult rv = aDirectory->Exists(&exists);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!exists) {
-    if (!aCreateIfNotExists) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
     nsString leafName;
     rv = aDirectory->GetLeafName(leafName);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (!IsSanitizedOriginValid(NS_ConvertUTF16toUTF8(leafName))) {
       QM_WARNING(
@@ -6256,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)) {
@@ -6511,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) {
@@ -7619,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,
@@ -7643,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);
@@ -7699,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();
 
@@ -7914,17 +8046,17 @@ nsresult InitOriginOp::DoDirectoryWork(Q
   MOZ_ASSERT(!mPersistenceType.IsNull());
 
   AUTO_PROFILER_LABEL("InitOriginOp::DoDirectoryWork", OTHER);
 
   nsCOMPtr<nsIFile> directory;
   bool created;
   nsresult rv = aQuotaManager->EnsureOriginIsInitializedInternal(
       mPersistenceType.Value(), mSuffix, mGroup, mOriginScope.GetOrigin(),
-      /* aCreateIfNotExists */ true, getter_AddRefs(directory), &created);
+      getter_AddRefs(directory), &created);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   mCreated = created;
 
   return NS_OK;
 }
@@ -8427,39 +8559,39 @@ nsresult PersistOp::DoDirectoryWork(Quot
   nsresult rv = aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(),
                                                      mOriginScope.GetOrigin(),
                                                      getter_AddRefs(directory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   bool created;
-  rv = aQuotaManager->EnsureOriginDirectory(directory,
-                                            /* aCreateIfNotExists */ true,
-                                            &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);
@@ -8544,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,42 +273,55 @@ 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
 
   nsresult EnsureStorageIsInitialized();
 
   nsresult EnsureOriginIsInitialized(PersistenceType aPersistenceType,
                                      const nsACString& aSuffix,
                                      const nsACString& aGroup,
                                      const nsACString& aOrigin,
-                                     bool aCreateIfNotExists,
                                      nsIFile** aDirectory);
 
-  nsresult EnsureOriginIsInitializedInternal(
-      PersistenceType aPersistenceType, const nsACString& aSuffix,
-      const nsACString& aGroup, const nsACString& aOrigin,
-      bool aCreateIfNotExists, nsIFile** aDirectory, bool* aCreated);
+  nsresult EnsureOriginIsInitializedInternal(PersistenceType aPersistenceType,
+                                             const nsACString& aSuffix,
+                                             const nsACString& aGroup,
+                                             const nsACString& aOrigin,
+                                             nsIFile** aDirectory,
+                                             bool* aCreated);
 
   nsresult EnsureTemporaryStorageIsInitialized();
 
-  nsresult EnsureOriginDirectory(nsIFile* aDirectory, bool aCreateIfNotExists,
-                                 bool* aCreated);
+  nsresult EnsureOriginDirectory(nsIFile* aDirectory, bool* aCreated);
 
   nsresult AboutToClearOrigins(
       const Nullable<PersistenceType>& aPersistenceType,
       const OriginScope& aOriginScope,
       const Nullable<Client::Type>& aClientType);
 
   void OriginClearCompleted(PersistenceType aPersistenceType,
                             const nsACString& aOrigin,
@@ -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();
 
@@ -435,20 +481,21 @@ class QuotaManager final : public Backgr
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult DowngradeLocalStorageArchive(
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult UpgradeLocalStorageArchiveFrom0To1(
       nsCOMPtr<mozIStorageConnection>& aConnection);
 
-  /*
-    nsresult UpgradeLocalStorageArchiveFrom1To2(
-        nsCOMPtr<mozIStorageConnection>& aConnection);
-  */
+  nsresult UpgradeLocalStorageArchiveFrom1To2(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
+
+  nsresult UpgradeLocalStorageArchiveFrom2To3(
+      nsCOMPtr<mozIStorageConnection>& aConnection);
 
   nsresult InitializeRepository(PersistenceType aPersistenceType);
 
   nsresult InitializeOrigin(PersistenceType aPersistenceType,
                             const nsACString& aGroup, const nsACString& aOrigin,
                             int64_t aAccessTime, bool aPersisted,
                             nsIFile* aDirectory);
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8883ea4630468f1c63076254109d53429752b8ca
GIT binary patch
literal 7792
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
ARsaA1
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e475d3d17540b10922e395f28afeb97a56e07bff
GIT binary patch
literal 7986
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
EKamI<D*ylh
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchive2upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are not removed
+ * during local storage archive upgrade from version 1 to version 2.
+ * See bug 1546310.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build with
+  // local storage archive version 1), specifically it was temporarily added to
+  // xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchive2upgrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_localStorageArchive3upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are not removed
+ * during local storage archive upgrade from version 2 to version 3.
+ * See bug 1513937.
+ */
+
+async function testSteps() {
+  const lsDirs = [
+    "storage/default/http+++example.com/ls",
+    "storage/default/http+++localhost/ls",
+    "storage/default/http+++www.mozilla.org/ls",
+  ];
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains three initialized origin directories with local
+  // storage data, local storage archive, a script for origin initialization,
+  // the storage database and the web apps store database:
+  // - storage/default/https+++example.com
+  // - storage/default/https+++localhost
+  // - storage/default/https+++www.mozilla.org
+  // - storage/ls-archive.sqlite
+  // - create_db.js
+  // - storage.sqlite
+  // - webappsstore.sqlite
+  // The file create_db.js in the package was run locally (with a build with
+  // local storage archive version 2), specifically it was temporarily added to
+  // xpcshell.ini and then executed:
+  //   mach xpcshell-test --interactive dom/localstorage/test/unit/create_db.js
+  // Note: to make it become the profile in the test, additional manual steps
+  // are needed.
+  // 1. Remove the folder "storage/temporary".
+  installPackage("localStorageArchive3upgrade_profile");
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+
+  request = init();
+  request = await requestFinished(request);
+
+  info("Checking ls dirs");
+
+  for (let lsDir of lsDirs) {
+    let dir = getRelativeFile(lsDir);
+
+    exists = dir.exists();
+    ok(exists, "ls directory does exist");
+  }
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -9,16 +9,18 @@ support-files =
   clearStorageForPrincipal_profile.zip
   createLocalStorage_profile.zip
   defaultStorageUpgrade_profile.zip
   getUsage_profile.zip
   groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
   localStorageArchive1upgrade_profile.zip
+  localStorageArchive2upgrade_profile.zip
+  localStorageArchive3upgrade_profile.zip
   localStorageArchiveDowngrade_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
   removeLocalStorage1_profile.zip
   removeLocalStorage2_profile.zip
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
@@ -31,16 +33,18 @@ support-files =
 [test_clearStorageForPrincipal.js]
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
 [test_groupMismatch.js]
 [test_idbSubdirUpgrade.js]
 [test_initTemporaryStorage.js]
 [test_listInitializedOrigins.js]
 [test_localStorageArchive1upgrade.js]
+[test_localStorageArchive2upgrade.js]
+[test_localStorageArchive3upgrade.js]
 [test_localStorageArchiveDowngrade.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_obsoleteOrigins.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
--- a/dom/simpledb/ActorsParent.cpp
+++ b/dom/simpledb/ActorsParent.cpp
@@ -1147,17 +1147,17 @@ nsresult OpenOp::DatabaseWork() {
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   nsCOMPtr<nsIFile> dbDirectory;
   nsresult rv = quotaManager->EnsureOriginIsInitialized(
       PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin,
-      /* aCreateIfNotExists */ true, getter_AddRefs(dbDirectory));
+      getter_AddRefs(dbDirectory));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = dbDirectory->Append(NS_LITERAL_STRING(SDB_DIRECTORY_NAME));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -259,17 +259,17 @@ parent:
 
   // The pipelineId is the same as the layersId
   async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
 
   async BeginRecording(TimeStamp aRecordingStart);
-  async EndRecording();
+  sync EndRecording();
 
 child:
   // Send back Compositor Frame Metrics from APZCs so tiled layers can
   // update progressively.
   async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, LayersId aLayersId, uint32_t aAPZCId);
   async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId);
 };
 
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -982,16 +982,18 @@ description =
 description =
 [PCompositorBridge::SyncWithCompositor]
 description =
 [PCompositorBridge::CheckContentOnlyTDR]
 description =
 [PCompositorWidget::EnterPresentLock]
 description =
 platform = win
+[PCompositorBridge::EndRecording]
+description = This call is only used for performance analysis scenarios
 [PCompositorWidget::LeavePresentLock]
 description =
 platform = win
 [PCompositorWidget::ClearTransparentWindow]
 description =
 platform = win
 [PImageBridge::WillClose]
 description =
--- a/python/mozbuild/mozbuild/backend/cpp_eclipse.py
+++ b/python/mozbuild/mozbuild/backend/cpp_eclipse.py
@@ -1,15 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
 import errno
+import glob
 import random
 import os
 import shutil
 import subprocess
 import types
 from xml.sax.saxutils import quoteattr
 import xml.etree.ElementTree as ET
 from .common import CommonBackend
@@ -36,16 +37,17 @@ class CppEclipseBackend(CommonBackend):
         super(CppEclipseBackend, self).__init__(environment)
 
     def _init(self):
         CommonBackend._init(self)
 
         self._args_for_dirs = {}
         self._project_name = 'Gecko'
         self._workspace_dir = self._get_workspace_path()
+        self._workspace_lang_dir = os.path.join(self._workspace_dir, '.metadata/.plugins/org.eclipse.cdt.core')
         self._project_dir = os.path.join(self._workspace_dir, self._project_name)
         self._overwriting_workspace = os.path.isdir(self._workspace_dir)
 
         self._macbundle = self.environment.substs['MOZ_MACBUNDLE_NAME']
         self._appname = self.environment.substs['MOZ_APP_NAME']
         self._bin_suffix = self.environment.substs['BIN_SUFFIX']
         self._cxx = self.environment.substs['CXX']
         # Note: We need the C Pre Processor (CPP) flags, not the CXX flags
@@ -99,19 +101,18 @@ class CppEclipseBackend(CommonBackend):
                 defs += obj.flags["LIBRARY_DEFINES"]
 
         return True
 
     def consume_finished(self):
         settings_dir = os.path.join(self._project_dir, '.settings')
         launch_dir = os.path.join(self._project_dir, 'RunConfigurations')
         workspace_settings_dir = os.path.join(self._workspace_dir, '.metadata/.plugins/org.eclipse.core.runtime/.settings')
-        workspace_language_dir = os.path.join(self._workspace_dir, '.metadata/.plugins/org.eclipse.cdt.core')
 
-        for dir_name in [self._project_dir, settings_dir, launch_dir, workspace_settings_dir, workspace_language_dir]:
+        for dir_name in [self._project_dir, settings_dir, launch_dir, workspace_settings_dir, self._workspace_lang_dir]:
             try:
                 os.makedirs(dir_name)
             except OSError as e:
                 if e.errno != errno.EEXIST:
                     raise
 
         project_path = os.path.join(self._project_dir, '.project')
         with open(project_path, 'wb') as fh:
@@ -120,17 +121,17 @@ class CppEclipseBackend(CommonBackend):
         cproject_path = os.path.join(self._project_dir, '.cproject')
         with open(cproject_path, 'wb') as fh:
             self._write_cproject(fh)
 
         language_path = os.path.join(settings_dir, 'language.settings.xml')
         with open(language_path, 'wb') as fh:
             self._write_language_settings(fh)
 
-        workspace_language_path = os.path.join(workspace_language_dir, 'language.settings.xml')
+        workspace_language_path = os.path.join(self._workspace_lang_dir, 'language.settings.xml')
         with open(workspace_language_path, 'wb') as fh:
             workspace_lang_settings = WORKSPACE_LANGUAGE_SETTINGS_TEMPLATE
             workspace_lang_settings = workspace_lang_settings.replace("@COMPILER_FLAGS@", self._cxx + " " + self._cppflags);
             fh.write(workspace_lang_settings)
 
         self._write_launch_files(launch_dir)
 
         core_resources_prefs_path = os.path.join(workspace_settings_dir, 'org.eclipse.core.resources.prefs')
@@ -205,16 +206,23 @@ class CppEclipseBackend(CommonBackend):
             self._remove_noindex()
 
     def _write_noindex(self):
         noindex_path = os.path.join(self._project_dir, '.settings/org.eclipse.cdt.core.prefs')
         with open(noindex_path, 'wb') as fh:
             fh.write(NOINDEX_TEMPLATE);
 
     def _remove_noindex(self):
+        # Below we remove the config file that temporarily disabled the indexer
+        # while we were importing the project. Unfornutanely, CDT doesn't
+        # notice indexer settings changes in config files when it restarts. To
+        # work around that we remove the index database here to force it to:
+        for f in glob.glob(os.path.join(self._workspace_lang_dir, "*.pdom")):
+            os.remove(f)
+
         noindex_path = os.path.join(self._project_dir, '.settings/org.eclipse.cdt.core.prefs')
         # This may fail if the entire tree has been removed; that's fine.
         try:
             os.remove(noindex_path)
         except OSError as e:
             if e.errno != errno.ENOENT:
                 raise