diff --git a/dom/localstorage/ActorsChild.cpp b/dom/localstorage/ActorsChild.cpp index cab8cc777441..f84de1950605 100644 --- a/dom/localstorage/ActorsChild.cpp +++ b/dom/localstorage/ActorsChild.cpp @@ -70,9 +70,9 @@ mozilla::ipc::IPCResult LSDatabaseChild::RecvRequestAllowToClose() { } 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!"); } diff --git a/dom/localstorage/ActorsChild.h b/dom/localstorage/ActorsChild.h index f26489bd8694..80838af83787 100644 --- a/dom/localstorage/ActorsChild.h +++ b/dom/localstorage/ActorsChild.h @@ -78,9 +78,9 @@ class LSDatabaseChild final : public PBackgroundLSDatabaseChild { 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; diff --git a/dom/localstorage/ActorsParent.cpp b/dom/localstorage/ActorsParent.cpp index 0f4c370ff292..d7b4ed440515 100644 --- a/dom/localstorage/ActorsParent.cpp +++ b/dom/localstorage/ActorsParent.cpp @@ -1713,7 +1713,8 @@ class Datastore final void NoteInactiveDatabase(Database* aDatabase); - void GetSnapshotInitInfo(nsTHashtable& aLoadedItems, + void GetSnapshotInitInfo(const nsString& aKey, bool& aAddKeyToUnknownItems, + nsTHashtable& aLoadedItems, nsTArray& aItemInfos, uint32_t& aNextLoadIndex, uint32_t& aTotalLength, int64_t& aInitialUsage, int64_t& aPeakUsage, @@ -1929,14 +1930,15 @@ class Database final 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; @@ -2051,6 +2053,7 @@ class Snapshot final : public PBackgroundLSSnapshotParent { Snapshot(Database* aDatabase, const nsAString& aDocumentURI); void Init(nsTHashtable& aLoadedItems, + nsTHashtable& aUnknownItems, uint32_t aNextLoadIndex, uint32_t aTotalLength, int64_t aInitialUsage, int64_t aPeakUsage, LSSnapshot::LoadState aLoadState) { @@ -2064,14 +2067,17 @@ class Snapshot final : public PBackgroundLSSnapshotParent { 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; @@ -4646,7 +4652,9 @@ void Datastore::NoteInactiveDatabase(Database* aDatabase) { } } -void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, +void Datastore::GetSnapshotInitInfo(const nsString& aKey, + bool& aAddKeyToUnknownItems, + nsTHashtable& aLoadedItems, nsTArray& aItemInfos, uint32_t& aNextLoadIndex, uint32_t& aTotalLength, @@ -4656,6 +4664,20 @@ void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, MOZ_ASSERT(!mClosed); MOZ_ASSERT(!mInUpdateBatch); + nsString value; + int64_t sizeOfKey = 0; + int64_t sizeOfItem = 0; + bool checkKey = false; + + if (!aKey.IsVoid()) { + GetItem(aKey, value); + if (!value.IsVoid()) { + sizeOfKey = aKey.Length(); + sizeOfItem = sizeOfKey + value.Length(); + checkKey = true; + } + } + #ifdef DEBUG int64_t sizeOfKeys = 0; int64_t sizeOfItems = 0; @@ -4668,44 +4690,81 @@ void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, MOZ_ASSERT(mSizeOfItems == sizeOfItems); #endif - if (mSizeOfKeys <= gSnapshotPrefill) { - if (mSizeOfItems <= gSnapshotPrefill) { + if (mSizeOfKeys - sizeOfKey <= gSnapshotPrefill) { + if (mSizeOfItems - sizeOfItem <= gSnapshotPrefill) { + // 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(); + aAddKeyToUnknownItems = false; + aLoadState = LSSnapshot::LoadState::AllOrderedItems; } else { + // 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(); + const nsString& value = item.value(); - if (!value.IsVoid()) { - value = item.value(); - - size += static_cast(value.Length()); - - if (size <= gSnapshotPrefill) { - aLoadedItems.PutEntry(key); + if (checkKey && key == aKey) { + checkKey = false; + setVoidValue = false; + } else if (!setVoidValue) { + if (doneSendingValues) { + setVoidValue = true; } else { - value.SetIsVoid(true); + size += static_cast(value.Length()); - // 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; + 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; - itemInfo->value() = value; + if (setVoidValue) { + itemInfo->value().SetIsVoid(true); + } else { + aLoadedItems.PutEntry(key); + itemInfo->value() = value; + } } + aAddKeyToUnknownItems = false; + aLoadState = LSSnapshot::LoadState::AllOrderedKeys; } } else { @@ -4716,12 +4775,16 @@ void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, const nsString& key = item.key(); const nsString& value = item.value(); - size += static_cast(key.Length()) + - static_cast(value.Length()); + if (checkKey && key == aKey) { + checkKey = false; + } else { + size += static_cast(key.Length()) + + static_cast(value.Length()); - if (size > gSnapshotPrefill) { - aNextLoadIndex = index; - break; + if (size > gSnapshotPrefill) { + aNextLoadIndex = index; + break; + } } aLoadedItems.PutEntry(key); @@ -4731,6 +4794,18 @@ void Datastore::GetSnapshotInitInfo(nsTHashtable& aLoadedItems, itemInfo->value() = value; } + aAddKeyToUnknownItems = false; + + if (!aKey.IsVoid()) { + if (value.IsVoid()) { + aAddKeyToUnknownItems = true; + } else if (checkKey) { + LSItemInfo* itemInfo = aItemInfos.AppendElement(); + itemInfo->key() = aKey; + itemInfo->value() = value; + } + } + MOZ_ASSERT(aItemInfos.Length() < mOrderedItems.Length()); aLoadState = LSSnapshot::LoadState::Partial; } @@ -5283,9 +5358,9 @@ mozilla::ipc::IPCResult Database::RecvAllowToClose() { } 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)) { @@ -5311,8 +5386,9 @@ PBackgroundLSSnapshotParent* Database::AllocPBackgroundLSSnapshotParent( 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); @@ -5321,8 +5397,7 @@ mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor( auto* snapshot = static_cast(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 loadedItems; nsTArray itemInfos; uint32_t nextLoadIndex; @@ -5330,20 +5405,26 @@ mozilla::ipc::IPCResult Database::RecvPBackgroundLSSnapshotConstructor( 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 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; diff --git a/dom/localstorage/LSDatabase.cpp b/dom/localstorage/LSDatabase.cpp index d5881ebf09f5..6169b5c1382d 100644 --- a/dom/localstorage/LSDatabase.cpp +++ b/dom/localstorage/LSDatabase.cpp @@ -120,7 +120,7 @@ nsresult LSDatabase::GetLength(LSObject* aObject, uint32_t* aResult) { MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, VoidString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -140,7 +140,7 @@ nsresult LSDatabase::GetKey(LSObject* aObject, uint32_t aIndex, MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, VoidString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -160,7 +160,7 @@ nsresult LSDatabase::GetItem(LSObject* aObject, const nsAString& aKey, MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, aKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -179,7 +179,7 @@ nsresult LSDatabase::GetKeys(LSObject* aObject, nsTArray& aKeys) { MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, VoidString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -200,7 +200,7 @@ nsresult LSDatabase::SetItem(LSObject* aObject, const nsAString& aKey, MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, aKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -220,7 +220,7 @@ nsresult LSDatabase::RemoveItem(LSObject* aObject, const nsAString& aKey, MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, aKey); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -239,7 +239,7 @@ nsresult LSDatabase::Clear(LSObject* aObject, LSNotifyInfo& aNotifyInfo) { MOZ_ASSERT(mActor); MOZ_ASSERT(!mAllowedToClose); - nsresult rv = EnsureSnapshot(aObject); + nsresult rv = EnsureSnapshot(aObject, VoidString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -262,7 +262,7 @@ nsresult LSDatabase::BeginExplicitSnapshot(LSObject* aObject) { 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; } @@ -290,7 +290,8 @@ nsresult LSDatabase::EndExplicitSnapshot(LSObject* aObject) { 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); @@ -306,7 +307,7 @@ nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, bool aExplicit) { LSSnapshotInitInfo initInfo; bool ok = mActor->SendPBackgroundLSSnapshotConstructor( - actor, aObject->DocumentURI(), + actor, aObject->DocumentURI(), nsString(aKey), /* increasePeakUsage */ true, /* requestedSize */ 131072, /* minSize */ 4096, &initInfo); @@ -317,7 +318,7 @@ nsresult LSDatabase::EnsureSnapshot(LSObject* aObject, bool aExplicit) { 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; } diff --git a/dom/localstorage/LSDatabase.h b/dom/localstorage/LSDatabase.h index 61ea2007a92a..7a8a5c4a4b41 100644 --- a/dom/localstorage/LSDatabase.h +++ b/dom/localstorage/LSDatabase.h @@ -79,7 +79,8 @@ class LSDatabase final { private: ~LSDatabase(); - nsresult EnsureSnapshot(LSObject* aObject, bool aExplicit = false); + nsresult EnsureSnapshot(LSObject* aObject, const nsAString& aKey, + bool aExplicit = false); void AllowToClose(); }; diff --git a/dom/localstorage/LSSnapshot.cpp b/dom/localstorage/LSSnapshot.cpp index 3e5f8bd3cc22..eace15be5c9c 100644 --- a/dom/localstorage/LSSnapshot.cpp +++ b/dom/localstorage/LSSnapshot.cpp @@ -59,7 +59,8 @@ void LSSnapshot::SetActor(LSSnapshotChild* aActor) { 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); @@ -85,6 +86,9 @@ nsresult LSSnapshot::Init(const LSSnapshotInitInfo& aInitInfo, bool aExplicit) { } if (loadState == LoadState::Partial) { + if (aInitInfo.addKeyToUnknownItems()) { + mUnknownItems.PutEntry(aKey); + } mInitLength = aInitInfo.totalLength(); mLength = mInitLength; } else if (loadState == LoadState::AllOrderedKeys) { diff --git a/dom/localstorage/LSSnapshot.h b/dom/localstorage/LSSnapshot.h index 1194d3cc4e32..175faf222b54 100644 --- a/dom/localstorage/LSSnapshot.h +++ b/dom/localstorage/LSSnapshot.h @@ -112,7 +112,8 @@ class LSSnapshot final : public nsIRunnable { 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); diff --git a/dom/localstorage/PBackgroundLSDatabase.ipdl b/dom/localstorage/PBackgroundLSDatabase.ipdl index 3da6d32ded84..f0305273aa38 100644 --- a/dom/localstorage/PBackgroundLSDatabase.ipdl +++ b/dom/localstorage/PBackgroundLSDatabase.ipdl @@ -22,6 +22,14 @@ namespace dom { */ 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. @@ -99,11 +107,20 @@ parent: * 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)