Bug 1610775 [wpt PR 21336] - Update urllib3 to 1.25.8, a=testonly
[gecko.git] / dom / storage / LocalStorageCache.cpp
blob47167501d32fcc61afc21ded0454ecbd8edc9631
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/. */
7 #include "LocalStorageCache.h"
9 #include "Storage.h"
10 #include "StorageDBThread.h"
11 #include "StorageIPC.h"
12 #include "StorageUtils.h"
13 #include "LocalStorageManager.h"
15 #include "nsAutoPtr.h"
16 #include "nsDOMString.h"
17 #include "nsXULAppAPI.h"
18 #include "mozilla/Unused.h"
19 #include "nsProxyRelease.h"
20 #include "nsThreadUtils.h"
22 namespace mozilla {
23 namespace dom {
25 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
27 namespace {
29 const uint32_t kDefaultSet = 0;
30 const uint32_t kPrivateSet = 1;
31 const uint32_t kSessionSet = 2;
33 inline uint32_t GetDataSetIndex(bool aPrivate, bool aSessionOnly) {
34 if (aPrivate) {
35 return kPrivateSet;
38 if (aSessionOnly) {
39 return kSessionSet;
42 return kDefaultSet;
45 inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
46 return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly());
49 } // namespace
51 // LocalStorageCacheBridge
53 NS_IMPL_ADDREF(LocalStorageCacheBridge)
55 // Since there is no consumer of return value of Release, we can turn this
56 // method to void to make implementation of asynchronous
57 // LocalStorageCache::Release much simpler.
58 NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
59 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
60 nsrefcnt count = --mRefCnt;
61 NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
62 if (0 == count) {
63 mRefCnt = 1; /* stabilize */
64 /* enable this to find non-threadsafe destructors: */
65 /* NS_ASSERT_OWNINGTHREAD(_class); */
66 delete (this);
70 // LocalStorageCache
72 LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
73 : mActor(nullptr),
74 mOriginNoSuffix(*aOriginNoSuffix),
75 mMonitor("LocalStorageCache"),
76 mLoaded(false),
77 mLoadResult(NS_OK),
78 mInitialized(false),
79 mPersistent(false),
80 mPreloadTelemetryRecorded(false) {
81 MOZ_COUNT_CTOR(LocalStorageCache);
84 LocalStorageCache::~LocalStorageCache() {
85 if (mActor) {
86 mActor->SendDeleteMeInternal();
87 MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
90 if (mManager) {
91 mManager->DropCache(this);
94 MOZ_COUNT_DTOR(LocalStorageCache);
97 void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
98 AssertIsOnOwningThread();
99 MOZ_ASSERT(aActor);
100 MOZ_ASSERT(!mActor);
102 mActor = aActor;
105 NS_IMETHODIMP_(void)
106 LocalStorageCache::Release(void) {
107 // We must actually release on the main thread since the cache removes it
108 // self from the manager's hash table. And we don't want to lock access to
109 // that hash table.
110 if (NS_IsMainThread()) {
111 LocalStorageCacheBridge::Release();
112 return;
115 RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
116 NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
117 static_cast<LocalStorageCacheBridge*>(this),
118 &LocalStorageCacheBridge::Release);
120 nsresult rv = NS_DispatchToMainThread(event);
121 if (NS_FAILED(rv)) {
122 NS_WARNING("LocalStorageCache::Release() on a non-main thread");
123 LocalStorageCacheBridge::Release();
127 void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
128 nsIPrincipal* aPrincipal,
129 const nsACString& aQuotaOriginScope) {
130 if (mInitialized) {
131 return;
134 mInitialized = true;
135 aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
136 mPersistent = aPersistent;
137 if (aQuotaOriginScope.IsEmpty()) {
138 mQuotaOriginScope = Origin();
139 } else {
140 mQuotaOriginScope = aQuotaOriginScope;
143 if (mPersistent) {
144 mManager = aManager;
145 Preload();
148 // Check the quota string has (or has not) the identical origin suffix as
149 // this storage cache is bound to.
150 MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
151 MOZ_ASSERT(mOriginSuffix.IsEmpty() !=
152 StringBeginsWith(mQuotaOriginScope, NS_LITERAL_CSTRING("^")));
154 mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
157 void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
158 const nsString& aKey,
159 const nsString& aOldValue,
160 const nsString& aNewValue) {
161 AssertIsOnOwningThread();
162 MOZ_ASSERT(aStorage);
164 if (!mActor) {
165 return;
168 // We want to send a message to the parent in order to broadcast the
169 // StorageEvent correctly to any child process.
171 Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
172 aNewValue);
175 inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
176 return mPersistent && !aStorage->IsSessionOnly() && !aStorage->IsPrivate();
179 const nsCString LocalStorageCache::Origin() const {
180 return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
183 LocalStorageCache::Data& LocalStorageCache::DataSet(
184 const LocalStorage* aStorage) {
185 return mData[GetDataSetIndex(aStorage)];
188 bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
189 int64_t aDelta,
190 const MutationSource aSource) {
191 return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
194 bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
195 const int64_t aDelta,
196 const MutationSource aSource) {
197 // Check limit per this origin
198 Data& data = mData[aGetDataSetIndex];
199 uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
200 if (aSource == ContentMutation && aDelta > 0 &&
201 newOriginUsage > LocalStorageManager::GetQuota()) {
202 return false;
205 // Now check eTLD+1 limit
206 if (mUsage &&
207 !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
208 return false;
211 // Update size in our data set
212 data.mOriginQuotaUsage = newOriginUsage;
213 return true;
216 void LocalStorageCache::Preload() {
217 if (mLoaded || !mPersistent) {
218 return;
221 StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
222 if (!storageChild) {
223 mLoaded = true;
224 mLoadResult = NS_ERROR_FAILURE;
225 return;
228 storageChild->AsyncPreload(this);
231 void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID) {
232 if (!mPersistent) {
233 return;
236 bool loaded = mLoaded;
238 // Telemetry of rates of pending preloads
239 if (!mPreloadTelemetryRecorded) {
240 mPreloadTelemetryRecorded = true;
241 Telemetry::Accumulate(
242 Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, !loaded);
245 if (loaded) {
246 return;
249 // Measure which operation blocks and for how long
250 Telemetry::RuntimeAutoTimer timer(aTelemetryID);
252 // If preload already started (i.e. we got some first data, but not all)
253 // SyncPreload will just wait for it to finish rather then synchronously
254 // read from the database. It seems to me more optimal.
256 // TODO place for A/B testing (force main thread load vs. let preload finish)
258 // No need to check sDatabase for being non-null since preload is either
259 // done before we've shut the DB down or when the DB could not start,
260 // preload has not even be started.
261 StorageDBChild::Get()->SyncPreload(this);
264 nsresult LocalStorageCache::GetLength(const LocalStorage* aStorage,
265 uint32_t* aRetval) {
266 if (Persist(aStorage)) {
267 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
268 if (NS_FAILED(mLoadResult)) {
269 return mLoadResult;
273 *aRetval = DataSet(aStorage).mKeys.Count();
274 return NS_OK;
277 nsresult LocalStorageCache::GetKey(const LocalStorage* aStorage,
278 uint32_t aIndex, nsAString& aRetval) {
279 // XXX: This does a linear search for the key at index, which would
280 // suck if there's a large numer of indexes. Do we care? If so,
281 // maybe we need to have a lazily populated key array here or
282 // something?
283 if (Persist(aStorage)) {
284 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
285 if (NS_FAILED(mLoadResult)) {
286 return mLoadResult;
290 aRetval.SetIsVoid(true);
291 for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
292 if (aIndex == 0) {
293 aRetval = iter.Key();
294 break;
296 aIndex--;
299 return NS_OK;
302 void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
303 nsTArray<nsString>& aKeys) {
304 if (Persist(aStorage)) {
305 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
308 if (NS_FAILED(mLoadResult)) {
309 return;
312 for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
313 aKeys.AppendElement(iter.Key());
317 nsresult LocalStorageCache::GetItem(const LocalStorage* aStorage,
318 const nsAString& aKey, nsAString& aRetval) {
319 if (Persist(aStorage)) {
320 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
321 if (NS_FAILED(mLoadResult)) {
322 return mLoadResult;
326 // not using AutoString since we don't want to copy buffer to result
327 nsString value;
328 if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
329 SetDOMStringToNull(value);
332 aRetval = value;
334 return NS_OK;
337 nsresult LocalStorageCache::SetItem(const LocalStorage* aStorage,
338 const nsAString& aKey,
339 const nsString& aValue, nsString& aOld,
340 const MutationSource aSource) {
341 // Size of the cache that will change after this action.
342 int64_t delta = 0;
344 if (Persist(aStorage)) {
345 WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
346 if (NS_FAILED(mLoadResult)) {
347 return mLoadResult;
351 Data& data = DataSet(aStorage);
352 if (!data.mKeys.Get(aKey, &aOld)) {
353 SetDOMStringToNull(aOld);
355 // We only consider key size if the key doesn't exist before.
356 delta += static_cast<int64_t>(aKey.Length());
359 delta += static_cast<int64_t>(aValue.Length()) -
360 static_cast<int64_t>(aOld.Length());
362 if (!ProcessUsageDelta(aStorage, delta, aSource)) {
363 return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
366 if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
367 return NS_SUCCESS_DOM_NO_OPERATION;
370 data.mKeys.Put(aKey, aValue);
372 if (aSource != ContentMutation) {
373 return NS_OK;
376 #if !defined(MOZ_WIDGET_ANDROID)
377 NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
378 #endif
380 if (Persist(aStorage)) {
381 StorageDBChild* storageChild = StorageDBChild::Get();
382 if (!storageChild) {
383 NS_ERROR(
384 "Writing to localStorage after the database has been shut down"
385 ", data lose!");
386 return NS_ERROR_NOT_INITIALIZED;
389 if (DOMStringIsNull(aOld)) {
390 return storageChild->AsyncAddItem(this, aKey, aValue);
393 return storageChild->AsyncUpdateItem(this, aKey, aValue);
396 return NS_OK;
399 nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
400 const nsAString& aKey, nsString& aOld,
401 const MutationSource aSource) {
402 if (Persist(aStorage)) {
403 WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
404 if (NS_FAILED(mLoadResult)) {
405 return mLoadResult;
409 Data& data = DataSet(aStorage);
410 if (!data.mKeys.Get(aKey, &aOld)) {
411 SetDOMStringToNull(aOld);
412 return NS_SUCCESS_DOM_NO_OPERATION;
415 // Recalculate the cached data size
416 const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
417 static_cast<int64_t>(aKey.Length()));
418 Unused << ProcessUsageDelta(aStorage, delta, aSource);
419 data.mKeys.Remove(aKey);
421 if (aSource != ContentMutation) {
422 return NS_OK;
425 #if !defined(MOZ_WIDGET_ANDROID)
426 NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
427 #endif
429 if (Persist(aStorage)) {
430 StorageDBChild* storageChild = StorageDBChild::Get();
431 if (!storageChild) {
432 NS_ERROR(
433 "Writing to localStorage after the database has been shut down"
434 ", data lose!");
435 return NS_ERROR_NOT_INITIALIZED;
438 return storageChild->AsyncRemoveItem(this, aKey);
441 return NS_OK;
444 nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
445 const MutationSource aSource) {
446 bool refresh = false;
447 if (Persist(aStorage)) {
448 // We need to preload all data (know the size) before we can proceeed
449 // to correctly decrease cached usage number.
450 // XXX as in case of unload, this is not technically needed now, but
451 // after super-scope quota introduction we have to do this. Get telemetry
452 // right now.
453 WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
454 if (NS_FAILED(mLoadResult)) {
455 // When we failed to load data from the database, force delete of the
456 // scope data and make use of the storage possible again.
457 refresh = true;
458 mLoadResult = NS_OK;
462 Data& data = DataSet(aStorage);
463 bool hadData = !!data.mKeys.Count();
465 if (hadData) {
466 Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
467 data.mKeys.Clear();
470 if (aSource != ContentMutation) {
471 return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
474 #if !defined(MOZ_WIDGET_ANDROID)
475 if (hadData) {
476 NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
478 #endif
480 if (Persist(aStorage) && (refresh || hadData)) {
481 StorageDBChild* storageChild = StorageDBChild::Get();
482 if (!storageChild) {
483 NS_ERROR(
484 "Writing to localStorage after the database has been shut down"
485 ", data lose!");
486 return NS_ERROR_NOT_INITIALIZED;
489 return storageChild->AsyncClear(this);
492 return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
495 int64_t LocalStorageCache::GetOriginQuotaUsage(
496 const LocalStorage* aStorage) const {
497 return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
500 void LocalStorageCache::UnloadItems(uint32_t aUnloadFlags) {
501 if (aUnloadFlags & kUnloadDefault) {
502 // Must wait for preload to pass correct usage to ProcessUsageDelta
503 // XXX this is not technically needed right now since there is just
504 // per-origin isolated quota handling, but when we introduce super-
505 // -scope quotas, we have to do this. Better to start getting
506 // telemetry right now.
507 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
509 mData[kDefaultSet].mKeys.Clear();
510 ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
513 if (aUnloadFlags & kUnloadPrivate) {
514 mData[kPrivateSet].mKeys.Clear();
515 ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
518 if (aUnloadFlags & kUnloadSession) {
519 mData[kSessionSet].mKeys.Clear();
520 ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
523 #ifdef DOM_STORAGE_TESTS
524 if (aUnloadFlags & kTestReload) {
525 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
527 mData[kDefaultSet].mKeys.Clear();
528 mLoaded = false; // This is only used in testing code
529 Preload();
531 #endif
534 // LocalStorageCacheBridge
536 uint32_t LocalStorageCache::LoadedCount() {
537 MonitorAutoLock monitor(mMonitor);
538 Data& data = mData[kDefaultSet];
539 return data.mKeys.Count();
542 bool LocalStorageCache::LoadItem(const nsAString& aKey,
543 const nsString& aValue) {
544 MonitorAutoLock monitor(mMonitor);
545 if (mLoaded) {
546 return false;
549 Data& data = mData[kDefaultSet];
550 if (data.mKeys.Get(aKey, nullptr)) {
551 return true; // don't stop, just don't override
554 data.mKeys.Put(aKey, aValue);
555 data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
556 return true;
559 void LocalStorageCache::LoadDone(nsresult aRv) {
560 MonitorAutoLock monitor(mMonitor);
561 mLoadResult = aRv;
562 mLoaded = true;
563 monitor.Notify();
566 void LocalStorageCache::LoadWait() {
567 MonitorAutoLock monitor(mMonitor);
568 while (!mLoaded) {
569 monitor.Wait();
573 // StorageUsage
575 StorageUsage::StorageUsage(const nsACString& aOriginScope)
576 : mOriginScope(aOriginScope) {
577 mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
580 namespace {
582 class LoadUsageRunnable : public Runnable {
583 public:
584 LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
585 : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
587 private:
588 int64_t* mTarget;
589 int64_t mDelta;
591 NS_IMETHOD Run() override {
592 *mTarget = mDelta;
593 return NS_OK;
597 } // namespace
599 void StorageUsage::LoadUsage(const int64_t aUsage) {
600 // Using kDefaultSet index since it is the index for the persitent data
601 // stored in the database we have just loaded usage for.
602 if (!NS_IsMainThread()) {
603 // In single process scenario we get this call from the DB thread
604 RefPtr<LoadUsageRunnable> r =
605 new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
606 NS_DispatchToMainThread(r);
607 } else {
608 // On a child process we get this on the main thread already
609 mUsage[kDefaultSet] += aUsage;
613 bool StorageUsage::CheckAndSetETLD1UsageDelta(
614 uint32_t aDataSetIndex, const int64_t aDelta,
615 const LocalStorageCache::MutationSource aSource) {
616 MOZ_ASSERT(NS_IsMainThread());
618 int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
619 if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
620 newUsage > LocalStorageManager::GetQuota()) {
621 return false;
624 mUsage[aDataSetIndex] = newUsage;
625 return true;
628 } // namespace dom
629 } // namespace mozilla