forked from mirrors/gecko-dev
		
	This prevents copies and avoids the hack we have to avoid this, which
right now is using nsDependent{C,}String.
Non-virtual actors can still use `nsString` if they need to on the
receiving end.
Differential Revision: https://phabricator.services.mozilla.com/D152519
		
	
			
		
			
				
	
	
		
			615 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			615 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=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 "LocalStorageCache.h"
 | 
						|
 | 
						|
#include "Storage.h"
 | 
						|
#include "StorageDBThread.h"
 | 
						|
#include "StorageIPC.h"
 | 
						|
#include "StorageUtils.h"
 | 
						|
#include "LocalStorageManager.h"
 | 
						|
 | 
						|
#include "nsDOMString.h"
 | 
						|
#include "nsXULAppAPI.h"
 | 
						|
#include "mozilla/Unused.h"
 | 
						|
#include "nsProxyRelease.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
 | 
						|
namespace mozilla::dom {
 | 
						|
 | 
						|
#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
const uint32_t kDefaultSet = 0;
 | 
						|
const uint32_t kSessionSet = 1;
 | 
						|
 | 
						|
inline uint32_t GetDataSetIndex(bool aPrivateBrowsing,
 | 
						|
                                bool aSessionScopedOrLess) {
 | 
						|
  if (!aPrivateBrowsing && aSessionScopedOrLess) {
 | 
						|
    return kSessionSet;
 | 
						|
  }
 | 
						|
 | 
						|
  return kDefaultSet;
 | 
						|
}
 | 
						|
 | 
						|
inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
 | 
						|
  return GetDataSetIndex(aStorage->IsPrivateBrowsing(),
 | 
						|
                         aStorage->IsSessionScopedOrLess());
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
// LocalStorageCacheBridge
 | 
						|
 | 
						|
NS_IMPL_ADDREF(LocalStorageCacheBridge)
 | 
						|
 | 
						|
// Since there is no consumer of return value of Release, we can turn this
 | 
						|
// method to void to make implementation of asynchronous
 | 
						|
// LocalStorageCache::Release much simpler.
 | 
						|
NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
 | 
						|
  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
 | 
						|
  nsrefcnt count = --mRefCnt;
 | 
						|
  NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
 | 
						|
  if (0 == count) {
 | 
						|
    mRefCnt = 1; /* stabilize */
 | 
						|
    /* enable this to find non-threadsafe destructors: */
 | 
						|
    /* NS_ASSERT_OWNINGTHREAD(_class); */
 | 
						|
    delete (this);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// LocalStorageCache
 | 
						|
 | 
						|
LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
 | 
						|
    : mActor(nullptr),
 | 
						|
      mOriginNoSuffix(*aOriginNoSuffix),
 | 
						|
      mMonitor("LocalStorageCache"),
 | 
						|
      mLoaded(false),
 | 
						|
      mLoadResult(NS_OK),
 | 
						|
      mInitialized(false),
 | 
						|
      mPersistent(false),
 | 
						|
      mPreloadTelemetryRecorded(false) {
 | 
						|
  MOZ_COUNT_CTOR(LocalStorageCache);
 | 
						|
}
 | 
						|
 | 
						|
LocalStorageCache::~LocalStorageCache() {
 | 
						|
  if (mActor) {
 | 
						|
    mActor->SendDeleteMeInternal();
 | 
						|
    MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
 | 
						|
  }
 | 
						|
 | 
						|
  if (mManager) {
 | 
						|
    mManager->DropCache(this);
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_COUNT_DTOR(LocalStorageCache);
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aActor);
 | 
						|
  MOZ_ASSERT(!mActor);
 | 
						|
 | 
						|
  mActor = aActor;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP_(void)
 | 
						|
LocalStorageCache::Release(void) {
 | 
						|
  // We must actually release on the main thread since the cache removes it
 | 
						|
  // self from the manager's hash table.  And we don't want to lock access to
 | 
						|
  // that hash table.
 | 
						|
  if (NS_IsMainThread()) {
 | 
						|
    LocalStorageCacheBridge::Release();
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
 | 
						|
      NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
 | 
						|
                                 static_cast<LocalStorageCacheBridge*>(this),
 | 
						|
                                 &LocalStorageCacheBridge::Release);
 | 
						|
 | 
						|
  nsresult rv = NS_DispatchToMainThread(event);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    NS_WARNING("LocalStorageCache::Release() on a non-main thread");
 | 
						|
    LocalStorageCacheBridge::Release();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
 | 
						|
                             nsIPrincipal* aPrincipal,
 | 
						|
                             const nsACString& aQuotaOriginScope) {
 | 
						|
  MOZ_ASSERT(!aQuotaOriginScope.IsEmpty());
 | 
						|
 | 
						|
  if (mInitialized) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mInitialized = true;
 | 
						|
  aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
 | 
						|
  mPrivateBrowsingId = aPrincipal->GetPrivateBrowsingId();
 | 
						|
  mPersistent = aPersistent;
 | 
						|
  mQuotaOriginScope = aQuotaOriginScope;
 | 
						|
 | 
						|
  if (mPersistent) {
 | 
						|
    mManager = aManager;
 | 
						|
    Preload();
 | 
						|
  }
 | 
						|
 | 
						|
  // Check the quota string has (or has not) the identical origin suffix as
 | 
						|
  // this storage cache is bound to.
 | 
						|
  MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
 | 
						|
  MOZ_ASSERT(mOriginSuffix.IsEmpty() !=
 | 
						|
             StringBeginsWith(mQuotaOriginScope, "^"_ns));
 | 
						|
 | 
						|
  mUsage = aManager->GetOriginUsage(mQuotaOriginScope, mPrivateBrowsingId);
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
 | 
						|
                                        const nsAString& aKey,
 | 
						|
                                        const nsAString& aOldValue,
 | 
						|
                                        const nsAString& aNewValue) {
 | 
						|
  AssertIsOnOwningThread();
 | 
						|
  MOZ_ASSERT(aStorage);
 | 
						|
 | 
						|
  if (!mActor) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // We want to send a message to the parent in order to broadcast the
 | 
						|
  // StorageEvent correctly to any child process.
 | 
						|
 | 
						|
  Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
 | 
						|
                               aNewValue);
 | 
						|
}
 | 
						|
 | 
						|
inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
 | 
						|
  return mPersistent &&
 | 
						|
         (aStorage->IsPrivateBrowsing() || !aStorage->IsSessionScopedOrLess());
 | 
						|
}
 | 
						|
 | 
						|
const nsCString LocalStorageCache::Origin() const {
 | 
						|
  return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
 | 
						|
}
 | 
						|
 | 
						|
LocalStorageCache::Data& LocalStorageCache::DataSet(
 | 
						|
    const LocalStorage* aStorage) {
 | 
						|
  return mData[GetDataSetIndex(aStorage)];
 | 
						|
}
 | 
						|
 | 
						|
bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
 | 
						|
                                          int64_t aDelta,
 | 
						|
                                          const MutationSource aSource) {
 | 
						|
  return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
 | 
						|
}
 | 
						|
 | 
						|
bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
 | 
						|
                                          const int64_t aDelta,
 | 
						|
                                          const MutationSource aSource) {
 | 
						|
  // Check limit per this origin
 | 
						|
  Data& data = mData[aGetDataSetIndex];
 | 
						|
  uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
 | 
						|
  if (aSource == ContentMutation && aDelta > 0 &&
 | 
						|
      newOriginUsage > LocalStorageManager::GetOriginQuota()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Now check eTLD+1 limit
 | 
						|
  if (mUsage &&
 | 
						|
      !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Update size in our data set
 | 
						|
  data.mOriginQuotaUsage = newOriginUsage;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::Preload() {
 | 
						|
  if (mLoaded || !mPersistent) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  StorageDBChild* storageChild =
 | 
						|
      StorageDBChild::GetOrCreate(mPrivateBrowsingId);
 | 
						|
  if (!storageChild) {
 | 
						|
    mLoaded = true;
 | 
						|
    mLoadResult = NS_ERROR_FAILURE;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  storageChild->AsyncPreload(this);
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID) {
 | 
						|
  if (!mPersistent) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  bool loaded = mLoaded;
 | 
						|
 | 
						|
  // Telemetry of rates of pending preloads
 | 
						|
  if (!mPreloadTelemetryRecorded) {
 | 
						|
    mPreloadTelemetryRecorded = true;
 | 
						|
    Telemetry::Accumulate(
 | 
						|
        Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, !loaded);
 | 
						|
  }
 | 
						|
 | 
						|
  if (loaded) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // Measure which operation blocks and for how long
 | 
						|
  Telemetry::RuntimeAutoTimer timer(aTelemetryID);
 | 
						|
 | 
						|
  // If preload already started (i.e. we got some first data, but not all)
 | 
						|
  // SyncPreload will just wait for it to finish rather then synchronously
 | 
						|
  // read from the database.  It seems to me more optimal.
 | 
						|
 | 
						|
  // TODO place for A/B testing (force main thread load vs. let preload finish)
 | 
						|
 | 
						|
  // No need to check sDatabase for being non-null since preload is either
 | 
						|
  // done before we've shut the DB down or when the DB could not start,
 | 
						|
  // preload has not even be started.
 | 
						|
  StorageDBChild::Get(mPrivateBrowsingId)->SyncPreload(this);
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::GetLength(const LocalStorage* aStorage,
 | 
						|
                                      uint32_t* aRetval) {
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      return mLoadResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  *aRetval = DataSet(aStorage).mKeys.Count();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::GetKey(const LocalStorage* aStorage,
 | 
						|
                                   uint32_t aIndex, nsAString& aRetval) {
 | 
						|
  // XXX: This does a linear search for the key at index, which would
 | 
						|
  // suck if there's a large numer of indexes. Do we care? If so,
 | 
						|
  // maybe we need to have a lazily populated key array here or
 | 
						|
  // something?
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      return mLoadResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  aRetval.SetIsVoid(true);
 | 
						|
  for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
 | 
						|
    if (aIndex == 0) {
 | 
						|
      aRetval = iter.Key();
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    aIndex--;
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
 | 
						|
                                nsTArray<nsString>& aKeys) {
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
 | 
						|
  }
 | 
						|
 | 
						|
  if (NS_FAILED(mLoadResult)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AppendToArray(aKeys, DataSet(aStorage).mKeys.Keys());
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::GetItem(const LocalStorage* aStorage,
 | 
						|
                                    const nsAString& aKey, nsAString& aRetval) {
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      return mLoadResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // not using AutoString since we don't want to copy buffer to result
 | 
						|
  nsString value;
 | 
						|
  if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
 | 
						|
    SetDOMStringToNull(value);
 | 
						|
  }
 | 
						|
 | 
						|
  aRetval = value;
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::SetItem(const LocalStorage* aStorage,
 | 
						|
                                    const nsAString& aKey,
 | 
						|
                                    const nsAString& aValue, nsString& aOld,
 | 
						|
                                    const MutationSource aSource) {
 | 
						|
  // Size of the cache that will change after this action.
 | 
						|
  int64_t delta = 0;
 | 
						|
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      return mLoadResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Data& data = DataSet(aStorage);
 | 
						|
  if (!data.mKeys.Get(aKey, &aOld)) {
 | 
						|
    SetDOMStringToNull(aOld);
 | 
						|
 | 
						|
    // We only consider key size if the key doesn't exist before.
 | 
						|
    delta += static_cast<int64_t>(aKey.Length());
 | 
						|
  }
 | 
						|
 | 
						|
  delta += static_cast<int64_t>(aValue.Length()) -
 | 
						|
           static_cast<int64_t>(aOld.Length());
 | 
						|
 | 
						|
  if (!ProcessUsageDelta(aStorage, delta, aSource)) {
 | 
						|
    return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
 | 
						|
    return NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
  }
 | 
						|
 | 
						|
  data.mKeys.InsertOrUpdate(aKey, aValue);
 | 
						|
 | 
						|
  if (aSource != ContentMutation) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
#if !defined(MOZ_WIDGET_ANDROID)
 | 
						|
  NotifyObservers(aStorage, aKey, aOld, aValue);
 | 
						|
#endif
 | 
						|
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
 | 
						|
    if (!storageChild) {
 | 
						|
      NS_ERROR(
 | 
						|
          "Writing to localStorage after the database has been shut down"
 | 
						|
          ", data lose!");
 | 
						|
      return NS_ERROR_NOT_INITIALIZED;
 | 
						|
    }
 | 
						|
 | 
						|
    if (DOMStringIsNull(aOld)) {
 | 
						|
      return storageChild->AsyncAddItem(this, aKey, aValue);
 | 
						|
    }
 | 
						|
 | 
						|
    return storageChild->AsyncUpdateItem(this, aKey, aValue);
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
 | 
						|
                                       const nsAString& aKey, nsString& aOld,
 | 
						|
                                       const MutationSource aSource) {
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      return mLoadResult;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Data& data = DataSet(aStorage);
 | 
						|
  if (!data.mKeys.Get(aKey, &aOld)) {
 | 
						|
    SetDOMStringToNull(aOld);
 | 
						|
    return NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
  }
 | 
						|
 | 
						|
  // Recalculate the cached data size
 | 
						|
  const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
 | 
						|
                          static_cast<int64_t>(aKey.Length()));
 | 
						|
  Unused << ProcessUsageDelta(aStorage, delta, aSource);
 | 
						|
  data.mKeys.Remove(aKey);
 | 
						|
 | 
						|
  if (aSource != ContentMutation) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
#if !defined(MOZ_WIDGET_ANDROID)
 | 
						|
  NotifyObservers(aStorage, aKey, aOld, VoidString());
 | 
						|
#endif
 | 
						|
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
 | 
						|
    if (!storageChild) {
 | 
						|
      NS_ERROR(
 | 
						|
          "Writing to localStorage after the database has been shut down"
 | 
						|
          ", data lose!");
 | 
						|
      return NS_ERROR_NOT_INITIALIZED;
 | 
						|
    }
 | 
						|
 | 
						|
    return storageChild->AsyncRemoveItem(this, aKey);
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
 | 
						|
                                  const MutationSource aSource) {
 | 
						|
  bool refresh = false;
 | 
						|
  if (Persist(aStorage)) {
 | 
						|
    // We need to preload all data (know the size) before we can proceeed
 | 
						|
    // to correctly decrease cached usage number.
 | 
						|
    // XXX as in case of unload, this is not technically needed now, but
 | 
						|
    // after super-scope quota introduction we have to do this.  Get telemetry
 | 
						|
    // right now.
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
 | 
						|
    if (NS_FAILED(mLoadResult)) {
 | 
						|
      // When we failed to load data from the database, force delete of the
 | 
						|
      // scope data and make use of the storage possible again.
 | 
						|
      refresh = true;
 | 
						|
      mLoadResult = NS_OK;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Data& data = DataSet(aStorage);
 | 
						|
  bool hadData = !!data.mKeys.Count();
 | 
						|
 | 
						|
  if (hadData) {
 | 
						|
    Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
 | 
						|
    data.mKeys.Clear();
 | 
						|
  }
 | 
						|
 | 
						|
  if (aSource != ContentMutation) {
 | 
						|
    return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
  }
 | 
						|
 | 
						|
#if !defined(MOZ_WIDGET_ANDROID)
 | 
						|
  if (hadData) {
 | 
						|
    NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  if (Persist(aStorage) && (refresh || hadData)) {
 | 
						|
    StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
 | 
						|
    if (!storageChild) {
 | 
						|
      NS_ERROR(
 | 
						|
          "Writing to localStorage after the database has been shut down"
 | 
						|
          ", data lose!");
 | 
						|
      return NS_ERROR_NOT_INITIALIZED;
 | 
						|
    }
 | 
						|
 | 
						|
    return storageChild->AsyncClear(this);
 | 
						|
  }
 | 
						|
 | 
						|
  return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
 | 
						|
}
 | 
						|
 | 
						|
int64_t LocalStorageCache::GetOriginQuotaUsage(
 | 
						|
    const LocalStorage* aStorage) const {
 | 
						|
  return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::UnloadItems(uint32_t aUnloadFlags) {
 | 
						|
  if (aUnloadFlags & kUnloadDefault) {
 | 
						|
    // Must wait for preload to pass correct usage to ProcessUsageDelta
 | 
						|
    // XXX this is not technically needed right now since there is just
 | 
						|
    // per-origin isolated quota handling, but when we introduce super-
 | 
						|
    // -scope quotas, we have to do this.  Better to start getting
 | 
						|
    // telemetry right now.
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
 | 
						|
 | 
						|
    mData[kDefaultSet].mKeys.Clear();
 | 
						|
    ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
 | 
						|
  }
 | 
						|
 | 
						|
  if (aUnloadFlags & kUnloadSession) {
 | 
						|
    mData[kSessionSet].mKeys.Clear();
 | 
						|
    ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef DOM_STORAGE_TESTS
 | 
						|
  if (aUnloadFlags & kTestReload) {
 | 
						|
    WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
 | 
						|
 | 
						|
    mData[kDefaultSet].mKeys.Clear();
 | 
						|
    mLoaded = false;  // This is only used in testing code
 | 
						|
    Preload();
 | 
						|
  }
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
// LocalStorageCacheBridge
 | 
						|
 | 
						|
uint32_t LocalStorageCache::LoadedCount() {
 | 
						|
  MonitorAutoLock monitor(mMonitor);
 | 
						|
  Data& data = mData[kDefaultSet];
 | 
						|
  return data.mKeys.Count();
 | 
						|
}
 | 
						|
 | 
						|
bool LocalStorageCache::LoadItem(const nsAString& aKey,
 | 
						|
                                 const nsAString& aValue) {
 | 
						|
  MonitorAutoLock monitor(mMonitor);
 | 
						|
  if (mLoaded) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  Data& data = mData[kDefaultSet];
 | 
						|
  data.mKeys.LookupOrInsertWith(aKey, [&] {
 | 
						|
    data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
 | 
						|
    return nsString(aValue);
 | 
						|
  });
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::LoadDone(nsresult aRv) {
 | 
						|
  MonitorAutoLock monitor(mMonitor);
 | 
						|
  mLoadResult = aRv;
 | 
						|
  mLoaded = true;
 | 
						|
  monitor.Notify();
 | 
						|
}
 | 
						|
 | 
						|
void LocalStorageCache::LoadWait() {
 | 
						|
  MonitorAutoLock monitor(mMonitor);
 | 
						|
  while (!mLoaded) {
 | 
						|
    monitor.Wait();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// StorageUsage
 | 
						|
 | 
						|
StorageUsage::StorageUsage(const nsACString& aOriginScope)
 | 
						|
    : mOriginScope(aOriginScope) {
 | 
						|
  mUsage[kDefaultSet] = mUsage[kSessionSet] = 0LL;
 | 
						|
}
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
class LoadUsageRunnable : public Runnable {
 | 
						|
 public:
 | 
						|
  LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
 | 
						|
      : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  int64_t* mTarget;
 | 
						|
  int64_t mDelta;
 | 
						|
 | 
						|
  NS_IMETHOD Run() override {
 | 
						|
    *mTarget = mDelta;
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
void StorageUsage::LoadUsage(const int64_t aUsage) {
 | 
						|
  // Using kDefaultSet index since it is the index for the persitent data
 | 
						|
  // stored in the database we have just loaded usage for.
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    // In single process scenario we get this call from the DB thread
 | 
						|
    RefPtr<LoadUsageRunnable> r =
 | 
						|
        new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
 | 
						|
    NS_DispatchToMainThread(r);
 | 
						|
  } else {
 | 
						|
    // On a child process we get this on the main thread already
 | 
						|
    mUsage[kDefaultSet] += aUsage;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool StorageUsage::CheckAndSetETLD1UsageDelta(
 | 
						|
    uint32_t aDataSetIndex, const int64_t aDelta,
 | 
						|
    const LocalStorageCache::MutationSource aSource) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
 | 
						|
  if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
 | 
						|
      newUsage > LocalStorageManager::GetSiteQuota()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  mUsage[aDataSetIndex] = newUsage;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::dom
 |