michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DOMStorageCache.h" michael@0: michael@0: #include "DOMStorage.h" michael@0: #include "DOMStorageDBThread.h" michael@0: #include "DOMStorageIPC.h" michael@0: #include "DOMStorageManager.h" michael@0: michael@0: #include "nsDOMString.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/unused.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000 michael@0: michael@0: // static michael@0: DOMStorageDBBridge* DOMStorageCache::sDatabase = nullptr; michael@0: bool DOMStorageCache::sDatabaseDown = false; michael@0: michael@0: namespace { // anon michael@0: michael@0: const uint32_t kDefaultSet = 0; michael@0: const uint32_t kPrivateSet = 1; michael@0: const uint32_t kSessionSet = 2; michael@0: michael@0: inline uint32_t michael@0: GetDataSetIndex(bool aPrivate, bool aSessionOnly) michael@0: { michael@0: if (aPrivate) { michael@0: return kPrivateSet; michael@0: } michael@0: michael@0: if (aSessionOnly) { michael@0: return kSessionSet; michael@0: } michael@0: michael@0: return kDefaultSet; michael@0: } michael@0: michael@0: inline uint32_t michael@0: GetDataSetIndex(const DOMStorage* aStorage) michael@0: { michael@0: return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly()); michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: // DOMStorageCacheBridge michael@0: michael@0: NS_IMPL_ADDREF(DOMStorageCacheBridge) michael@0: michael@0: // Since there is no consumer of return value of Release, we can turn this michael@0: // method to void to make implementation of asynchronous DOMStorageCache::Release michael@0: // much simpler. michael@0: NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void) michael@0: { michael@0: MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); michael@0: nsrefcnt count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "DOMStorageCacheBridge"); michael@0: if (0 == count) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: /* enable this to find non-threadsafe destructors: */ michael@0: /* NS_ASSERT_OWNINGTHREAD(_class); */ michael@0: delete (this); michael@0: } michael@0: } michael@0: michael@0: // DOMStorageCache michael@0: michael@0: DOMStorageCache::DOMStorageCache(const nsACString* aScope) michael@0: : mScope(*aScope) michael@0: , mMonitor("DOMStorageCache") michael@0: , mLoaded(false) michael@0: , mLoadResult(NS_OK) michael@0: , mInitialized(false) michael@0: , mPersistent(false) michael@0: , mSessionOnlyDataSetActive(false) michael@0: , mPreloadTelemetryRecorded(false) michael@0: { michael@0: MOZ_COUNT_CTOR(DOMStorageCache); michael@0: } michael@0: michael@0: DOMStorageCache::~DOMStorageCache() michael@0: { michael@0: if (mManager) { michael@0: mManager->DropCache(this); michael@0: } michael@0: michael@0: MOZ_COUNT_DTOR(DOMStorageCache); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: DOMStorageCache::Release(void) michael@0: { michael@0: // We must actually release on the main thread since the cache removes it michael@0: // self from the manager's hash table. And we don't want to lock access to michael@0: // that hash table. michael@0: if (NS_IsMainThread()) { michael@0: DOMStorageCacheBridge::Release(); michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr > event = michael@0: NS_NewNonOwningRunnableMethod(static_cast(this), michael@0: &DOMStorageCacheBridge::Release); michael@0: michael@0: nsresult rv = NS_DispatchToMainThread(event); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("DOMStorageCache::Release() on a non-main thread"); michael@0: DOMStorageCacheBridge::Release(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::Init(DOMStorageManager* aManager, michael@0: bool aPersistent, michael@0: nsIURI* aFirstPartyIsolationURI, michael@0: nsIPrincipal* aPrincipal, michael@0: const nsACString& aQuotaScope) michael@0: { michael@0: if (mInitialized) { michael@0: return; michael@0: } michael@0: michael@0: mInitialized = true; michael@0: mFirstPartyIsolationURI = aFirstPartyIsolationURI; michael@0: mPrincipal = aPrincipal; michael@0: mPersistent = aPersistent; michael@0: mQuotaScope = aQuotaScope.IsEmpty() ? mScope : aQuotaScope; michael@0: michael@0: if (mPersistent) { michael@0: mManager = aManager; michael@0: Preload(); michael@0: } michael@0: michael@0: mUsage = aManager->GetScopeUsage(mQuotaScope); michael@0: } michael@0: michael@0: inline bool michael@0: DOMStorageCache::Persist(const DOMStorage* aStorage) const michael@0: { michael@0: return mPersistent && michael@0: !aStorage->IsSessionOnly() && michael@0: !aStorage->IsPrivate(); michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: PLDHashOperator michael@0: CloneSetData(const nsAString& aKey, const nsString aValue, void* aArg) michael@0: { michael@0: DOMStorageCache::Data* target = static_cast(aArg); michael@0: target->mKeys.Put(aKey, aValue); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: DOMStorageCache::Data& michael@0: DOMStorageCache::DataSet(const DOMStorage* aStorage) michael@0: { michael@0: uint32_t index = GetDataSetIndex(aStorage); michael@0: michael@0: if (index == kSessionSet && !mSessionOnlyDataSetActive) { michael@0: // Session only data set is demanded but not filled with michael@0: // current data set, copy to session only set now. michael@0: michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS); michael@0: michael@0: Data& defaultSet = mData[kDefaultSet]; michael@0: Data& sessionSet = mData[kSessionSet]; michael@0: michael@0: defaultSet.mKeys.EnumerateRead(CloneSetData, &sessionSet); michael@0: michael@0: mSessionOnlyDataSetActive = true; michael@0: michael@0: // This updates sessionSet.mOriginQuotaUsage and also updates global usage michael@0: // for all session only data michael@0: ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage); michael@0: } michael@0: michael@0: return mData[index]; michael@0: } michael@0: michael@0: bool michael@0: DOMStorageCache::ProcessUsageDelta(const DOMStorage* aStorage, int64_t aDelta) michael@0: { michael@0: return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta); michael@0: } michael@0: michael@0: bool michael@0: DOMStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta) michael@0: { michael@0: // Check if we are in a low disk space situation michael@0: if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) { michael@0: return false; michael@0: } michael@0: michael@0: // Check limit per this origin michael@0: Data& data = mData[aGetDataSetIndex]; michael@0: uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta; michael@0: if (aDelta > 0 && newOriginUsage > DOMStorageManager::GetQuota()) { michael@0: return false; michael@0: } michael@0: michael@0: // Now check eTLD+1 limit michael@0: if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) { michael@0: return false; michael@0: } michael@0: michael@0: // Update size in our data set michael@0: data.mOriginQuotaUsage = newOriginUsage; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::Preload() michael@0: { michael@0: if (mLoaded || !mPersistent) { michael@0: return; michael@0: } michael@0: michael@0: if (!StartDatabase()) { michael@0: mLoaded = true; michael@0: mLoadResult = NS_ERROR_FAILURE; michael@0: return; michael@0: } michael@0: michael@0: sDatabase->AsyncPreload(this); michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: // This class is passed to timer as a tick observer. It refers the cache michael@0: // and keeps it alive for a time. michael@0: class DOMStorageCacheHolder : public nsITimerCallback michael@0: { michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHODIMP michael@0: Notify(nsITimer* aTimer) michael@0: { michael@0: mCache = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual ~DOMStorageCacheHolder() {} michael@0: michael@0: nsRefPtr mCache; michael@0: michael@0: public: michael@0: DOMStorageCacheHolder(DOMStorageCache* aCache) : mCache(aCache) {} michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(DOMStorageCacheHolder, nsITimerCallback) michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: DOMStorageCache::KeepAlive() michael@0: { michael@0: // Missing reference back to the manager means the cache is not responsible michael@0: // for its lifetime. Used for keeping sessionStorage live forever. michael@0: if (!mManager) { michael@0: return; michael@0: } michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: // Timer and the holder must be initialized on the main thread. michael@0: nsRefPtr > event = michael@0: NS_NewRunnableMethod(this, &DOMStorageCache::KeepAlive); michael@0: michael@0: NS_DispatchToMainThread(event); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (!timer) { michael@0: return; michael@0: } michael@0: michael@0: nsRefPtr holder = new DOMStorageCacheHolder(this); michael@0: timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: michael@0: mKeepAliveTimer.swap(timer); michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: // The AutoTimer provided by telemetry headers is only using static, michael@0: // i.e. compile time known ID, but here we know the ID only at run time. michael@0: // Hence a new class. michael@0: class TelemetryAutoTimer michael@0: { michael@0: public: michael@0: TelemetryAutoTimer(Telemetry::ID aId) michael@0: : id(aId), start(TimeStamp::Now()) {} michael@0: ~TelemetryAutoTimer() michael@0: { Telemetry::AccumulateDelta_impl::compute(id, start); } michael@0: private: michael@0: Telemetry::ID id; michael@0: const TimeStamp start; michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: DOMStorageCache::WaitForPreload(Telemetry::ID aTelemetryID) michael@0: { michael@0: if (!mPersistent) { michael@0: return; michael@0: } michael@0: michael@0: bool loaded = mLoaded; michael@0: michael@0: // Telemetry of rates of pending preloads michael@0: if (!mPreloadTelemetryRecorded) { michael@0: mPreloadTelemetryRecorded = true; michael@0: Telemetry::Accumulate( michael@0: Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, michael@0: !loaded); michael@0: } michael@0: michael@0: if (loaded) { michael@0: return; michael@0: } michael@0: michael@0: // Measure which operation blocks and for how long michael@0: TelemetryAutoTimer timer(aTelemetryID); michael@0: michael@0: // If preload already started (i.e. we got some first data, but not all) michael@0: // SyncPreload will just wait for it to finish rather then synchronously michael@0: // read from the database. It seems to me more optimal. michael@0: michael@0: // TODO place for A/B testing (force main thread load vs. let preload finish) michael@0: michael@0: // No need to check sDatabase for being non-null since preload is either michael@0: // done before we've shut the DB down or when the DB could not start, michael@0: // preload has not even be started. michael@0: sDatabase->SyncPreload(this); michael@0: } michael@0: michael@0: nsresult michael@0: DOMStorageCache::GetLength(const DOMStorage* aStorage, uint32_t* aRetval) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: return mLoadResult; michael@0: } michael@0: } michael@0: michael@0: *aRetval = DataSet(aStorage).mKeys.Count(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class IndexFinderData michael@0: { michael@0: public: michael@0: IndexFinderData(uint32_t aIndex, nsAString& aRetval) michael@0: : mIndex(aIndex), mKey(aRetval) michael@0: { michael@0: mKey.SetIsVoid(true); michael@0: } michael@0: michael@0: uint32_t mIndex; michael@0: nsAString& mKey; michael@0: }; michael@0: michael@0: PLDHashOperator michael@0: FindKeyOrder(const nsAString& aKey, const nsString aValue, void* aArg) michael@0: { michael@0: IndexFinderData* data = static_cast(aArg); michael@0: michael@0: if (data->mIndex--) { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: data->mKey = aKey; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: nsresult michael@0: DOMStorageCache::GetKey(const DOMStorage* aStorage, uint32_t aIndex, nsAString& aRetval) michael@0: { michael@0: // XXX: This does a linear search for the key at index, which would michael@0: // suck if there's a large numer of indexes. Do we care? If so, michael@0: // maybe we need to have a lazily populated key array here or michael@0: // something? michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: return mLoadResult; michael@0: } michael@0: } michael@0: michael@0: IndexFinderData data(aIndex, aRetval); michael@0: DataSet(aStorage).mKeys.EnumerateRead(FindKeyOrder, &data); michael@0: return NS_OK; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: static PLDHashOperator michael@0: KeysArrayBuilder(const nsAString& aKey, const nsString aValue, void* aArg) michael@0: { michael@0: nsTArray* keys = static_cast* >(aArg); michael@0: michael@0: keys->AppendElement(aKey); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: nsTArray* michael@0: DOMStorageCache::GetKeys(const DOMStorage* aStorage) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS); michael@0: } michael@0: michael@0: nsTArray* result = new nsTArray(); michael@0: if (NS_SUCCEEDED(mLoadResult)) { michael@0: DataSet(aStorage).mKeys.EnumerateRead(KeysArrayBuilder, result); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: DOMStorageCache::GetItem(const DOMStorage* aStorage, const nsAString& aKey, michael@0: nsAString& aRetval) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: return mLoadResult; michael@0: } michael@0: } michael@0: michael@0: // not using AutoString since we don't want to copy buffer to result michael@0: nsString value; michael@0: if (!DataSet(aStorage).mKeys.Get(aKey, &value)) { michael@0: SetDOMStringToNull(value); michael@0: } michael@0: michael@0: aRetval = value; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DOMStorageCache::SetItem(const DOMStorage* aStorage, const nsAString& aKey, michael@0: const nsString& aValue, nsString& aOld) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: return mLoadResult; michael@0: } michael@0: } michael@0: michael@0: Data& data = DataSet(aStorage); michael@0: if (!data.mKeys.Get(aKey, &aOld)) { michael@0: SetDOMStringToNull(aOld); michael@0: } michael@0: michael@0: // Check the quota first michael@0: const int64_t delta = static_cast(aValue.Length()) - michael@0: static_cast(aOld.Length()); michael@0: if (!ProcessUsageDelta(aStorage, delta)) { michael@0: return NS_ERROR_DOM_QUOTA_REACHED; michael@0: } michael@0: michael@0: if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) { michael@0: return NS_SUCCESS_DOM_NO_OPERATION; michael@0: } michael@0: michael@0: data.mKeys.Put(aKey, aValue); michael@0: michael@0: if (Persist(aStorage)) { michael@0: if (!sDatabase) { michael@0: NS_ERROR("Writing to localStorage after the database has been shut down" michael@0: ", data lose!"); michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: if (DOMStringIsNull(aOld)) { michael@0: return sDatabase->AsyncAddItem(this, aKey, aValue); michael@0: } michael@0: michael@0: return sDatabase->AsyncUpdateItem(this, aKey, aValue); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DOMStorageCache::RemoveItem(const DOMStorage* aStorage, const nsAString& aKey, michael@0: nsString& aOld) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: if (Persist(aStorage)) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: return mLoadResult; michael@0: } michael@0: } michael@0: michael@0: Data& data = DataSet(aStorage); michael@0: if (!data.mKeys.Get(aKey, &aOld)) { michael@0: SetDOMStringToNull(aOld); michael@0: return NS_SUCCESS_DOM_NO_OPERATION; michael@0: } michael@0: michael@0: // Recalculate the cached data size michael@0: const int64_t delta = -(static_cast(aOld.Length())); michael@0: unused << ProcessUsageDelta(aStorage, delta); michael@0: data.mKeys.Remove(aKey); michael@0: michael@0: if (Persist(aStorage)) { michael@0: if (!sDatabase) { michael@0: NS_ERROR("Writing to localStorage after the database has been shut down" michael@0: ", data lose!"); michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: return sDatabase->AsyncRemoveItem(this, aKey); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DOMStorageCache::Clear(const DOMStorage* aStorage) michael@0: { michael@0: Telemetry::AutoTimer autoTimer; michael@0: michael@0: bool refresh = false; michael@0: if (Persist(aStorage)) { michael@0: // We need to preload all data (know the size) before we can proceeed michael@0: // to correctly decrease cached usage number. michael@0: // XXX as in case of unload, this is not technically needed now, but michael@0: // after super-scope quota introduction we have to do this. Get telemetry michael@0: // right now. michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS); michael@0: if (NS_FAILED(mLoadResult)) { michael@0: // When we failed to load data from the database, force delete of the michael@0: // scope data and make use of the storage possible again. michael@0: refresh = true; michael@0: mLoadResult = NS_OK; michael@0: } michael@0: } michael@0: michael@0: Data& data = DataSet(aStorage); michael@0: bool hadData = !!data.mKeys.Count(); michael@0: michael@0: if (hadData) { michael@0: unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage); michael@0: data.mKeys.Clear(); michael@0: } michael@0: michael@0: if (Persist(aStorage) && (refresh || hadData)) { michael@0: if (!sDatabase) { michael@0: NS_ERROR("Writing to localStorage after the database has been shut down" michael@0: ", data lose!"); michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: return sDatabase->AsyncClear(this); michael@0: } michael@0: michael@0: return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::CloneFrom(const DOMStorageCache* aThat) michael@0: { michael@0: mLoaded = aThat->mLoaded; michael@0: mInitialized = aThat->mInitialized; michael@0: mPersistent = aThat->mPersistent; michael@0: mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive; michael@0: michael@0: for (uint32_t i = 0; i < kDataSetCount; ++i) { michael@0: aThat->mData[i].mKeys.EnumerateRead(CloneSetData, &mData[i]); michael@0: ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage); michael@0: } michael@0: } michael@0: michael@0: // Defined in DOMStorageManager.cpp michael@0: extern bool michael@0: PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal); michael@0: michael@0: bool michael@0: DOMStorageCache::CheckPrincipal(nsIPrincipal* aPrincipal) const michael@0: { michael@0: return PrincipalsEqual(mPrincipal, aPrincipal); michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::UnloadItems(uint32_t aUnloadFlags) michael@0: { michael@0: if (aUnloadFlags & kUnloadDefault) { michael@0: // Must wait for preload to pass correct usage to ProcessUsageDelta michael@0: // XXX this is not technically needed right now since there is just michael@0: // per-origin isolated quota handling, but when we introduce super- michael@0: // -scope quotas, we have to do this. Better to start getting michael@0: // telemetry right now. michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); michael@0: michael@0: mData[kDefaultSet].mKeys.Clear(); michael@0: ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage); michael@0: } michael@0: michael@0: if (aUnloadFlags & kUnloadPrivate) { michael@0: mData[kPrivateSet].mKeys.Clear(); michael@0: ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage); michael@0: } michael@0: michael@0: if (aUnloadFlags & kUnloadSession) { michael@0: mData[kSessionSet].mKeys.Clear(); michael@0: ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage); michael@0: mSessionOnlyDataSetActive = false; michael@0: } michael@0: michael@0: #ifdef DOM_STORAGE_TESTS michael@0: if (aUnloadFlags & kTestReload) { michael@0: WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); michael@0: michael@0: mData[kDefaultSet].mKeys.Clear(); michael@0: mLoaded = false; // This is only used in testing code michael@0: Preload(); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // DOMStorageCacheBridge michael@0: michael@0: uint32_t michael@0: DOMStorageCache::LoadedCount() michael@0: { michael@0: MonitorAutoLock monitor(mMonitor); michael@0: Data& data = mData[kDefaultSet]; michael@0: return data.mKeys.Count(); michael@0: } michael@0: michael@0: bool michael@0: DOMStorageCache::LoadItem(const nsAString& aKey, const nsString& aValue) michael@0: { michael@0: MonitorAutoLock monitor(mMonitor); michael@0: if (mLoaded) { michael@0: return false; michael@0: } michael@0: michael@0: Data& data = mData[kDefaultSet]; michael@0: if (data.mKeys.Get(aKey, nullptr)) { michael@0: return true; // don't stop, just don't override michael@0: } michael@0: michael@0: data.mKeys.Put(aKey, aValue); michael@0: data.mOriginQuotaUsage += aKey.Length() + aValue.Length(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::LoadDone(nsresult aRv) michael@0: { michael@0: // Keep the preloaded cache alive for a time michael@0: KeepAlive(); michael@0: michael@0: MonitorAutoLock monitor(mMonitor); michael@0: mLoadResult = aRv; michael@0: mLoaded = true; michael@0: monitor.Notify(); michael@0: } michael@0: michael@0: void michael@0: DOMStorageCache::LoadWait() michael@0: { michael@0: MonitorAutoLock monitor(mMonitor); michael@0: while (!mLoaded) { michael@0: monitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: // DOMStorageUsage michael@0: michael@0: DOMStorageUsage::DOMStorageUsage(const nsACString& aScope) michael@0: : mScope(aScope) michael@0: { michael@0: mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL; michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class LoadUsageRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta) michael@0: : mTarget(aUsage) michael@0: , mDelta(aDelta) michael@0: {} michael@0: michael@0: private: michael@0: int64_t* mTarget; michael@0: int64_t mDelta; michael@0: michael@0: NS_IMETHOD Run() { *mTarget = mDelta; return NS_OK; } michael@0: }; michael@0: michael@0: } // anon michael@0: michael@0: void michael@0: DOMStorageUsage::LoadUsage(const int64_t aUsage) michael@0: { michael@0: // Using kDefaultSet index since it is the index for the persitent data michael@0: // stored in the database we have just loaded usage for. michael@0: if (!NS_IsMainThread()) { michael@0: // In single process scenario we get this call from the DB thread michael@0: nsRefPtr r = michael@0: new LoadUsageRunnable(mUsage + kDefaultSet, aUsage); michael@0: NS_DispatchToMainThread(r); michael@0: } else { michael@0: // On a child process we get this on the main thread already michael@0: mUsage[kDefaultSet] += aUsage; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: DOMStorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, const int64_t aDelta) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: int64_t newUsage = mUsage[aDataSetIndex] + aDelta; michael@0: if (aDelta > 0 && newUsage > DOMStorageManager::GetQuota()) { michael@0: return false; michael@0: } michael@0: michael@0: mUsage[aDataSetIndex] = newUsage; michael@0: return true; michael@0: } michael@0: michael@0: michael@0: // static michael@0: DOMStorageDBBridge* michael@0: DOMStorageCache::StartDatabase() michael@0: { michael@0: if (sDatabase || sDatabaseDown) { michael@0: // When sDatabaseDown is at true, sDatabase is null. michael@0: // Checking sDatabaseDown flag here prevents reinitialization of michael@0: // the database after shutdown. michael@0: return sDatabase; michael@0: } michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsAutoPtr db(new DOMStorageDBThread()); michael@0: michael@0: nsresult rv = db->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: sDatabase = db.forget(); michael@0: } else { michael@0: nsRefPtr db = new DOMStorageDBChild( michael@0: DOMLocalStorageManager::Self()); michael@0: michael@0: nsresult rv = db->Init(); michael@0: if (NS_FAILED(rv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: db.forget(&sDatabase); michael@0: } michael@0: michael@0: return sDatabase; michael@0: } michael@0: michael@0: // static michael@0: DOMStorageDBBridge* michael@0: DOMStorageCache::GetDatabase() michael@0: { michael@0: return sDatabase; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: DOMStorageCache::StopDatabase() michael@0: { michael@0: if (!sDatabase) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: sDatabaseDown = true; michael@0: michael@0: nsresult rv = sDatabase->Shutdown(); michael@0: if (XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: delete sDatabase; michael@0: } else { michael@0: DOMStorageDBChild* child = static_cast(sDatabase); michael@0: NS_RELEASE(child); michael@0: } michael@0: michael@0: sDatabase = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: } // ::dom michael@0: } // ::mozilla