Bug 1513892 - Part 1: Cache usage in the database table; r=asuth
authorJan Varga <jan.varga@gmail.com>
Fri, 21 Dec 2018 06:26:25 +0100
changeset 451644 2a2c0b1ebd5c5a8add518189b96456bd3380b134
parent 451643 644731a71be81b31a049deee70170af3ca36efa1
child 451645 7bff7a09483d3390388033324f69ebe4b7248a85
push id35248
push userebalazs@mozilla.com
push dateFri, 21 Dec 2018 09:37:52 +0000
treeherdermozilla-central@e16b548dc14c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1513892
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513892 - Part 1: Cache usage in the database table; r=asuth This patch slightly speedups origin initialization by having a special column in the database table for current data usage. This patch also fixes a problem with length computation of some unicode strings.
dom/localstorage/ActorsParent.cpp
dom/localstorage/test/unit/stringLength_profile.zip
dom/localstorage/test/unit/test_stringLength.js
dom/localstorage/test/unit/xpcshell.ini
storage/mozStorageSQLFunctions.cpp
storage/mozStorageSQLFunctions.h
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -75,17 +75,17 @@ class Snapshot;
 typedef nsClassHashtable<nsCStringHashKey, ArchivedOriginInfo>
     ArchivedOriginHashtable;
 
 /*******************************************************************************
  * Constants
  ******************************************************************************/
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 1;
+const uint32_t kMajorSchemaVersion = 2;
 
 // 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.
@@ -206,38 +206,37 @@ const uint32_t kShadowJournalSizeLimit =
 bool IsOnConnectionThread();
 
 void AssertIsOnConnectionThread();
 
 /*******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
-#if 0
 int32_t
 MakeSchemaVersion(uint32_t aMajorSchemaVersion,
                   uint32_t aMinorSchemaVersion)
 {
   return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
 }
-#endif
 
 nsCString GetArchivedOriginHashKey(const nsACString& aOriginSuffix,
                                    const nsACString& aOriginNoSuffix) {
   return aOriginSuffix + NS_LITERAL_CSTRING(":") + aOriginNoSuffix;
 }
 
 nsresult CreateTables(mozIStorageConnection* aConnection) {
   AssertIsOnIOThread();
   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"
                          ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
                          ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
                          ");"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -256,31 +255,43 @@ nsresult CreateTables(mozIStorageConnect
   rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-#if 0
 nsresult
 UpgradeSchemaFrom1_0To2_0(mozIStorageConnection* aConnection)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aConnection);
 
-  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0));
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE database ADD COLUMN usage INTEGER NOT NULL DEFAULT 0;"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "UPDATE database "
+      "SET usage = (SELECT total(utf16Length(key) + utf16Length(value)) "
+      "FROM data);"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(2, 0));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
-#endif
 
 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))) {
@@ -405,32 +416,28 @@ 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((1 << 4) + 0),
+      static_assert(kSQLiteSchemaVersion == int32_t((2 << 4) + 0),
                     "Upgrade function needed due to schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
-#if 0
         if (schemaVersion == MakeSchemaVersion(1, 0)) {
           rv = UpgradeSchemaFrom1_0To2_0(connection);
         } else {
-#endif
-        LS_WARNING(
-            "Unable to open LocalStorage database, no upgrade path is "
-            "available!");
-        return NS_ERROR_FAILURE;
-#if 0
+          LS_WARNING(
+              "Unable to open LocalStorage database, no upgrade path is "
+              "available!");
+          return NS_ERROR_FAILURE;
         }
-#endif
 
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
         }
 
         rv = connection->GetSchemaVersion(&schemaVersion);
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return rv;
@@ -977,45 +984,51 @@ class WriteOptimizer final {
   class WriteInfo;
   class AddItemInfo;
   class UpdateItemInfo;
   class RemoveItemInfo;
   class ClearInfo;
 
   nsAutoPtr<WriteInfo> mClearInfo;
   nsClassHashtable<nsStringHashKey, WriteInfo> mWriteInfos;
+  int64_t mTotalDelta;
 
  public:
-  WriteOptimizer() {}
+  WriteOptimizer() : mTotalDelta(0) {}
 
   WriteOptimizer(WriteOptimizer&& aWriteOptimizer)
       : mClearInfo(std::move(aWriteOptimizer.mClearInfo)) {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(&aWriteOptimizer != this);
 
     mWriteInfos.SwapElements(aWriteOptimizer.mWriteInfos);
-  }
-
-  void AddItem(const nsString& aKey, const nsString& aValue);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue);
-
-  void RemoveItem(const nsString& aKey);
-
-  void Clear();
+    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,
+                  int64_t aDelta = 0);
+
+  void RemoveItem(const nsString& aKey, int64_t aDelta = 0);
+
+  void Clear(int64_t aDelta = 0);
 
   bool HasWrites() const {
     AssertIsOnBackgroundThread();
 
     return mClearInfo || !mWriteInfos.IsEmpty();
   }
 
   void ApplyWrites(nsTArray<LSItemInfo>& aOrderedItems);
 
-  nsresult PerformWrites(Connection* aConnection, bool aShadowWrites);
+  nsresult PerformWrites(Connection* aConnection, bool aShadowWrites,
+                         int64_t& aOutUsage);
 };
 
 /**
  * Base class for specific mutations.  Each subclass knows how to `Perform` the
  * manipulation against a `Connection` and the "shadow" database (legacy
  * webappsstore.sqlite database that exists so LSNG can be disabled/safely
  * downgraded from.)
  */
