Bug 1826183 - Remove the pageload essential category from mach-try-perf. r=perftest...
[gecko.git] / dom / localstorage / LSSnapshot.cpp
blob85920b8b095a6ca615ad08f1db64032d8576c690
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/LSSnapshot.h"
9 // Local includes
10 #include "ActorsChild.h"
11 #include "LSDatabase.h"
12 #include "LSWriteOptimizer.h"
13 #include "LSWriteOptimizerImpl.h"
14 #include "LocalStorageCommon.h"
16 // Global includes
17 #include <cstdint>
18 #include <cstdlib>
19 #include <new>
20 #include <type_traits>
21 #include <utility>
22 #include "ErrorList.h"
23 #include "mozilla/DebugOnly.h"
24 #include "mozilla/MacroForEach.h"
25 #include "mozilla/Maybe.h"
26 #include "mozilla/Preferences.h"
27 #include "mozilla/RefPtr.h"
28 #include "mozilla/ScopeExit.h"
29 #include "mozilla/StaticPrefs_dom.h"
30 #include "mozilla/UniquePtr.h"
31 #include "mozilla/dom/BindingDeclarations.h"
32 #include "mozilla/dom/LSValue.h"
33 #include "mozilla/dom/quota/QuotaCommon.h"
34 #include "mozilla/dom/quota/ResultExtensions.h"
35 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
36 #include "mozilla/dom/PBackgroundLSDatabase.h"
37 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
38 #include "mozilla/dom/PBackgroundLSSnapshot.h"
39 #include "nsBaseHashtable.h"
40 #include "nsCOMPtr.h"
41 #include "nsContentUtils.h"
42 #include "nsDebug.h"
43 #include "nsError.h"
44 #include "nsITimer.h"
45 #include "nsString.h"
46 #include "nsStringFlags.h"
47 #include "nsStringFwd.h"
48 #include "nsTArray.h"
49 #include "nsTStringRepr.h"
50 #include "nscore.h"
52 namespace mozilla::dom {
54 /**
55 * Coalescing manipulation queue used by `LSSnapshot`. Used by `LSSnapshot` to
56 * buffer and coalesce manipulations before they are sent to the parent process,
57 * when a Snapshot Checkpoints. (This can only be done when there are no
58 * observers for other content processes.)
60 class SnapshotWriteOptimizer final : public LSWriteOptimizer<LSValue> {
61 public:
62 void Enumerate(nsTArray<LSWriteInfo>& aWriteInfos);
65 void SnapshotWriteOptimizer::Enumerate(nsTArray<LSWriteInfo>& aWriteInfos) {
66 AssertIsOnOwningThread();
68 // The mWriteInfos hash table contains all write infos, but it keeps them in
69 // an arbitrary order, which means write infos need to be sorted before being
70 // processed.
72 nsTArray<NotNull<WriteInfo*>> writeInfos;
73 GetSortedWriteInfos(writeInfos);
75 for (WriteInfo* writeInfo : writeInfos) {
76 switch (writeInfo->GetType()) {
77 case WriteInfo::InsertItem: {
78 auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
80 LSSetItemInfo setItemInfo;
81 setItemInfo.key() = insertItemInfo->GetKey();
82 setItemInfo.value() = insertItemInfo->GetValue();
84 aWriteInfos.AppendElement(std::move(setItemInfo));
86 break;
89 case WriteInfo::UpdateItem: {
90 auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo);
92 if (updateItemInfo->UpdateWithMove()) {
93 // See the comment in LSWriteOptimizer::InsertItem for more details
94 // about the UpdateWithMove flag.
96 LSRemoveItemInfo removeItemInfo;
97 removeItemInfo.key() = updateItemInfo->GetKey();
99 aWriteInfos.AppendElement(std::move(removeItemInfo));
102 LSSetItemInfo setItemInfo;
103 setItemInfo.key() = updateItemInfo->GetKey();
104 setItemInfo.value() = updateItemInfo->GetValue();
106 aWriteInfos.AppendElement(std::move(setItemInfo));
108 break;
111 case WriteInfo::DeleteItem: {
112 auto deleteItemInfo = static_cast<DeleteItemInfo*>(writeInfo);
114 LSRemoveItemInfo removeItemInfo;
115 removeItemInfo.key() = deleteItemInfo->GetKey();
117 aWriteInfos.AppendElement(std::move(removeItemInfo));
119 break;
122 case WriteInfo::Truncate: {
123 LSClearInfo clearInfo;
125 aWriteInfos.AppendElement(std::move(clearInfo));
127 break;
130 default:
131 MOZ_CRASH("Bad type!");
136 LSSnapshot::LSSnapshot(LSDatabase* aDatabase)
137 : mDatabase(aDatabase),
138 mActor(nullptr),
139 mInitLength(0),
140 mLength(0),
141 mUsage(0),
142 mPeakUsage(0),
143 mLoadState(LoadState::Initial),
144 mHasOtherProcessDatabases(false),
145 mHasOtherProcessObservers(false),
146 mExplicit(false),
147 mHasPendingStableStateCallback(false),
148 mHasPendingIdleTimerCallback(false),
149 mDirty(false)
150 #ifdef DEBUG
152 mInitialized(false),
153 mSentFinish(false)
154 #endif
156 AssertIsOnOwningThread();
159 LSSnapshot::~LSSnapshot() {
160 AssertIsOnOwningThread();
161 MOZ_ASSERT(mDatabase);
162 MOZ_ASSERT(!mHasPendingStableStateCallback);
163 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
164 MOZ_ASSERT_IF(mInitialized, mSentFinish);
166 if (mActor) {
167 mActor->SendDeleteMeInternal();
168 MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
172 void LSSnapshot::SetActor(LSSnapshotChild* aActor) {
173 AssertIsOnOwningThread();
174 MOZ_ASSERT(aActor);
175 MOZ_ASSERT(!mActor);
177 mActor = aActor;
180 nsresult LSSnapshot::Init(const nsAString& aKey,
181 const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
182 AssertIsOnOwningThread();
183 MOZ_ASSERT(!mSelfRef);
184 MOZ_ASSERT(mActor);
185 MOZ_ASSERT(mLoadState == LoadState::Initial);
186 MOZ_ASSERT(!mInitialized);
187 MOZ_ASSERT(!mSentFinish);
189 mSelfRef = this;
191 LoadState loadState = aInitInfo.loadState();
193 const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
194 for (uint32_t i = 0; i < itemInfos.Length(); i++) {
195 const LSItemInfo& itemInfo = itemInfos[i];
197 const LSValue& value = itemInfo.value();
199 if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
200 mLoadedItems.Insert(itemInfo.key());
203 mValues.InsertOrUpdate(itemInfo.key(), value.AsString());
206 if (loadState == LoadState::Partial) {
207 if (aInitInfo.addKeyToUnknownItems()) {
208 mUnknownItems.Insert(aKey);
210 mInitLength = aInitInfo.totalLength();
211 mLength = mInitLength;
212 } else if (loadState == LoadState::AllOrderedKeys) {
213 mInitLength = aInitInfo.totalLength();
214 } else {
215 MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
218 mUsage = aInitInfo.usage();
219 mPeakUsage = aInitInfo.peakUsage();
221 mLoadState = aInitInfo.loadState();
223 mHasOtherProcessDatabases = aInitInfo.hasOtherProcessDatabases();
224 mHasOtherProcessObservers = aInitInfo.hasOtherProcessObservers();
226 mExplicit = aExplicit;
228 #ifdef DEBUG
229 mInitialized = true;
230 #endif
232 if (mHasOtherProcessObservers) {
233 mWriteAndNotifyInfos = MakeUnique<nsTArray<LSWriteAndNotifyInfo>>();
234 } else {
235 mWriteOptimizer = MakeUnique<SnapshotWriteOptimizer>();
238 if (!mExplicit) {
239 mIdleTimer = NS_NewTimer();
240 MOZ_ASSERT(mIdleTimer);
242 ScheduleStableStateCallback();
245 return NS_OK;
248 nsresult LSSnapshot::GetLength(uint32_t* aResult) {
249 AssertIsOnOwningThread();
250 MOZ_ASSERT(mActor);
251 MOZ_ASSERT(mInitialized);
252 MOZ_ASSERT(!mSentFinish);
254 MaybeScheduleStableStateCallback();
256 if (mLoadState == LoadState::Partial) {
257 *aResult = mLength;
258 } else {
259 *aResult = mValues.Count();
262 return NS_OK;
265 nsresult LSSnapshot::GetKey(uint32_t aIndex, nsAString& aResult) {
266 AssertIsOnOwningThread();
267 MOZ_ASSERT(mActor);
268 MOZ_ASSERT(mInitialized);
269 MOZ_ASSERT(!mSentFinish);
271 MaybeScheduleStableStateCallback();
273 nsresult rv = EnsureAllKeys();
274 if (NS_WARN_IF(NS_FAILED(rv))) {
275 return rv;
278 aResult.SetIsVoid(true);
279 for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
280 if (aIndex == 0) {
281 aResult = iter.Key();
282 return NS_OK;
284 aIndex--;
287 return NS_OK;
290 nsresult LSSnapshot::GetItem(const nsAString& aKey, nsAString& aResult) {
291 AssertIsOnOwningThread();
292 MOZ_ASSERT(mActor);
293 MOZ_ASSERT(mInitialized);
294 MOZ_ASSERT(!mSentFinish);
296 MaybeScheduleStableStateCallback();
298 nsString result;
299 nsresult rv = GetItemInternal(aKey, Optional<nsString>(), result);
300 if (NS_WARN_IF(NS_FAILED(rv))) {
301 return rv;
304 aResult = result;
305 return NS_OK;
308 nsresult LSSnapshot::GetKeys(nsTArray<nsString>& aKeys) {
309 AssertIsOnOwningThread();
310 MOZ_ASSERT(mActor);
311 MOZ_ASSERT(mInitialized);
312 MOZ_ASSERT(!mSentFinish);
314 MaybeScheduleStableStateCallback();
316 nsresult rv = EnsureAllKeys();
317 if (NS_WARN_IF(NS_FAILED(rv))) {
318 return rv;
321 AppendToArray(aKeys, mValues.Keys());
323 return NS_OK;
326 nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue,
327 LSNotifyInfo& aNotifyInfo) {
328 AssertIsOnOwningThread();
329 MOZ_ASSERT(mActor);
330 MOZ_ASSERT(mInitialized);
331 MOZ_ASSERT(!mSentFinish);
333 MaybeScheduleStableStateCallback();
335 nsString oldValue;
336 nsresult rv =
337 GetItemInternal(aKey, Optional<nsString>(nsString(aValue)), oldValue);
338 if (NS_WARN_IF(NS_FAILED(rv))) {
339 return rv;
342 bool changed;
343 if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) {
344 changed = false;
345 } else {
346 changed = true;
348 auto autoRevertValue = MakeScopeExit([&] {
349 if (oldValue.IsVoid()) {
350 mValues.Remove(aKey);
351 } else {
352 mValues.InsertOrUpdate(aKey, oldValue);
356 // Anything that can fail must be done early before we start modifying the
357 // state.
359 Maybe<LSValue> oldValueFromString;
360 if (mHasOtherProcessObservers) {
361 oldValueFromString.emplace();
362 if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) {
363 return NS_ERROR_FAILURE;
367 LSValue valueFromString;
368 if (NS_WARN_IF(!valueFromString.InitFromString(aValue))) {
369 return NS_ERROR_FAILURE;
372 int64_t delta = static_cast<int64_t>(aValue.Length()) -
373 static_cast<int64_t>(oldValue.Length());
375 if (oldValue.IsVoid()) {
376 delta += static_cast<int64_t>(aKey.Length());
380 quota::ScopedLogExtraInfo scope{
381 quota::ScopedLogExtraInfo::kTagContext,
382 "dom::localstorage::LSSnapshot::SetItem::UpdateUsage"_ns};
383 QM_TRY(MOZ_TO_RESULT(UpdateUsage(delta)), QM_PROPAGATE, QM_NO_CLEANUP,
384 ([]() {
385 static uint32_t counter = 0u;
386 const bool result = 0u != (counter & (1u + counter));
387 ++counter;
388 return result;
389 }));
392 if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
393 mLength++;
396 if (mHasOtherProcessObservers) {
397 MOZ_ASSERT(mWriteAndNotifyInfos);
398 MOZ_ASSERT(oldValueFromString.isSome());
400 LSSetItemAndNotifyInfo setItemAndNotifyInfo;
401 setItemAndNotifyInfo.key() = aKey;
402 setItemAndNotifyInfo.oldValue() = oldValueFromString.value();
403 setItemAndNotifyInfo.value() = valueFromString;
405 mWriteAndNotifyInfos->AppendElement(std::move(setItemAndNotifyInfo));
406 } else {
407 MOZ_ASSERT(mWriteOptimizer);
409 if (oldValue.IsVoid()) {
410 mWriteOptimizer->InsertItem(aKey, valueFromString);
411 } else {
412 mWriteOptimizer->UpdateItem(aKey, valueFromString);
416 autoRevertValue.release();
419 aNotifyInfo.changed() = changed;
420 aNotifyInfo.oldValue() = oldValue;
422 return NS_OK;
425 nsresult LSSnapshot::RemoveItem(const nsAString& aKey,
426 LSNotifyInfo& aNotifyInfo) {
427 AssertIsOnOwningThread();
428 MOZ_ASSERT(mActor);
429 MOZ_ASSERT(mInitialized);
430 MOZ_ASSERT(!mSentFinish);
432 MaybeScheduleStableStateCallback();
434 nsString oldValue;
435 nsresult rv =
436 GetItemInternal(aKey, Optional<nsString>(VoidString()), oldValue);
437 if (NS_WARN_IF(NS_FAILED(rv))) {
438 return rv;
441 bool changed;
442 if (oldValue.IsVoid()) {
443 changed = false;
444 } else {
445 changed = true;
447 auto autoRevertValue = MakeScopeExit([&] {
448 MOZ_ASSERT(!oldValue.IsVoid());
449 mValues.InsertOrUpdate(aKey, oldValue);
452 // Anything that can fail must be done early before we start modifying the
453 // state.
455 Maybe<LSValue> oldValueFromString;
456 if (mHasOtherProcessObservers) {
457 oldValueFromString.emplace();
458 if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) {
459 return NS_ERROR_FAILURE;
463 int64_t delta = -(static_cast<int64_t>(aKey.Length()) +
464 static_cast<int64_t>(oldValue.Length()));
466 DebugOnly<nsresult> rv = UpdateUsage(delta);
467 MOZ_ASSERT(NS_SUCCEEDED(rv));
469 if (mLoadState == LoadState::Partial) {
470 mLength--;
473 if (mHasOtherProcessObservers) {
474 MOZ_ASSERT(mWriteAndNotifyInfos);
475 MOZ_ASSERT(oldValueFromString.isSome());
477 LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo;
478 removeItemAndNotifyInfo.key() = aKey;
479 removeItemAndNotifyInfo.oldValue() = oldValueFromString.value();
481 mWriteAndNotifyInfos->AppendElement(std::move(removeItemAndNotifyInfo));
482 } else {
483 MOZ_ASSERT(mWriteOptimizer);
485 mWriteOptimizer->DeleteItem(aKey);
488 autoRevertValue.release();
491 aNotifyInfo.changed() = changed;
492 aNotifyInfo.oldValue() = oldValue;
494 return NS_OK;
497 nsresult LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo) {
498 AssertIsOnOwningThread();
499 MOZ_ASSERT(mActor);
500 MOZ_ASSERT(mInitialized);
501 MOZ_ASSERT(!mSentFinish);
503 MaybeScheduleStableStateCallback();
505 uint32_t length;
506 if (mLoadState == LoadState::Partial) {
507 length = mLength;
508 MOZ_ASSERT(length);
510 MOZ_ALWAYS_TRUE(mActor->SendLoaded());
512 mLoadedItems.Clear();
513 mUnknownItems.Clear();
514 mLength = 0;
515 mLoadState = LoadState::AllOrderedItems;
516 } else {
517 length = mValues.Count();
520 bool changed;
521 if (!length) {
522 changed = false;
523 } else {
524 changed = true;
526 int64_t delta = 0;
527 for (const auto& entry : mValues) {
528 const nsAString& key = entry.GetKey();
529 const nsString& value = entry.GetData();
531 delta += -static_cast<int64_t>(key.Length()) -
532 static_cast<int64_t>(value.Length());
535 DebugOnly<nsresult> rv = UpdateUsage(delta);
536 MOZ_ASSERT(NS_SUCCEEDED(rv));
538 mValues.Clear();
540 if (mHasOtherProcessObservers) {
541 MOZ_ASSERT(mWriteAndNotifyInfos);
543 LSClearInfo clearInfo;
545 mWriteAndNotifyInfos->AppendElement(std::move(clearInfo));
546 } else {
547 MOZ_ASSERT(mWriteOptimizer);
549 mWriteOptimizer->Truncate();
553 aNotifyInfo.changed() = changed;
555 return NS_OK;
558 void LSSnapshot::MarkDirty() {
559 AssertIsOnOwningThread();
560 MOZ_ASSERT(mActor);
561 MOZ_ASSERT(mInitialized);
562 MOZ_ASSERT(!mSentFinish);
564 if (mDirty) {
565 return;
568 mDirty = true;
570 if (!mExplicit && !mHasPendingStableStateCallback) {
571 CancelIdleTimer();
573 MOZ_ALWAYS_SUCCEEDS(Checkpoint());
575 MOZ_ALWAYS_SUCCEEDS(Finish());
576 } else {
577 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
581 nsresult LSSnapshot::ExplicitCheckpoint() {
582 AssertIsOnOwningThread();
583 MOZ_ASSERT(mActor);
584 MOZ_ASSERT(mExplicit);
585 MOZ_ASSERT(!mHasPendingStableStateCallback);
586 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
587 MOZ_ASSERT(mInitialized);
588 MOZ_ASSERT(!mSentFinish);
590 nsresult rv = Checkpoint(/* aSync */ true);
591 if (NS_WARN_IF(NS_FAILED(rv))) {
592 return rv;
595 return NS_OK;
598 nsresult LSSnapshot::ExplicitEnd() {
599 AssertIsOnOwningThread();
600 MOZ_ASSERT(mActor);
601 MOZ_ASSERT(mExplicit);
602 MOZ_ASSERT(!mHasPendingStableStateCallback);
603 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
604 MOZ_ASSERT(mInitialized);
605 MOZ_ASSERT(!mSentFinish);
607 nsresult rv = Checkpoint();
608 if (NS_WARN_IF(NS_FAILED(rv))) {
609 return rv;
612 RefPtr<LSSnapshot> kungFuDeathGrip = this;
614 rv = Finish(/* aSync */ true);
615 if (NS_WARN_IF(NS_FAILED(rv))) {
616 return rv;
619 return NS_OK;
622 int64_t LSSnapshot::GetUsage() const {
623 AssertIsOnOwningThread();
624 MOZ_ASSERT(mActor);
625 MOZ_ASSERT(mInitialized);
626 MOZ_ASSERT(!mSentFinish);
628 return mUsage;
631 void LSSnapshot::ScheduleStableStateCallback() {
632 AssertIsOnOwningThread();
633 MOZ_ASSERT(mIdleTimer);
634 MOZ_ASSERT(!mExplicit);
635 MOZ_ASSERT(!mHasPendingStableStateCallback);
637 CancelIdleTimer();
639 nsCOMPtr<nsIRunnable> runnable = this;
640 nsContentUtils::RunInStableState(runnable.forget());
642 mHasPendingStableStateCallback = true;
645 void LSSnapshot::MaybeScheduleStableStateCallback() {
646 AssertIsOnOwningThread();
648 if (!mExplicit && !mHasPendingStableStateCallback) {
649 ScheduleStableStateCallback();
650 } else {
651 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
655 nsresult LSSnapshot::GetItemInternal(const nsAString& aKey,
656 const Optional<nsString>& aValue,
657 nsAString& aResult) {
658 AssertIsOnOwningThread();
659 MOZ_ASSERT(mActor);
660 MOZ_ASSERT(mInitialized);
661 MOZ_ASSERT(!mSentFinish);
663 nsString result;
665 switch (mLoadState) {
666 case LoadState::Partial: {
667 if (mValues.Get(aKey, &result)) {
668 MOZ_ASSERT(!result.IsVoid());
669 } else if (mLoadedItems.Contains(aKey) || mUnknownItems.Contains(aKey)) {
670 result.SetIsVoid(true);
671 } else {
672 LSValue value;
673 nsTArray<LSItemInfo> itemInfos;
674 if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
675 nsString(aKey), &value, &itemInfos))) {
676 return NS_ERROR_FAILURE;
679 result = value.AsString();
681 if (result.IsVoid()) {
682 mUnknownItems.Insert(aKey);
683 } else {
684 mLoadedItems.Insert(aKey);
685 mValues.InsertOrUpdate(aKey, result);
687 // mLoadedItems.Count()==mInitLength is checked below.
690 for (uint32_t i = 0; i < itemInfos.Length(); i++) {
691 const LSItemInfo& itemInfo = itemInfos[i];
693 mLoadedItems.Insert(itemInfo.key());
694 mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString());
697 if (mLoadedItems.Count() == mInitLength) {
698 mLoadedItems.Clear();
699 mUnknownItems.Clear();
700 mLength = 0;
701 mLoadState = LoadState::AllUnorderedItems;
705 if (aValue.WasPassed()) {
706 const nsString& value = aValue.Value();
707 if (!value.IsVoid()) {
708 mValues.InsertOrUpdate(aKey, value);
709 } else if (!result.IsVoid()) {
710 mValues.Remove(aKey);
714 break;
717 case LoadState::AllOrderedKeys: {
718 if (mValues.Get(aKey, &result)) {
719 if (result.IsVoid()) {
720 LSValue value;
721 nsTArray<LSItemInfo> itemInfos;
722 if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
723 nsString(aKey), &value, &itemInfos))) {
724 return NS_ERROR_FAILURE;
727 result = value.AsString();
729 MOZ_ASSERT(!result.IsVoid());
731 mLoadedItems.Insert(aKey);
732 mValues.InsertOrUpdate(aKey, result);
734 // mLoadedItems.Count()==mInitLength is checked below.
736 for (uint32_t i = 0; i < itemInfos.Length(); i++) {
737 const LSItemInfo& itemInfo = itemInfos[i];
739 mLoadedItems.Insert(itemInfo.key());
740 mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString());
743 if (mLoadedItems.Count() == mInitLength) {
744 mLoadedItems.Clear();
745 MOZ_ASSERT(mLength == 0);
746 mLoadState = LoadState::AllOrderedItems;
749 } else {
750 result.SetIsVoid(true);
753 if (aValue.WasPassed()) {
754 const nsString& value = aValue.Value();
755 if (!value.IsVoid()) {
756 mValues.InsertOrUpdate(aKey, value);
757 } else if (!result.IsVoid()) {
758 mValues.Remove(aKey);
762 break;
765 case LoadState::AllUnorderedItems:
766 case LoadState::AllOrderedItems: {
767 if (aValue.WasPassed()) {
768 const nsString& value = aValue.Value();
769 if (!value.IsVoid()) {
770 mValues.WithEntryHandle(aKey, [&](auto&& entry) {
771 if (entry) {
772 result = std::exchange(entry.Data(), value);
773 } else {
774 result.SetIsVoid(true);
775 entry.Insert(value);
778 } else {
779 if (auto entry = mValues.Lookup(aKey)) {
780 result = entry.Data();
781 MOZ_ASSERT(!result.IsVoid());
782 entry.Remove();
783 } else {
784 result.SetIsVoid(true);
787 } else {
788 if (mValues.Get(aKey, &result)) {
789 MOZ_ASSERT(!result.IsVoid());
790 } else {
791 result.SetIsVoid(true);
795 break;
798 default:
799 MOZ_CRASH("Bad state!");
802 aResult = result;
803 return NS_OK;
806 nsresult LSSnapshot::EnsureAllKeys() {
807 AssertIsOnOwningThread();
808 MOZ_ASSERT(mActor);
809 MOZ_ASSERT(mInitialized);
810 MOZ_ASSERT(!mSentFinish);
811 MOZ_ASSERT(mLoadState != LoadState::Initial);
813 if (mLoadState == LoadState::AllOrderedKeys ||
814 mLoadState == LoadState::AllOrderedItems) {
815 return NS_OK;
818 nsTArray<nsString> keys;
819 if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) {
820 return NS_ERROR_FAILURE;
823 nsTHashMap<nsStringHashKey, nsString> newValues;
825 for (auto key : keys) {
826 newValues.InsertOrUpdate(key, VoidString());
829 if (mHasOtherProcessObservers) {
830 MOZ_ASSERT(mWriteAndNotifyInfos);
832 if (!mWriteAndNotifyInfos->IsEmpty()) {
833 for (uint32_t index = 0; index < mWriteAndNotifyInfos->Length();
834 index++) {
835 const LSWriteAndNotifyInfo& writeAndNotifyInfo =
836 mWriteAndNotifyInfos->ElementAt(index);
838 switch (writeAndNotifyInfo.type()) {
839 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
840 newValues.InsertOrUpdate(
841 writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(),
842 VoidString());
843 break;
845 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
846 newValues.Remove(
847 writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key());
848 break;
850 case LSWriteAndNotifyInfo::TLSClearInfo: {
851 newValues.Clear();
852 break;
855 default:
856 MOZ_CRASH("Should never get here!");
860 } else {
861 MOZ_ASSERT(mWriteOptimizer);
863 if (mWriteOptimizer->HasWrites()) {
864 nsTArray<LSWriteInfo> writeInfos;
865 mWriteOptimizer->Enumerate(writeInfos);
867 MOZ_ASSERT(!writeInfos.IsEmpty());
869 for (uint32_t index = 0; index < writeInfos.Length(); index++) {
870 const LSWriteInfo& writeInfo = writeInfos[index];
872 switch (writeInfo.type()) {
873 case LSWriteInfo::TLSSetItemInfo: {
874 newValues.InsertOrUpdate(writeInfo.get_LSSetItemInfo().key(),
875 VoidString());
876 break;
878 case LSWriteInfo::TLSRemoveItemInfo: {
879 newValues.Remove(writeInfo.get_LSRemoveItemInfo().key());
880 break;
882 case LSWriteInfo::TLSClearInfo: {
883 newValues.Clear();
884 break;
887 default:
888 MOZ_CRASH("Should never get here!");
894 MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems,
895 newValues.Count() == mValues.Count());
897 for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) {
898 nsString value;
899 if (mValues.Get(iter.Key(), &value)) {
900 iter.Data() = value;
904 mValues.SwapElements(newValues);
906 if (mLoadState == LoadState::Partial) {
907 mUnknownItems.Clear();
908 mLength = 0;
909 mLoadState = LoadState::AllOrderedKeys;
910 } else {
911 MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems);
913 MOZ_ASSERT(mUnknownItems.Count() == 0);
914 MOZ_ASSERT(mLength == 0);
915 mLoadState = LoadState::AllOrderedItems;
918 return NS_OK;
921 nsresult LSSnapshot::UpdateUsage(int64_t aDelta) {
922 AssertIsOnOwningThread();
923 MOZ_ASSERT(mDatabase);
924 MOZ_ASSERT(mActor);
925 MOZ_ASSERT(mPeakUsage >= mUsage);
926 MOZ_ASSERT(mInitialized);
927 MOZ_ASSERT(!mSentFinish);
929 int64_t newUsage = mUsage + aDelta;
930 if (newUsage > mPeakUsage) {
931 const int64_t minSize = newUsage - mPeakUsage;
933 int64_t size;
934 if (NS_WARN_IF(!mActor->SendIncreasePeakUsage(minSize, &size))) {
935 return NS_ERROR_FAILURE;
938 MOZ_ASSERT(size >= 0);
940 if (size == 0) {
941 return NS_ERROR_FILE_NO_DEVICE_SPACE;
944 mPeakUsage += size;
947 mUsage = newUsage;
948 return NS_OK;
951 nsresult LSSnapshot::Checkpoint(bool aSync) {
952 AssertIsOnOwningThread();
953 MOZ_ASSERT(mActor);
954 MOZ_ASSERT(mInitialized);
955 MOZ_ASSERT(!mSentFinish);
957 if (mHasOtherProcessObservers) {
958 MOZ_ASSERT(mWriteAndNotifyInfos);
960 if (!mWriteAndNotifyInfos->IsEmpty()) {
961 if (aSync) {
962 MOZ_ALWAYS_TRUE(
963 mActor->SendSyncCheckpointAndNotify(*mWriteAndNotifyInfos));
964 } else {
965 MOZ_ALWAYS_TRUE(
966 mActor->SendAsyncCheckpointAndNotify(*mWriteAndNotifyInfos));
969 mWriteAndNotifyInfos->Clear();
971 } else {
972 MOZ_ASSERT(mWriteOptimizer);
974 if (mWriteOptimizer->HasWrites()) {
975 nsTArray<LSWriteInfo> writeInfos;
976 mWriteOptimizer->Enumerate(writeInfos);
978 MOZ_ASSERT(!writeInfos.IsEmpty());
980 if (aSync) {
981 MOZ_ALWAYS_TRUE(mActor->SendSyncCheckpoint(writeInfos));
982 } else {
983 MOZ_ALWAYS_TRUE(mActor->SendAsyncCheckpoint(writeInfos));
986 mWriteOptimizer->Reset();
990 return NS_OK;
993 nsresult LSSnapshot::Finish(bool aSync) {
994 AssertIsOnOwningThread();
995 MOZ_ASSERT(mDatabase);
996 MOZ_ASSERT(mActor);
997 MOZ_ASSERT(mInitialized);
998 MOZ_ASSERT(!mSentFinish);
1000 if (aSync) {
1001 MOZ_ALWAYS_TRUE(mActor->SendSyncFinish());
1002 } else {
1003 MOZ_ALWAYS_TRUE(mActor->SendAsyncFinish());
1006 mDatabase->NoteFinishedSnapshot(this);
1008 #ifdef DEBUG
1009 mSentFinish = true;
1010 #endif
1012 // Clear the self reference added in Init method.
1013 MOZ_ASSERT(mSelfRef);
1014 mSelfRef = nullptr;
1016 return NS_OK;
1019 void LSSnapshot::CancelIdleTimer() {
1020 AssertIsOnOwningThread();
1021 MOZ_ASSERT(mIdleTimer);
1023 if (mHasPendingIdleTimerCallback) {
1024 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
1025 mHasPendingIdleTimerCallback = false;
1029 // static
1030 void LSSnapshot::IdleTimerCallback(nsITimer* aTimer, void* aClosure) {
1031 MOZ_ASSERT(aTimer);
1033 auto* self = static_cast<LSSnapshot*>(aClosure);
1034 MOZ_ASSERT(self);
1035 MOZ_ASSERT(self->mIdleTimer);
1036 MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer));
1037 MOZ_ASSERT(!self->mHasPendingStableStateCallback);
1038 MOZ_ASSERT(self->mHasPendingIdleTimerCallback);
1040 self->mHasPendingIdleTimerCallback = false;
1042 MOZ_ALWAYS_SUCCEEDS(self->Finish());
1045 NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable)
1047 NS_IMETHODIMP
1048 LSSnapshot::Run() {
1049 AssertIsOnOwningThread();
1050 MOZ_ASSERT(!mExplicit);
1051 MOZ_ASSERT(mHasPendingStableStateCallback);
1052 MOZ_ASSERT(!mHasPendingIdleTimerCallback);
1054 mHasPendingStableStateCallback = false;
1056 MOZ_ALWAYS_SUCCEEDS(Checkpoint());
1058 // 1. The unused pre-incremented snapshot peak usage can't be undone when
1059 // there are other snapshots for the same database. We only add a pending
1060 // usage delta when a snapshot finishes and usage deltas are then applied
1061 // when the last database becomes inactive.
1062 // 2. If there's a snapshot with pre-incremented peak usage, the next
1063 // snapshot will use that as a base for its usage.
1064 // 3. When a task for given snapshot finishes, we try to reuse the snapshot
1065 // by only checkpointing the snapshot and delaying the finish by a timer.
1066 // 4. If two or more tabs for the same origin use localStorage periodically
1067 // at the same time the usage gradually grows until it hits the quota
1068 // limit.
1069 // 5. We prevent that from happening by finishing the snapshot immediatelly
1070 // if there are databases in other processess.
1072 if (mDirty || mHasOtherProcessDatabases ||
1073 !Preferences::GetBool("dom.storage.snapshot_reusing")) {
1074 MOZ_ALWAYS_SUCCEEDS(Finish());
1075 } else {
1076 MOZ_ASSERT(mIdleTimer);
1078 MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback(
1079 IdleTimerCallback, this,
1080 StaticPrefs::dom_storage_snapshot_idle_timeout_ms(),
1081 nsITimer::TYPE_ONE_SHOT, "LSSnapshot::IdleTimerCallback"));
1083 mHasPendingIdleTimerCallback = true;
1086 return NS_OK;
1089 } // namespace mozilla::dom