|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "DOMStorageCache.h" |
|
7 |
|
8 #include "DOMStorage.h" |
|
9 #include "DOMStorageDBThread.h" |
|
10 #include "DOMStorageIPC.h" |
|
11 #include "DOMStorageManager.h" |
|
12 |
|
13 #include "nsDOMString.h" |
|
14 #include "nsXULAppAPI.h" |
|
15 #include "mozilla/unused.h" |
|
16 #include "nsProxyRelease.h" |
|
17 #include "nsThreadUtils.h" |
|
18 |
|
19 namespace mozilla { |
|
20 namespace dom { |
|
21 |
|
22 #define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000 |
|
23 |
|
24 // static |
|
25 DOMStorageDBBridge* DOMStorageCache::sDatabase = nullptr; |
|
26 bool DOMStorageCache::sDatabaseDown = false; |
|
27 |
|
28 namespace { // anon |
|
29 |
|
30 const uint32_t kDefaultSet = 0; |
|
31 const uint32_t kPrivateSet = 1; |
|
32 const uint32_t kSessionSet = 2; |
|
33 |
|
34 inline uint32_t |
|
35 GetDataSetIndex(bool aPrivate, bool aSessionOnly) |
|
36 { |
|
37 if (aPrivate) { |
|
38 return kPrivateSet; |
|
39 } |
|
40 |
|
41 if (aSessionOnly) { |
|
42 return kSessionSet; |
|
43 } |
|
44 |
|
45 return kDefaultSet; |
|
46 } |
|
47 |
|
48 inline uint32_t |
|
49 GetDataSetIndex(const DOMStorage* aStorage) |
|
50 { |
|
51 return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly()); |
|
52 } |
|
53 |
|
54 } // anon |
|
55 |
|
56 // DOMStorageCacheBridge |
|
57 |
|
58 NS_IMPL_ADDREF(DOMStorageCacheBridge) |
|
59 |
|
60 // Since there is no consumer of return value of Release, we can turn this |
|
61 // method to void to make implementation of asynchronous DOMStorageCache::Release |
|
62 // much simpler. |
|
63 NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void) |
|
64 { |
|
65 MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); |
|
66 nsrefcnt count = --mRefCnt; |
|
67 NS_LOG_RELEASE(this, count, "DOMStorageCacheBridge"); |
|
68 if (0 == count) { |
|
69 mRefCnt = 1; /* stabilize */ |
|
70 /* enable this to find non-threadsafe destructors: */ |
|
71 /* NS_ASSERT_OWNINGTHREAD(_class); */ |
|
72 delete (this); |
|
73 } |
|
74 } |
|
75 |
|
76 // DOMStorageCache |
|
77 |
|
78 DOMStorageCache::DOMStorageCache(const nsACString* aScope) |
|
79 : mScope(*aScope) |
|
80 , mMonitor("DOMStorageCache") |
|
81 , mLoaded(false) |
|
82 , mLoadResult(NS_OK) |
|
83 , mInitialized(false) |
|
84 , mPersistent(false) |
|
85 , mSessionOnlyDataSetActive(false) |
|
86 , mPreloadTelemetryRecorded(false) |
|
87 { |
|
88 MOZ_COUNT_CTOR(DOMStorageCache); |
|
89 } |
|
90 |
|
91 DOMStorageCache::~DOMStorageCache() |
|
92 { |
|
93 if (mManager) { |
|
94 mManager->DropCache(this); |
|
95 } |
|
96 |
|
97 MOZ_COUNT_DTOR(DOMStorageCache); |
|
98 } |
|
99 |
|
100 NS_IMETHODIMP_(void) |
|
101 DOMStorageCache::Release(void) |
|
102 { |
|
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 DOMStorageCacheBridge::Release(); |
|
108 return; |
|
109 } |
|
110 |
|
111 nsRefPtr<nsRunnableMethod<DOMStorageCacheBridge, void, false> > event = |
|
112 NS_NewNonOwningRunnableMethod(static_cast<DOMStorageCacheBridge*>(this), |
|
113 &DOMStorageCacheBridge::Release); |
|
114 |
|
115 nsresult rv = NS_DispatchToMainThread(event); |
|
116 if (NS_FAILED(rv)) { |
|
117 NS_WARNING("DOMStorageCache::Release() on a non-main thread"); |
|
118 DOMStorageCacheBridge::Release(); |
|
119 } |
|
120 } |
|
121 |
|
122 void |
|
123 DOMStorageCache::Init(DOMStorageManager* aManager, |
|
124 bool aPersistent, |
|
125 nsIURI* aFirstPartyIsolationURI, |
|
126 nsIPrincipal* aPrincipal, |
|
127 const nsACString& aQuotaScope) |
|
128 { |
|
129 if (mInitialized) { |
|
130 return; |
|
131 } |
|
132 |
|
133 mInitialized = true; |
|
134 mFirstPartyIsolationURI = aFirstPartyIsolationURI; |
|
135 mPrincipal = aPrincipal; |
|
136 mPersistent = aPersistent; |
|
137 mQuotaScope = aQuotaScope.IsEmpty() ? mScope : aQuotaScope; |
|
138 |
|
139 if (mPersistent) { |
|
140 mManager = aManager; |
|
141 Preload(); |
|
142 } |
|
143 |
|
144 mUsage = aManager->GetScopeUsage(mQuotaScope); |
|
145 } |
|
146 |
|
147 inline bool |
|
148 DOMStorageCache::Persist(const DOMStorage* aStorage) const |
|
149 { |
|
150 return mPersistent && |
|
151 !aStorage->IsSessionOnly() && |
|
152 !aStorage->IsPrivate(); |
|
153 } |
|
154 |
|
155 namespace { // anon |
|
156 |
|
157 PLDHashOperator |
|
158 CloneSetData(const nsAString& aKey, const nsString aValue, void* aArg) |
|
159 { |
|
160 DOMStorageCache::Data* target = static_cast<DOMStorageCache::Data*>(aArg); |
|
161 target->mKeys.Put(aKey, aValue); |
|
162 |
|
163 return PL_DHASH_NEXT; |
|
164 } |
|
165 |
|
166 } // anon |
|
167 |
|
168 DOMStorageCache::Data& |
|
169 DOMStorageCache::DataSet(const DOMStorage* aStorage) |
|
170 { |
|
171 uint32_t index = GetDataSetIndex(aStorage); |
|
172 |
|
173 if (index == kSessionSet && !mSessionOnlyDataSetActive) { |
|
174 // Session only data set is demanded but not filled with |
|
175 // current data set, copy to session only set now. |
|
176 |
|
177 WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS); |
|
178 |
|
179 Data& defaultSet = mData[kDefaultSet]; |
|
180 Data& sessionSet = mData[kSessionSet]; |
|
181 |
|
182 defaultSet.mKeys.EnumerateRead(CloneSetData, &sessionSet); |
|
183 |
|
184 mSessionOnlyDataSetActive = true; |
|
185 |
|
186 // This updates sessionSet.mOriginQuotaUsage and also updates global usage |
|
187 // for all session only data |
|
188 ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage); |
|
189 } |
|
190 |
|
191 return mData[index]; |
|
192 } |
|
193 |
|
194 bool |
|
195 DOMStorageCache::ProcessUsageDelta(const DOMStorage* aStorage, int64_t aDelta) |
|
196 { |
|
197 return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta); |
|
198 } |
|
199 |
|
200 bool |
|
201 DOMStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta) |
|
202 { |
|
203 // Check if we are in a low disk space situation |
|
204 if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) { |
|
205 return false; |
|
206 } |
|
207 |
|
208 // Check limit per this origin |
|
209 Data& data = mData[aGetDataSetIndex]; |
|
210 uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta; |
|
211 if (aDelta > 0 && newOriginUsage > DOMStorageManager::GetQuota()) { |
|
212 return false; |
|
213 } |
|
214 |
|
215 // Now check eTLD+1 limit |
|
216 if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) { |
|
217 return false; |
|
218 } |
|
219 |
|
220 // Update size in our data set |
|
221 data.mOriginQuotaUsage = newOriginUsage; |
|
222 return true; |
|
223 } |
|
224 |
|
225 void |
|
226 DOMStorageCache::Preload() |
|
227 { |
|
228 if (mLoaded || !mPersistent) { |
|
229 return; |
|
230 } |
|
231 |
|
232 if (!StartDatabase()) { |
|
233 mLoaded = true; |
|
234 mLoadResult = NS_ERROR_FAILURE; |
|
235 return; |
|
236 } |
|
237 |
|
238 sDatabase->AsyncPreload(this); |
|
239 } |
|
240 |
|
241 namespace { // anon |
|
242 |
|
243 // This class is passed to timer as a tick observer. It refers the cache |
|
244 // and keeps it alive for a time. |
|
245 class DOMStorageCacheHolder : public nsITimerCallback |
|
246 { |
|
247 NS_DECL_ISUPPORTS |
|
248 |
|
249 NS_IMETHODIMP |
|
250 Notify(nsITimer* aTimer) |
|
251 { |
|
252 mCache = nullptr; |
|
253 return NS_OK; |
|
254 } |
|
255 |
|
256 virtual ~DOMStorageCacheHolder() {} |
|
257 |
|
258 nsRefPtr<DOMStorageCache> mCache; |
|
259 |
|
260 public: |
|
261 DOMStorageCacheHolder(DOMStorageCache* aCache) : mCache(aCache) {} |
|
262 }; |
|
263 |
|
264 NS_IMPL_ISUPPORTS(DOMStorageCacheHolder, nsITimerCallback) |
|
265 |
|
266 } // anon |
|
267 |
|
268 void |
|
269 DOMStorageCache::KeepAlive() |
|
270 { |
|
271 // Missing reference back to the manager means the cache is not responsible |
|
272 // for its lifetime. Used for keeping sessionStorage live forever. |
|
273 if (!mManager) { |
|
274 return; |
|
275 } |
|
276 |
|
277 if (!NS_IsMainThread()) { |
|
278 // Timer and the holder must be initialized on the main thread. |
|
279 nsRefPtr<nsRunnableMethod<DOMStorageCache> > event = |
|
280 NS_NewRunnableMethod(this, &DOMStorageCache::KeepAlive); |
|
281 |
|
282 NS_DispatchToMainThread(event); |
|
283 return; |
|
284 } |
|
285 |
|
286 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); |
|
287 if (!timer) { |
|
288 return; |
|
289 } |
|
290 |
|
291 nsRefPtr<DOMStorageCacheHolder> holder = new DOMStorageCacheHolder(this); |
|
292 timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS, |
|
293 nsITimer::TYPE_ONE_SHOT); |
|
294 |
|
295 mKeepAliveTimer.swap(timer); |
|
296 } |
|
297 |
|
298 namespace { // anon |
|
299 |
|
300 // The AutoTimer provided by telemetry headers is only using static, |
|
301 // i.e. compile time known ID, but here we know the ID only at run time. |
|
302 // Hence a new class. |
|
303 class TelemetryAutoTimer |
|
304 { |
|
305 public: |
|
306 TelemetryAutoTimer(Telemetry::ID aId) |
|
307 : id(aId), start(TimeStamp::Now()) {} |
|
308 ~TelemetryAutoTimer() |
|
309 { Telemetry::AccumulateDelta_impl<Telemetry::Millisecond>::compute(id, start); } |
|
310 private: |
|
311 Telemetry::ID id; |
|
312 const TimeStamp start; |
|
313 }; |
|
314 |
|
315 } // anon |
|
316 |
|
317 void |
|
318 DOMStorageCache::WaitForPreload(Telemetry::ID aTelemetryID) |
|
319 { |
|
320 if (!mPersistent) { |
|
321 return; |
|
322 } |
|
323 |
|
324 bool loaded = mLoaded; |
|
325 |
|
326 // Telemetry of rates of pending preloads |
|
327 if (!mPreloadTelemetryRecorded) { |
|
328 mPreloadTelemetryRecorded = true; |
|
329 Telemetry::Accumulate( |
|
330 Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, |
|
331 !loaded); |
|
332 } |
|
333 |
|
334 if (loaded) { |
|
335 return; |
|
336 } |
|
337 |
|
338 // Measure which operation blocks and for how long |
|
339 TelemetryAutoTimer timer(aTelemetryID); |
|
340 |
|
341 // If preload already started (i.e. we got some first data, but not all) |
|
342 // SyncPreload will just wait for it to finish rather then synchronously |
|
343 // read from the database. It seems to me more optimal. |
|
344 |
|
345 // TODO place for A/B testing (force main thread load vs. let preload finish) |
|
346 |
|
347 // No need to check sDatabase for being non-null since preload is either |
|
348 // done before we've shut the DB down or when the DB could not start, |
|
349 // preload has not even be started. |
|
350 sDatabase->SyncPreload(this); |
|
351 } |
|
352 |
|
353 nsresult |
|
354 DOMStorageCache::GetLength(const DOMStorage* aStorage, uint32_t* aRetval) |
|
355 { |
|
356 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETLENGTH_MS> autoTimer; |
|
357 |
|
358 if (Persist(aStorage)) { |
|
359 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS); |
|
360 if (NS_FAILED(mLoadResult)) { |
|
361 return mLoadResult; |
|
362 } |
|
363 } |
|
364 |
|
365 *aRetval = DataSet(aStorage).mKeys.Count(); |
|
366 return NS_OK; |
|
367 } |
|
368 |
|
369 namespace { // anon |
|
370 |
|
371 class IndexFinderData |
|
372 { |
|
373 public: |
|
374 IndexFinderData(uint32_t aIndex, nsAString& aRetval) |
|
375 : mIndex(aIndex), mKey(aRetval) |
|
376 { |
|
377 mKey.SetIsVoid(true); |
|
378 } |
|
379 |
|
380 uint32_t mIndex; |
|
381 nsAString& mKey; |
|
382 }; |
|
383 |
|
384 PLDHashOperator |
|
385 FindKeyOrder(const nsAString& aKey, const nsString aValue, void* aArg) |
|
386 { |
|
387 IndexFinderData* data = static_cast<IndexFinderData*>(aArg); |
|
388 |
|
389 if (data->mIndex--) { |
|
390 return PL_DHASH_NEXT; |
|
391 } |
|
392 |
|
393 data->mKey = aKey; |
|
394 return PL_DHASH_STOP; |
|
395 } |
|
396 |
|
397 } // anon |
|
398 |
|
399 nsresult |
|
400 DOMStorageCache::GetKey(const DOMStorage* aStorage, uint32_t aIndex, nsAString& aRetval) |
|
401 { |
|
402 // XXX: This does a linear search for the key at index, which would |
|
403 // suck if there's a large numer of indexes. Do we care? If so, |
|
404 // maybe we need to have a lazily populated key array here or |
|
405 // something? |
|
406 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETKEY_MS> autoTimer; |
|
407 |
|
408 if (Persist(aStorage)) { |
|
409 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS); |
|
410 if (NS_FAILED(mLoadResult)) { |
|
411 return mLoadResult; |
|
412 } |
|
413 } |
|
414 |
|
415 IndexFinderData data(aIndex, aRetval); |
|
416 DataSet(aStorage).mKeys.EnumerateRead(FindKeyOrder, &data); |
|
417 return NS_OK; |
|
418 } |
|
419 |
|
420 namespace { // anon |
|
421 |
|
422 static PLDHashOperator |
|
423 KeysArrayBuilder(const nsAString& aKey, const nsString aValue, void* aArg) |
|
424 { |
|
425 nsTArray<nsString>* keys = static_cast<nsTArray<nsString>* >(aArg); |
|
426 |
|
427 keys->AppendElement(aKey); |
|
428 return PL_DHASH_NEXT; |
|
429 } |
|
430 |
|
431 } // anon |
|
432 |
|
433 nsTArray<nsString>* |
|
434 DOMStorageCache::GetKeys(const DOMStorage* aStorage) |
|
435 { |
|
436 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETALLKEYS_MS> autoTimer; |
|
437 |
|
438 if (Persist(aStorage)) { |
|
439 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS); |
|
440 } |
|
441 |
|
442 nsTArray<nsString>* result = new nsTArray<nsString>(); |
|
443 if (NS_SUCCEEDED(mLoadResult)) { |
|
444 DataSet(aStorage).mKeys.EnumerateRead(KeysArrayBuilder, result); |
|
445 } |
|
446 |
|
447 return result; |
|
448 } |
|
449 |
|
450 nsresult |
|
451 DOMStorageCache::GetItem(const DOMStorage* aStorage, const nsAString& aKey, |
|
452 nsAString& aRetval) |
|
453 { |
|
454 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETVALUE_MS> autoTimer; |
|
455 |
|
456 if (Persist(aStorage)) { |
|
457 WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS); |
|
458 if (NS_FAILED(mLoadResult)) { |
|
459 return mLoadResult; |
|
460 } |
|
461 } |
|
462 |
|
463 // not using AutoString since we don't want to copy buffer to result |
|
464 nsString value; |
|
465 if (!DataSet(aStorage).mKeys.Get(aKey, &value)) { |
|
466 SetDOMStringToNull(value); |
|
467 } |
|
468 |
|
469 aRetval = value; |
|
470 |
|
471 return NS_OK; |
|
472 } |
|
473 |
|
474 nsresult |
|
475 DOMStorageCache::SetItem(const DOMStorage* aStorage, const nsAString& aKey, |
|
476 const nsString& aValue, nsString& aOld) |
|
477 { |
|
478 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SETVALUE_MS> autoTimer; |
|
479 |
|
480 if (Persist(aStorage)) { |
|
481 WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS); |
|
482 if (NS_FAILED(mLoadResult)) { |
|
483 return mLoadResult; |
|
484 } |
|
485 } |
|
486 |
|
487 Data& data = DataSet(aStorage); |
|
488 if (!data.mKeys.Get(aKey, &aOld)) { |
|
489 SetDOMStringToNull(aOld); |
|
490 } |
|
491 |
|
492 // Check the quota first |
|
493 const int64_t delta = static_cast<int64_t>(aValue.Length()) - |
|
494 static_cast<int64_t>(aOld.Length()); |
|
495 if (!ProcessUsageDelta(aStorage, delta)) { |
|
496 return NS_ERROR_DOM_QUOTA_REACHED; |
|
497 } |
|
498 |
|
499 if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) { |
|
500 return NS_SUCCESS_DOM_NO_OPERATION; |
|
501 } |
|
502 |
|
503 data.mKeys.Put(aKey, aValue); |
|
504 |
|
505 if (Persist(aStorage)) { |
|
506 if (!sDatabase) { |
|
507 NS_ERROR("Writing to localStorage after the database has been shut down" |
|
508 ", data lose!"); |
|
509 return NS_ERROR_NOT_INITIALIZED; |
|
510 } |
|
511 |
|
512 if (DOMStringIsNull(aOld)) { |
|
513 return sDatabase->AsyncAddItem(this, aKey, aValue); |
|
514 } |
|
515 |
|
516 return sDatabase->AsyncUpdateItem(this, aKey, aValue); |
|
517 } |
|
518 |
|
519 return NS_OK; |
|
520 } |
|
521 |
|
522 nsresult |
|
523 DOMStorageCache::RemoveItem(const DOMStorage* aStorage, const nsAString& aKey, |
|
524 nsString& aOld) |
|
525 { |
|
526 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_REMOVEKEY_MS> autoTimer; |
|
527 |
|
528 if (Persist(aStorage)) { |
|
529 WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS); |
|
530 if (NS_FAILED(mLoadResult)) { |
|
531 return mLoadResult; |
|
532 } |
|
533 } |
|
534 |
|
535 Data& data = DataSet(aStorage); |
|
536 if (!data.mKeys.Get(aKey, &aOld)) { |
|
537 SetDOMStringToNull(aOld); |
|
538 return NS_SUCCESS_DOM_NO_OPERATION; |
|
539 } |
|
540 |
|
541 // Recalculate the cached data size |
|
542 const int64_t delta = -(static_cast<int64_t>(aOld.Length())); |
|
543 unused << ProcessUsageDelta(aStorage, delta); |
|
544 data.mKeys.Remove(aKey); |
|
545 |
|
546 if (Persist(aStorage)) { |
|
547 if (!sDatabase) { |
|
548 NS_ERROR("Writing to localStorage after the database has been shut down" |
|
549 ", data lose!"); |
|
550 return NS_ERROR_NOT_INITIALIZED; |
|
551 } |
|
552 |
|
553 return sDatabase->AsyncRemoveItem(this, aKey); |
|
554 } |
|
555 |
|
556 return NS_OK; |
|
557 } |
|
558 |
|
559 nsresult |
|
560 DOMStorageCache::Clear(const DOMStorage* aStorage) |
|
561 { |
|
562 Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_CLEAR_MS> autoTimer; |
|
563 |
|
564 bool refresh = false; |
|
565 if (Persist(aStorage)) { |
|
566 // We need to preload all data (know the size) before we can proceeed |
|
567 // to correctly decrease cached usage number. |
|
568 // XXX as in case of unload, this is not technically needed now, but |
|
569 // after super-scope quota introduction we have to do this. Get telemetry |
|
570 // right now. |
|
571 WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS); |
|
572 if (NS_FAILED(mLoadResult)) { |
|
573 // When we failed to load data from the database, force delete of the |
|
574 // scope data and make use of the storage possible again. |
|
575 refresh = true; |
|
576 mLoadResult = NS_OK; |
|
577 } |
|
578 } |
|
579 |
|
580 Data& data = DataSet(aStorage); |
|
581 bool hadData = !!data.mKeys.Count(); |
|
582 |
|
583 if (hadData) { |
|
584 unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage); |
|
585 data.mKeys.Clear(); |
|
586 } |
|
587 |
|
588 if (Persist(aStorage) && (refresh || hadData)) { |
|
589 if (!sDatabase) { |
|
590 NS_ERROR("Writing to localStorage after the database has been shut down" |
|
591 ", data lose!"); |
|
592 return NS_ERROR_NOT_INITIALIZED; |
|
593 } |
|
594 |
|
595 return sDatabase->AsyncClear(this); |
|
596 } |
|
597 |
|
598 return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; |
|
599 } |
|
600 |
|
601 void |
|
602 DOMStorageCache::CloneFrom(const DOMStorageCache* aThat) |
|
603 { |
|
604 mLoaded = aThat->mLoaded; |
|
605 mInitialized = aThat->mInitialized; |
|
606 mPersistent = aThat->mPersistent; |
|
607 mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive; |
|
608 |
|
609 for (uint32_t i = 0; i < kDataSetCount; ++i) { |
|
610 aThat->mData[i].mKeys.EnumerateRead(CloneSetData, &mData[i]); |
|
611 ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage); |
|
612 } |
|
613 } |
|
614 |
|
615 // Defined in DOMStorageManager.cpp |
|
616 extern bool |
|
617 PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal); |
|
618 |
|
619 bool |
|
620 DOMStorageCache::CheckPrincipal(nsIPrincipal* aPrincipal) const |
|
621 { |
|
622 return PrincipalsEqual(mPrincipal, aPrincipal); |
|
623 } |
|
624 |
|
625 void |
|
626 DOMStorageCache::UnloadItems(uint32_t aUnloadFlags) |
|
627 { |
|
628 if (aUnloadFlags & kUnloadDefault) { |
|
629 // Must wait for preload to pass correct usage to ProcessUsageDelta |
|
630 // XXX this is not technically needed right now since there is just |
|
631 // per-origin isolated quota handling, but when we introduce super- |
|
632 // -scope quotas, we have to do this. Better to start getting |
|
633 // telemetry right now. |
|
634 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); |
|
635 |
|
636 mData[kDefaultSet].mKeys.Clear(); |
|
637 ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage); |
|
638 } |
|
639 |
|
640 if (aUnloadFlags & kUnloadPrivate) { |
|
641 mData[kPrivateSet].mKeys.Clear(); |
|
642 ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage); |
|
643 } |
|
644 |
|
645 if (aUnloadFlags & kUnloadSession) { |
|
646 mData[kSessionSet].mKeys.Clear(); |
|
647 ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage); |
|
648 mSessionOnlyDataSetActive = false; |
|
649 } |
|
650 |
|
651 #ifdef DOM_STORAGE_TESTS |
|
652 if (aUnloadFlags & kTestReload) { |
|
653 WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); |
|
654 |
|
655 mData[kDefaultSet].mKeys.Clear(); |
|
656 mLoaded = false; // This is only used in testing code |
|
657 Preload(); |
|
658 } |
|
659 #endif |
|
660 } |
|
661 |
|
662 // DOMStorageCacheBridge |
|
663 |
|
664 uint32_t |
|
665 DOMStorageCache::LoadedCount() |
|
666 { |
|
667 MonitorAutoLock monitor(mMonitor); |
|
668 Data& data = mData[kDefaultSet]; |
|
669 return data.mKeys.Count(); |
|
670 } |
|
671 |
|
672 bool |
|
673 DOMStorageCache::LoadItem(const nsAString& aKey, const nsString& aValue) |
|
674 { |
|
675 MonitorAutoLock monitor(mMonitor); |
|
676 if (mLoaded) { |
|
677 return false; |
|
678 } |
|
679 |
|
680 Data& data = mData[kDefaultSet]; |
|
681 if (data.mKeys.Get(aKey, nullptr)) { |
|
682 return true; // don't stop, just don't override |
|
683 } |
|
684 |
|
685 data.mKeys.Put(aKey, aValue); |
|
686 data.mOriginQuotaUsage += aKey.Length() + aValue.Length(); |
|
687 return true; |
|
688 } |
|
689 |
|
690 void |
|
691 DOMStorageCache::LoadDone(nsresult aRv) |
|
692 { |
|
693 // Keep the preloaded cache alive for a time |
|
694 KeepAlive(); |
|
695 |
|
696 MonitorAutoLock monitor(mMonitor); |
|
697 mLoadResult = aRv; |
|
698 mLoaded = true; |
|
699 monitor.Notify(); |
|
700 } |
|
701 |
|
702 void |
|
703 DOMStorageCache::LoadWait() |
|
704 { |
|
705 MonitorAutoLock monitor(mMonitor); |
|
706 while (!mLoaded) { |
|
707 monitor.Wait(); |
|
708 } |
|
709 } |
|
710 |
|
711 // DOMStorageUsage |
|
712 |
|
713 DOMStorageUsage::DOMStorageUsage(const nsACString& aScope) |
|
714 : mScope(aScope) |
|
715 { |
|
716 mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL; |
|
717 } |
|
718 |
|
719 namespace { // anon |
|
720 |
|
721 class LoadUsageRunnable : public nsRunnable |
|
722 { |
|
723 public: |
|
724 LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta) |
|
725 : mTarget(aUsage) |
|
726 , mDelta(aDelta) |
|
727 {} |
|
728 |
|
729 private: |
|
730 int64_t* mTarget; |
|
731 int64_t mDelta; |
|
732 |
|
733 NS_IMETHOD Run() { *mTarget = mDelta; return NS_OK; } |
|
734 }; |
|
735 |
|
736 } // anon |
|
737 |
|
738 void |
|
739 DOMStorageUsage::LoadUsage(const int64_t aUsage) |
|
740 { |
|
741 // Using kDefaultSet index since it is the index for the persitent data |
|
742 // stored in the database we have just loaded usage for. |
|
743 if (!NS_IsMainThread()) { |
|
744 // In single process scenario we get this call from the DB thread |
|
745 nsRefPtr<LoadUsageRunnable> r = |
|
746 new LoadUsageRunnable(mUsage + kDefaultSet, aUsage); |
|
747 NS_DispatchToMainThread(r); |
|
748 } else { |
|
749 // On a child process we get this on the main thread already |
|
750 mUsage[kDefaultSet] += aUsage; |
|
751 } |
|
752 } |
|
753 |
|
754 bool |
|
755 DOMStorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, const int64_t aDelta) |
|
756 { |
|
757 MOZ_ASSERT(NS_IsMainThread()); |
|
758 |
|
759 int64_t newUsage = mUsage[aDataSetIndex] + aDelta; |
|
760 if (aDelta > 0 && newUsage > DOMStorageManager::GetQuota()) { |
|
761 return false; |
|
762 } |
|
763 |
|
764 mUsage[aDataSetIndex] = newUsage; |
|
765 return true; |
|
766 } |
|
767 |
|
768 |
|
769 // static |
|
770 DOMStorageDBBridge* |
|
771 DOMStorageCache::StartDatabase() |
|
772 { |
|
773 if (sDatabase || sDatabaseDown) { |
|
774 // When sDatabaseDown is at true, sDatabase is null. |
|
775 // Checking sDatabaseDown flag here prevents reinitialization of |
|
776 // the database after shutdown. |
|
777 return sDatabase; |
|
778 } |
|
779 |
|
780 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
781 nsAutoPtr<DOMStorageDBThread> db(new DOMStorageDBThread()); |
|
782 |
|
783 nsresult rv = db->Init(); |
|
784 if (NS_FAILED(rv)) { |
|
785 return nullptr; |
|
786 } |
|
787 |
|
788 sDatabase = db.forget(); |
|
789 } else { |
|
790 nsRefPtr<DOMStorageDBChild> db = new DOMStorageDBChild( |
|
791 DOMLocalStorageManager::Self()); |
|
792 |
|
793 nsresult rv = db->Init(); |
|
794 if (NS_FAILED(rv)) { |
|
795 return nullptr; |
|
796 } |
|
797 |
|
798 db.forget(&sDatabase); |
|
799 } |
|
800 |
|
801 return sDatabase; |
|
802 } |
|
803 |
|
804 // static |
|
805 DOMStorageDBBridge* |
|
806 DOMStorageCache::GetDatabase() |
|
807 { |
|
808 return sDatabase; |
|
809 } |
|
810 |
|
811 // static |
|
812 nsresult |
|
813 DOMStorageCache::StopDatabase() |
|
814 { |
|
815 if (!sDatabase) { |
|
816 return NS_OK; |
|
817 } |
|
818 |
|
819 sDatabaseDown = true; |
|
820 |
|
821 nsresult rv = sDatabase->Shutdown(); |
|
822 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
823 delete sDatabase; |
|
824 } else { |
|
825 DOMStorageDBChild* child = static_cast<DOMStorageDBChild*>(sDatabase); |
|
826 NS_RELEASE(child); |
|
827 } |
|
828 |
|
829 sDatabase = nullptr; |
|
830 return rv; |
|
831 } |
|
832 |
|
833 } // ::dom |
|
834 } // ::mozilla |