Bug 1866894 - Update failing subtest for content-visibility-auto-resize.html. r=fredw
[gecko.git] / dom / storage / LocalStorageCache.cpp
blob4b5548244166ce32ac41c2be03c4ea3d4bc0e508
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 "nsDOMString.h"
16 #include "nsXULAppAPI.h"
17 #include "mozilla/Unused.h"
18 #include "nsProxyRelease.h"
19 #include "nsThreadUtils.h"
21 namespace mozilla::dom {
23 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
25 namespace {
27 const uint32_t kDefaultSet = 0;
28 const uint32_t kSessionSet = 1;
30 inline uint32_t GetDataSetIndex(bool aPrivateBrowsing,
31 bool aSessionScopedOrLess) {
32 if (!aPrivateBrowsing && aSessionScopedOrLess) {
33 return kSessionSet;
36 return kDefaultSet;
39 inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
40 return GetDataSetIndex(aStorage->IsPrivateBrowsing(),
41 aStorage->IsSessionScopedOrLess());
44 } // namespace
46 // LocalStorageCacheBridge
48 NS_IMPL_ADDREF(LocalStorageCacheBridge)
50 // Since there is no consumer of return value of Release, we can turn this
51 // method to void to make implementation of asynchronous
52 // LocalStorageCache::Release much simpler.
53 NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
54 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
55 nsrefcnt count = --mRefCnt;
56 NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
57 if (0 == count) {
58 mRefCnt = 1; /* stabilize */
59 /* enable this to find non-threadsafe destructors: */
60 /* NS_ASSERT_OWNINGTHREAD(_class); */
61 delete (this);
65 // LocalStorageCache
67 LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
68 : mActor(nullptr),
69 mOriginNoSuffix(*aOriginNoSuffix),
70 mMonitor("LocalStorageCache"),
71 mLoaded(false),
72 mLoadResult(NS_OK),
73 mInitialized(false),
74 mPersistent(false),
75 mPreloadTelemetryRecorded(false) {
76 MOZ_COUNT_CTOR(LocalStorageCache);
79 LocalStorageCache::~LocalStorageCache() {
80 if (mActor) {
81 mActor->SendDeleteMeInternal();
82 MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
85 if (mManager) {
86 mManager->DropCache(this);
89 MOZ_COUNT_DTOR(LocalStorageCache);
92 void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
93 AssertIsOnOwningThread();
94 MOZ_ASSERT(aActor);
95 MOZ_ASSERT(!mActor);
97 mActor = aActor;
100 NS_IMETHODIMP_(void)
101 LocalStorageCache::Release(void) {
102 // We must actually release on the main thread since the cache removes it
103 // self from the manager's hash table. And we don't want to lock access to
104 // that hash table.
105 if (NS_IsMainThread()) {
106 LocalStorageCacheBridge::Release();
107 return;
110 RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
111 NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
112 static_cast<LocalStorageCacheBridge*>(this),
113 &LocalStorageCacheBridge::Release);
115 nsresult rv = NS_DispatchToMainThread(event);
116 if (NS_FAILED(rv)) {
117 NS_WARNING("LocalStorageCache::Release() on a non-main thread");
118 LocalStorageCacheBridge::Release();
122 void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
123 nsIPrincipal* aPrincipal,
124 const nsACString& aQuotaOriginScope) {
125 MOZ_ASSERT(!aQuotaOriginScope.IsEmpty());
127 if (mInitialized) {
128 return;
131 mInitialized = true;
132 aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
133 mPrivateBrowsingId = aPrincipal->GetPrivateBrowsingId();
134 mPersistent = aPersistent;
135 mQuotaOriginScope = aQuotaOriginScope;
137 if (mPersistent) {
138 mManager = aManager;
139 Preload();
142 // Check the quota string has (or has not) the identical origin suffix as
143 // this storage cache is bound to.
144 MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
145 MOZ_ASSERT(mOriginSuffix.IsEmpty() !=
146 StringBeginsWith(mQuotaOriginScope, "^"_ns));
148 mUsage = aManager->GetOriginUsage(mQuotaOriginScope, mPrivateBrowsingId);
151 void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
152 const nsAString& aKey,
153 const nsAString& aOldValue,
154 const nsAString& aNewValue) {
155 AssertIsOnOwningThread();
156 MOZ_ASSERT(aStorage);
158 if (!mActor) {
159 return;
162 // We want to send a message to the parent in order to broadcast the
163 // StorageEvent correctly to any child process.
165 Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
166 aNewValue);
169 inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
170 return mPersistent &&
171 (aStorage->IsPrivateBrowsing() || !aStorage->IsSessionScopedOrLess());
174 const nsCString LocalStorageCache::Origin() const {
175 return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
178 LocalStorageCache::Data& LocalStorageCache::DataSet(
179 const LocalStorage* aStorage) {
180 return mData[GetDataSetIndex(aStorage)];
183 bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
184 int64_t aDelta,
185 const MutationSource aSource) {
186 return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
189 bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
190 const int64_t aDelta,
191 const MutationSource aSource) {
192 // Check limit per this origin
193 Data& data = mData[aGetDataSetIndex];
194 uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
195 if (aSource == ContentMutation && aDelta > 0 &&
196 newOriginUsage > LocalStorageManager::GetOriginQuota()) {
197 return false;
200 // Now check eTLD+1 limit
201 if (mUsage &&
202 !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
203 return false;
206 // Update size in our data set
207 data.mOriginQuotaUsage = newOriginUsage;
208 return true;
211 void LocalStorageCache::Preload() {
212 if (mLoaded || !mPersistent) {
213 return;
216 StorageDBChild* storageChild =
217 StorageDBChild::GetOrCreate(mPrivateBrowsingId);
218 if (!storageChild) {
219 mLoaded = true;
220 mLoadResult = NS_ERROR_FAILURE;
221 return;
224 storageChild->AsyncPreload(this);
227 void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID) {
228 if (!mPersistent) {
229 return;
232 bool loaded = mLoaded;
234 // Telemetry of rates of pending preloads
235 if (!mPreloadTelemetryRecorded) {
236 mPreloadTelemetryRecorded = true;
237 Telemetry::Accumulate(
238 Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, !loaded);
241 if (loaded) {
242 return;
245 // Measure which operation blocks and for how long
246 Telemetry::RuntimeAutoTimer timer(aTelemetryID);
248 // If preload already started (i.e. we got some first data, but not all)
249 // SyncPreload will just wait for it to finish rather then synchronously
250 // read from the database. It seems to me more optimal.
252 // TODO place for A/B testing (force main thread load vs. let preload finish)
254 // No need to check sDatabase for being non-null since preload is either
255 // done before we've shut the DB down or when the DB could not start,
256 // preload has not even be started.
257 StorageDBChild::Get(mPrivateBrowsingId)->SyncPreload(this);
260 nsresult LocalStorageCache::GetLength(const LocalStorage* aStorage,
261 uint32_t* aRetval) {
262 if (Persist(aStorage)) {
263 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
264 if (NS_FAILED(mLoadResult)) {
265 return mLoadResult;
269 *aRetval = DataSet(aStorage).mKeys.Count();
270 return NS_OK;
273 nsresult LocalStorageCache::GetKey(const LocalStorage* aStorage,
274 uint32_t aIndex, nsAString& aRetval) {
275 // XXX: This does a linear search for the key at index, which would
276 // suck if there's a large numer of indexes. Do we care? If so,
277 // maybe we need to have a lazily populated key array here or
278 // something?
279 if (Persist(aStorage)) {
280 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
281 if (NS_FAILED(mLoadResult)) {
282 return mLoadResult;
286 aRetval.SetIsVoid(true);
287 for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
288 if (aIndex == 0) {
289 aRetval = iter.Key();
290 break;
292 aIndex--;
295 return NS_OK;
298 void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
299 nsTArray<nsString>& aKeys) {
300 if (Persist(aStorage)) {
301 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
304 if (NS_FAILED(mLoadResult)) {
305 return;
308 AppendToArray(aKeys, DataSet(aStorage).mKeys.Keys());
311 nsresult LocalStorageCache::GetItem(const LocalStorage* aStorage,
312 const nsAString& aKey, nsAString& aRetval) {
313 if (Persist(aStorage)) {
314 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
315 if (NS_FAILED(mLoadResult)) {
316 return mLoadResult;
320 // not using AutoString since we don't want to copy buffer to result
321 nsString value;
322 if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
323 SetDOMStringToNull(value);
326 aRetval = value;
328 return NS_OK;
331 nsresult LocalStorageCache::SetItem(const LocalStorage* aStorage,
332 const nsAString& aKey,
333 const nsAString& aValue, nsString& aOld,
334 const MutationSource aSource) {
335 // Size of the cache that will change after this action.
336 int64_t delta = 0;
338 if (Persist(aStorage)) {
339 WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
340 if (NS_FAILED(mLoadResult)) {
341 return mLoadResult;
345 Data& data = DataSet(aStorage);
346 if (!data.mKeys.Get(aKey, &aOld)) {
347 SetDOMStringToNull(aOld);
349 // We only consider key size if the key doesn't exist before.
350 delta += static_cast<int64_t>(aKey.Length());
353 delta += static_cast<int64_t>(aValue.Length()) -
354 static_cast<int64_t>(aOld.Length());
356 if (!ProcessUsageDelta(aStorage, delta, aSource)) {
357 return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
360 if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
361 return NS_SUCCESS_DOM_NO_OPERATION;
364 data.mKeys.InsertOrUpdate(aKey, aValue);
366 if (aSource != ContentMutation) {
367 return NS_OK;
370 #if !defined(MOZ_WIDGET_ANDROID)
371 NotifyObservers(aStorage, aKey, aOld, aValue);
372 #endif
374 if (Persist(aStorage)) {
375 StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
376 if (!storageChild) {
377 NS_ERROR(
378 "Writing to localStorage after the database has been shut down"
379 ", data lose!");
380 return NS_ERROR_NOT_INITIALIZED;
383 if (DOMStringIsNull(aOld)) {
384 return storageChild->AsyncAddItem(this, aKey, aValue);
387 return storageChild->AsyncUpdateItem(this, aKey, aValue);
390 return NS_OK;
393 nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
394 const nsAString& aKey, nsString& aOld,
395 const MutationSource aSource) {
396 if (Persist(aStorage)) {
397 WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
398 if (NS_FAILED(mLoadResult)) {
399 return mLoadResult;
403 Data& data = DataSet(aStorage);
404 if (!data.mKeys.Get(aKey, &aOld)) {
405 SetDOMStringToNull(aOld);
406 return NS_SUCCESS_DOM_NO_OPERATION;
409 // Recalculate the cached data size
410 const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
411 static_cast<int64_t>(aKey.Length()));
412 Unused << ProcessUsageDelta(aStorage, delta, aSource);
413 data.mKeys.Remove(aKey);
415 if (aSource != ContentMutation) {
416 return NS_OK;
419 #if !defined(MOZ_WIDGET_ANDROID)
420 NotifyObservers(aStorage, aKey, aOld, VoidString());
421 #endif
423 if (Persist(aStorage)) {
424 StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
425 if (!storageChild) {
426 NS_ERROR(
427 "Writing to localStorage after the database has been shut down"
428 ", data lose!");
429 return NS_ERROR_NOT_INITIALIZED;
432 return storageChild->AsyncRemoveItem(this, aKey);
435 return NS_OK;
438 nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
439 const MutationSource aSource) {
440 bool refresh = false;
441 if (Persist(aStorage)) {
442 // We need to preload all data (know the size) before we can proceeed
443 // to correctly decrease cached usage number.
444 // XXX as in case of unload, this is not technically needed now, but
445 // after super-scope quota introduction we have to do this. Get telemetry
446 // right now.
447 WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
448 if (NS_FAILED(mLoadResult)) {
449 // When we failed to load data from the database, force delete of the
450 // scope data and make use of the storage possible again.
451 refresh = true;
452 mLoadResult = NS_OK;
456 Data& data = DataSet(aStorage);
457 bool hadData = !!data.mKeys.Count();
459 if (hadData) {
460 Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
461 data.mKeys.Clear();
464 if (aSource != ContentMutation) {
465 return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
468 #if !defined(MOZ_WIDGET_ANDROID)
469 if (hadData) {
470 NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
472 #endif
474 if (Persist(aStorage) && (refresh || hadData)) {
475 StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
476 if (!storageChild) {
477 NS_ERROR(
478 "Writing to localStorage after the database has been shut down"
479 ", data lose!");
480 return NS_ERROR_NOT_INITIALIZED;
483 return storageChild->AsyncClear(this);
486 return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
489 int64_t LocalStorageCache::GetOriginQuotaUsage(
490 const LocalStorage* aStorage) const {
491 return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
494 void LocalStorageCache::UnloadItems(uint32_t aUnloadFlags) {
495 if (aUnloadFlags & kUnloadDefault) {
496 // Must wait for preload to pass correct usage to ProcessUsageDelta
497 // XXX this is not technically needed right now since there is just
498 // per-origin isolated quota handling, but when we introduce super-
499 // -scope quotas, we have to do this. Better to start getting
500 // telemetry right now.
501 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
503 mData[kDefaultSet].mKeys.Clear();
504 ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
507 if (aUnloadFlags & kUnloadSession) {
508 mData[kSessionSet].mKeys.Clear();
509 ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
512 #ifdef DOM_STORAGE_TESTS
513 if (aUnloadFlags & kTestReload) {
514 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
516 mData[kDefaultSet].mKeys.Clear();
517 mLoaded = false; // This is only used in testing code
518 Preload();
520 #endif
523 // LocalStorageCacheBridge
525 uint32_t LocalStorageCache::LoadedCount() {
526 MonitorAutoLock monitor(mMonitor);
527 Data& data = mData[kDefaultSet];
528 return data.mKeys.Count();
531 bool LocalStorageCache::LoadItem(const nsAString& aKey,
532 const nsAString& aValue) {
533 MonitorAutoLock monitor(mMonitor);
534 if (mLoaded) {
535 return false;
538 Data& data = mData[kDefaultSet];
539 data.mKeys.LookupOrInsertWith(aKey, [&] {
540 data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
541 return nsString(aValue);
543 return true;
546 void LocalStorageCache::LoadDone(nsresult aRv) {
547 MonitorAutoLock monitor(mMonitor);
548 mLoadResult = aRv;
549 mLoaded = true;
550 monitor.Notify();
553 void LocalStorageCache::LoadWait() {
554 MonitorAutoLock monitor(mMonitor);
555 while (!mLoaded) {
556 monitor.Wait();
560 // StorageUsage
562 StorageUsage::StorageUsage(const nsACString& aOriginScope)
563 : mOriginScope(aOriginScope) {
564 mUsage[kDefaultSet] = mUsage[kSessionSet] = 0LL;
567 namespace {
569 class LoadUsageRunnable : public Runnable {
570 public:
571 LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
572 : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
574 private:
575 int64_t* mTarget;
576 int64_t mDelta;
578 NS_IMETHOD Run() override {
579 *mTarget = mDelta;
580 return NS_OK;
584 } // namespace
586 void StorageUsage::LoadUsage(const int64_t aUsage) {
587 // Using kDefaultSet index since it is the index for the persitent data
588 // stored in the database we have just loaded usage for.
589 if (!NS_IsMainThread()) {
590 // In single process scenario we get this call from the DB thread
591 RefPtr<LoadUsageRunnable> r =
592 new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
593 NS_DispatchToMainThread(r);
594 } else {
595 // On a child process we get this on the main thread already
596 mUsage[kDefaultSet] += aUsage;
600 bool StorageUsage::CheckAndSetETLD1UsageDelta(
601 uint32_t aDataSetIndex, const int64_t aDelta,
602 const LocalStorageCache::MutationSource aSource) {
603 MOZ_ASSERT(NS_IsMainThread());
605 int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
606 if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
607 newUsage > LocalStorageManager::GetSiteQuota()) {
608 return false;
611 mUsage[aDataSetIndex] = newUsage;
612 return true;
615 } // namespace mozilla::dom