Source code

Revision control

Other Tools

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/. */
6
7
#include "LocalStorageCache.h"
8
9
#include "Storage.h"
10
#include "StorageDBThread.h"
11
#include "StorageIPC.h"
12
#include "StorageUtils.h"
13
#include "LocalStorageManager.h"
14
15
#include "nsDOMString.h"
16
#include "nsXULAppAPI.h"
17
#include "mozilla/Unused.h"
18
#include "nsProxyRelease.h"
19
#include "nsThreadUtils.h"
20
21
namespace mozilla {
22
namespace dom {
23
24
#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
25
26
namespace {
27
28
const uint32_t kDefaultSet = 0;
29
const uint32_t kPrivateSet = 1;
30
const uint32_t kSessionSet = 2;
31
32
inline uint32_t GetDataSetIndex(bool aPrivate, bool aSessionOnly) {
33
if (aPrivate) {
34
return kPrivateSet;
35
}
36
37
if (aSessionOnly) {
38
return kSessionSet;
39
}
40
41
return kDefaultSet;
42
}
43
44
inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
45
return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly());
46
}
47
48
} // namespace
49
50
// LocalStorageCacheBridge
51
52
NS_IMPL_ADDREF(LocalStorageCacheBridge)
53
54
// Since there is no consumer of return value of Release, we can turn this
55
// method to void to make implementation of asynchronous
56
// LocalStorageCache::Release much simpler.
57
NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
58
MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
59
nsrefcnt count = --mRefCnt;
60
NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
61
if (0 == count) {
62
mRefCnt = 1; /* stabilize */
63
/* enable this to find non-threadsafe destructors: */
64
/* NS_ASSERT_OWNINGTHREAD(_class); */
65
delete (this);
66
}
67
}
68
69
// LocalStorageCache
70
71
LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
72
: mActor(nullptr),
73
mOriginNoSuffix(*aOriginNoSuffix),
74
mMonitor("LocalStorageCache"),
75
mLoaded(false),
76
mLoadResult(NS_OK),
77
mInitialized(false),
78
mPersistent(false),
79
mPreloadTelemetryRecorded(false) {
80
MOZ_COUNT_CTOR(LocalStorageCache);
81
}
82
83
LocalStorageCache::~LocalStorageCache() {
84
if (mActor) {
85
mActor->SendDeleteMeInternal();
86
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
87
}
88
89
if (mManager) {
90
mManager->DropCache(this);
91
}
92
93
MOZ_COUNT_DTOR(LocalStorageCache);
94
}
95
96
void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
97
AssertIsOnOwningThread();
98
MOZ_ASSERT(aActor);
99
MOZ_ASSERT(!mActor);
100
101
mActor = aActor;
102
}
103
104
NS_IMETHODIMP_(void)
105
LocalStorageCache::Release(void) {
106
// We must actually release on the main thread since the cache removes it
107
// self from the manager's hash table. And we don't want to lock access to
108
// that hash table.
109
if (NS_IsMainThread()) {
110
LocalStorageCacheBridge::Release();
111
return;
112
}
113
114
RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
115
NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
116
static_cast<LocalStorageCacheBridge*>(this),
117
&LocalStorageCacheBridge::Release);
118
119
nsresult rv = NS_DispatchToMainThread(event);
120
if (NS_FAILED(rv)) {
121
NS_WARNING("LocalStorageCache::Release() on a non-main thread");
122
LocalStorageCacheBridge::Release();
123
}
124
}
125
126
void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
127
nsIPrincipal* aPrincipal,
128
const nsACString& aQuotaOriginScope) {
129
if (mInitialized) {
130
return;
131
}
132
133
mInitialized = true;
134
aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
135
mPersistent = aPersistent;
136
if (aQuotaOriginScope.IsEmpty()) {
137
mQuotaOriginScope = Origin();
138
} else {
139
mQuotaOriginScope = aQuotaOriginScope;
140
}
141
142
if (mPersistent) {
143
mManager = aManager;
144
Preload();
145
}
146
147
// Check the quota string has (or has not) the identical origin suffix as
148
// this storage cache is bound to.
149
MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
150
MOZ_ASSERT(mOriginSuffix.IsEmpty() !=
151
StringBeginsWith(mQuotaOriginScope, NS_LITERAL_CSTRING("^")));
152
153
mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
154
}
155
156
void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
157
const nsString& aKey,
158
const nsString& aOldValue,
159
const nsString& aNewValue) {
160
AssertIsOnOwningThread();
161
MOZ_ASSERT(aStorage);
162
163
if (!mActor) {
164
return;
165
}
166
167
// We want to send a message to the parent in order to broadcast the
168
// StorageEvent correctly to any child process.
169
170
Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
171
aNewValue);
172
}
173
174
inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
175
return mPersistent && !aStorage->IsSessionOnly() && !aStorage->IsPrivate();
176
}
177
178
const nsCString LocalStorageCache::Origin() const {
179
return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
180
}
181
182
LocalStorageCache::Data& LocalStorageCache::DataSet(
183
const LocalStorage* aStorage) {
184
return mData[GetDataSetIndex(aStorage)];
185
}
186
187
bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
188
int64_t aDelta,
189
const MutationSource aSource) {
190
return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
191
}
192
193
bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
194
const int64_t aDelta,
195
const MutationSource aSource) {
196
// Check limit per this origin
197
Data& data = mData[aGetDataSetIndex];
198
uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
199
if (aSource == ContentMutation && aDelta > 0 &&
200
newOriginUsage > LocalStorageManager::GetQuota()) {
201
return false;
202
}
203
204
// Now check eTLD+1 limit
205
if (mUsage &&
206
!mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
207
return false;
208
}
209
210
// Update size in our data set
211
data.mOriginQuotaUsage = newOriginUsage;
212
return true;
213
}
214
215
void LocalStorageCache::Preload() {
216
if (mLoaded || !mPersistent) {
217
return;
218
}
219
220
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
221
if (!storageChild) {
222
mLoaded = true;
223
mLoadResult = NS_ERROR_FAILURE;
224
return;
225
}
226
227
storageChild->AsyncPreload(this);
228
}
229
230
void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID) {
231
if (!mPersistent) {
232
return;
233
}
234
235
bool loaded = mLoaded;
236
237
// Telemetry of rates of pending preloads
238
if (!mPreloadTelemetryRecorded) {
239
mPreloadTelemetryRecorded = true;
240
Telemetry::Accumulate(
241
Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, !loaded);
242
}
243
244
if (loaded) {
245
return;
246
}
247
248
// Measure which operation blocks and for how long
249
Telemetry::RuntimeAutoTimer timer(aTelemetryID);
250
251
// If preload already started (i.e. we got some first data, but not all)
252
// SyncPreload will just wait for it to finish rather then synchronously
253
// read from the database. It seems to me more optimal.
254
255
// TODO place for A/B testing (force main thread load vs. let preload finish)
256
257
// No need to check sDatabase for being non-null since preload is either
258
// done before we've shut the DB down or when the DB could not start,
259
// preload has not even be started.
260
StorageDBChild::Get()->SyncPreload(this);
261
}
262
263
nsresult LocalStorageCache::GetLength(const LocalStorage* aStorage,
264
uint32_t* aRetval) {
265
if (Persist(aStorage)) {
266
WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
267
if (NS_FAILED(mLoadResult)) {
268
return mLoadResult;
269
}
270
}
271
272
*aRetval = DataSet(aStorage).mKeys.Count();
273
return NS_OK;
274
}
275
276
nsresult LocalStorageCache::GetKey(const LocalStorage* aStorage,
277
uint32_t aIndex, nsAString& aRetval) {
278
// XXX: This does a linear search for the key at index, which would
279
// suck if there's a large numer of indexes. Do we care? If so,
280
// maybe we need to have a lazily populated key array here or
281
// something?
282
if (Persist(aStorage)) {
283
WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
284
if (NS_FAILED(mLoadResult)) {
285
return mLoadResult;
286
}
287
}
288
289
aRetval.SetIsVoid(true);
290
for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
291
if (aIndex == 0) {
292
aRetval = iter.Key();
293
break;
294
}
295
aIndex--;
296
}
297
298
return NS_OK;
299
}
300
301
void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
302
nsTArray<nsString>& aKeys) {
303
if (Persist(aStorage)) {
304
WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
305
}
306
307
if (NS_FAILED(mLoadResult)) {
308
return;
309
}
310
311
for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
312
aKeys.AppendElement(iter.Key());
313
}
314
}
315
316
nsresult LocalStorageCache::GetItem(const LocalStorage* aStorage,
317
const nsAString& aKey, nsAString& aRetval) {
318
if (Persist(aStorage)) {
319
WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
320
if (NS_FAILED(mLoadResult)) {
321
return mLoadResult;
322
}
323
}
324
325
// not using AutoString since we don't want to copy buffer to result
326
nsString value;
327
if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
328
SetDOMStringToNull(value);
329
}
330
331
aRetval = value;
332
333
return NS_OK;
334
}
335
336
nsresult LocalStorageCache::SetItem(const LocalStorage* aStorage,
337
const nsAString& aKey,
338
const nsString& aValue, nsString& aOld,
339
const MutationSource aSource) {
340
// Size of the cache that will change after this action.
341
int64_t delta = 0;
342
343
if (Persist(aStorage)) {
344
WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
345
if (NS_FAILED(mLoadResult)) {
346
return mLoadResult;
347
}
348
}
349
350
Data& data = DataSet(aStorage);
351
if (!data.mKeys.Get(aKey, &aOld)) {
352
SetDOMStringToNull(aOld);
353
354
// We only consider key size if the key doesn't exist before.
355
delta += static_cast<int64_t>(aKey.Length());
356
}
357
358
delta += static_cast<int64_t>(aValue.Length()) -
359
static_cast<int64_t>(aOld.Length());
360
361
if (!ProcessUsageDelta(aStorage, delta, aSource)) {
362
return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
363
}
364
365
if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
366
return NS_SUCCESS_DOM_NO_OPERATION;
367
}
368
369
data.mKeys.Put(aKey, aValue);
370
371
if (aSource != ContentMutation) {
372
return NS_OK;
373
}
374
375
#if !defined(MOZ_WIDGET_ANDROID)
376
NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
377
#endif
378
379
if (Persist(aStorage)) {
380
StorageDBChild* storageChild = StorageDBChild::Get();
381
if (!storageChild) {
382
NS_ERROR(
383
"Writing to localStorage after the database has been shut down"
384
", data lose!");
385
return NS_ERROR_NOT_INITIALIZED;
386
}
387
388
if (DOMStringIsNull(aOld)) {
389
return storageChild->AsyncAddItem(this, aKey, aValue);
390
}
391
392
return storageChild->AsyncUpdateItem(this, aKey, aValue);
393
}
394
395
return NS_OK;
396
}
397
398
nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
399
const nsAString& aKey, nsString& aOld,
400
const MutationSource aSource) {
401
if (Persist(aStorage)) {
402
WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
403
if (NS_FAILED(mLoadResult)) {
404
return mLoadResult;
405
}
406
}
407
408
Data& data = DataSet(aStorage);
409
if (!data.mKeys.Get(aKey, &aOld)) {
410
SetDOMStringToNull(aOld);
411
return NS_SUCCESS_DOM_NO_OPERATION;
412
}
413
414
// Recalculate the cached data size
415
const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
416
static_cast<int64_t>(aKey.Length()));
417
Unused << ProcessUsageDelta(aStorage, delta, aSource);
418
data.mKeys.Remove(aKey);
419
420
if (aSource != ContentMutation) {
421
return NS_OK;
422
}
423
424
#if !defined(MOZ_WIDGET_ANDROID)
425
NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
426
#endif
427
428
if (Persist(aStorage)) {
429
StorageDBChild* storageChild = StorageDBChild::Get();
430
if (!storageChild) {
431
NS_ERROR(
432
"Writing to localStorage after the database has been shut down"
433
", data lose!");
434
return NS_ERROR_NOT_INITIALIZED;
435
}
436
437
return storageChild->AsyncRemoveItem(this, aKey);
438
}
439
440
return NS_OK;
441
}
442
443
nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
444
const MutationSource aSource) {
445
bool refresh = false;
446
if (Persist(aStorage)) {
447
// We need to preload all data (know the size) before we can proceeed
448
// to correctly decrease cached usage number.
449
// XXX as in case of unload, this is not technically needed now, but
450
// after super-scope quota introduction we have to do this. Get telemetry
451
// right now.
452
WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
453
if (NS_FAILED(mLoadResult)) {
454
// When we failed to load data from the database, force delete of the
455
// scope data and make use of the storage possible again.
456
refresh = true;
457
mLoadResult = NS_OK;
458
}
459
}
460
461
Data& data = DataSet(aStorage);
462
bool hadData = !!data.mKeys.Count();
463
464
if (hadData) {
465
Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
466
data.mKeys.Clear();
467
}
468
469
if (aSource != ContentMutation) {
470
return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
471
}
472
473
#if !defined(MOZ_WIDGET_ANDROID)
474
if (hadData) {
475
NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
476
}
477
#endif
478
479
if (Persist(aStorage) && (refresh || hadData)) {
480
StorageDBChild* storageChild = StorageDBChild::Get();
481
if (!storageChild) {
482
NS_ERROR(
483
"Writing to localStorage after the database has been shut down"
484
", data lose!");
485
return NS_ERROR_NOT_INITIALIZED;
486
}
487
488
return storageChild->AsyncClear(this);
489
}
490
491
return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
492
}
493
494
int64_t LocalStorageCache::GetOriginQuotaUsage(
495
const LocalStorage* aStorage) const {
496
return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
497
}
498
499
void LocalStorageCache::UnloadItems(uint32_t aUnloadFlags) {
500
if (aUnloadFlags & kUnloadDefault) {
501
// Must wait for preload to pass correct usage to ProcessUsageDelta
502
// XXX this is not technically needed right now since there is just
503
// per-origin isolated quota handling, but when we introduce super-
504
// -scope quotas, we have to do this. Better to start getting
505
// telemetry right now.
506
WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
507
508
mData[kDefaultSet].mKeys.Clear();
509
ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
510
}
511
512
if (aUnloadFlags & kUnloadPrivate) {
513
mData[kPrivateSet].mKeys.Clear();
514
ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
515
}
516
517
if (aUnloadFlags & kUnloadSession) {
518
mData[kSessionSet].mKeys.Clear();
519
ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
520
}
521
522
#ifdef DOM_STORAGE_TESTS
523
if (aUnloadFlags & kTestReload) {
524
WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
525
526
mData[kDefaultSet].mKeys.Clear();
527
mLoaded = false; // This is only used in testing code
528
Preload();
529
}
530
#endif
531
}
532
533
// LocalStorageCacheBridge
534
535
uint32_t LocalStorageCache::LoadedCount() {
536
MonitorAutoLock monitor(mMonitor);
537
Data& data = mData[kDefaultSet];
538
return data.mKeys.Count();
539
}
540
541
bool LocalStorageCache::LoadItem(const nsAString& aKey,
542
const nsString& aValue) {
543
MonitorAutoLock monitor(mMonitor);
544
if (mLoaded) {
545
return false;
546
}
547
548
Data& data = mData[kDefaultSet];
549
if (data.mKeys.Get(aKey, nullptr)) {
550
return true; // don't stop, just don't override
551
}
552
553
data.mKeys.Put(aKey, aValue);
554
data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
555
return true;
556
}
557
558
void LocalStorageCache::LoadDone(nsresult aRv) {
559
MonitorAutoLock monitor(mMonitor);
560
mLoadResult = aRv;
561
mLoaded = true;
562
monitor.Notify();
563
}
564
565
void LocalStorageCache::LoadWait() {
566
MonitorAutoLock monitor(mMonitor);
567
while (!mLoaded) {
568
monitor.Wait();
569
}
570
}
571
572
// StorageUsage
573
574
StorageUsage::StorageUsage(const nsACString& aOriginScope)
575
: mOriginScope(aOriginScope) {
576
mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
577
}
578
579
namespace {
580
581
class LoadUsageRunnable : public Runnable {
582
public:
583
LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
584
: Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
585
586
private:
587
int64_t* mTarget;
588
int64_t mDelta;
589
590
NS_IMETHOD Run() override {
591
*mTarget = mDelta;
592
return NS_OK;
593
}
594
};
595
596
} // namespace
597
598
void StorageUsage::LoadUsage(const int64_t aUsage) {
599
// Using kDefaultSet index since it is the index for the persitent data
600
// stored in the database we have just loaded usage for.
601
if (!NS_IsMainThread()) {
602
// In single process scenario we get this call from the DB thread
603
RefPtr<LoadUsageRunnable> r =
604
new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
605
NS_DispatchToMainThread(r);
606
} else {
607
// On a child process we get this on the main thread already
608
mUsage[kDefaultSet] += aUsage;
609
}
610
}
611
612
bool StorageUsage::CheckAndSetETLD1UsageDelta(
613
uint32_t aDataSetIndex, const int64_t aDelta,
614
const LocalStorageCache::MutationSource aSource) {
615
MOZ_ASSERT(NS_IsMainThread());
616
617
int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
618
if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
619
newUsage > LocalStorageManager::GetQuota()) {
620
return false;
621
}
622
623
mUsage[aDataSetIndex] = newUsage;
624
return true;
625
}
626
627
} // namespace dom
628
} // namespace mozilla