tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

LSSnapshot.cpp (28644B)


      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/. */
      6 
      7 #include "mozilla/dom/LSSnapshot.h"
      8 
      9 // Local includes
     10 #include "ActorsChild.h"
     11 #include "LSDatabase.h"
     12 #include "LSWriteOptimizer.h"
     13 #include "LSWriteOptimizerImpl.h"
     14 #include "LocalStorageCommon.h"
     15 
     16 // Global includes
     17 #include <cstdint>
     18 #include <new>
     19 #include <utility>
     20 
     21 #include "ErrorList.h"
     22 #include "mozilla/DebugOnly.h"
     23 #include "mozilla/GeckoTrace.h"
     24 #include "mozilla/Maybe.h"
     25 #include "mozilla/Preferences.h"
     26 #include "mozilla/RefPtr.h"
     27 #include "mozilla/ScopeExit.h"
     28 #include "mozilla/StaticPrefs_dom.h"
     29 #include "mozilla/UniquePtr.h"
     30 #include "mozilla/dom/BindingDeclarations.h"
     31 #include "mozilla/dom/LSValue.h"
     32 #include "mozilla/dom/PBackgroundLSDatabase.h"
     33 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
     34 #include "mozilla/dom/PBackgroundLSSnapshot.h"
     35 #include "mozilla/dom/quota/QuotaCommon.h"
     36 #include "mozilla/dom/quota/ResultExtensions.h"
     37 #include "mozilla/dom/quota/ScopedLogExtraInfo.h"
     38 #include "nsBaseHashtable.h"
     39 #include "nsCOMPtr.h"
     40 #include "nsContentUtils.h"
     41 #include "nsDebug.h"
     42 #include "nsError.h"
     43 #include "nsITimer.h"
     44 #include "nsString.h"
     45 #include "nsStringFlags.h"
     46 #include "nsStringFwd.h"
     47 #include "nsTArray.h"
     48 #include "nsTStringRepr.h"
     49 #include "nscore.h"
     50 
     51 namespace mozilla::dom {
     52 
     53 /**
     54 * Coalescing manipulation queue used by `LSSnapshot`.  Used by `LSSnapshot` to
     55 * buffer and coalesce manipulations before they are sent to the parent process,
     56 * when a Snapshot Checkpoints. (This can only be done when there are no
     57 * observers for other content processes.)
     58 */
     59 class SnapshotWriteOptimizer final : public LSWriteOptimizer<LSValue> {
     60 public:
     61  void Enumerate(nsTArray<LSWriteInfo>& aWriteInfos);
     62 };
     63 
     64 void SnapshotWriteOptimizer::Enumerate(nsTArray<LSWriteInfo>& aWriteInfos) {
     65  AssertIsOnOwningThread();
     66 
     67  // The mWriteInfos hash table contains all write infos, but it keeps them in
     68  // an arbitrary order, which means write infos need to be sorted before being
     69  // processed.
     70 
     71  nsTArray<NotNull<WriteInfo*>> writeInfos;
     72  GetSortedWriteInfos(writeInfos);
     73 
     74  for (WriteInfo* writeInfo : writeInfos) {
     75    switch (writeInfo->GetType()) {
     76      case WriteInfo::InsertItem: {
     77        auto insertItemInfo = static_cast<InsertItemInfo*>(writeInfo);
     78 
     79        LSSetItemInfo setItemInfo;
     80        setItemInfo.key() = insertItemInfo->GetKey();
     81        setItemInfo.value() = insertItemInfo->GetValue();
     82 
     83        aWriteInfos.AppendElement(std::move(setItemInfo));
     84 
     85        break;
     86      }
     87 
     88      case WriteInfo::UpdateItem: {
     89        auto updateItemInfo = static_cast<UpdateItemInfo*>(writeInfo);
     90 
     91        if (updateItemInfo->UpdateWithMove()) {
     92          // See the comment in LSWriteOptimizer::InsertItem for more details
     93          // about the UpdateWithMove flag.
     94 
     95          LSRemoveItemInfo removeItemInfo;
     96          removeItemInfo.key() = updateItemInfo->GetKey();
     97 
     98          aWriteInfos.AppendElement(std::move(removeItemInfo));
     99        }
    100 
    101        LSSetItemInfo setItemInfo;
    102        setItemInfo.key() = updateItemInfo->GetKey();
    103        setItemInfo.value() = updateItemInfo->GetValue();
    104 
    105        aWriteInfos.AppendElement(std::move(setItemInfo));
    106 
    107        break;
    108      }
    109 
    110      case WriteInfo::DeleteItem: {
    111        auto deleteItemInfo = static_cast<DeleteItemInfo*>(writeInfo);
    112 
    113        LSRemoveItemInfo removeItemInfo;
    114        removeItemInfo.key() = deleteItemInfo->GetKey();
    115 
    116        aWriteInfos.AppendElement(std::move(removeItemInfo));
    117 
    118        break;
    119      }
    120 
    121      case WriteInfo::Truncate: {
    122        LSClearInfo clearInfo;
    123 
    124        aWriteInfos.AppendElement(std::move(clearInfo));
    125 
    126        break;
    127      }
    128 
    129      default:
    130        MOZ_CRASH("Bad type!");
    131    }
    132  }
    133 }
    134 
    135 LSSnapshot::LSSnapshot(LSDatabase* aDatabase)
    136    : mDatabase(aDatabase),
    137      mActor(nullptr),
    138      mInitLength(0),
    139      mLength(0),
    140      mUsage(0),
    141      mPeakUsage(0),
    142      mLoadState(LoadState::Initial),
    143      mHasOtherProcessDatabases(false),
    144      mHasOtherProcessObservers(false),
    145      mExplicit(false),
    146      mHasPendingStableStateCallback(false),
    147      mHasPendingIdleTimerCallback(false),
    148      mDirty(false)
    149 #ifdef DEBUG
    150      ,
    151      mInitialized(false),
    152      mSentFinish(false)
    153 #endif
    154 {
    155  AssertIsOnOwningThread();
    156 }
    157 
    158 LSSnapshot::~LSSnapshot() {
    159  AssertIsOnOwningThread();
    160  MOZ_ASSERT(mDatabase);
    161  MOZ_ASSERT(!mHasPendingStableStateCallback);
    162  MOZ_ASSERT(!mHasPendingIdleTimerCallback);
    163  MOZ_ASSERT_IF(mInitialized, mSentFinish);
    164 
    165  if (mActor) {
    166    mActor->SendDeleteMeInternal();
    167    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
    168  }
    169 }
    170 
    171 void LSSnapshot::SetActor(LSSnapshotChild* aActor) {
    172  AssertIsOnOwningThread();
    173  MOZ_ASSERT(aActor);
    174  MOZ_ASSERT(!mActor);
    175 
    176  mActor = aActor;
    177 }
    178 
    179 nsresult LSSnapshot::Init(const nsAString& aKey,
    180                          const LSSnapshotInitInfo& aInitInfo, bool aExplicit) {
    181  AssertIsOnOwningThread();
    182  MOZ_ASSERT(!mSelfRef);
    183  MOZ_ASSERT(mActor);
    184  MOZ_ASSERT(mLoadState == LoadState::Initial);
    185  MOZ_ASSERT(!mInitialized);
    186  MOZ_ASSERT(!mSentFinish);
    187 
    188  mSelfRef = this;
    189 
    190  LoadState loadState = aInitInfo.loadState();
    191 
    192  const nsTArray<LSItemInfo>& itemInfos = aInitInfo.itemInfos();
    193  for (uint32_t i = 0; i < itemInfos.Length(); i++) {
    194    const LSItemInfo& itemInfo = itemInfos[i];
    195 
    196    const LSValue& value = itemInfo.value();
    197 
    198    if (loadState != LoadState::AllOrderedItems && !value.IsVoid()) {
    199      mLoadedItems.Insert(itemInfo.key());
    200    }
    201 
    202    mValues.InsertOrUpdate(itemInfo.key(), value.AsString());
    203  }
    204 
    205  if (loadState == LoadState::Partial) {
    206    if (aInitInfo.addKeyToUnknownItems()) {
    207      mUnknownItems.Insert(aKey);
    208    }
    209    mInitLength = aInitInfo.totalLength();
    210    mLength = mInitLength;
    211  } else if (loadState == LoadState::AllOrderedKeys) {
    212    mInitLength = aInitInfo.totalLength();
    213  } else {
    214    MOZ_ASSERT(loadState == LoadState::AllOrderedItems);
    215  }
    216 
    217  mUsage = aInitInfo.usage();
    218  mPeakUsage = aInitInfo.peakUsage();
    219 
    220  mLoadState = aInitInfo.loadState();
    221 
    222  mHasOtherProcessDatabases = aInitInfo.hasOtherProcessDatabases();
    223  mHasOtherProcessObservers = aInitInfo.hasOtherProcessObservers();
    224 
    225  mExplicit = aExplicit;
    226 
    227 #ifdef DEBUG
    228  mInitialized = true;
    229 #endif
    230 
    231  if (mHasOtherProcessObservers) {
    232    mWriteAndNotifyInfos = MakeUnique<nsTArray<LSWriteAndNotifyInfo>>();
    233  } else {
    234    mWriteOptimizer = MakeUnique<SnapshotWriteOptimizer>();
    235  }
    236 
    237  if (!mExplicit) {
    238    mIdleTimer = NS_NewTimer();
    239    MOZ_ASSERT(mIdleTimer);
    240 
    241    ScheduleStableStateCallback();
    242  }
    243 
    244  return NS_OK;
    245 }
    246 
    247 nsresult LSSnapshot::GetLength(uint32_t* aResult) {
    248  AssertIsOnOwningThread();
    249  MOZ_ASSERT(mActor);
    250  MOZ_ASSERT(mInitialized);
    251  MOZ_ASSERT(!mSentFinish);
    252 
    253  MaybeScheduleStableStateCallback();
    254 
    255  if (mLoadState == LoadState::Partial) {
    256    *aResult = mLength;
    257  } else {
    258    *aResult = mValues.Count();
    259  }
    260 
    261  return NS_OK;
    262 }
    263 
    264 nsresult LSSnapshot::GetKey(uint32_t aIndex, nsAString& aResult) {
    265  AssertIsOnOwningThread();
    266  MOZ_ASSERT(mActor);
    267  MOZ_ASSERT(mInitialized);
    268  MOZ_ASSERT(!mSentFinish);
    269 
    270  MaybeScheduleStableStateCallback();
    271 
    272  nsresult rv = EnsureAllKeys();
    273  if (NS_WARN_IF(NS_FAILED(rv))) {
    274    return rv;
    275  }
    276 
    277  aResult.SetIsVoid(true);
    278  for (auto iter = mValues.ConstIter(); !iter.Done(); iter.Next()) {
    279    if (aIndex == 0) {
    280      aResult = iter.Key();
    281      return NS_OK;
    282    }
    283    aIndex--;
    284  }
    285 
    286  return NS_OK;
    287 }
    288 
    289 nsresult LSSnapshot::GetItem(const nsAString& aKey, nsAString& aResult) {
    290  AssertIsOnOwningThread();
    291  MOZ_ASSERT(mActor);
    292  MOZ_ASSERT(mInitialized);
    293  MOZ_ASSERT(!mSentFinish);
    294 
    295  MaybeScheduleStableStateCallback();
    296 
    297  nsString result;
    298  nsresult rv = GetItemInternal(aKey, Optional<nsString>(), result);
    299  if (NS_WARN_IF(NS_FAILED(rv))) {
    300    return rv;
    301  }
    302 
    303  aResult = result;
    304  return NS_OK;
    305 }
    306 
    307 nsresult LSSnapshot::GetKeys(nsTArray<nsString>& aKeys) {
    308  AssertIsOnOwningThread();
    309  MOZ_ASSERT(mActor);
    310  MOZ_ASSERT(mInitialized);
    311  MOZ_ASSERT(!mSentFinish);
    312 
    313  MaybeScheduleStableStateCallback();
    314 
    315  nsresult rv = EnsureAllKeys();
    316  if (NS_WARN_IF(NS_FAILED(rv))) {
    317    return rv;
    318  }
    319 
    320  AppendToArray(aKeys, mValues.Keys());
    321 
    322  return NS_OK;
    323 }
    324 
    325 nsresult LSSnapshot::SetItem(const nsAString& aKey, const nsAString& aValue,
    326                             LSNotifyInfo& aNotifyInfo) {
    327  GECKO_TRACE_SCOPE("dom::localstorage", "LSSnapshot::SetItem");
    328 
    329  AssertIsOnOwningThread();
    330  MOZ_ASSERT(mActor);
    331  MOZ_ASSERT(mInitialized);
    332  MOZ_ASSERT(!mSentFinish);
    333 
    334  MaybeScheduleStableStateCallback();
    335 
    336  nsString oldValue;
    337  nsresult rv =
    338      GetItemInternal(aKey, Optional<nsString>(nsString(aValue)), oldValue);
    339  if (NS_WARN_IF(NS_FAILED(rv))) {
    340    return rv;
    341  }
    342 
    343  bool changed;
    344  if (oldValue == aValue && oldValue.IsVoid() == aValue.IsVoid()) {
    345    changed = false;
    346  } else {
    347    changed = true;
    348 
    349    auto autoRevertValue = MakeScopeExit([&] {
    350      if (oldValue.IsVoid()) {
    351        mValues.Remove(aKey);
    352      } else {
    353        mValues.InsertOrUpdate(aKey, oldValue);
    354      }
    355    });
    356 
    357    // Anything that can fail must be done early before we start modifying the
    358    // state.
    359 
    360    Maybe<LSValue> oldValueFromString;
    361    if (mHasOtherProcessObservers) {
    362      oldValueFromString.emplace();
    363      if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) {
    364        return NS_ERROR_FAILURE;
    365      }
    366    }
    367 
    368    LSValue valueFromString;
    369    if (NS_WARN_IF(!valueFromString.InitFromString(aValue))) {
    370      return NS_ERROR_FAILURE;
    371    }
    372 
    373    int64_t delta = static_cast<int64_t>(aValue.Length()) -
    374                    static_cast<int64_t>(oldValue.Length());
    375 
    376    if (oldValue.IsVoid()) {
    377      delta += static_cast<int64_t>(aKey.Length());
    378    }
    379 
    380    {
    381      quota::ScopedLogExtraInfo scope{
    382          quota::ScopedLogExtraInfo::kTagContextTainted,
    383          "dom::localstorage::LSSnapshot::SetItem::UpdateUsage"_ns};
    384      GECKO_TRACE_SCOPE("dom::localstorage",
    385                        "LSSnapshot::SetItem::UpdateUsage");
    386      QM_TRY(MOZ_TO_RESULT(UpdateUsage(delta)), QM_PROPAGATE, QM_NO_CLEANUP,
    387             ([]() {
    388               static uint32_t counter = 0u;
    389               // To not flood the product maintenance diagnostics,
    390               // this predicate qualifies an error by returning true
    391               // only when counter and 1 + counter bits are like
    392               // 0111... and 1000... , i.e. when counter is one less
    393               // than a power of two.
    394               const bool result = 0u == (counter & (1u + counter));
    395               ++counter;
    396               return result;
    397             }));
    398    }
    399 
    400    if (oldValue.IsVoid() && mLoadState == LoadState::Partial) {
    401      mLength++;
    402    }
    403 
    404    if (mHasOtherProcessObservers) {
    405      MOZ_ASSERT(mWriteAndNotifyInfos);
    406      MOZ_ASSERT(oldValueFromString.isSome());
    407 
    408      LSSetItemAndNotifyInfo setItemAndNotifyInfo;
    409      setItemAndNotifyInfo.key() = aKey;
    410      setItemAndNotifyInfo.oldValue() = oldValueFromString.value();
    411      setItemAndNotifyInfo.value() = valueFromString;
    412 
    413      mWriteAndNotifyInfos->AppendElement(std::move(setItemAndNotifyInfo));
    414    } else {
    415      MOZ_ASSERT(mWriteOptimizer);
    416 
    417      if (oldValue.IsVoid()) {
    418        mWriteOptimizer->InsertItem(aKey, valueFromString);
    419      } else {
    420        mWriteOptimizer->UpdateItem(aKey, valueFromString);
    421      }
    422    }
    423 
    424    autoRevertValue.release();
    425  }
    426 
    427  aNotifyInfo.changed() = changed;
    428  aNotifyInfo.oldValue() = oldValue;
    429 
    430  return NS_OK;
    431 }
    432 
    433 nsresult LSSnapshot::RemoveItem(const nsAString& aKey,
    434                                LSNotifyInfo& aNotifyInfo) {
    435  AssertIsOnOwningThread();
    436  MOZ_ASSERT(mActor);
    437  MOZ_ASSERT(mInitialized);
    438  MOZ_ASSERT(!mSentFinish);
    439 
    440  MaybeScheduleStableStateCallback();
    441 
    442  nsString oldValue;
    443  nsresult rv =
    444      GetItemInternal(aKey, Optional<nsString>(VoidString()), oldValue);
    445  if (NS_WARN_IF(NS_FAILED(rv))) {
    446    return rv;
    447  }
    448 
    449  bool changed;
    450  if (oldValue.IsVoid()) {
    451    changed = false;
    452  } else {
    453    changed = true;
    454 
    455    auto autoRevertValue = MakeScopeExit([&] {
    456      MOZ_ASSERT(!oldValue.IsVoid());
    457      mValues.InsertOrUpdate(aKey, oldValue);
    458    });
    459 
    460    // Anything that can fail must be done early before we start modifying the
    461    // state.
    462 
    463    Maybe<LSValue> oldValueFromString;
    464    if (mHasOtherProcessObservers) {
    465      oldValueFromString.emplace();
    466      if (NS_WARN_IF(!oldValueFromString->InitFromString(oldValue))) {
    467        return NS_ERROR_FAILURE;
    468      }
    469    }
    470 
    471    int64_t delta = -(static_cast<int64_t>(aKey.Length()) +
    472                      static_cast<int64_t>(oldValue.Length()));
    473 
    474    DebugOnly<nsresult> rv = UpdateUsage(delta);
    475    MOZ_ASSERT(NS_SUCCEEDED(rv));
    476 
    477    if (mLoadState == LoadState::Partial) {
    478      mLength--;
    479    }
    480 
    481    if (mHasOtherProcessObservers) {
    482      MOZ_ASSERT(mWriteAndNotifyInfos);
    483      MOZ_ASSERT(oldValueFromString.isSome());
    484 
    485      LSRemoveItemAndNotifyInfo removeItemAndNotifyInfo;
    486      removeItemAndNotifyInfo.key() = aKey;
    487      removeItemAndNotifyInfo.oldValue() = oldValueFromString.value();
    488 
    489      mWriteAndNotifyInfos->AppendElement(std::move(removeItemAndNotifyInfo));
    490    } else {
    491      MOZ_ASSERT(mWriteOptimizer);
    492 
    493      mWriteOptimizer->DeleteItem(aKey);
    494    }
    495 
    496    autoRevertValue.release();
    497  }
    498 
    499  aNotifyInfo.changed() = changed;
    500  aNotifyInfo.oldValue() = oldValue;
    501 
    502  return NS_OK;
    503 }
    504 
    505 nsresult LSSnapshot::Clear(LSNotifyInfo& aNotifyInfo) {
    506  AssertIsOnOwningThread();
    507  MOZ_ASSERT(mActor);
    508  MOZ_ASSERT(mInitialized);
    509  MOZ_ASSERT(!mSentFinish);
    510 
    511  MaybeScheduleStableStateCallback();
    512 
    513  uint32_t length;
    514  if (mLoadState == LoadState::Partial) {
    515    length = mLength;
    516    MOZ_ASSERT(length);
    517 
    518    MOZ_ALWAYS_TRUE(mActor->SendLoaded());
    519 
    520    mLoadedItems.Clear();
    521    mUnknownItems.Clear();
    522    mLength = 0;
    523    mLoadState = LoadState::AllOrderedItems;
    524  } else {
    525    length = mValues.Count();
    526  }
    527 
    528  bool changed;
    529  if (!length) {
    530    changed = false;
    531  } else {
    532    changed = true;
    533 
    534    int64_t delta = 0;
    535    for (const auto& entry : mValues) {
    536      const nsAString& key = entry.GetKey();
    537      const nsString& value = entry.GetData();
    538 
    539      delta += -static_cast<int64_t>(key.Length()) -
    540               static_cast<int64_t>(value.Length());
    541    }
    542 
    543    DebugOnly<nsresult> rv = UpdateUsage(delta);
    544    MOZ_ASSERT(NS_SUCCEEDED(rv));
    545 
    546    mValues.Clear();
    547 
    548    if (mHasOtherProcessObservers) {
    549      MOZ_ASSERT(mWriteAndNotifyInfos);
    550 
    551      LSClearInfo clearInfo;
    552 
    553      mWriteAndNotifyInfos->AppendElement(std::move(clearInfo));
    554    } else {
    555      MOZ_ASSERT(mWriteOptimizer);
    556 
    557      mWriteOptimizer->Truncate();
    558    }
    559  }
    560 
    561  aNotifyInfo.changed() = changed;
    562 
    563  return NS_OK;
    564 }
    565 
    566 void LSSnapshot::MarkDirty() {
    567  AssertIsOnOwningThread();
    568  MOZ_ASSERT(mActor);
    569  MOZ_ASSERT(mInitialized);
    570  MOZ_ASSERT(!mSentFinish);
    571 
    572  if (mDirty) {
    573    return;
    574  }
    575 
    576  mDirty = true;
    577 
    578  if (!mExplicit && !mHasPendingStableStateCallback) {
    579    CancelIdleTimer();
    580 
    581    MOZ_ALWAYS_SUCCEEDS(Checkpoint());
    582 
    583    MOZ_ALWAYS_SUCCEEDS(Finish());
    584  } else {
    585    MOZ_ASSERT(!mHasPendingIdleTimerCallback);
    586  }
    587 }
    588 
    589 nsresult LSSnapshot::ExplicitCheckpoint() {
    590  AssertIsOnOwningThread();
    591  MOZ_ASSERT(mActor);
    592  MOZ_ASSERT(mExplicit);
    593  MOZ_ASSERT(!mHasPendingStableStateCallback);
    594  MOZ_ASSERT(!mHasPendingIdleTimerCallback);
    595  MOZ_ASSERT(mInitialized);
    596  MOZ_ASSERT(!mSentFinish);
    597 
    598  nsresult rv = Checkpoint(/* aSync */ true);
    599  if (NS_WARN_IF(NS_FAILED(rv))) {
    600    return rv;
    601  }
    602 
    603  return NS_OK;
    604 }
    605 
    606 nsresult LSSnapshot::ExplicitEnd() {
    607  AssertIsOnOwningThread();
    608  MOZ_ASSERT(mActor);
    609  MOZ_ASSERT(mExplicit);
    610  MOZ_ASSERT(!mHasPendingStableStateCallback);
    611  MOZ_ASSERT(!mHasPendingIdleTimerCallback);
    612  MOZ_ASSERT(mInitialized);
    613  MOZ_ASSERT(!mSentFinish);
    614 
    615  nsresult rv = Checkpoint();
    616  if (NS_WARN_IF(NS_FAILED(rv))) {
    617    return rv;
    618  }
    619 
    620  RefPtr<LSSnapshot> kungFuDeathGrip = this;
    621 
    622  rv = Finish(/* aSync */ true);
    623  if (NS_WARN_IF(NS_FAILED(rv))) {
    624    return rv;
    625  }
    626 
    627  return NS_OK;
    628 }
    629 
    630 int64_t LSSnapshot::GetUsage() const {
    631  AssertIsOnOwningThread();
    632  MOZ_ASSERT(mActor);
    633  MOZ_ASSERT(mInitialized);
    634  MOZ_ASSERT(!mSentFinish);
    635 
    636  return mUsage;
    637 }
    638 
    639 void LSSnapshot::ScheduleStableStateCallback() {
    640  AssertIsOnOwningThread();
    641  MOZ_ASSERT(mIdleTimer);
    642  MOZ_ASSERT(!mExplicit);
    643  MOZ_ASSERT(!mHasPendingStableStateCallback);
    644 
    645  CancelIdleTimer();
    646 
    647  nsCOMPtr<nsIRunnable> runnable = this;
    648  nsContentUtils::RunInStableState(runnable.forget());
    649 
    650  mHasPendingStableStateCallback = true;
    651 }
    652 
    653 void LSSnapshot::MaybeScheduleStableStateCallback() {
    654  AssertIsOnOwningThread();
    655 
    656  if (!mExplicit && !mHasPendingStableStateCallback) {
    657    ScheduleStableStateCallback();
    658  } else {
    659    MOZ_ASSERT(!mHasPendingIdleTimerCallback);
    660  }
    661 }
    662 
    663 nsresult LSSnapshot::GetItemInternal(const nsAString& aKey,
    664                                     const Optional<nsString>& aValue,
    665                                     nsAString& aResult) {
    666  AssertIsOnOwningThread();
    667  MOZ_ASSERT(mActor);
    668  MOZ_ASSERT(mInitialized);
    669  MOZ_ASSERT(!mSentFinish);
    670 
    671  nsString result;
    672 
    673  switch (mLoadState) {
    674    case LoadState::Partial: {
    675      if (mValues.Get(aKey, &result)) {
    676        MOZ_ASSERT(!result.IsVoid());
    677      } else if (mLoadedItems.Contains(aKey) || mUnknownItems.Contains(aKey)) {
    678        result.SetIsVoid(true);
    679      } else {
    680        LSValue value;
    681        nsTArray<LSItemInfo> itemInfos;
    682        if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
    683                nsString(aKey), &value, &itemInfos))) {
    684          return NS_ERROR_FAILURE;
    685        }
    686 
    687        result = value.AsString();
    688 
    689        if (result.IsVoid()) {
    690          mUnknownItems.Insert(aKey);
    691        } else {
    692          mLoadedItems.Insert(aKey);
    693          mValues.InsertOrUpdate(aKey, result);
    694 
    695          // mLoadedItems.Count()==mInitLength is checked below.
    696        }
    697 
    698        for (uint32_t i = 0; i < itemInfos.Length(); i++) {
    699          const LSItemInfo& itemInfo = itemInfos[i];
    700 
    701          mLoadedItems.Insert(itemInfo.key());
    702          mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString());
    703        }
    704 
    705        if (mLoadedItems.Count() == mInitLength) {
    706          mLoadedItems.Clear();
    707          mUnknownItems.Clear();
    708          mLength = 0;
    709          mLoadState = LoadState::AllUnorderedItems;
    710        }
    711      }
    712 
    713      if (aValue.WasPassed()) {
    714        const nsString& value = aValue.Value();
    715        if (!value.IsVoid()) {
    716          mValues.InsertOrUpdate(aKey, value);
    717        } else if (!result.IsVoid()) {
    718          mValues.Remove(aKey);
    719        }
    720      }
    721 
    722      break;
    723    }
    724 
    725    case LoadState::AllOrderedKeys: {
    726      if (mValues.Get(aKey, &result)) {
    727        if (result.IsVoid()) {
    728          LSValue value;
    729          nsTArray<LSItemInfo> itemInfos;
    730          if (NS_WARN_IF(!mActor->SendLoadValueAndMoreItems(
    731                  nsString(aKey), &value, &itemInfos))) {
    732            return NS_ERROR_FAILURE;
    733          }
    734 
    735          result = value.AsString();
    736 
    737          MOZ_ASSERT(!result.IsVoid());
    738 
    739          mLoadedItems.Insert(aKey);
    740          mValues.InsertOrUpdate(aKey, result);
    741 
    742          // mLoadedItems.Count()==mInitLength is checked below.
    743 
    744          for (uint32_t i = 0; i < itemInfos.Length(); i++) {
    745            const LSItemInfo& itemInfo = itemInfos[i];
    746 
    747            mLoadedItems.Insert(itemInfo.key());
    748            mValues.InsertOrUpdate(itemInfo.key(), itemInfo.value().AsString());
    749          }
    750 
    751          if (mLoadedItems.Count() == mInitLength) {
    752            mLoadedItems.Clear();
    753            MOZ_ASSERT(mLength == 0);
    754            mLoadState = LoadState::AllOrderedItems;
    755          }
    756        }
    757      } else {
    758        result.SetIsVoid(true);
    759      }
    760 
    761      if (aValue.WasPassed()) {
    762        const nsString& value = aValue.Value();
    763        if (!value.IsVoid()) {
    764          mValues.InsertOrUpdate(aKey, value);
    765        } else if (!result.IsVoid()) {
    766          mValues.Remove(aKey);
    767        }
    768      }
    769 
    770      break;
    771    }
    772 
    773    case LoadState::AllUnorderedItems:
    774    case LoadState::AllOrderedItems: {
    775      if (aValue.WasPassed()) {
    776        const nsString& value = aValue.Value();
    777        if (!value.IsVoid()) {
    778          mValues.WithEntryHandle(aKey, [&](auto&& entry) {
    779            if (entry) {
    780              result = std::exchange(entry.Data(), value);
    781            } else {
    782              result.SetIsVoid(true);
    783              entry.Insert(value);
    784            }
    785          });
    786        } else {
    787          if (auto entry = mValues.Lookup(aKey)) {
    788            result = entry.Data();
    789            MOZ_ASSERT(!result.IsVoid());
    790            entry.Remove();
    791          } else {
    792            result.SetIsVoid(true);
    793          }
    794        }
    795      } else {
    796        if (mValues.Get(aKey, &result)) {
    797          MOZ_ASSERT(!result.IsVoid());
    798        } else {
    799          result.SetIsVoid(true);
    800        }
    801      }
    802 
    803      break;
    804    }
    805 
    806    default:
    807      MOZ_CRASH("Bad state!");
    808  }
    809 
    810  aResult = result;
    811  return NS_OK;
    812 }
    813 
    814 nsresult LSSnapshot::EnsureAllKeys() {
    815  AssertIsOnOwningThread();
    816  MOZ_ASSERT(mActor);
    817  MOZ_ASSERT(mInitialized);
    818  MOZ_ASSERT(!mSentFinish);
    819  MOZ_ASSERT(mLoadState != LoadState::Initial);
    820 
    821  if (mLoadState == LoadState::AllOrderedKeys ||
    822      mLoadState == LoadState::AllOrderedItems) {
    823    return NS_OK;
    824  }
    825 
    826  nsTArray<nsString> keys;
    827  if (NS_WARN_IF(!mActor->SendLoadKeys(&keys))) {
    828    return NS_ERROR_FAILURE;
    829  }
    830 
    831  nsTHashMap<nsStringHashKey, nsString> newValues;
    832 
    833  for (auto key : keys) {
    834    newValues.InsertOrUpdate(key, VoidString());
    835  }
    836 
    837  if (mHasOtherProcessObservers) {
    838    MOZ_ASSERT(mWriteAndNotifyInfos);
    839 
    840    if (!mWriteAndNotifyInfos->IsEmpty()) {
    841      for (uint32_t index = 0; index < mWriteAndNotifyInfos->Length();
    842           index++) {
    843        const LSWriteAndNotifyInfo& writeAndNotifyInfo =
    844            mWriteAndNotifyInfos->ElementAt(index);
    845 
    846        switch (writeAndNotifyInfo.type()) {
    847          case LSWriteAndNotifyInfo::TLSSetItemAndNotifyInfo: {
    848            newValues.InsertOrUpdate(
    849                writeAndNotifyInfo.get_LSSetItemAndNotifyInfo().key(),
    850                VoidString());
    851            break;
    852          }
    853          case LSWriteAndNotifyInfo::TLSRemoveItemAndNotifyInfo: {
    854            newValues.Remove(
    855                writeAndNotifyInfo.get_LSRemoveItemAndNotifyInfo().key());
    856            break;
    857          }
    858          case LSWriteAndNotifyInfo::TLSClearInfo: {
    859            newValues.Clear();
    860            break;
    861          }
    862 
    863          default:
    864            MOZ_CRASH("Should never get here!");
    865        }
    866      }
    867    }
    868  } else {
    869    MOZ_ASSERT(mWriteOptimizer);
    870 
    871    if (mWriteOptimizer->HasWrites()) {
    872      nsTArray<LSWriteInfo> writeInfos;
    873      mWriteOptimizer->Enumerate(writeInfos);
    874 
    875      MOZ_ASSERT(!writeInfos.IsEmpty());
    876 
    877      for (uint32_t index = 0; index < writeInfos.Length(); index++) {
    878        const LSWriteInfo& writeInfo = writeInfos[index];
    879 
    880        switch (writeInfo.type()) {
    881          case LSWriteInfo::TLSSetItemInfo: {
    882            newValues.InsertOrUpdate(writeInfo.get_LSSetItemInfo().key(),
    883                                     VoidString());
    884            break;
    885          }
    886          case LSWriteInfo::TLSRemoveItemInfo: {
    887            newValues.Remove(writeInfo.get_LSRemoveItemInfo().key());
    888            break;
    889          }
    890          case LSWriteInfo::TLSClearInfo: {
    891            newValues.Clear();
    892            break;
    893          }
    894 
    895          default:
    896            MOZ_CRASH("Should never get here!");
    897        }
    898      }
    899    }
    900  }
    901 
    902  MOZ_ASSERT_IF(mLoadState == LoadState::AllUnorderedItems,
    903                newValues.Count() == mValues.Count());
    904 
    905  for (auto iter = newValues.Iter(); !iter.Done(); iter.Next()) {
    906    nsString value;
    907    if (mValues.Get(iter.Key(), &value)) {
    908      iter.Data() = value;
    909    }
    910  }
    911 
    912  mValues.SwapElements(newValues);
    913 
    914  if (mLoadState == LoadState::Partial) {
    915    mUnknownItems.Clear();
    916    mLength = 0;
    917    mLoadState = LoadState::AllOrderedKeys;
    918  } else {
    919    MOZ_ASSERT(mLoadState == LoadState::AllUnorderedItems);
    920 
    921    MOZ_ASSERT(mUnknownItems.Count() == 0);
    922    MOZ_ASSERT(mLength == 0);
    923    mLoadState = LoadState::AllOrderedItems;
    924  }
    925 
    926  return NS_OK;
    927 }
    928 
    929 nsresult LSSnapshot::UpdateUsage(int64_t aDelta) {
    930  AssertIsOnOwningThread();
    931  MOZ_ASSERT(mDatabase);
    932  MOZ_ASSERT(mActor);
    933  MOZ_ASSERT(mPeakUsage >= mUsage);
    934  MOZ_ASSERT(mInitialized);
    935  MOZ_ASSERT(!mSentFinish);
    936 
    937  int64_t newUsage = mUsage + aDelta;
    938  if (newUsage > mPeakUsage) {
    939    const int64_t minSize = newUsage - mPeakUsage;
    940 
    941    int64_t size;
    942    if (NS_WARN_IF(!mActor->SendIncreasePeakUsage(minSize, &size))) {
    943      return NS_ERROR_FAILURE;
    944    }
    945 
    946    MOZ_ASSERT(size >= 0);
    947 
    948    if (size == 0) {
    949      return NS_ERROR_FILE_NO_DEVICE_SPACE;
    950    }
    951 
    952    mPeakUsage += size;
    953  }
    954 
    955  mUsage = newUsage;
    956  return NS_OK;
    957 }
    958 
    959 nsresult LSSnapshot::Checkpoint(bool aSync) {
    960  AssertIsOnOwningThread();
    961  MOZ_ASSERT(mActor);
    962  MOZ_ASSERT(mInitialized);
    963  MOZ_ASSERT(!mSentFinish);
    964 
    965  if (mHasOtherProcessObservers) {
    966    MOZ_ASSERT(mWriteAndNotifyInfos);
    967 
    968    if (!mWriteAndNotifyInfos->IsEmpty()) {
    969      if (aSync) {
    970        MOZ_ALWAYS_TRUE(
    971            mActor->SendSyncCheckpointAndNotify(*mWriteAndNotifyInfos));
    972      } else {
    973        MOZ_ALWAYS_TRUE(
    974            mActor->SendAsyncCheckpointAndNotify(*mWriteAndNotifyInfos));
    975      }
    976 
    977      mWriteAndNotifyInfos->Clear();
    978    }
    979  } else {
    980    MOZ_ASSERT(mWriteOptimizer);
    981 
    982    if (mWriteOptimizer->HasWrites()) {
    983      nsTArray<LSWriteInfo> writeInfos;
    984      mWriteOptimizer->Enumerate(writeInfos);
    985 
    986      MOZ_ASSERT(!writeInfos.IsEmpty());
    987 
    988      if (aSync) {
    989        MOZ_ALWAYS_TRUE(mActor->SendSyncCheckpoint(writeInfos));
    990      } else {
    991        MOZ_ALWAYS_TRUE(mActor->SendAsyncCheckpoint(writeInfos));
    992      }
    993 
    994      mWriteOptimizer->Reset();
    995    }
    996  }
    997 
    998  return NS_OK;
    999 }
   1000 
   1001 nsresult LSSnapshot::Finish(bool aSync) {
   1002  AssertIsOnOwningThread();
   1003  MOZ_ASSERT(mDatabase);
   1004  MOZ_ASSERT(mActor);
   1005  MOZ_ASSERT(mInitialized);
   1006  MOZ_ASSERT(!mSentFinish);
   1007 
   1008  if (aSync) {
   1009    MOZ_ALWAYS_TRUE(mActor->SendSyncFinish());
   1010  } else {
   1011    MOZ_ALWAYS_TRUE(mActor->SendAsyncFinish());
   1012  }
   1013 
   1014  mDatabase->NoteFinishedSnapshot(this);
   1015 
   1016 #ifdef DEBUG
   1017  mSentFinish = true;
   1018 #endif
   1019 
   1020  // Clear the self reference added in Init method.
   1021  MOZ_ASSERT(mSelfRef);
   1022  mSelfRef = nullptr;
   1023 
   1024  return NS_OK;
   1025 }
   1026 
   1027 void LSSnapshot::CancelIdleTimer() {
   1028  AssertIsOnOwningThread();
   1029  MOZ_ASSERT(mIdleTimer);
   1030 
   1031  if (mHasPendingIdleTimerCallback) {
   1032    MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
   1033    mHasPendingIdleTimerCallback = false;
   1034  }
   1035 }
   1036 
   1037 // static
   1038 void LSSnapshot::IdleTimerCallback(nsITimer* aTimer, void* aClosure) {
   1039  MOZ_ASSERT(aTimer);
   1040 
   1041  auto* self = static_cast<LSSnapshot*>(aClosure);
   1042  MOZ_ASSERT(self);
   1043  MOZ_ASSERT(self->mIdleTimer);
   1044  MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer));
   1045  MOZ_ASSERT(!self->mHasPendingStableStateCallback);
   1046  MOZ_ASSERT(self->mHasPendingIdleTimerCallback);
   1047 
   1048  self->mHasPendingIdleTimerCallback = false;
   1049 
   1050  MOZ_ALWAYS_SUCCEEDS(self->Finish());
   1051 }
   1052 
   1053 NS_IMPL_ISUPPORTS(LSSnapshot, nsIRunnable)
   1054 
   1055 NS_IMETHODIMP
   1056 LSSnapshot::Run() {
   1057  AssertIsOnOwningThread();
   1058  MOZ_ASSERT(!mExplicit);
   1059  MOZ_ASSERT(mHasPendingStableStateCallback);
   1060  MOZ_ASSERT(!mHasPendingIdleTimerCallback);
   1061 
   1062  mHasPendingStableStateCallback = false;
   1063 
   1064  MOZ_ALWAYS_SUCCEEDS(Checkpoint());
   1065 
   1066  // 1. The unused pre-incremented snapshot peak usage can't be undone when
   1067  //    there are other snapshots for the same database. We only add a pending
   1068  //    usage delta when a snapshot finishes and usage deltas are then applied
   1069  //    when the last database becomes inactive.
   1070  // 2. If there's a snapshot with pre-incremented peak usage, the next
   1071  //    snapshot will use that as a base for its usage.
   1072  // 3. When a task for given snapshot finishes, we try to reuse the snapshot
   1073  //    by only checkpointing the snapshot and delaying the finish by a timer.
   1074  // 4. If two or more tabs for the same origin use localStorage periodically
   1075  //    at the same time the usage gradually grows until it hits the quota
   1076  //    limit.
   1077  // 5. We prevent that from happening by finishing the snapshot immediatelly
   1078  //    if there are databases in other processess.
   1079 
   1080  if (mDirty || mHasOtherProcessDatabases ||
   1081      !Preferences::GetBool("dom.storage.snapshot_reusing")) {
   1082    MOZ_ALWAYS_SUCCEEDS(Finish());
   1083  } else {
   1084    MOZ_ASSERT(mIdleTimer);
   1085 
   1086    MOZ_ALWAYS_SUCCEEDS(mIdleTimer->InitWithNamedFuncCallback(
   1087        IdleTimerCallback, this,
   1088        StaticPrefs::dom_storage_snapshot_idle_timeout_ms(),
   1089        nsITimer::TYPE_ONE_SHOT, "LSSnapshot::IdleTimerCallback"_ns));
   1090 
   1091    mHasPendingIdleTimerCallback = true;
   1092  }
   1093 
   1094  return NS_OK;
   1095 }
   1096 
   1097 }  // namespace mozilla::dom