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 "nsAutoPtr.h"
16
#include "nsDOMString.h"
17
#include "nsXULAppAPI.h"
18
#include "mozilla/Unused.h"
19
#include "nsProxyRelease.h"
20
#include "nsThreadUtils.h"
21
22
namespace mozilla {
23
namespace dom {
24
25
#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
26
27
namespace {
28
29
const uint32_t kDefaultSet = 0;
30
const uint32_t kPrivateSet = 1;
31
const uint32_t kSessionSet = 2;
32
33
inline uint32_t GetDataSetIndex(bool aPrivate, bool aSessionOnly) {
34
if (aPrivate) {
35
return kPrivateSet;
36
}
37
38
if (aSessionOnly) {
39
return kSessionSet;
40
}
41
42
return kDefaultSet;
43
}
44
45
inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
46
return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly());
47
}
48
49
} // namespace
50
51
// LocalStorageCacheBridge
52
53
NS_IMPL_ADDREF(LocalStorageCacheBridge)
54
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);
67
}
68
}
69
70
// LocalStorageCache
71
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);
82
}
83
84
LocalStorageCache::~LocalStorageCache() {
85
if (mActor) {
86
mActor->SendDeleteMeInternal();
87
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
88
}
89
90
if (mManager) {
91
mManager->DropCache(this);
92
}
93
94
MOZ_COUNT_DTOR(LocalStorageCache);
95
}
96
97
void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
98
AssertIsOnOwningThread();
99
MOZ_ASSERT(aActor);
100
MOZ_ASSERT(!mActor);
101
102
mActor = aActor;
103
}
104
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;
113
}
114
115
RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
116
NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
117
static_cast<LocalStorageCacheBridge*>(this),
118
&LocalStorageCacheBridge::Release);
119
120
nsresult rv = NS_DispatchToMainThread(event);
121
if (NS_FAILED(rv)) {
122
NS_WARNING("LocalStorageCache::Release() on a non-main thread");
123
LocalStorageCacheBridge::Release();
124
}
125
}
126
127
void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
128
nsIPrincipal* aPrincipal,
129
const nsACString& aQuotaOriginScope) {
130
if (mInitialized) {
131
return;
132
}
133
134
mInitialized = true;
135
aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
136
mPersistent = aPersistent;
137
if (aQuotaOriginScope.IsEmpty()) {
138
mQuotaOriginScope = Origin();
139
} else {
140
mQuotaOriginScope = aQuotaOriginScope;
141
}
142
143
if (mPersistent) {
144
mManager = aManager;
145
Preload();
146
}
147
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("^")));
153
154
mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
155
}
156
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);
163
164
if (!mActor) {
165
return;
166
}
167
168
// We want to send a message to the parent in order to broadcast the
169
// StorageEvent correctly to any child process.
170
171
Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
172
aNewValue);
173
}
174
175
inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
176
return mPersistent && !aStorage->IsSessionOnly() && !aStorage->IsPrivate();
177
}
178
179
const nsCString LocalStorageCache::Origin() const {
180
return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
181
}
182
183
LocalStorageCache::Data& LocalStorageCache::DataSet(
184
const LocalStorage* aStorage) {
185
return mData[GetDataSetIndex(aStorage)];
186
}
187
188
bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
189
int64_t aDelta,
190
const MutationSource aSource) {
191
return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
192
}
193
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;
203
}
204
205
// Now check eTLD+1 limit
206
if (mUsage &&
207
!mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
208
return false;
209
}
210
211
// Update size in our data set
212
data.mOriginQuotaUsage = newOriginUsage;
213
return true;
214
}
215
216
void LocalStorageCache::Preload() {
217
if (mLoaded || !mPersistent) {
218
return;
219
}
220
221
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
222
if (!storageChild) {
223
mLoaded = true;
224
mLoadResult = NS_ERROR_FAILURE;
225
return;
226
}
227
228
storageChild->AsyncPreload(this);
229
}
230
231
void LocalStorageCache::WaitForPreload(Telemetry::HistogramID aTelemetryID) {
232
if (!mPersistent) {
233
return;
234
}
235
236
bool loaded = mLoaded;
237
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);
243
}
244
245
if (loaded) {
246
return;
247
}
248
249
// Measure which operation blocks and for how long
250
Telemetry::RuntimeAutoTimer timer(aTelemetryID);
251
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.
255
256
// TODO place for A/B testing (force main thread load vs. let preload finish)
257
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);
262
}
263
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;
270
}
271
}
272
273
*aRetval = DataSet(aStorage).mKeys.Count();
274
return NS_OK;
275
}
276
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;
287
}
288
}
289
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;
295
}
296
aIndex--;
297
}
298
299
return NS_OK;
300
}
301
302
void LocalStorageCache::GetKeys(const LocalStorage* aStorage,
303
nsTArray<nsString>& aKeys) {
304
if (Persist(aStorage)) {
305
WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
306
}
307
308
if (NS_FAILED(mLoadResult)) {
309
return;
310
}
311
312
for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
313
aKeys.AppendElement(iter.Key());
314
}
315
}
316
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;
323
}
324
}
325
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);
330
}
331
332
aRetval = value;
333
334
return NS_OK;
335
}
336
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;
343
344
if (Persist(aStorage)) {
345
WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
346
if (NS_FAILED(mLoadResult)) {
347
return mLoadResult;
348
}
349
}
350
351
Data& data = DataSet(aStorage);
352
if (!data.mKeys.Get(aKey, &aOld)) {
353
SetDOMStringToNull(aOld);
354
355
// We only consider key size if the key doesn't exist before.
356
delta += static_cast<int64_t>(aKey.Length());
357
}
358
359
delta += static_cast<int64_t>(aValue.Length()) -
360
static_cast<int64_t>(aOld.Length());
361
362
if (!ProcessUsageDelta(aStorage, delta, aSource)) {
363
return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
364
}
365
366
if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
367
return NS_SUCCESS_DOM_NO_OPERATION;
368
}
369
370
data.mKeys.Put(aKey, aValue);
371
372
if (aSource != ContentMutation) {
373
return NS_OK;
374
}
375
376
#if !defined(MOZ_WIDGET_ANDROID)
377
NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
378
#endif
379
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;
387
}
388
389
if (DOMStringIsNull(aOld)) {
390
return storageChild->AsyncAddItem(this, aKey, aValue);
391
}
392
393
return storageChild->AsyncUpdateItem(this, aKey, aValue);
394
}
395
396
return NS_OK;
397
}
398
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;
406
}
407
}
408
409
Data& data = DataSet(aStorage);
410
if (!data.mKeys.Get(aKey, &aOld)) {
411
SetDOMStringToNull(aOld);
412
return NS_SUCCESS_DOM_NO_OPERATION;
413
}
414
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);
420
421
if (aSource != ContentMutation) {
422
return NS_OK;
423
}
424
425
#if !defined(MOZ_WIDGET_ANDROID)
426
NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
427
#endif
428
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;
436
}
437
438
return storageChild->AsyncRemoveItem(this, aKey);
439
}
440
441
return NS_OK;
442
}
443
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;
459
}
460
}
461
462
Data& data = DataSet(aStorage);
463
bool hadData = !!data.mKeys.Count();
464
465
if (hadData) {
466
Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage, aSource);
467
data.mKeys.Clear();
468
}
469
470
if (aSource != ContentMutation) {
471
return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
472
}
473
474
#if !defined(MOZ_WIDGET_ANDROID)
475
if (hadData) {
476
NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
477
}
478
#endif
479
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;
487
}
488
489
return storageChild->AsyncClear(this);
490
}
491
492
return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
493
}
494
495
int64_t LocalStorageCache::GetOriginQuotaUsage(
496
const LocalStorage* aStorage) const {
497
return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
498
}
499
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);
508
509
mData[kDefaultSet].mKeys.Clear();
510
ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
511
}
512
513
if (aUnloadFlags & kUnloadPrivate) {
514
mData[kPrivateSet].mKeys.Clear();
515
ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
516
}
517
518
if (aUnloadFlags & kUnloadSession) {
519
mData[kSessionSet].mKeys.Clear();
520
ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
521
}
522
523
#ifdef DOM_STORAGE_TESTS
524
if (aUnloadFlags & kTestReload) {
525
WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
526
527
mData[kDefaultSet].mKeys.Clear();
528
mLoaded = false; // This is only used in testing code
529
Preload();
530
}
531
#endif
532
}
533
534
// LocalStorageCacheBridge
535
536
uint32_t LocalStorageCache::LoadedCount() {
537
MonitorAutoLock monitor(mMonitor);
538
Data& data = mData[kDefaultSet];
539
return data.mKeys.Count();
540
}
541
542
bool LocalStorageCache::LoadItem(const nsAString& aKey,
543
const nsString& aValue) {
544
MonitorAutoLock monitor(mMonitor);
545
if (mLoaded) {
546
return false;
547
}
548
549
Data& data = mData[kDefaultSet];
550
if (data.mKeys.Get(aKey, nullptr)) {
551
return true; // don't stop, just don't override
552
}
553
554
data.mKeys.Put(aKey, aValue);
555
data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
556
return true;
557
}
558
559
void LocalStorageCache::LoadDone(nsresult aRv) {
560
MonitorAutoLock monitor(mMonitor);
561
mLoadResult = aRv;
562
mLoaded = true;
563
monitor.Notify();
564
}
565
566
void LocalStorageCache::LoadWait() {
567
MonitorAutoLock monitor(mMonitor);
568
while (!mLoaded) {
569
monitor.Wait();
570
}
571
}
572
573
// StorageUsage
574
575
StorageUsage::StorageUsage(const nsACString& aOriginScope)
576
: mOriginScope(aOriginScope) {
577
mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
578
}
579
580
namespace {
581
582
class LoadUsageRunnable : public Runnable {
583
public:
584
LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
585
: Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
586
587
private:
588
int64_t* mTarget;
589
int64_t mDelta;
590
591
NS_IMETHOD Run() override {
592
*mTarget = mDelta;
593
return NS_OK;
594
}
595
};
596
597
} // namespace
598
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;
610
}
611
}
612
613
bool StorageUsage::CheckAndSetETLD1UsageDelta(
614
uint32_t aDataSetIndex, const int64_t aDelta,
615
const LocalStorageCache::MutationSource aSource) {
616
MOZ_ASSERT(NS_IsMainThread());
617
618
int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
619
if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
620
newUsage > LocalStorageManager::GetQuota()) {
621
return false;
622
}
623
624
mUsage[aDataSetIndex] = newUsage;
625
return true;
626
}
627
628
} // namespace dom
629
} // namespace mozilla