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"
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"
25 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
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
) {
45 inline uint32_t GetDataSetIndex(const LocalStorage
* aStorage
) {
46 return GetDataSetIndex(aStorage
->IsPrivate(), aStorage
->IsSessionOnly());
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");
63 mRefCnt
= 1; /* stabilize */
64 /* enable this to find non-threadsafe destructors: */
65 /* NS_ASSERT_OWNINGTHREAD(_class); */
72 LocalStorageCache::LocalStorageCache(const nsACString
* aOriginNoSuffix
)
74 mOriginNoSuffix(*aOriginNoSuffix
),
75 mMonitor("LocalStorageCache"),
80 mPreloadTelemetryRecorded(false) {
81 MOZ_COUNT_CTOR(LocalStorageCache
);
84 LocalStorageCache::~LocalStorageCache() {
86 mActor
->SendDeleteMeInternal();
87 MOZ_ASSERT(!mActor
, "SendDeleteMeInternal should have cleared!");
91 mManager
->DropCache(this);
94 MOZ_COUNT_DTOR(LocalStorageCache
);
97 void LocalStorageCache::SetActor(LocalStorageCacheChild
* aActor
) {
98 AssertIsOnOwningThread();
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
110 if (NS_IsMainThread()) {
111 LocalStorageCacheBridge::Release();
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
);
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
) {
135 aPrincipal
->OriginAttributesRef().CreateSuffix(mOriginSuffix
);
136 mPersistent
= aPersistent
;
137 if (aQuotaOriginScope
.IsEmpty()) {
138 mQuotaOriginScope
= Origin();
140 mQuotaOriginScope
= aQuotaOriginScope
;
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
);
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
,
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
,
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()) {
205 // Now check eTLD+1 limit
207 !mUsage
->CheckAndSetETLD1UsageDelta(aGetDataSetIndex
, aDelta
, aSource
)) {
211 // Update size in our data set
212 data
.mOriginQuotaUsage
= newOriginUsage
;
216 void LocalStorageCache::Preload() {
217 if (mLoaded
|| !mPersistent
) {
221 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate();
224 mLoadResult
= NS_ERROR_FAILURE
;
228 storageChild
->AsyncPreload(this);
231 void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID
) {
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
);
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
,
266 if (Persist(aStorage
)) {
267 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS
);
268 if (NS_FAILED(mLoadResult
)) {
273 *aRetval
= DataSet(aStorage
).mKeys
.Count();
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
283 if (Persist(aStorage
)) {
284 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS
);
285 if (NS_FAILED(mLoadResult
)) {
290 aRetval
.SetIsVoid(true);
291 for (auto iter
= DataSet(aStorage
).mKeys
.Iter(); !iter
.Done(); iter
.Next()) {
293 aRetval
= iter
.Key();
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
)) {
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
)) {
326 // not using AutoString since we don't want to copy buffer to result
328 if (!DataSet(aStorage
).mKeys
.Get(aKey
, &value
)) {
329 SetDOMStringToNull(value
);
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.
344 if (Persist(aStorage
)) {
345 WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS
);
346 if (NS_FAILED(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
) {
376 #if !defined(MOZ_WIDGET_ANDROID)
377 NotifyObservers(aStorage
, nsString(aKey
), aOld
, aValue
);
380 if (Persist(aStorage
)) {
381 StorageDBChild
* storageChild
= StorageDBChild::Get();
384 "Writing to localStorage after the database has been shut down"
386 return NS_ERROR_NOT_INITIALIZED
;
389 if (DOMStringIsNull(aOld
)) {
390 return storageChild
->AsyncAddItem(this, aKey
, aValue
);
393 return storageChild
->AsyncUpdateItem(this, aKey
, aValue
);
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
)) {
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
) {
425 #if !defined(MOZ_WIDGET_ANDROID)
426 NotifyObservers(aStorage
, nsString(aKey
), aOld
, VoidString());
429 if (Persist(aStorage
)) {
430 StorageDBChild
* storageChild
= StorageDBChild::Get();
433 "Writing to localStorage after the database has been shut down"
435 return NS_ERROR_NOT_INITIALIZED
;
438 return storageChild
->AsyncRemoveItem(this, aKey
);
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
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.
462 Data
& data
= DataSet(aStorage
);
463 bool hadData
= !!data
.mKeys
.Count();
466 Unused
<< ProcessUsageDelta(aStorage
, -data
.mOriginQuotaUsage
, aSource
);
470 if (aSource
!= ContentMutation
) {
471 return hadData
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION
;
474 #if !defined(MOZ_WIDGET_ANDROID)
476 NotifyObservers(aStorage
, VoidString(), VoidString(), VoidString());
480 if (Persist(aStorage
) && (refresh
|| hadData
)) {
481 StorageDBChild
* storageChild
= StorageDBChild::Get();
484 "Writing to localStorage after the database has been shut down"
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
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
);
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();
559 void LocalStorageCache::LoadDone(nsresult aRv
) {
560 MonitorAutoLock
monitor(mMonitor
);
566 void LocalStorageCache::LoadWait() {
567 MonitorAutoLock
monitor(mMonitor
);
575 StorageUsage::StorageUsage(const nsACString
& aOriginScope
)
576 : mOriginScope(aOriginScope
) {
577 mUsage
[kDefaultSet
] = mUsage
[kPrivateSet
] = mUsage
[kSessionSet
] = 0LL;
582 class LoadUsageRunnable
: public Runnable
{
584 LoadUsageRunnable(int64_t* aUsage
, const int64_t aDelta
)
585 : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage
), mDelta(aDelta
) {}
591 NS_IMETHOD
Run() override
{
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
);
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()) {
624 mUsage
[aDataSetIndex
] = newUsage
;
629 } // namespace mozilla