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"
10 #include "ActorsChild.h"
11 #include "LSDatabase.h"
12 #include "LSWriteOptimizer.h"
13 #include "LSWriteOptimizerImpl.h"
14 #include "LocalStorageCommon.h"
20 #include <type_traits>
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"
41 #include "nsContentUtils.h"
46 #include "nsStringFlags.h"
47 #include "nsStringFwd.h"
49 #include "nsTStringRepr.h"
52 namespace mozilla::dom
{
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
> {
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
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
));
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
));
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
));
122 case WriteInfo::Truncate
: {
123 LSClearInfo clearInfo
;
125 aWriteInfos
.AppendElement(std::move(clearInfo
));
131 MOZ_CRASH("Bad type!");
136 LSSnapshot::LSSnapshot(LSDatabase
* aDatabase
)
137 : mDatabase(aDatabase
),
143 mLoadState(LoadState::Initial
),
144 mHasOtherProcessDatabases(false),
145 mHasOtherProcessObservers(false),
147 mHasPendingStableStateCallback(false),
148 mHasPendingIdleTimerCallback(false),
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
);
167 mActor
->SendDeleteMeInternal();
168 MOZ_ASSERT(!mActor
, "SendDeleteMeInternal should have cleared!");
172 void LSSnapshot::SetActor(LSSnapshotChild
* aActor
) {
173 AssertIsOnOwningThread();
180 nsresult
LSSnapshot::Init(const nsAString
& aKey
,
181 const LSSnapshotInitInfo
& aInitInfo
, bool aExplicit
) {
182 AssertIsOnOwningThread();
183 MOZ_ASSERT(!mSelfRef
);
185 MOZ_ASSERT(mLoadState
== LoadState::Initial
);
186 MOZ_ASSERT(!mInitialized
);
187 MOZ_ASSERT(!mSentFinish
);
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();
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
;
232 if (mHasOtherProcessObservers
) {
233 mWriteAndNotifyInfos
= MakeUnique
<nsTArray
<LSWriteAndNotifyInfo
>>();
235 mWriteOptimizer
= MakeUnique
<SnapshotWriteOptimizer
>();
239 mIdleTimer
= NS_NewTimer();
240 MOZ_ASSERT(mIdleTimer
);
242 ScheduleStableStateCallback();
248 nsresult
LSSnapshot::GetLength(uint32_t* aResult
) {
249 AssertIsOnOwningThread();
251 MOZ_ASSERT(mInitialized
);
252 MOZ_ASSERT(!mSentFinish
);
254 MaybeScheduleStableStateCallback();
256 if (mLoadState
== LoadState::Partial
) {
259 *aResult
= mValues
.Count();
265 nsresult
LSSnapshot::GetKey(uint32_t aIndex
, nsAString
& aResult
) {
266 AssertIsOnOwningThread();
268 MOZ_ASSERT(mInitialized
);
269 MOZ_ASSERT(!mSentFinish
);
271 MaybeScheduleStableStateCallback();
273 nsresult rv
= EnsureAllKeys();
274 if (NS_WARN_IF(NS_FAILED(rv
))) {
278 aResult
.SetIsVoid(true);
279 for (auto iter
= mValues
.ConstIter(); !iter
.Done(); iter
.Next()) {
281 aResult
= iter
.Key();
290 nsresult
LSSnapshot::GetItem(const nsAString
& aKey
, nsAString
& aResult
) {
291 AssertIsOnOwningThread();
293 MOZ_ASSERT(mInitialized
);
294 MOZ_ASSERT(!mSentFinish
);
296 MaybeScheduleStableStateCallback();
299 nsresult rv
= GetItemInternal(aKey
, Optional
<nsString
>(), result
);
300 if (NS_WARN_IF(NS_FAILED(rv
))) {
308 nsresult
LSSnapshot::GetKeys(nsTArray
<nsString
>& aKeys
) {
309 AssertIsOnOwningThread();
311 MOZ_ASSERT(mInitialized
);
312 MOZ_ASSERT(!mSentFinish
);
314 MaybeScheduleStableStateCallback();
316 nsresult rv
= EnsureAllKeys();
317 if (NS_WARN_IF(NS_FAILED(rv
))) {
321 AppendToArray(aKeys
, mValues
.Keys());
326 nsresult
LSSnapshot::SetItem(const nsAString
& aKey
, const nsAString
& aValue
,
327 LSNotifyInfo
& aNotifyInfo
) {
328 AssertIsOnOwningThread();
330 MOZ_ASSERT(mInitialized
);
331 MOZ_ASSERT(!mSentFinish
);
333 MaybeScheduleStableStateCallback();
337 GetItemInternal(aKey
, Optional
<nsString
>(nsString(aValue
)), oldValue
);
338 if (NS_WARN_IF(NS_FAILED(rv
))) {
343 if (oldValue
== aValue
&& oldValue
.IsVoid() == aValue
.IsVoid()) {
348 auto autoRevertValue
= MakeScopeExit([&] {
349 if (oldValue
.IsVoid()) {
350 mValues
.Remove(aKey
);
352 mValues
.InsertOrUpdate(aKey
, oldValue
);
356 // Anything that can fail must be done early before we start modifying the
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
,
385 static uint32_t counter
= 0u;
386 const bool result
= 0u != (counter
& (1u + counter
));
392 if (oldValue
.IsVoid() && mLoadState
== LoadState::Partial
) {
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
));
407 MOZ_ASSERT(mWriteOptimizer
);
409 if (oldValue
.IsVoid()) {
410 mWriteOptimizer
->InsertItem(aKey
, valueFromString
);
412 mWriteOptimizer
->UpdateItem(aKey
, valueFromString
);
416 autoRevertValue
.release();
419 aNotifyInfo
.changed() = changed
;
420 aNotifyInfo
.oldValue() = oldValue
;
425 nsresult
LSSnapshot::RemoveItem(const nsAString
& aKey
,
426 LSNotifyInfo
& aNotifyInfo
) {
427 AssertIsOnOwningThread();
429 MOZ_ASSERT(mInitialized
);
430 MOZ_ASSERT(!mSentFinish
);
432 MaybeScheduleStableStateCallback();
436 GetItemInternal(aKey
, Optional
<nsString
>(VoidString()), oldValue
);
437 if (NS_WARN_IF(NS_FAILED(rv
))) {
442 if (oldValue
.IsVoid()) {
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
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
) {
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
));
483 MOZ_ASSERT(mWriteOptimizer
);
485 mWriteOptimizer
->DeleteItem(aKey
);
488 autoRevertValue
.release();
491 aNotifyInfo
.changed() = changed
;
492 aNotifyInfo
.oldValue() = oldValue
;
497 nsresult
LSSnapshot::Clear(LSNotifyInfo
& aNotifyInfo
) {
498 AssertIsOnOwningThread();
500 MOZ_ASSERT(mInitialized
);
501 MOZ_ASSERT(!mSentFinish
);
503 MaybeScheduleStableStateCallback();
506 if (mLoadState
== LoadState::Partial
) {
510 MOZ_ALWAYS_TRUE(mActor
->SendLoaded());
512 mLoadedItems
.Clear();
513 mUnknownItems
.Clear();
515 mLoadState
= LoadState::AllOrderedItems
;
517 length
= mValues
.Count();
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
));
540 if (mHasOtherProcessObservers
) {
541 MOZ_ASSERT(mWriteAndNotifyInfos
);
543 LSClearInfo clearInfo
;
545 mWriteAndNotifyInfos
->AppendElement(std::move(clearInfo
));
547 MOZ_ASSERT(mWriteOptimizer
);
549 mWriteOptimizer
->Truncate();
553 aNotifyInfo
.changed() = changed
;
558 void LSSnapshot::MarkDirty() {
559 AssertIsOnOwningThread();
561 MOZ_ASSERT(mInitialized
);
562 MOZ_ASSERT(!mSentFinish
);
570 if (!mExplicit
&& !mHasPendingStableStateCallback
) {
573 MOZ_ALWAYS_SUCCEEDS(Checkpoint());
575 MOZ_ALWAYS_SUCCEEDS(Finish());
577 MOZ_ASSERT(!mHasPendingIdleTimerCallback
);
581 nsresult
LSSnapshot::ExplicitCheckpoint() {
582 AssertIsOnOwningThread();
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
))) {
598 nsresult
LSSnapshot::ExplicitEnd() {
599 AssertIsOnOwningThread();
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
))) {
612 RefPtr
<LSSnapshot
> kungFuDeathGrip
= this;
614 rv
= Finish(/* aSync */ true);
615 if (NS_WARN_IF(NS_FAILED(rv
))) {
622 int64_t LSSnapshot::GetUsage() const {
623 AssertIsOnOwningThread();
625 MOZ_ASSERT(mInitialized
);
626 MOZ_ASSERT(!mSentFinish
);
631 void LSSnapshot::ScheduleStableStateCallback() {
632 AssertIsOnOwningThread();
633 MOZ_ASSERT(mIdleTimer
);
634 MOZ_ASSERT(!mExplicit
);
635 MOZ_ASSERT(!mHasPendingStableStateCallback
);
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();
651 MOZ_ASSERT(!mHasPendingIdleTimerCallback
);
655 nsresult
LSSnapshot::GetItemInternal(const nsAString
& aKey
,
656 const Optional
<nsString
>& aValue
,
657 nsAString
& aResult
) {
658 AssertIsOnOwningThread();
660 MOZ_ASSERT(mInitialized
);
661 MOZ_ASSERT(!mSentFinish
);
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);
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
);
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();
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
);
717 case LoadState::AllOrderedKeys
: {
718 if (mValues
.Get(aKey
, &result
)) {
719 if (result
.IsVoid()) {
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
;
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
);
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
) {
772 result
= std::exchange(entry
.Data(), value
);
774 result
.SetIsVoid(true);
779 if (auto entry
= mValues
.Lookup(aKey
)) {
780 result
= entry
.Data();
781 MOZ_ASSERT(!result
.IsVoid());
784 result
.SetIsVoid(true);
788 if (mValues
.Get(aKey
, &result
)) {
789 MOZ_ASSERT(!result
.IsVoid());
791 result
.SetIsVoid(true);
799 MOZ_CRASH("Bad state!");
806 nsresult
LSSnapshot::EnsureAllKeys() {
807 AssertIsOnOwningThread();
809 MOZ_ASSERT(mInitialized
);
810 MOZ_ASSERT(!mSentFinish
);
811 MOZ_ASSERT(mLoadState
!= LoadState::Initial
);
813 if (mLoadState
== LoadState::AllOrderedKeys
||
814 mLoadState
== LoadState::AllOrderedItems
) {
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();
835 const LSWriteAndNotifyInfo
& writeAndNotifyInfo
=
836 mWriteAndNotifyInfos
->ElementAt(index
);
838 switch (writeAndNotifyInfo
.type()) {
839 case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo
: {
840 newValues
.InsertOrUpdate(
841 writeAndNotifyInfo
.get_LSSetItemAndNotifyInfo().key(),
845 case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo
: {
847 writeAndNotifyInfo
.get_LSRemoveItemAndNotifyInfo().key());
850 case LSWriteAndNotifyInfo::TLSClearInfo
: {
856 MOZ_CRASH("Should never get here!");
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(),
878 case LSWriteInfo::TLSRemoveItemInfo
: {
879 newValues
.Remove(writeInfo
.get_LSRemoveItemInfo().key());
882 case LSWriteInfo::TLSClearInfo
: {
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()) {
899 if (mValues
.Get(iter
.Key(), &value
)) {
904 mValues
.SwapElements(newValues
);
906 if (mLoadState
== LoadState::Partial
) {
907 mUnknownItems
.Clear();
909 mLoadState
= LoadState::AllOrderedKeys
;
911 MOZ_ASSERT(mLoadState
== LoadState::AllUnorderedItems
);
913 MOZ_ASSERT(mUnknownItems
.Count() == 0);
914 MOZ_ASSERT(mLength
== 0);
915 mLoadState
= LoadState::AllOrderedItems
;
921 nsresult
LSSnapshot::UpdateUsage(int64_t aDelta
) {
922 AssertIsOnOwningThread();
923 MOZ_ASSERT(mDatabase
);
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
;
934 if (NS_WARN_IF(!mActor
->SendIncreasePeakUsage(minSize
, &size
))) {
935 return NS_ERROR_FAILURE
;
938 MOZ_ASSERT(size
>= 0);
941 return NS_ERROR_FILE_NO_DEVICE_SPACE
;
951 nsresult
LSSnapshot::Checkpoint(bool aSync
) {
952 AssertIsOnOwningThread();
954 MOZ_ASSERT(mInitialized
);
955 MOZ_ASSERT(!mSentFinish
);
957 if (mHasOtherProcessObservers
) {
958 MOZ_ASSERT(mWriteAndNotifyInfos
);
960 if (!mWriteAndNotifyInfos
->IsEmpty()) {
963 mActor
->SendSyncCheckpointAndNotify(*mWriteAndNotifyInfos
));
966 mActor
->SendAsyncCheckpointAndNotify(*mWriteAndNotifyInfos
));
969 mWriteAndNotifyInfos
->Clear();
972 MOZ_ASSERT(mWriteOptimizer
);
974 if (mWriteOptimizer
->HasWrites()) {
975 nsTArray
<LSWriteInfo
> writeInfos
;
976 mWriteOptimizer
->Enumerate(writeInfos
);
978 MOZ_ASSERT(!writeInfos
.IsEmpty());
981 MOZ_ALWAYS_TRUE(mActor
->SendSyncCheckpoint(writeInfos
));
983 MOZ_ALWAYS_TRUE(mActor
->SendAsyncCheckpoint(writeInfos
));
986 mWriteOptimizer
->Reset();
993 nsresult
LSSnapshot::Finish(bool aSync
) {
994 AssertIsOnOwningThread();
995 MOZ_ASSERT(mDatabase
);
997 MOZ_ASSERT(mInitialized
);
998 MOZ_ASSERT(!mSentFinish
);
1001 MOZ_ALWAYS_TRUE(mActor
->SendSyncFinish());
1003 MOZ_ALWAYS_TRUE(mActor
->SendAsyncFinish());
1006 mDatabase
->NoteFinishedSnapshot(this);
1012 // Clear the self reference added in Init method.
1013 MOZ_ASSERT(mSelfRef
);
1019 void LSSnapshot::CancelIdleTimer() {
1020 AssertIsOnOwningThread();
1021 MOZ_ASSERT(mIdleTimer
);
1023 if (mHasPendingIdleTimerCallback
) {
1024 MOZ_ALWAYS_SUCCEEDS(mIdleTimer
->Cancel());
1025 mHasPendingIdleTimerCallback
= false;
1030 void LSSnapshot::IdleTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
1033 auto* self
= static_cast<LSSnapshot
*>(aClosure
);
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
)
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
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());
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;
1089 } // namespace mozilla::dom