tor-browser

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

LocalStorageCache.cpp (17648B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "LocalStorageCache.h"
      8 
      9 #include "LocalStorageManager.h"
     10 #include "Storage.h"
     11 #include "StorageDBThread.h"
     12 #include "StorageIPC.h"
     13 #include "StorageUtils.h"
     14 #include "mozilla/glean/DomStorageMetrics.h"
     15 #include "nsDOMString.h"
     16 #include "nsProxyRelease.h"
     17 #include "nsThreadUtils.h"
     18 #include "nsXULAppAPI.h"
     19 
     20 namespace mozilla::dom {
     21 
     22 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
     23 
     24 namespace {
     25 
     26 const uint32_t kDefaultSet = 0;
     27 const uint32_t kSessionSet = 1;
     28 
     29 inline uint32_t GetDataSetIndex(bool aPrivateBrowsing,
     30                                bool aSessionScopedOrLess) {
     31  if (!aPrivateBrowsing && aSessionScopedOrLess) {
     32    return kSessionSet;
     33  }
     34 
     35  return kDefaultSet;
     36 }
     37 
     38 inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
     39  // A session only mode doesn't exist anymore, so having a separate data set
     40  // for it here is basically useless. This code is only kept until we remove
     41  // the old / legacy LocalStorage implementation.
     42  return GetDataSetIndex(aStorage->IsPrivateBrowsing(),
     43                         aStorage->IsPrivateBrowsingOrLess());
     44 }
     45 
     46 }  // namespace
     47 
     48 // LocalStorageCacheBridge
     49 
     50 NS_IMPL_ADDREF(LocalStorageCacheBridge)
     51 
     52 // Since there is no consumer of return value of Release, we can turn this
     53 // method to void to make implementation of asynchronous
     54 // LocalStorageCache::Release much simpler.
     55 NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
     56  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
     57  nsrefcnt count = --mRefCnt;
     58  NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
     59  if (0 == count) {
     60    mRefCnt = 1; /* stabilize */
     61    /* enable this to find non-threadsafe destructors: */
     62    /* NS_ASSERT_OWNINGTHREAD(_class); */
     63    delete (this);
     64  }
     65 }
     66 
     67 // LocalStorageCache
     68 
     69 LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
     70    : mActor(nullptr),
     71      mOriginNoSuffix(*aOriginNoSuffix),
     72      mMonitor("LocalStorageCache"),
     73      mLoaded(false),
     74      mLoadResult(NS_OK),
     75      mInitialized(false),
     76      mPersistent(false),
     77      mPreloadTelemetryRecorded(false) {
     78  MOZ_COUNT_CTOR(LocalStorageCache);
     79 }
     80 
     81 LocalStorageCache::~LocalStorageCache() {
     82  if (mActor) {
     83    mActor->SendDeleteMeInternal();
     84    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
     85  }
     86 
     87  if (mManager) {
     88    mManager->DropCache(this);
     89  }
     90 
     91  MOZ_COUNT_DTOR(LocalStorageCache);
     92 }
     93 
     94 void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
     95  AssertIsOnOwningThread();
     96  MOZ_ASSERT(aActor);
     97  MOZ_ASSERT(!mActor);
     98 
     99  mActor = aActor;
    100 }
    101 
    102 NS_IMETHODIMP_(void)
    103 LocalStorageCache::Release(void) {
    104  // We must actually release on the main thread since the cache removes it
    105  // self from the manager's hash table.  And we don't want to lock access to
    106  // that hash table.
    107  if (NS_IsMainThread()) {
    108    LocalStorageCacheBridge::Release();
    109    return;
    110  }
    111 
    112  RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
    113      NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
    114                                 static_cast<LocalStorageCacheBridge*>(this),
    115                                 &LocalStorageCacheBridge::Release);
    116 
    117  nsresult rv = NS_DispatchToMainThread(event);
    118  if (NS_FAILED(rv)) {
    119    NS_WARNING("LocalStorageCache::Release() on a non-main thread");
    120    LocalStorageCacheBridge::Release();
    121  }
    122 }
    123 
    124 void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
    125                             nsIPrincipal* aPrincipal,
    126                             const nsACString& aQuotaOriginScope) {
    127  MOZ_ASSERT(!aQuotaOriginScope.IsEmpty());
    128 
    129  if (mInitialized) {
    130    return;
    131  }
    132 
    133  mInitialized = true;
    134  aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
    135  mPrivateBrowsingId = aPrincipal->GetPrivateBrowsingId();
    136  mPersistent = aPersistent;
    137  mQuotaOriginScope = aQuotaOriginScope;
    138 
    139  if (mPersistent) {
    140    mManager = aManager;
    141    Preload();
    142  }
    143 
    144  // Check the quota string has (or has not) the identical origin suffix as
    145  // this storage cache is bound to.
    146  MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
    147  MOZ_ASSERT(mOriginSuffix.IsEmpty() !=
    148             StringBeginsWith(mQuotaOriginScope, "^"_ns));
    149 
    150  mUsage = aManager->GetOriginUsage(mQuotaOriginScope, mPrivateBrowsingId);
    151 }
    152 
    153 void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
    154                                        const nsAString& aKey,
    155                                        const nsAString& aOldValue,
    156                                        const nsAString& aNewValue) {
    157  AssertIsOnOwningThread();
    158  MOZ_ASSERT(aStorage);
    159 
    160  if (!mActor) {
    161    return;
    162  }
    163 
    164  // We want to send a message to the parent in order to broadcast the
    165  // StorageEvent correctly to any child process.
    166 
    167  (void)mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue, aNewValue);
    168 }
    169 
    170 inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
    171  return mPersistent && (aStorage->IsPrivateBrowsing() ||
    172                         !aStorage->IsPrivateBrowsingOrLess());
    173 }
    174 
    175 const nsCString LocalStorageCache::Origin() const {
    176  return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
    177 }
    178 
    179 LocalStorageCache::Data& LocalStorageCache::DataSet(
    180    const LocalStorage* aStorage) {
    181  return mData[GetDataSetIndex(aStorage)];
    182 }
    183 
    184 bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
    185                                          int64_t aDelta,
    186                                          const MutationSource aSource) {
    187  return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
    188 }
    189 
    190 bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
    191                                          const int64_t aDelta,
    192                                          const MutationSource aSource) {
    193  // Check limit per this origin
    194  Data& data = mData[aGetDataSetIndex];
    195  uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
    196  if (aSource == ContentMutation && aDelta > 0 &&
    197      newOriginUsage > LocalStorageManager::GetOriginQuota()) {
    198    return false;
    199  }
    200 
    201  // Now check eTLD+1 limit
    202  if (mUsage &&
    203      !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
    204    return false;
    205  }
    206 
    207  // Update size in our data set
    208  data.mOriginQuotaUsage = newOriginUsage;
    209  return true;
    210 }
    211 
    212 void LocalStorageCache::Preload() {
    213  if (mLoaded || !mPersistent) {
    214    return;
    215  }
    216 
    217  StorageDBChild* storageChild =
    218      StorageDBChild::GetOrCreate(mPrivateBrowsingId);
    219  if (!storageChild) {
    220    mLoaded = true;
    221    mLoadResult = NS_ERROR_FAILURE;
    222    return;
    223  }
    224 
    225  storageChild->AsyncPreload(this);
    226 }
    227 
    228 void LocalStorageCache::WaitForPreload() {
    229  if (!mPersistent) {
    230    return;
    231  }
    232 
    233  bool loaded = mLoaded;
    234 
    235  // Telemetry of rates of pending preloads
    236  if (!mPreloadTelemetryRecorded) {
    237    mPreloadTelemetryRecorded = true;
    238    glean::localdomstorage::preload_pending_on_first_access
    239        .EnumGet(static_cast<
    240                 glean::localdomstorage::PreloadPendingOnFirstAccessLabel>(
    241            !loaded))
    242        .Add();
    243  }
    244 
    245  if (loaded) {
    246    return;
    247  }
    248 
    249  // If preload already started (i.e. we got some first data, but not all)
    250  // SyncPreload will just wait for it to finish rather then synchronously
    251  // read from the database.  It seems to me more optimal.
    252 
    253  // TODO place for A/B testing (force main thread load vs. let preload finish)
    254 
    255  // No need to check sDatabase for being non-null since preload is either
    256  // done before we've shut the DB down or when the DB could not start,
    257  // preload has not even be started.
    258  StorageDBChild::Get(mPrivateBrowsingId)->SyncPreload(this);
    259 }
    260 
    261 nsresult LocalStorageCache::GetLength(const LocalStorage* aStorage,
    262                                      uint32_t* aRetval) {
    263  if (Persist(aStorage)) {
    264    WaitForPreload();
    265    if (NS_FAILED(mLoadResult)) {
    266      return mLoadResult;
    267    }
    268  }
    269 
    270  *aRetval = DataSet(aStorage).mKeys.Count();
    271  return NS_OK;
    272 }
    273 
    274 nsresult LocalStorageCache::GetKey(const LocalStorage* aStorage,
    275                                   uint32_t aIndex, nsAString& aRetval) {
    276  // XXX: This does a linear search for the key at index, which would
    277  // suck if there's a large numer of indexes. Do we care? If so,
    278  // maybe we need to have a lazily populated key array here or
    279  // something?
    280  if (Persist(aStorage)) {
    281    WaitForPreload();
    282    if (NS_FAILED(mLoadResult)) {
    283      return mLoadResult;
    284    }
    285  }
    286 
    287  aRetval.SetIsVoid(true);
    288  for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
    289    if (aIndex == 0) {
    290      aRetval = iter.Key();
    291      break;
    292    }
    293    aIndex--;
    294  }
    295 
    296  return NS_OK;
    297 }
    298 
    299 void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
    300                                nsTArray<nsString>& aKeys) {
    301  if (Persist(aStorage)) {
    302    WaitForPreload();
    303  }
    304 
    305  if (NS_FAILED(mLoadResult)) {
    306    return;
    307  }
    308 
    309  AppendToArray(aKeys, DataSet(aStorage).mKeys.Keys());
    310 }
    311 
    312 nsresult LocalStorageCache::GetItem(const LocalStorage* aStorage,
    313                                    const nsAString& aKey, nsAString& aRetval) {
    314  if (Persist(aStorage)) {
    315    WaitForPreload();
    316    if (NS_FAILED(mLoadResult)) {
    317      return mLoadResult;
    318    }
    319  }
    320 
    321  // not using AutoString since we don't want to copy buffer to result
    322  nsString value;
    323  if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
    324    SetDOMStringToNull(value);
    325  }
    326 
    327  aRetval = value;
    328 
    329  return NS_OK;
    330 }
    331 
    332 nsresult LocalStorageCache::SetItem(const LocalStorage* aStorage,
    333                                    const nsAString& aKey,
    334                                    const nsAString& aValue, nsString& aOld,
    335                                    const MutationSource aSource) {
    336  // Size of the cache that will change after this action.
    337  int64_t delta = 0;
    338 
    339  if (Persist(aStorage)) {
    340    WaitForPreload();
    341    if (NS_FAILED(mLoadResult)) {
    342      return mLoadResult;
    343    }
    344  }
    345 
    346  Data& data = DataSet(aStorage);
    347  if (!data.mKeys.Get(aKey, &aOld)) {
    348    SetDOMStringToNull(aOld);
    349 
    350    // We only consider key size if the key doesn't exist before.
    351    delta += static_cast<int64_t>(aKey.Length());
    352  }
    353 
    354  delta += static_cast<int64_t>(aValue.Length()) -
    355           static_cast<int64_t>(aOld.Length());
    356 
    357  if (!ProcessUsageDelta(aStorage, delta, aSource)) {
    358    return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
    359  }
    360 
    361  if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
    362    return NS_SUCCESS_DOM_NO_OPERATION;
    363  }
    364 
    365  data.mKeys.InsertOrUpdate(aKey, aValue);
    366 
    367  if (aSource != ContentMutation) {
    368    return NS_OK;
    369  }
    370 
    371 #if !defined(MOZ_WIDGET_ANDROID)
    372  NotifyObservers(aStorage, aKey, aOld, aValue);
    373 #endif
    374 
    375  if (Persist(aStorage)) {
    376    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
    377    if (!storageChild) {
    378      NS_ERROR(
    379          "Writing to localStorage after the database has been shut down"
    380          ", data lose!");
    381      return NS_ERROR_NOT_INITIALIZED;
    382    }
    383 
    384    if (DOMStringIsNull(aOld)) {
    385      return storageChild->AsyncAddItem(this, aKey, aValue);
    386    }
    387 
    388    return storageChild->AsyncUpdateItem(this, aKey, aValue);
    389  }
    390 
    391  return NS_OK;
    392 }
    393 
    394 nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
    395                                       const nsAString& aKey, nsString& aOld,
    396                                       const MutationSource aSource) {
    397  if (Persist(aStorage)) {
    398    WaitForPreload();
    399    if (NS_FAILED(mLoadResult)) {
    400      return mLoadResult;
    401    }
    402  }
    403 
    404  Data& data = DataSet(aStorage);
    405  if (!data.mKeys.Get(aKey, &aOld)) {
    406    SetDOMStringToNull(aOld);
    407    return NS_SUCCESS_DOM_NO_OPERATION;
    408  }
    409 
    410  // Recalculate the cached data size
    411  const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
    412                          static_cast<int64_t>(aKey.Length()));
    413  (void)ProcessUsageDelta(aStorage, delta, aSource);
    414  data.mKeys.Remove(aKey);
    415 
    416  if (aSource != ContentMutation) {
    417    return NS_OK;
    418  }
    419 
    420 #if !defined(MOZ_WIDGET_ANDROID)
    421  NotifyObservers(aStorage, aKey, aOld, VoidString());
    422 #endif
    423 
    424  if (Persist(aStorage)) {
    425    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
    426    if (!storageChild) {
    427      NS_ERROR(
    428          "Writing to localStorage after the database has been shut down"
    429          ", data lose!");
    430      return NS_ERROR_NOT_INITIALIZED;
    431    }
    432 
    433    return storageChild->AsyncRemoveItem(this, aKey);
    434  }
    435 
    436  return NS_OK;
    437 }
    438 
    439 nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
    440                                  const MutationSource aSource) {
    441  bool refresh = false;
    442  if (Persist(aStorage)) {
    443    // We need to preload all data (know the size) before we can proceeed
    444    // to correctly decrease cached usage number.
    445    // XXX as in case of unload, this is not technically needed now, but
    446    // after super-scope quota introduction we have to do this.  Get telemetry
    447    // right now.
    448    WaitForPreload();
    449    if (NS_FAILED(mLoadResult)) {
    450      // When we failed to load data from the database, force delete of the
    451      // scope data and make use of the storage possible again.
    452      refresh = true;
    453      mLoadResult = NS_OK;
    454    }
    455  }
    456 
    457  Data& data = DataSet(aStorage);
    458  bool hadData = !!data.mKeys.Count();
    459 
    460  if (hadData) {
    461    (void)ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
    462    data.mKeys.Clear();
    463  }
    464 
    465  if (aSource != ContentMutation) {
    466    return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
    467  }
    468 
    469 #if !defined(MOZ_WIDGET_ANDROID)
    470  if (hadData) {
    471    NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
    472  }
    473 #endif
    474 
    475  if (Persist(aStorage) && (refresh || hadData)) {
    476    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
    477    if (!storageChild) {
    478      NS_ERROR(
    479          "Writing to localStorage after the database has been shut down"
    480          ", data lose!");
    481      return NS_ERROR_NOT_INITIALIZED;
    482    }
    483 
    484    return storageChild->AsyncClear(this);
    485  }
    486 
    487  return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
    488 }
    489 
    490 int64_t LocalStorageCache::GetOriginQuotaUsage(
    491    const LocalStorage* aStorage) const {
    492  return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
    493 }
    494 
    495 void LocalStorageCache::UnloadItems(uint32_t aUnloadFlags) {
    496  if (aUnloadFlags & kUnloadDefault) {
    497    // Must wait for preload to pass correct usage to ProcessUsageDelta
    498    // XXX this is not technically needed right now since there is just
    499    // per-origin isolated quota handling, but when we introduce super-
    500    // -scope quotas, we have to do this.  Better to start getting
    501    // telemetry right now.
    502    WaitForPreload();
    503 
    504    mData[kDefaultSet].mKeys.Clear();
    505    ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
    506  }
    507 
    508  if (aUnloadFlags & kUnloadSession) {
    509    mData[kSessionSet].mKeys.Clear();
    510    ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
    511  }
    512 
    513 #ifdef DOM_STORAGE_TESTS
    514  if (aUnloadFlags & kTestReload) {
    515    WaitForPreload();
    516 
    517    mData[kDefaultSet].mKeys.Clear();
    518    mLoaded = false;  // This is only used in testing code
    519    Preload();
    520  }
    521 #endif
    522 }
    523 
    524 // LocalStorageCacheBridge
    525 
    526 uint32_t LocalStorageCache::LoadedCount() {
    527  MonitorAutoLock monitor(mMonitor);
    528  Data& data = mData[kDefaultSet];
    529  return data.mKeys.Count();
    530 }
    531 
    532 bool LocalStorageCache::LoadItem(const nsAString& aKey,
    533                                 const nsAString& aValue) {
    534  MonitorAutoLock monitor(mMonitor);
    535  if (mLoaded) {
    536    return false;
    537  }
    538 
    539  Data& data = mData[kDefaultSet];
    540  data.mKeys.LookupOrInsertWith(aKey, [&] {
    541    data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
    542    return nsString(aValue);
    543  });
    544  return true;
    545 }
    546 
    547 void LocalStorageCache::LoadDone(nsresult aRv) {
    548  MonitorAutoLock monitor(mMonitor);
    549  mLoadResult = aRv;
    550  mLoaded = true;
    551  monitor.Notify();
    552 }
    553 
    554 void LocalStorageCache::LoadWait() {
    555  MonitorAutoLock monitor(mMonitor);
    556  while (!mLoaded) {
    557    monitor.Wait();
    558  }
    559 }
    560 
    561 // StorageUsage
    562 
    563 StorageUsage::StorageUsage(const nsACString& aOriginScope)
    564    : mOriginScope(aOriginScope) {
    565  mUsage[kDefaultSet] = mUsage[kSessionSet] = 0LL;
    566 }
    567 
    568 namespace {
    569 
    570 class LoadUsageRunnable : public Runnable {
    571 public:
    572  LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
    573      : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
    574 
    575 private:
    576  int64_t* mTarget;
    577  int64_t mDelta;
    578 
    579  NS_IMETHOD Run() override {
    580    *mTarget = mDelta;
    581    return NS_OK;
    582  }
    583 };
    584 
    585 }  // namespace
    586 
    587 void StorageUsage::LoadUsage(const int64_t aUsage) {
    588  // Using kDefaultSet index since it is the index for the persitent data
    589  // stored in the database we have just loaded usage for.
    590  if (!NS_IsMainThread()) {
    591    // In single process scenario we get this call from the DB thread
    592    RefPtr<LoadUsageRunnable> r =
    593        new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
    594    NS_DispatchToMainThread(r);
    595  } else {
    596    // On a child process we get this on the main thread already
    597    mUsage[kDefaultSet] += aUsage;
    598  }
    599 }
    600 
    601 bool StorageUsage::CheckAndSetETLD1UsageDelta(
    602    uint32_t aDataSetIndex, const int64_t aDelta,
    603    const LocalStorageCache::MutationSource aSource) {
    604  MOZ_ASSERT(NS_IsMainThread());
    605 
    606  int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
    607  if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
    608      newUsage > LocalStorageManager::GetSiteQuota()) {
    609    return false;
    610  }
    611 
    612  mUsage[aDataSetIndex] = newUsage;
    613  return true;
    614 }
    615 
    616 }  // namespace mozilla::dom