@@ -1226,34 +1239,36 @@ class Connection final {
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::Connection)
 
   void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(Connection); }
 
   ArchivedOriginScope* GetArchivedOriginScope() const {
     return mArchivedOriginScope;
   }
 
+  const nsCString& Origin() const { return mOrigin; }
+
   //////////////////////////////////////////////////////////////////////////////
   // Methods which can only be called on the owning thread.
 
   // This method is used to asynchronously execute a connection datastore
   // operation on the connection thread.
   void Dispatch(ConnectionDatastoreOperationBase* aOp);
 
   // This method is used to asynchronously close the storage connection on the
   // connection thread.
   void Close(nsIRunnable* aCallback);
 
-  void AddItem(const nsString& aKey, const nsString& aValue);
-
-  void UpdateItem(const nsString& aKey, const nsString& aValue);
-
-  void RemoveItem(const nsString& aKey);
-
-  void Clear();
+  void AddItem(const nsString& aKey, const nsString& aValue, int64_t aDelta);
+
+  void UpdateItem(const nsString& aKey, const nsString& 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.
 
@@ -2667,30 +2682,31 @@ nsresult GetUsage(mozIStorageConnection*
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(aUsage);
 
   nsresult rv;
 
   nsCOMPtr<mozIStorageStatement> stmt;
   if (aArchivedOriginScope) {
     rv = aConnection->CreateStatement(
-        NS_LITERAL_CSTRING("SELECT sum(length(key) + length(value)) "
+        NS_LITERAL_CSTRING("SELECT "
+                           "total(utf16Length(key) + utf16Length(value)) "
                            "FROM webappsstore2 "
                            "WHERE originKey = :originKey "
                            "AND originAttributes = :originAttributes;"),
         getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = aArchivedOriginScope->BindToStatement(stmt);
   } else {
     rv = aConnection->CreateStatement(
-        NS_LITERAL_CSTRING("SELECT sum(length(key) + length(value)) "
-                           "FROM data"),
+        NS_LITERAL_CSTRING("SELECT usage "
+                           "FROM database"),
         getter_AddRefs(stmt));
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   bool hasResult;
   rv = stmt->ExecuteStep(&hasResult);
@@ -3026,66 +3042,75 @@ 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 nsString& 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());
-}
-
-void WriteOptimizer::UpdateItem(const nsString& aKey, const nsString& aValue) {
+
+  mTotalDelta += aDelta;
+}
+
+void WriteOptimizer::UpdateItem(const nsString& aKey, const nsString& aValue,
+                                int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   nsAutoPtr<WriteInfo> newWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::AddItem) {
     newWriteInfo = new AddItemInfo(aKey, aValue);
   } else {
     newWriteInfo = new UpdateItemInfo(aKey, aValue);
   }
   mWriteInfos.Put(aKey, newWriteInfo.forget());
-}
-
-void WriteOptimizer::RemoveItem(const nsString& aKey) {
+
+  mTotalDelta += aDelta;
+}
+
+void WriteOptimizer::RemoveItem(const nsString& aKey, int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   WriteInfo* existingWriteInfo;
   if (mWriteInfos.Get(aKey, &existingWriteInfo) &&
       existingWriteInfo->GetType() == WriteInfo::AddItem) {
     mWriteInfos.Remove(aKey);
-    return;
-  }
-
-  nsAutoPtr<WriteInfo> newWriteInfo(new RemoveItemInfo(aKey));
-  mWriteInfos.Put(aKey, newWriteInfo.forget());
-}
-
-void WriteOptimizer::Clear() {
+  } else {
+    nsAutoPtr<WriteInfo> newWriteInfo(new RemoveItemInfo(aKey));
+    mWriteInfos.Put(aKey, newWriteInfo.forget());
+  }
+
+  mTotalDelta += aDelta;
+}
+
+void WriteOptimizer::Clear(int64_t aDelta) {
   AssertIsOnBackgroundThread();
 
   mWriteInfos.Clear();
 
   if (!mClearInfo) {
     mClearInfo = new ClearInfo();
   }
+
+  mTotalDelta += aDelta;
 }
 
 void WriteOptimizer::ApplyWrites(nsTArray<LSItemInfo>& aOrderedItems) {
   AssertIsOnBackgroundThread();
 
   if (mClearInfo) {
     aOrderedItems.Clear();
     mClearInfo = nullptr;
@@ -3130,17 +3155,17 @@ void WriteOptimizer::ApplyWrites(nsTArra
     itemInfo->key() = addItemInfo->GetKey();
     itemInfo->value() = addItemInfo->GetValue();
   }
 
   mWriteInfos.Clear();
 }
 
 nsresult WriteOptimizer::PerformWrites(Connection* aConnection,
-                                       bool aShadowWrites) {
+                                       bool aShadowWrites, int64_t& aOutUsage) {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection);
 
   nsresult rv;
 
   if (mClearInfo) {
     rv = mClearInfo->Perform(aConnection, aShadowWrites);
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -3150,16 +3175,60 @@ nsresult WriteOptimizer::PerformWrites(C
 
   for (auto iter = mWriteInfos.ConstIter(); !iter.Done(); iter.Next()) {
     rv = iter.Data()->Perform(aConnection, aShadowWrites);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
+  Connection::CachedStatement stmt;
+  rv = aConnection->GetCachedStatement(
+      NS_LITERAL_CSTRING("UPDATE database "
+                         "SET usage = usage + :delta"),
+      &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), mTotalDelta);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->Execute();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aConnection->GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT usage "
+                         "FROM database"),
+      &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  bool hasResult;
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(!hasResult)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  int64_t usage;
+  rv = stmt->GetInt64(0, &usage);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aOutUsage = usage;
   return NS_OK;
 }
 
 nsresult WriteOptimizer::AddItemInfo::Perform(Connection* aConnection,
                                               bool aShadowWrites) {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection);
 
@@ -3473,42 +3542,44 @@ 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 nsString& aValue,
+                         int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
-  mWriteOptimizer.AddItem(aKey, aValue);
-}
-
-void Connection::UpdateItem(const nsString& aKey, const nsString& aValue) {
+  mWriteOptimizer.AddItem(aKey, aValue, aDelta);
+}
+
+void Connection::UpdateItem(const nsString& aKey, const nsString& aValue,
+                            int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
-  mWriteOptimizer.UpdateItem(aKey, aValue);
-}
-
-void Connection::RemoveItem(const nsString& aKey) {
+  mWriteOptimizer.UpdateItem(aKey, aValue, aDelta);
+}
+
+void Connection::RemoveItem(const nsString& aKey, int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
-  mWriteOptimizer.RemoveItem(aKey);
-}
-
-void Connection::Clear() {
+  mWriteOptimizer.RemoveItem(aKey, aDelta);
+}
+
+void Connection::Clear(int64_t aDelta) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mInUpdateBatch);
 
