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