/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/LSSnapshot.h" // Local includes #include "ActorsChild.h" #include "LSDatabase.h" #include "LSWriteOptimizer.h" #include "LSWriteOptimizerImpl.h" #include "LocalStorageCommon.h" // Global includes #include #include #include #include #include #include "ErrorList.h" #include "mozilla/DebugOnly.h" #include "mozilla/MacroForEach.h" #include "mozilla/Maybe.h" #include "mozilla/Preferences.h" #include "mozilla/RefPtr.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/LSValue.h" #include "mozilla/dom/PBackgroundLSDatabase.h" #include "mozilla/dom/PBackgroundLSSharedTypes.h" #include "mozilla/dom/PBackgroundLSSnapshot.h" #include "nsBaseHashtable.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsITimer.h" #include "nsString.h" #include "nsStringFlags.h" #include "nsStringFwd.h" #include "nsTArray.h" #include "nsTStringRepr.h" #include "nscore.h" namespace mozilla::dom { /** * Coalescing manipulation queue used by `LSSnapshot`. Used by `LSSnapshot` to * buffer and coalesce manipulations before they are sent to the parent process, * when a Snapshot Checkpoints. (This can only be done when there are no * observers for other content processes.) */ class SnapshotWriteOptimizer final : public LSWriteOptimizer { public: void Enumerate(nsTArray& aWriteInfos); }; void SnapshotWriteOptimizer::Enumerate(nsTArray& aWriteInfos) { AssertIsOnOwningThread(); // The mWriteInfos hash table contains all write infos, but it keeps them in // an arbitrary order, which means write infos need to be sorted before being // processed. nsTArray> writeInfos; GetSortedWriteInfos(writeInfos); for (WriteInfo* writeInfo : writeInfos) { switch (writeInfo->GetType()) { case WriteInfo::InsertItem: { auto insertItemInfo = static_cast(writeInfo); LSSetItemInfo setItemInfo; setItemInfo.key() = insertItemInfo->GetKey(); setItemInfo.value() = insertItemInfo->GetValue(); aWriteInfos.AppendElement(std::move(setItemInfo)); break; } case WriteInfo::UpdateItem: { auto updateItemInfo = static_cast(writeInfo); if (updateItemInfo->UpdateWithMove()) { // See the comment in LSWriteOptimizer::InsertItem for more details // about the UpdateWithMove flag. LSRemoveItemInfo removeItemInfo; removeItemInfo.key() = updateItemInfo->GetKey(); aWriteInfos.AppendElement(std::move(removeItemInfo)); } LSSetItemInfo setItemInfo; setItemInfo.key() = updateItemInfo->GetKey(); setItemInfo.value() = updateItemInfo->GetValue(); aWriteInfos.AppendElement(std::move(setItemInfo)); break; } case WriteInfo::DeleteItem: { auto deleteItemInfo = static_cast(writeInfo); LSRemoveItemInfo removeItemInfo; removeItemInfo.key() = deleteItemInfo->GetKey(); aWriteInfos.AppendElement(std::move(removeItemInfo)); break; } case WriteInfo::Truncate: { LSClearInfo clearInfo; aWriteInfos.AppendElement(std::move(clearInfo)); break; } default: MOZ_CRASH("Bad type!"); } } } LSSnapshot::LSSnapshot(LSDatabase* aDatabase) : mDatabase(aDatabase), mActor(nullptr), mInitLength(0), mLength(0), mUsage(0), mPeakUsage(0), mLoadState(LoadState::Initial), mHasOtherProcessDatabases(false), mHasOtherProcessObservers(false), mExplicit(false), mHasPendingStableStateCallback(false), mHasPendingIdleTimerCallback(false), mDirty(false) #ifdef DEBUG , mInitialized(false), mSentFinish(false) #endif { AssertIsOnOwningThread(); } LSSnapshot::~LSSnapshot() { AssertIsOnOwningThread(); MOZ_ASSERT(mDatabase); MOZ_ASSERT(!mHasPendingStableStateCallback); MOZ_ASSERT(!mHasPendingIdleTimerCallback); MOZ_ASSERT_IF(mInitialized, mSentFinish); if (mActor) { mActor->SendDeleteMeInternal(); MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!"); } } void LSSnapshot::SetActor(LSSnapshotChild* aActor) { AssertIsOnOwningThread(); MOZ_ASSERT(aActor); MOZ_ASSERT(!mActor); mActor = aActor; } nsresult LSSnapshot::Init(const nsAString& aKey, const LSSnapshotInitInfo& aInitInfo, bool aExplicit) { AssertIsOnOwningThread(); MOZ_ASSERT(!mSelfRef); MOZ_ASSERT(mActor); MOZ_ASSERT(mLoadState == LoadState::Initial); MOZ_ASSERT(!mInitialized); MOZ_ASSERT(!mSentFinish); mSelfRef = this; LoadState loadState = aInitInfo.loadState(); const nsTArray& itemInfos = aInitInfo.itemInfos(); for (uint32_t i = 0; i < itemInfos.Length(); i++) { const LSItemInfo& itemInfo = itemInfos[i]; const LSValue& value = itemInfo.value(); if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) { mLoadedItems.Insert(itemInfo.key()); } mValues.InsertOrUpdate(itemInfo.key(), value.AsString()); } if (loadState == LoadState::Partial) { if (aInitInfo.addKeyToUnknownItems()) { mUnknownItems.Insert(aKey); } mInitLength = aInitInfo.totalLength(); mLength = mInitLength; } else if (loadState == LoadState::AllOrderedKeys) { mInitLength = aInitInfo.totalLength(); } else { MOZ_ASSERT(loadState == LoadState::AllOrderedItems); } mUsage = aInitInfo.usage(); mPeakUsage = aInitInfo.peakUsage(); mLoadState = aInitInfo.loadState(); mHasOtherProcessDatabases = aInitInfo.hasOtherProcessDatabases(); mHasOtherProcessObservers = aInitInfo.hasOtherProcessObservers(); mExplicit = aExplicit; #ifdef DEBUG mInitialized = true; #endif if (mHasOtherProcessObservers) { mWriteAndNotifyInfos = MakeUnique>(); } else { mWriteOptimizer = MakeUnique(); } if (!mExplicit) { mIdleTimer = NS_NewTimer(); MOZ_ASSERT(mIdleTimer); ScheduleStableStateCallback(); } return NS_OK; } nsresult LSSnapshot::GetLength(uint32_t* aResult) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); if (mLoadState == LoadState::Partial) { *aResult = mLength; } else { *aResult = mValues.Count(); } return NS_OK; } nsresult LSSnapshot::GetKey(uint32_t aIndex, nsAString& aResult) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); nsresult rv = EnsureAllKeys(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aResult.SetIsVoid(true); for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) { if (aIndex == 0) { aResult = iter.Key(); return NS_OK; } aIndex--; } return NS_OK; } nsresult LSSnapshot::GetItem(const nsAString& aKey, nsAString& aResult) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); nsString result; nsresult rv = GetItemInternal(aKey, Optional(), result); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aResult = result; return NS_OK; } nsresult LSSnapshot::GetKeys(nsTArray& aKeys) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); nsresult rv = EnsureAllKeys(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } AppendToArray(aKeys, mValues.Keys()); return NS_OK; } nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue, LSNotifyInfo& aNotifyInfo) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); nsString oldValue; nsresult rv = GetItemInternal(aKey, Optional(nsString(aValue)), oldValue); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool changed; if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) { changed = false; } else { changed = true; auto autoRevertValue = MakeScopeExit([&] { if (oldValue.IsVoid()) { mValues.Remove(aKey); } else { mValues.InsertOrUpdate(aKey, oldValue); } }); // Anything that can fail must be done early before we start modifying the // state. Maybe oldValueFromString; if (mHasOtherProcessObservers) { oldValueFromString.emplace(); if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) { return NS_ERROR_FAILURE; } } LSValue valueFromString; if (NS_WARN_IF(!valueFromString.InitFromString(aValue))) { return NS_ERROR_FAILURE; } int64_t delta = static_cast(aValue.Length()) - static_cast(oldValue.Length()); if (oldValue.IsVoid()) { delta += static_cast(aKey.Length()); } rv = UpdateUsage(delta); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (oldValue.IsVoid() && mLoadState == LoadState::Partial) { mLength++; } if (mHasOtherProcessObservers) { MOZ_ASSERT(mWriteAndNotifyInfos); MOZ_ASSERT(oldValueFromString.isSome()); LSSetItemAndNotifyInfo setItemAndNotifyInfo; setItemAndNotifyInfo.key() = aKey; setItemAndNotifyInfo.oldValue() = oldValueFromString.value(); setItemAndNotifyInfo.value() = valueFromString; mWriteAndNotifyInfos->AppendElement(std::move(setItemAndNotifyInfo)); } else { MOZ_ASSERT(mWriteOptimizer); if (oldValue.IsVoid()) { mWriteOptimizer->InsertItem(aKey, valueFromString); } else { mWriteOptimizer->UpdateItem(aKey, valueFromString); } } autoRevertValue.release(); } aNotifyInfo.changed() = changed; aNotifyInfo.oldValue() = oldValue; return NS_OK; } nsresult LSSnapshot::RemoveItem(const nsAString& aKey, LSNotifyInfo& aNotifyInfo) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); nsString oldValue; nsresult rv = GetItemInternal(aKey, Optional(VoidString()), oldValue); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool changed; if (oldValue.IsVoid()) { changed = false; } else { changed = true; auto autoRevertValue = MakeScopeExit([&] { MOZ_ASSERT(!oldValue.IsVoid()); mValues.InsertOrUpdate(aKey, oldValue); }); // Anything that can fail must be done early before we start modifying the // state. Maybe oldValueFromString; if (mHasOtherProcessObservers) { oldValueFromString.emplace(); if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) { return NS_ERROR_FAILURE; } } int64_t delta = -(static_cast(aKey.Length()) + static_cast(oldValue.Length())); DebugOnly rv = UpdateUsage(delta); MOZ_ASSERT(NS_SUCCEEDED(rv)); if (mLoadState == LoadState::Partial) { mLength--; } if (mHasOtherProcessObservers) { MOZ_ASSERT(mWriteAndNotifyInfos); MOZ_ASSERT(oldValueFromString.isSome()); LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo; removeItemAndNotifyInfo.key() = aKey; removeItemAndNotifyInfo.oldValue() = oldValueFromString.value(); mWriteAndNotifyInfos->AppendElement(std::move(removeItemAndNotifyInfo)); } else { MOZ_ASSERT(mWriteOptimizer); mWriteOptimizer->DeleteItem(aKey); } autoRevertValue.release(); } aNotifyInfo.changed() = changed; aNotifyInfo.oldValue() = oldValue; return NS_OK; } nsresult LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MaybeScheduleStableStateCallback(); uint32_t length; if (mLoadState == LoadState::Partial) { length = mLength; MOZ_ASSERT(length); MOZ_ALWAYS_TRUE(mActor->SendLoaded()); mLoadedItems.Clear(); mUnknownItems.Clear(); mLength = 0; mLoadState = LoadState::AllOrderedItems; } else { length = mValues.Count(); } bool changed; if (!length) { changed = false; } else { changed = true; int64_t delta = 0; for (const auto& entry : mValues) { const nsAString& key = entry.GetKey(); const nsString& value = entry.GetData(); delta += -static_cast(key.Length()) - static_cast(value.Length()); } DebugOnly rv = UpdateUsage(delta); MOZ_ASSERT(NS_SUCCEEDED(rv)); mValues.Clear(); if (mHasOtherProcessObservers) { MOZ_ASSERT(mWriteAndNotifyInfos); LSClearInfo clearInfo; mWriteAndNotifyInfos->AppendElement(std::move(clearInfo)); } else { MOZ_ASSERT(mWriteOptimizer); mWriteOptimizer->Truncate(); } } aNotifyInfo.changed() = changed; return NS_OK; } void LSSnapshot::MarkDirty() { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); if (mDirty) { return; } mDirty = true; if (!mExplicit && !mHasPendingStableStateCallback) { CancelIdleTimer(); MOZ_ALWAYS_SUCCEEDS(Checkpoint()); MOZ_ALWAYS_SUCCEEDS(Finish()); } else { MOZ_ASSERT(!mHasPendingIdleTimerCallback); } } nsresult LSSnapshot::ExplicitCheckpoint() { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mExplicit); MOZ_ASSERT(!mHasPendingStableStateCallback); MOZ_ASSERT(!mHasPendingIdleTimerCallback); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); nsresult rv = Checkpoint(/* aSync */ true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult LSSnapshot::ExplicitEnd() { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mExplicit); MOZ_ASSERT(!mHasPendingStableStateCallback); MOZ_ASSERT(!mHasPendingIdleTimerCallback); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); nsresult rv = Checkpoint(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } RefPtr kungFuDeathGrip = this; rv = Finish(/* aSync */ true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } int64_t LSSnapshot::GetUsage() const { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); return mUsage; } void LSSnapshot::ScheduleStableStateCallback() { AssertIsOnOwningThread(); MOZ_ASSERT(mIdleTimer); MOZ_ASSERT(!mExplicit); MOZ_ASSERT(!mHasPendingStableStateCallback); CancelIdleTimer(); nsCOMPtr runnable = this; nsContentUtils::RunInStableState(runnable.forget()); mHasPendingStableStateCallback = true; } void LSSnapshot::MaybeScheduleStableStateCallback() { AssertIsOnOwningThread(); if (!mExplicit && !mHasPendingStableStateCallback) { ScheduleStableStateCallback(); } else { MOZ_ASSERT(!mHasPendingIdleTimerCallback); } } nsresult LSSnapshot::GetItemInternal(const nsAString& aKey, const Optional& aValue, nsAString& aResult) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); nsString result; switch (mLoadState) { case LoadState::Partial: { if (mValues.Get(aKey, &result)) { MOZ_ASSERT(!result.IsVoid()); } else if (mLoadedItems.Contains(aKey) || mUnknownItems.Contains(aKey)) { result.SetIsVoid(true); } else { LSValue value; nsTArray itemInfos; if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems( nsString(aKey), &value, &itemInfos))) { return NS_ERROR_FAILURE; } result = value.AsString(); if (result.IsVoid()) { mUnknownItems.Insert(aKey); } else { mLoadedItems.Insert(aKey); mValues.InsertOrUpdate(aKey, result); // mLoadedItems.Count()==mInitLength is checked below. } for (uint32_t i = 0; i < itemInfos.Length(); i++) { const LSItemInfo& itemInfo = itemInfos[i]; mLoadedItems.Insert(itemInfo.key()); mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString()); } if (mLoadedItems.Count() == mInitLength) { mLoadedItems.Clear(); mUnknownItems.Clear(); mLength = 0; mLoadState = LoadState::AllUnorderedItems; } } if (aValue.WasPassed()) { const nsString& value = aValue.Value(); if (!value.IsVoid()) { mValues.InsertOrUpdate(aKey, value); } else if (!result.IsVoid()) { mValues.Remove(aKey); } } break; } case LoadState::AllOrderedKeys: { if (mValues.Get(aKey, &result)) { if (result.IsVoid()) { LSValue value; nsTArray itemInfos; if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems( nsString(aKey), &value, &itemInfos))) { return NS_ERROR_FAILURE; } result = value.AsString(); MOZ_ASSERT(!result.IsVoid()); mLoadedItems.Insert(aKey); mValues.InsertOrUpdate(aKey, result); // mLoadedItems.Count()==mInitLength is checked below. for (uint32_t i = 0; i < itemInfos.Length(); i++) { const LSItemInfo& itemInfo = itemInfos[i]; mLoadedItems.Insert(itemInfo.key()); mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString()); } if (mLoadedItems.Count() == mInitLength) { mLoadedItems.Clear(); MOZ_ASSERT(mLength == 0); mLoadState = LoadState::AllOrderedItems; } } } else { result.SetIsVoid(true); } if (aValue.WasPassed()) { const nsString& value = aValue.Value(); if (!value.IsVoid()) { mValues.InsertOrUpdate(aKey, value); } else if (!result.IsVoid()) { mValues.Remove(aKey); } } break; } case LoadState::AllUnorderedItems: case LoadState::AllOrderedItems: { if (aValue.WasPassed()) { const nsString& value = aValue.Value(); if (!value.IsVoid()) { mValues.WithEntryHandle(aKey, [&](auto&& entry) { if (entry) { result = std::exchange(entry.Data(), value); } else { result.SetIsVoid(true); entry.Insert(value); } }); } else { if (auto entry = mValues.Lookup(aKey)) { result = entry.Data(); MOZ_ASSERT(!result.IsVoid()); entry.Remove(); } else { result.SetIsVoid(true); } } } else { if (mValues.Get(aKey, &result)) { MOZ_ASSERT(!result.IsVoid()); } else { result.SetIsVoid(true); } } break; } default: MOZ_CRASH("Bad state!"); } aResult = result; return NS_OK; } nsresult LSSnapshot::EnsureAllKeys() { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); MOZ_ASSERT(mLoadState != LoadState::Initial); if (mLoadState == LoadState::AllOrderedKeys || mLoadState == LoadState::AllOrderedItems) { return NS_OK; } nsTArray keys; if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) { return NS_ERROR_FAILURE; } nsTHashMap newValues; for (auto key : keys) { newValues.InsertOrUpdate(key, VoidString()); } if (mHasOtherProcessObservers) { MOZ_ASSERT(mWriteAndNotifyInfos); if (!mWriteAndNotifyInfos->IsEmpty()) { for (uint32_t index = 0; index < mWriteAndNotifyInfos->Length(); index++) { const LSWriteAndNotifyInfo& writeAndNotifyInfo = mWriteAndNotifyInfos->ElementAt(index); switch (writeAndNotifyInfo.type()) { case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: { newValues.InsertOrUpdate( writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(), VoidString()); break; } case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: { newValues.Remove( writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key()); break; } case LSWriteAndNotifyInfo::TLSClearInfo: { newValues.Clear(); break; } default: MOZ_CRASH("Should never get here!"); } } } } else { MOZ_ASSERT(mWriteOptimizer); if (mWriteOptimizer->HasWrites()) { nsTArray writeInfos; mWriteOptimizer->Enumerate(writeInfos); MOZ_ASSERT(!writeInfos.IsEmpty()); for (uint32_t index = 0; index < writeInfos.Length(); index++) { const LSWriteInfo& writeInfo = writeInfos[index]; switch (writeInfo.type()) { case LSWriteInfo::TLSSetItemInfo: { newValues.InsertOrUpdate(writeInfo.get_LSSetItemInfo().key(), VoidString()); break; } case LSWriteInfo::TLSRemoveItemInfo: { newValues.Remove(writeInfo.get_LSRemoveItemInfo().key()); break; } case LSWriteInfo::TLSClearInfo: { newValues.Clear(); break; } default: MOZ_CRASH("Should never get here!"); } } } } MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems, newValues.Count() == mValues.Count()); for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) { nsString value; if (mValues.Get(iter.Key(), &value)) { iter.Data() = value; } } mValues.SwapElements(newValues); if (mLoadState == LoadState::Partial) { mUnknownItems.Clear(); mLength = 0; mLoadState = LoadState::AllOrderedKeys; } else { MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems); MOZ_ASSERT(mUnknownItems.Count() == 0); MOZ_ASSERT(mLength == 0); mLoadState = LoadState::AllOrderedItems; } return NS_OK; } nsresult LSSnapshot::UpdateUsage(int64_t aDelta) { AssertIsOnOwningThread(); MOZ_ASSERT(mDatabase); MOZ_ASSERT(mActor); MOZ_ASSERT(mPeakUsage >= mUsage); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); int64_t newUsage = mUsage + aDelta; if (newUsage > mPeakUsage) { const int64_t minSize = newUsage - mPeakUsage; int64_t size; if (NS_WARN_IF(!mActor->SendIncreasePeakUsage(minSize, &size))) { return NS_ERROR_FAILURE; } MOZ_ASSERT(size >= 0); if (size == 0) { return NS_ERROR_FILE_NO_DEVICE_SPACE; } mPeakUsage += size; } mUsage = newUsage; return NS_OK; } nsresult LSSnapshot::Checkpoint(bool aSync) { AssertIsOnOwningThread(); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); if (mHasOtherProcessObservers) { MOZ_ASSERT(mWriteAndNotifyInfos); if (!mWriteAndNotifyInfos->IsEmpty()) { if (aSync) { MOZ_ALWAYS_TRUE( mActor->SendSyncCheckpointAndNotify(*mWriteAndNotifyInfos)); } else { MOZ_ALWAYS_TRUE( mActor->SendAsyncCheckpointAndNotify(*mWriteAndNotifyInfos)); } mWriteAndNotifyInfos->Clear(); } } else { MOZ_ASSERT(mWriteOptimizer); if (mWriteOptimizer->HasWrites()) { nsTArray writeInfos; mWriteOptimizer->Enumerate(writeInfos); MOZ_ASSERT(!writeInfos.IsEmpty()); if (aSync) { MOZ_ALWAYS_TRUE(mActor->SendSyncCheckpoint(writeInfos)); } else { MOZ_ALWAYS_TRUE(mActor->SendAsyncCheckpoint(writeInfos)); } mWriteOptimizer->Reset(); } } return NS_OK; } nsresult LSSnapshot::Finish(bool aSync) { AssertIsOnOwningThread(); MOZ_ASSERT(mDatabase); MOZ_ASSERT(mActor); MOZ_ASSERT(mInitialized); MOZ_ASSERT(!mSentFinish); if (aSync) { MOZ_ALWAYS_TRUE(mActor->SendSyncFinish()); } else { MOZ_ALWAYS_TRUE(mActor->SendAsyncFinish()); } mDatabase->NoteFinishedSnapshot(this); #ifdef DEBUG mSentFinish = true; #endif // Clear the self reference added in Init method. MOZ_ASSERT(mSelfRef); mSelfRef = nullptr; return NS_OK; } void LSSnapshot::CancelIdleTimer() { AssertIsOnOwningThread(); MOZ_ASSERT(mIdleTimer); if (mHasPendingIdleTimerCallback) { MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel()); mHasPendingIdleTimerCallback = false; } } // static void LSSnapshot::IdleTimerCallback(nsITimer* aTimer, void* aClosure) { MOZ_ASSERT(aTimer); auto* self = static_cast(aClosure); MOZ_ASSERT(self); MOZ_ASSERT(self->mIdleTimer); MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer)); MOZ_ASSERT(!self->mHasPendingStableStateCallback); MOZ_ASSERT(self->mHasPendingIdleTimerCallback); self->mHasPendingIdleTimerCallback = false; MOZ_ALWAYS_SUCCEEDS(self->Finish()); } NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable) NS_IMETHODIMP LSSnapshot::Run() { AssertIsOnOwningThread(); MOZ_ASSERT(!mExplicit); MOZ_ASSERT(mHasPendingStableStateCallback); MOZ_ASSERT(!mHasPendingIdleTimerCallback); mHasPendingStableStateCallback = false; MOZ_ALWAYS_SUCCEEDS(Checkpoint()); // 1. The unused pre-incremented snapshot peak usage can't be undone when // there are other snapshots for the same database. We only add a pending // usage delta when a snapshot finishes and usage deltas are then applied // when the last database becomes inactive. // 2. If there's a snapshot with pre-incremented peak usage, the next // snapshot will use that as a base for its usage. // 3. When a task for given snapshot finishes, we try to reuse the snapshot // by only checkpointing the snapshot and delaying the finish by a timer. // 4. If two or more tabs for the same origin use localStorage periodically // at the same time the usage gradually grows until it hits the quota // limit. // 5. We prevent that from happening by finishing the snapshot immediatelly // if there are databases in other processess. if (mDirty || mHasOtherProcessDatabases || !Preferences::GetBool("dom.storage.snapshot_reusing")) { MOZ_ALWAYS_SUCCEEDS(Finish()); } else { MOZ_ASSERT(mIdleTimer); MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback( IdleTimerCallback, this, StaticPrefs::dom_storage_snapshot_idle_timeout_ms(), nsITimer::TYPE_ONE_SHOT, "LSSnapshot::IdleTimerCallback")); mHasPendingIdleTimerCallback = true; } return NS_OK; } } // namespace mozilla::dom