-  mWriteOptimizer.Clear();
+  mWriteOptimizer.Clear(aDelta);
 }
 
 void Connection::BeginUpdateBatch() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!mInUpdateBatch);
 
 #ifdef DEBUG
   mInUpdateBatch = true;
@@ -3711,17 +3782,18 @@ nsresult Connection::FlushOp::DoDatastor
     return rv;
   }
 
   rv = stmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = mWriteOptimizer.PerformWrites(mConnection, mShadowWrites);
+  int64_t usage;
+  rv = mWriteOptimizer.PerformWrites(mConnection, mShadowWrites, usage);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = mConnection->GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -3731,17 +3803,30 @@ nsresult Connection::FlushOp::DoDatastor
     return rv;
   }
 
   if (mShadowWrites) {
     rv = DetachShadowDatabase(storageConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-  }
+
+    shadowDatabaseLock.reset();
+  }
+
+  RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+      "dom::localstorage::UpdateUsageRunnable",
+      [origin = mConnection->Origin(), usage]() {
+        MOZ_ASSERT(gUsages);
+        MOZ_ASSERT(gUsages->Contains(origin));
+        gUsages->Put(origin, usage);
+      });
+
+  MOZ_ALWAYS_SUCCEEDS(
+      quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
 nsresult Connection::CloseOp::DoDatastoreWork() {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(mConnection);
 
@@ -4122,42 +4207,44 @@ void Datastore::SetItem(Database* aDatab
 
   if (oldValue != aValue || oldValue.IsVoid() != aValue.IsVoid()) {
     bool isNewItem = oldValue.IsVoid();
 
     NotifySnapshots(aDatabase, aKey, oldValue, /* affectsOrder */ isNewItem);
 
     mValues.Put(aKey, aValue);
 
+    int64_t sizeOfItem;
+
     if (isNewItem) {
       mWriteOptimizer.AddItem(aKey, aValue);
 
       int64_t sizeOfKey = static_cast<int64_t>(aKey.Length());
-      int64_t sizeOfItem = sizeOfKey + static_cast<int64_t>(aValue.Length());
+      sizeOfItem = sizeOfKey + static_cast<int64_t>(aValue.Length());
 
       mUpdateBatchUsage += sizeOfItem;
 
       mSizeOfKeys += sizeOfKey;
       mSizeOfItems += sizeOfItem;
     } else {
       mWriteOptimizer.UpdateItem(aKey, aValue);
 
-      int64_t delta = static_cast<int64_t>(aValue.Length()) -
-                      static_cast<int64_t>(oldValue.Length());
-
-      mUpdateBatchUsage += delta;
-
-      mSizeOfItems += delta;
+      sizeOfItem = static_cast<int64_t>(aValue.Length()) -
+                   static_cast<int64_t>(oldValue.Length());
+
+      mUpdateBatchUsage += sizeOfItem;
+
+      mSizeOfItems += sizeOfItem;
     }
 
     if (IsPersistent()) {
       if (oldValue.IsVoid()) {
-        mConnection->AddItem(aKey, aValue);
+        mConnection->AddItem(aKey, aValue, sizeOfItem);
       } else {
-        mConnection->UpdateItem(aKey, aValue);
+        mConnection->UpdateItem(aKey, aValue, sizeOfItem);
       }
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, aValue);
 }
 
 void Datastore::RemoveItem(Database* aDatabase, const nsString& aDocumentURI,
@@ -4181,52 +4268,52 @@ void Datastore::RemoveItem(Database* aDa
     int64_t sizeOfItem = sizeOfKey + static_cast<int64_t>(oldValue.Length());
 
     mUpdateBatchUsage -= sizeOfItem;
 
     mSizeOfKeys -= sizeOfKey;
     mSizeOfItems -= sizeOfItem;
 
     if (IsPersistent()) {
-      mConnection->RemoveItem(aKey);
+      mConnection->RemoveItem(aKey, -sizeOfItem);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, aKey, aOldValue, VoidString());
 }
 
 void Datastore::Clear(Database* aDatabase, const nsString& aDocumentURI) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
   MOZ_ASSERT(!mClosed);
   MOZ_ASSERT(mInUpdateBatch);
 
   if (mValues.Count()) {
-    int64_t updateBatchUsage = mUpdateBatchUsage;
+    int64_t sizeOfItems = 0;
     for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
       const nsAString& key = iter.Key();
       const nsAString& value = iter.Data();
 
-      updateBatchUsage -= (static_cast<int64_t>(key.Length()) +
-                           static_cast<int64_t>(value.Length()));
+      sizeOfItems += (static_cast<int64_t>(key.Length()) +
+                      static_cast<int64_t>(value.Length()));
 
       NotifySnapshots(aDatabase, key, value, /* aAffectsOrder */ true);
     }
 
     mValues.Clear();
 
     mWriteOptimizer.Clear();
 
-    mUpdateBatchUsage = updateBatchUsage;
+    mUpdateBatchUsage -= sizeOfItems;
 
     mSizeOfKeys = 0;
     mSizeOfItems = 0;
 
     if (IsPersistent()) {
-      mConnection->Clear();
+      mConnection->Clear(-sizeOfItems);
     }
   }
 
   NotifyObservers(aDatabase, aDocumentURI, VoidString(), VoidString(),
                   VoidString());
 }
 
 void Datastore::PrivateBrowsingClear() {
@@ -4342,34 +4429,18 @@ bool Datastore::UpdateUsage(int64_t aDel
     MOZ_ASSERT(mQuotaObject);
 
     if (!mQuotaObject->MaybeUpdateSize(newUsage, /* aTruncate */ true)) {
       return false;
     }
   }
 
   // Quota checks passed, set new usage.
-
   mUsage = newUsage;
 
-  if (IsPersistent()) {
-    RefPtr<Runnable> runnable = NS_NewRunnableFunction(
-        "Datastore::UpdateUsage", [origin = mOrigin, newUsage]() {
-          MOZ_ASSERT(gUsages);
-          MOZ_ASSERT(gUsages->Contains(origin));
-          gUsages->Put(origin, newUsage);
-        });
-
-    QuotaManager* quotaManager = QuotaManager::Get();
-    MOZ_ASSERT(quotaManager);
-
-    MOZ_ALWAYS_SUCCEEDS(
-        quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
-  }
-
   return true;
 }
 
 void Datastore::MaybeClose() {
   AssertIsOnBackgroundThread();
 
   if (!mPrepareDatastoreOps.Count() && !mPreparedDatastores.Count() &&
       !mDatabases.Count()) {
@@ -5696,16 +5767,33 @@ nsresult PrepareDatastoreOp::DatabaseWor
     }
 
     rv = stmt->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     rv = connection->CreateStatement(
+        NS_LITERAL_CSTRING("UPDATE database SET usage = :usage;"),
+        getter_AddRefs(stmt));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("usage"), newUsage);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = stmt->Execute();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    rv = connection->CreateStatement(
         NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
                            "WHERE originKey = :originKey "
                            "AND originAttributes = :originAttributes;"),
         getter_AddRefs(stmt));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6cac8908604d544c8272230f25067362ee0fff19
GIT binary patch
literal 13919
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%0Dnn`#k;{rKs${Z%e#`y2K#hQra0t+
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
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/test_stringLength.js
@@ -0,0 +1,71 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+  const principal = getPrincipal("http://example.org");
+
+  const data = {};
+  data.key = "foobar";
+  data.secondKey = "foobaz";
+  data.value = {
+    length: 25637,
+  };
+  data.usage = data.key.length + data.value.length;
+
+  async function checkUsage(expectedUsage) {
+    info("Checking usage");
+
+    // This forces any pending changes to be flushed to disk.  It also forces
+    // data to be reloaded from disk at next localStorage API call.
+    request = resetOrigin(principal);
+    await requestFinished(request);
+
+    request = getOriginUsage(principal);
+    await requestFinished(request);
+
+    is(request.result.usage, expectedUsage, "Correct usage");
+  }
+
+  info("Setting pref");
+
+  Services.prefs.setBoolPref("dom.storage.next_gen", true);
+
+  info("Stage 1 - Checking usage after profile installation");
+
+  info("Clearing");
+
+  let request = clear();
+  await requestFinished(request);
+
+  info("Installing package");
+
+  // The profile contains storage.sqlite and webappsstore.sqlite.
+  installPackage("stringLength_profile");
+
+  await checkUsage(0);
+
+  info("Stage 2 - Checking usage after archived data migration");
+
+  info("Opening database");
+
+  let storage = getLocalStorage(principal);
+  storage.open();
+
+  await checkUsage(data.usage);
+
+  info("Stage 3 - Checking usage after copying the value");
+
+  info("Adding a second copy of the value");
+
+  let value = storage.getItem(data.key);
+  storage.setItem(data.secondKey, value);
+
+  await checkUsage(2 * data.usage);
+
+  info("Stage 4 - Checking length of the copied value");
+
+  value = storage.getItem(data.secondKey);
+  ok(value.length === data.value.length, "Correct string length");
+}
--- a/dom/localstorage/test/unit/xpcshell.ini
+++ b/dom/localstorage/test/unit/xpcshell.ini
@@ -2,16 +2,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [DEFAULT]
 head = head.js
 support-files =
   archive_profile.zip
   migration_profile.zip
+  stringLength_profile.zip
 
 [test_archive.js]
 [test_databaseShadowing1.js]
 run-sequentially = test_databaseShadowing2.js depends on a file produced by this test
 [test_databaseShadowing2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing1.js
 [test_databaseShadowing_clearOrigin1.js]
 run-sequentially = test_databaseShadowing_clearOrigin2.js depends on a file produced by this test
@@ -24,9 +25,10 @@ run-sequentially = this test depends on 
 [test_databaseShadowing_clearOriginsByPrefix1.js]
 run-sequentially = test_databaseShadowing_clearOriginsByPrefix2.js depends on a file produced by this test
 [test_databaseShadowing_clearOriginsByPrefix2.js]
 run-sequentially = this test depends on a file produced by test_databaseShadowing_clearOriginsByPrefix1.js
 [test_eviction.js]
 [test_groupLimit.js]
 [test_migration.js]
 [test_snapshotting.js]
+[test_stringLength.js]
 [test_usage.js]
--- a/storage/mozStorageSQLFunctions.cpp
+++ b/storage/mozStorageSQLFunctions.cpp
@@ -233,16 +233,19 @@ int registerFunctions(sqlite3 *aDB) {
 
       {"like", 2, SQLITE_UTF16, 0, likeFunction},
       {"like", 2, SQLITE_UTF8, 0, likeFunction},
       {"like", 3, SQLITE_UTF16, 0, likeFunction},
       {"like", 3, SQLITE_UTF8, 0, likeFunction},
 
       {"levenshteinDistance", 2, SQLITE_UTF16, 0, levenshteinDistanceFunction},
       {"levenshteinDistance", 2, SQLITE_UTF8, 0, levenshteinDistanceFunction},
+
+      {"utf16Length", 1, SQLITE_UTF16, 0, utf16LengthFunction},
+      {"utf16Length", 1, SQLITE_UTF8, 0, utf16LengthFunction},
   };
 
   int rv = SQLITE_OK;
   for (size_t i = 0; SQLITE_OK == rv && i < ArrayLength(functions); ++i) {
     struct Functions *p = &functions[i];
     rv = ::sqlite3_create_function(aDB, p->zName, p->nArg, p->enc, p->pContext,
                                    p->xFunc, nullptr, nullptr);
   }
@@ -334,10 +337,23 @@ void levenshteinDistanceFunction(sqlite3
     ::sqlite3_result_int(aCtx, distance);
   } else if (status == SQLITE_NOMEM) {
     ::sqlite3_result_error_nomem(aCtx);
   } else {
     ::sqlite3_result_error(aCtx, "User function returned error code", -1);
   }
 }
 
+void utf16LengthFunction(sqlite3_context *aCtx, int aArgc,
+                         sqlite3_value **aArgv) {
+  NS_ASSERTION(1 == aArgc, "Invalid number of arguments!");
+
+  nsDependentString data(
+      static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0])));
+
+  int len = data.Length();
+
+  // Set the result.
+  ::sqlite3_result_int(aCtx, len);
+}
+
 }  // namespace storage
 }  // namespace mozilla
--- a/storage/mozStorageSQLFunctions.h
+++ b/storage/mozStorageSQLFunctions.h
@@ -60,12 +60,26 @@ void likeFunction(sqlite3_context *aCtx,
  * @param aArgc
  *        The number of arguments the function is being called with.
  * @param aArgv
  *        An array of the arguments the functions is being called with.
  */
 void levenshteinDistanceFunction(sqlite3_context *aCtx, int aArgc,
                                  sqlite3_value **aArgv);
 
+/**
+ * An alternative string length function that uses XPCOM string classes for
+ * string length calculation.
+ *
+ * @param aCtx
+ *        The sqlite_context that this function is being called on.
+ * @param aArgc
+ *        The number of arguments the function is being called with.
+ * @param aArgv
+ *        An array of the arguments the functions is being called with.
+ */
+void utf16LengthFunction(sqlite3_context *aCtx, int aArgc,
+                         sqlite3_value **aArgv);
+
 }  // namespace storage
 }  // namespace mozilla
 
 #endif  // mozStorageSQLFunctions_h