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