1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/src/storage/DOMStorageCache.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,834 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "DOMStorageCache.h" 1.10 + 1.11 +#include "DOMStorage.h" 1.12 +#include "DOMStorageDBThread.h" 1.13 +#include "DOMStorageIPC.h" 1.14 +#include "DOMStorageManager.h" 1.15 + 1.16 +#include "nsDOMString.h" 1.17 +#include "nsXULAppAPI.h" 1.18 +#include "mozilla/unused.h" 1.19 +#include "nsProxyRelease.h" 1.20 +#include "nsThreadUtils.h" 1.21 + 1.22 +namespace mozilla { 1.23 +namespace dom { 1.24 + 1.25 +#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000 1.26 + 1.27 +// static 1.28 +DOMStorageDBBridge* DOMStorageCache::sDatabase = nullptr; 1.29 +bool DOMStorageCache::sDatabaseDown = false; 1.30 + 1.31 +namespace { // anon 1.32 + 1.33 +const uint32_t kDefaultSet = 0; 1.34 +const uint32_t kPrivateSet = 1; 1.35 +const uint32_t kSessionSet = 2; 1.36 + 1.37 +inline uint32_t 1.38 +GetDataSetIndex(bool aPrivate, bool aSessionOnly) 1.39 +{ 1.40 + if (aPrivate) { 1.41 + return kPrivateSet; 1.42 + } 1.43 + 1.44 + if (aSessionOnly) { 1.45 + return kSessionSet; 1.46 + } 1.47 + 1.48 + return kDefaultSet; 1.49 +} 1.50 + 1.51 +inline uint32_t 1.52 +GetDataSetIndex(const DOMStorage* aStorage) 1.53 +{ 1.54 + return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly()); 1.55 +} 1.56 + 1.57 +} // anon 1.58 + 1.59 +// DOMStorageCacheBridge 1.60 + 1.61 +NS_IMPL_ADDREF(DOMStorageCacheBridge) 1.62 + 1.63 +// Since there is no consumer of return value of Release, we can turn this 1.64 +// method to void to make implementation of asynchronous DOMStorageCache::Release 1.65 +// much simpler. 1.66 +NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void) 1.67 +{ 1.68 + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); 1.69 + nsrefcnt count = --mRefCnt; 1.70 + NS_LOG_RELEASE(this, count, "DOMStorageCacheBridge"); 1.71 + if (0 == count) { 1.72 + mRefCnt = 1; /* stabilize */ 1.73 + /* enable this to find non-threadsafe destructors: */ 1.74 + /* NS_ASSERT_OWNINGTHREAD(_class); */ 1.75 + delete (this); 1.76 + } 1.77 +} 1.78 + 1.79 +// DOMStorageCache 1.80 + 1.81 +DOMStorageCache::DOMStorageCache(const nsACString* aScope) 1.82 +: mScope(*aScope) 1.83 +, mMonitor("DOMStorageCache") 1.84 +, mLoaded(false) 1.85 +, mLoadResult(NS_OK) 1.86 +, mInitialized(false) 1.87 +, mPersistent(false) 1.88 +, mSessionOnlyDataSetActive(false) 1.89 +, mPreloadTelemetryRecorded(false) 1.90 +{ 1.91 + MOZ_COUNT_CTOR(DOMStorageCache); 1.92 +} 1.93 + 1.94 +DOMStorageCache::~DOMStorageCache() 1.95 +{ 1.96 + if (mManager) { 1.97 + mManager->DropCache(this); 1.98 + } 1.99 + 1.100 + MOZ_COUNT_DTOR(DOMStorageCache); 1.101 +} 1.102 + 1.103 +NS_IMETHODIMP_(void) 1.104 +DOMStorageCache::Release(void) 1.105 +{ 1.106 + // We must actually release on the main thread since the cache removes it 1.107 + // self from the manager's hash table. And we don't want to lock access to 1.108 + // that hash table. 1.109 + if (NS_IsMainThread()) { 1.110 + DOMStorageCacheBridge::Release(); 1.111 + return; 1.112 + } 1.113 + 1.114 + nsRefPtr<nsRunnableMethod<DOMStorageCacheBridge, void, false> > event = 1.115 + NS_NewNonOwningRunnableMethod(static_cast<DOMStorageCacheBridge*>(this), 1.116 + &DOMStorageCacheBridge::Release); 1.117 + 1.118 + nsresult rv = NS_DispatchToMainThread(event); 1.119 + if (NS_FAILED(rv)) { 1.120 + NS_WARNING("DOMStorageCache::Release() on a non-main thread"); 1.121 + DOMStorageCacheBridge::Release(); 1.122 + } 1.123 +} 1.124 + 1.125 +void 1.126 +DOMStorageCache::Init(DOMStorageManager* aManager, 1.127 + bool aPersistent, 1.128 + nsIURI* aFirstPartyIsolationURI, 1.129 + nsIPrincipal* aPrincipal, 1.130 + const nsACString& aQuotaScope) 1.131 +{ 1.132 + if (mInitialized) { 1.133 + return; 1.134 + } 1.135 + 1.136 + mInitialized = true; 1.137 + mFirstPartyIsolationURI = aFirstPartyIsolationURI; 1.138 + mPrincipal = aPrincipal; 1.139 + mPersistent = aPersistent; 1.140 + mQuotaScope = aQuotaScope.IsEmpty() ? mScope : aQuotaScope; 1.141 + 1.142 + if (mPersistent) { 1.143 + mManager = aManager; 1.144 + Preload(); 1.145 + } 1.146 + 1.147 + mUsage = aManager->GetScopeUsage(mQuotaScope); 1.148 +} 1.149 + 1.150 +inline bool 1.151 +DOMStorageCache::Persist(const DOMStorage* aStorage) const 1.152 +{ 1.153 + return mPersistent && 1.154 + !aStorage->IsSessionOnly() && 1.155 + !aStorage->IsPrivate(); 1.156 +} 1.157 + 1.158 +namespace { // anon 1.159 + 1.160 +PLDHashOperator 1.161 +CloneSetData(const nsAString& aKey, const nsString aValue, void* aArg) 1.162 +{ 1.163 + DOMStorageCache::Data* target = static_cast<DOMStorageCache::Data*>(aArg); 1.164 + target->mKeys.Put(aKey, aValue); 1.165 + 1.166 + return PL_DHASH_NEXT; 1.167 +} 1.168 + 1.169 +} // anon 1.170 + 1.171 +DOMStorageCache::Data& 1.172 +DOMStorageCache::DataSet(const DOMStorage* aStorage) 1.173 +{ 1.174 + uint32_t index = GetDataSetIndex(aStorage); 1.175 + 1.176 + if (index == kSessionSet && !mSessionOnlyDataSetActive) { 1.177 + // Session only data set is demanded but not filled with 1.178 + // current data set, copy to session only set now. 1.179 + 1.180 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS); 1.181 + 1.182 + Data& defaultSet = mData[kDefaultSet]; 1.183 + Data& sessionSet = mData[kSessionSet]; 1.184 + 1.185 + defaultSet.mKeys.EnumerateRead(CloneSetData, &sessionSet); 1.186 + 1.187 + mSessionOnlyDataSetActive = true; 1.188 + 1.189 + // This updates sessionSet.mOriginQuotaUsage and also updates global usage 1.190 + // for all session only data 1.191 + ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage); 1.192 + } 1.193 + 1.194 + return mData[index]; 1.195 +} 1.196 + 1.197 +bool 1.198 +DOMStorageCache::ProcessUsageDelta(const DOMStorage* aStorage, int64_t aDelta) 1.199 +{ 1.200 + return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta); 1.201 +} 1.202 + 1.203 +bool 1.204 +DOMStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta) 1.205 +{ 1.206 + // Check if we are in a low disk space situation 1.207 + if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) { 1.208 + return false; 1.209 + } 1.210 + 1.211 + // Check limit per this origin 1.212 + Data& data = mData[aGetDataSetIndex]; 1.213 + uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta; 1.214 + if (aDelta > 0 && newOriginUsage > DOMStorageManager::GetQuota()) { 1.215 + return false; 1.216 + } 1.217 + 1.218 + // Now check eTLD+1 limit 1.219 + if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) { 1.220 + return false; 1.221 + } 1.222 + 1.223 + // Update size in our data set 1.224 + data.mOriginQuotaUsage = newOriginUsage; 1.225 + return true; 1.226 +} 1.227 + 1.228 +void 1.229 +DOMStorageCache::Preload() 1.230 +{ 1.231 + if (mLoaded || !mPersistent) { 1.232 + return; 1.233 + } 1.234 + 1.235 + if (!StartDatabase()) { 1.236 + mLoaded = true; 1.237 + mLoadResult = NS_ERROR_FAILURE; 1.238 + return; 1.239 + } 1.240 + 1.241 + sDatabase->AsyncPreload(this); 1.242 +} 1.243 + 1.244 +namespace { // anon 1.245 + 1.246 +// This class is passed to timer as a tick observer. It refers the cache 1.247 +// and keeps it alive for a time. 1.248 +class DOMStorageCacheHolder : public nsITimerCallback 1.249 +{ 1.250 + NS_DECL_ISUPPORTS 1.251 + 1.252 + NS_IMETHODIMP 1.253 + Notify(nsITimer* aTimer) 1.254 + { 1.255 + mCache = nullptr; 1.256 + return NS_OK; 1.257 + } 1.258 + 1.259 + virtual ~DOMStorageCacheHolder() {} 1.260 + 1.261 + nsRefPtr<DOMStorageCache> mCache; 1.262 + 1.263 +public: 1.264 + DOMStorageCacheHolder(DOMStorageCache* aCache) : mCache(aCache) {} 1.265 +}; 1.266 + 1.267 +NS_IMPL_ISUPPORTS(DOMStorageCacheHolder, nsITimerCallback) 1.268 + 1.269 +} // anon 1.270 + 1.271 +void 1.272 +DOMStorageCache::KeepAlive() 1.273 +{ 1.274 + // Missing reference back to the manager means the cache is not responsible 1.275 + // for its lifetime. Used for keeping sessionStorage live forever. 1.276 + if (!mManager) { 1.277 + return; 1.278 + } 1.279 + 1.280 + if (!NS_IsMainThread()) { 1.281 + // Timer and the holder must be initialized on the main thread. 1.282 + nsRefPtr<nsRunnableMethod<DOMStorageCache> > event = 1.283 + NS_NewRunnableMethod(this, &DOMStorageCache::KeepAlive); 1.284 + 1.285 + NS_DispatchToMainThread(event); 1.286 + return; 1.287 + } 1.288 + 1.289 + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); 1.290 + if (!timer) { 1.291 + return; 1.292 + } 1.293 + 1.294 + nsRefPtr<DOMStorageCacheHolder> holder = new DOMStorageCacheHolder(this); 1.295 + timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS, 1.296 + nsITimer::TYPE_ONE_SHOT); 1.297 + 1.298 + mKeepAliveTimer.swap(timer); 1.299 +} 1.300 + 1.301 +namespace { // anon 1.302 + 1.303 +// The AutoTimer provided by telemetry headers is only using static, 1.304 +// i.e. compile time known ID, but here we know the ID only at run time. 1.305 +// Hence a new class. 1.306 +class TelemetryAutoTimer 1.307 +{ 1.308 +public: 1.309 + TelemetryAutoTimer(Telemetry::ID aId) 1.310 + : id(aId), start(TimeStamp::Now()) {} 1.311 + ~TelemetryAutoTimer() 1.312 + { Telemetry::AccumulateDelta_impl<Telemetry::Millisecond>::compute(id, start); } 1.313 +private: 1.314 + Telemetry::ID id; 1.315 + const TimeStamp start; 1.316 +}; 1.317 + 1.318 +} // anon 1.319 + 1.320 +void 1.321 +DOMStorageCache::WaitForPreload(Telemetry::ID aTelemetryID) 1.322 +{ 1.323 + if (!mPersistent) { 1.324 + return; 1.325 + } 1.326 + 1.327 + bool loaded = mLoaded; 1.328 + 1.329 + // Telemetry of rates of pending preloads 1.330 + if (!mPreloadTelemetryRecorded) { 1.331 + mPreloadTelemetryRecorded = true; 1.332 + Telemetry::Accumulate( 1.333 + Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS, 1.334 + !loaded); 1.335 + } 1.336 + 1.337 + if (loaded) { 1.338 + return; 1.339 + } 1.340 + 1.341 + // Measure which operation blocks and for how long 1.342 + TelemetryAutoTimer timer(aTelemetryID); 1.343 + 1.344 + // If preload already started (i.e. we got some first data, but not all) 1.345 + // SyncPreload will just wait for it to finish rather then synchronously 1.346 + // read from the database. It seems to me more optimal. 1.347 + 1.348 + // TODO place for A/B testing (force main thread load vs. let preload finish) 1.349 + 1.350 + // No need to check sDatabase for being non-null since preload is either 1.351 + // done before we've shut the DB down or when the DB could not start, 1.352 + // preload has not even be started. 1.353 + sDatabase->SyncPreload(this); 1.354 +} 1.355 + 1.356 +nsresult 1.357 +DOMStorageCache::GetLength(const DOMStorage* aStorage, uint32_t* aRetval) 1.358 +{ 1.359 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETLENGTH_MS> autoTimer; 1.360 + 1.361 + if (Persist(aStorage)) { 1.362 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS); 1.363 + if (NS_FAILED(mLoadResult)) { 1.364 + return mLoadResult; 1.365 + } 1.366 + } 1.367 + 1.368 + *aRetval = DataSet(aStorage).mKeys.Count(); 1.369 + return NS_OK; 1.370 +} 1.371 + 1.372 +namespace { // anon 1.373 + 1.374 +class IndexFinderData 1.375 +{ 1.376 +public: 1.377 + IndexFinderData(uint32_t aIndex, nsAString& aRetval) 1.378 + : mIndex(aIndex), mKey(aRetval) 1.379 + { 1.380 + mKey.SetIsVoid(true); 1.381 + } 1.382 + 1.383 + uint32_t mIndex; 1.384 + nsAString& mKey; 1.385 +}; 1.386 + 1.387 +PLDHashOperator 1.388 +FindKeyOrder(const nsAString& aKey, const nsString aValue, void* aArg) 1.389 +{ 1.390 + IndexFinderData* data = static_cast<IndexFinderData*>(aArg); 1.391 + 1.392 + if (data->mIndex--) { 1.393 + return PL_DHASH_NEXT; 1.394 + } 1.395 + 1.396 + data->mKey = aKey; 1.397 + return PL_DHASH_STOP; 1.398 +} 1.399 + 1.400 +} // anon 1.401 + 1.402 +nsresult 1.403 +DOMStorageCache::GetKey(const DOMStorage* aStorage, uint32_t aIndex, nsAString& aRetval) 1.404 +{ 1.405 + // XXX: This does a linear search for the key at index, which would 1.406 + // suck if there's a large numer of indexes. Do we care? If so, 1.407 + // maybe we need to have a lazily populated key array here or 1.408 + // something? 1.409 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETKEY_MS> autoTimer; 1.410 + 1.411 + if (Persist(aStorage)) { 1.412 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS); 1.413 + if (NS_FAILED(mLoadResult)) { 1.414 + return mLoadResult; 1.415 + } 1.416 + } 1.417 + 1.418 + IndexFinderData data(aIndex, aRetval); 1.419 + DataSet(aStorage).mKeys.EnumerateRead(FindKeyOrder, &data); 1.420 + return NS_OK; 1.421 +} 1.422 + 1.423 +namespace { // anon 1.424 + 1.425 +static PLDHashOperator 1.426 +KeysArrayBuilder(const nsAString& aKey, const nsString aValue, void* aArg) 1.427 +{ 1.428 + nsTArray<nsString>* keys = static_cast<nsTArray<nsString>* >(aArg); 1.429 + 1.430 + keys->AppendElement(aKey); 1.431 + return PL_DHASH_NEXT; 1.432 +} 1.433 + 1.434 +} // anon 1.435 + 1.436 +nsTArray<nsString>* 1.437 +DOMStorageCache::GetKeys(const DOMStorage* aStorage) 1.438 +{ 1.439 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETALLKEYS_MS> autoTimer; 1.440 + 1.441 + if (Persist(aStorage)) { 1.442 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS); 1.443 + } 1.444 + 1.445 + nsTArray<nsString>* result = new nsTArray<nsString>(); 1.446 + if (NS_SUCCEEDED(mLoadResult)) { 1.447 + DataSet(aStorage).mKeys.EnumerateRead(KeysArrayBuilder, result); 1.448 + } 1.449 + 1.450 + return result; 1.451 +} 1.452 + 1.453 +nsresult 1.454 +DOMStorageCache::GetItem(const DOMStorage* aStorage, const nsAString& aKey, 1.455 + nsAString& aRetval) 1.456 +{ 1.457 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_GETVALUE_MS> autoTimer; 1.458 + 1.459 + if (Persist(aStorage)) { 1.460 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS); 1.461 + if (NS_FAILED(mLoadResult)) { 1.462 + return mLoadResult; 1.463 + } 1.464 + } 1.465 + 1.466 + // not using AutoString since we don't want to copy buffer to result 1.467 + nsString value; 1.468 + if (!DataSet(aStorage).mKeys.Get(aKey, &value)) { 1.469 + SetDOMStringToNull(value); 1.470 + } 1.471 + 1.472 + aRetval = value; 1.473 + 1.474 + return NS_OK; 1.475 +} 1.476 + 1.477 +nsresult 1.478 +DOMStorageCache::SetItem(const DOMStorage* aStorage, const nsAString& aKey, 1.479 + const nsString& aValue, nsString& aOld) 1.480 +{ 1.481 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SETVALUE_MS> autoTimer; 1.482 + 1.483 + if (Persist(aStorage)) { 1.484 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS); 1.485 + if (NS_FAILED(mLoadResult)) { 1.486 + return mLoadResult; 1.487 + } 1.488 + } 1.489 + 1.490 + Data& data = DataSet(aStorage); 1.491 + if (!data.mKeys.Get(aKey, &aOld)) { 1.492 + SetDOMStringToNull(aOld); 1.493 + } 1.494 + 1.495 + // Check the quota first 1.496 + const int64_t delta = static_cast<int64_t>(aValue.Length()) - 1.497 + static_cast<int64_t>(aOld.Length()); 1.498 + if (!ProcessUsageDelta(aStorage, delta)) { 1.499 + return NS_ERROR_DOM_QUOTA_REACHED; 1.500 + } 1.501 + 1.502 + if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) { 1.503 + return NS_SUCCESS_DOM_NO_OPERATION; 1.504 + } 1.505 + 1.506 + data.mKeys.Put(aKey, aValue); 1.507 + 1.508 + if (Persist(aStorage)) { 1.509 + if (!sDatabase) { 1.510 + NS_ERROR("Writing to localStorage after the database has been shut down" 1.511 + ", data lose!"); 1.512 + return NS_ERROR_NOT_INITIALIZED; 1.513 + } 1.514 + 1.515 + if (DOMStringIsNull(aOld)) { 1.516 + return sDatabase->AsyncAddItem(this, aKey, aValue); 1.517 + } 1.518 + 1.519 + return sDatabase->AsyncUpdateItem(this, aKey, aValue); 1.520 + } 1.521 + 1.522 + return NS_OK; 1.523 +} 1.524 + 1.525 +nsresult 1.526 +DOMStorageCache::RemoveItem(const DOMStorage* aStorage, const nsAString& aKey, 1.527 + nsString& aOld) 1.528 +{ 1.529 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_REMOVEKEY_MS> autoTimer; 1.530 + 1.531 + if (Persist(aStorage)) { 1.532 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS); 1.533 + if (NS_FAILED(mLoadResult)) { 1.534 + return mLoadResult; 1.535 + } 1.536 + } 1.537 + 1.538 + Data& data = DataSet(aStorage); 1.539 + if (!data.mKeys.Get(aKey, &aOld)) { 1.540 + SetDOMStringToNull(aOld); 1.541 + return NS_SUCCESS_DOM_NO_OPERATION; 1.542 + } 1.543 + 1.544 + // Recalculate the cached data size 1.545 + const int64_t delta = -(static_cast<int64_t>(aOld.Length())); 1.546 + unused << ProcessUsageDelta(aStorage, delta); 1.547 + data.mKeys.Remove(aKey); 1.548 + 1.549 + if (Persist(aStorage)) { 1.550 + if (!sDatabase) { 1.551 + NS_ERROR("Writing to localStorage after the database has been shut down" 1.552 + ", data lose!"); 1.553 + return NS_ERROR_NOT_INITIALIZED; 1.554 + } 1.555 + 1.556 + return sDatabase->AsyncRemoveItem(this, aKey); 1.557 + } 1.558 + 1.559 + return NS_OK; 1.560 +} 1.561 + 1.562 +nsresult 1.563 +DOMStorageCache::Clear(const DOMStorage* aStorage) 1.564 +{ 1.565 + Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_CLEAR_MS> autoTimer; 1.566 + 1.567 + bool refresh = false; 1.568 + if (Persist(aStorage)) { 1.569 + // We need to preload all data (know the size) before we can proceeed 1.570 + // to correctly decrease cached usage number. 1.571 + // XXX as in case of unload, this is not technically needed now, but 1.572 + // after super-scope quota introduction we have to do this. Get telemetry 1.573 + // right now. 1.574 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS); 1.575 + if (NS_FAILED(mLoadResult)) { 1.576 + // When we failed to load data from the database, force delete of the 1.577 + // scope data and make use of the storage possible again. 1.578 + refresh = true; 1.579 + mLoadResult = NS_OK; 1.580 + } 1.581 + } 1.582 + 1.583 + Data& data = DataSet(aStorage); 1.584 + bool hadData = !!data.mKeys.Count(); 1.585 + 1.586 + if (hadData) { 1.587 + unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage); 1.588 + data.mKeys.Clear(); 1.589 + } 1.590 + 1.591 + if (Persist(aStorage) && (refresh || hadData)) { 1.592 + if (!sDatabase) { 1.593 + NS_ERROR("Writing to localStorage after the database has been shut down" 1.594 + ", data lose!"); 1.595 + return NS_ERROR_NOT_INITIALIZED; 1.596 + } 1.597 + 1.598 + return sDatabase->AsyncClear(this); 1.599 + } 1.600 + 1.601 + return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION; 1.602 +} 1.603 + 1.604 +void 1.605 +DOMStorageCache::CloneFrom(const DOMStorageCache* aThat) 1.606 +{ 1.607 + mLoaded = aThat->mLoaded; 1.608 + mInitialized = aThat->mInitialized; 1.609 + mPersistent = aThat->mPersistent; 1.610 + mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive; 1.611 + 1.612 + for (uint32_t i = 0; i < kDataSetCount; ++i) { 1.613 + aThat->mData[i].mKeys.EnumerateRead(CloneSetData, &mData[i]); 1.614 + ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage); 1.615 + } 1.616 +} 1.617 + 1.618 +// Defined in DOMStorageManager.cpp 1.619 +extern bool 1.620 +PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal); 1.621 + 1.622 +bool 1.623 +DOMStorageCache::CheckPrincipal(nsIPrincipal* aPrincipal) const 1.624 +{ 1.625 + return PrincipalsEqual(mPrincipal, aPrincipal); 1.626 +} 1.627 + 1.628 +void 1.629 +DOMStorageCache::UnloadItems(uint32_t aUnloadFlags) 1.630 +{ 1.631 + if (aUnloadFlags & kUnloadDefault) { 1.632 + // Must wait for preload to pass correct usage to ProcessUsageDelta 1.633 + // XXX this is not technically needed right now since there is just 1.634 + // per-origin isolated quota handling, but when we introduce super- 1.635 + // -scope quotas, we have to do this. Better to start getting 1.636 + // telemetry right now. 1.637 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); 1.638 + 1.639 + mData[kDefaultSet].mKeys.Clear(); 1.640 + ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage); 1.641 + } 1.642 + 1.643 + if (aUnloadFlags & kUnloadPrivate) { 1.644 + mData[kPrivateSet].mKeys.Clear(); 1.645 + ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage); 1.646 + } 1.647 + 1.648 + if (aUnloadFlags & kUnloadSession) { 1.649 + mData[kSessionSet].mKeys.Clear(); 1.650 + ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage); 1.651 + mSessionOnlyDataSetActive = false; 1.652 + } 1.653 + 1.654 +#ifdef DOM_STORAGE_TESTS 1.655 + if (aUnloadFlags & kTestReload) { 1.656 + WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS); 1.657 + 1.658 + mData[kDefaultSet].mKeys.Clear(); 1.659 + mLoaded = false; // This is only used in testing code 1.660 + Preload(); 1.661 + } 1.662 +#endif 1.663 +} 1.664 + 1.665 +// DOMStorageCacheBridge 1.666 + 1.667 +uint32_t 1.668 +DOMStorageCache::LoadedCount() 1.669 +{ 1.670 + MonitorAutoLock monitor(mMonitor); 1.671 + Data& data = mData[kDefaultSet]; 1.672 + return data.mKeys.Count(); 1.673 +} 1.674 + 1.675 +bool 1.676 +DOMStorageCache::LoadItem(const nsAString& aKey, const nsString& aValue) 1.677 +{ 1.678 + MonitorAutoLock monitor(mMonitor); 1.679 + if (mLoaded) { 1.680 + return false; 1.681 + } 1.682 + 1.683 + Data& data = mData[kDefaultSet]; 1.684 + if (data.mKeys.Get(aKey, nullptr)) { 1.685 + return true; // don't stop, just don't override 1.686 + } 1.687 + 1.688 + data.mKeys.Put(aKey, aValue); 1.689 + data.mOriginQuotaUsage += aKey.Length() + aValue.Length(); 1.690 + return true; 1.691 +} 1.692 + 1.693 +void 1.694 +DOMStorageCache::LoadDone(nsresult aRv) 1.695 +{ 1.696 + // Keep the preloaded cache alive for a time 1.697 + KeepAlive(); 1.698 + 1.699 + MonitorAutoLock monitor(mMonitor); 1.700 + mLoadResult = aRv; 1.701 + mLoaded = true; 1.702 + monitor.Notify(); 1.703 +} 1.704 + 1.705 +void 1.706 +DOMStorageCache::LoadWait() 1.707 +{ 1.708 + MonitorAutoLock monitor(mMonitor); 1.709 + while (!mLoaded) { 1.710 + monitor.Wait(); 1.711 + } 1.712 +} 1.713 + 1.714 +// DOMStorageUsage 1.715 + 1.716 +DOMStorageUsage::DOMStorageUsage(const nsACString& aScope) 1.717 + : mScope(aScope) 1.718 +{ 1.719 + mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL; 1.720 +} 1.721 + 1.722 +namespace { // anon 1.723 + 1.724 +class LoadUsageRunnable : public nsRunnable 1.725 +{ 1.726 +public: 1.727 + LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta) 1.728 + : mTarget(aUsage) 1.729 + , mDelta(aDelta) 1.730 + {} 1.731 + 1.732 +private: 1.733 + int64_t* mTarget; 1.734 + int64_t mDelta; 1.735 + 1.736 + NS_IMETHOD Run() { *mTarget = mDelta; return NS_OK; } 1.737 +}; 1.738 + 1.739 +} // anon 1.740 + 1.741 +void 1.742 +DOMStorageUsage::LoadUsage(const int64_t aUsage) 1.743 +{ 1.744 + // Using kDefaultSet index since it is the index for the persitent data 1.745 + // stored in the database we have just loaded usage for. 1.746 + if (!NS_IsMainThread()) { 1.747 + // In single process scenario we get this call from the DB thread 1.748 + nsRefPtr<LoadUsageRunnable> r = 1.749 + new LoadUsageRunnable(mUsage + kDefaultSet, aUsage); 1.750 + NS_DispatchToMainThread(r); 1.751 + } else { 1.752 + // On a child process we get this on the main thread already 1.753 + mUsage[kDefaultSet] += aUsage; 1.754 + } 1.755 +} 1.756 + 1.757 +bool 1.758 +DOMStorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, const int64_t aDelta) 1.759 +{ 1.760 + MOZ_ASSERT(NS_IsMainThread()); 1.761 + 1.762 + int64_t newUsage = mUsage[aDataSetIndex] + aDelta; 1.763 + if (aDelta > 0 && newUsage > DOMStorageManager::GetQuota()) { 1.764 + return false; 1.765 + } 1.766 + 1.767 + mUsage[aDataSetIndex] = newUsage; 1.768 + return true; 1.769 +} 1.770 + 1.771 + 1.772 +// static 1.773 +DOMStorageDBBridge* 1.774 +DOMStorageCache::StartDatabase() 1.775 +{ 1.776 + if (sDatabase || sDatabaseDown) { 1.777 + // When sDatabaseDown is at true, sDatabase is null. 1.778 + // Checking sDatabaseDown flag here prevents reinitialization of 1.779 + // the database after shutdown. 1.780 + return sDatabase; 1.781 + } 1.782 + 1.783 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.784 + nsAutoPtr<DOMStorageDBThread> db(new DOMStorageDBThread()); 1.785 + 1.786 + nsresult rv = db->Init(); 1.787 + if (NS_FAILED(rv)) { 1.788 + return nullptr; 1.789 + } 1.790 + 1.791 + sDatabase = db.forget(); 1.792 + } else { 1.793 + nsRefPtr<DOMStorageDBChild> db = new DOMStorageDBChild( 1.794 + DOMLocalStorageManager::Self()); 1.795 + 1.796 + nsresult rv = db->Init(); 1.797 + if (NS_FAILED(rv)) { 1.798 + return nullptr; 1.799 + } 1.800 + 1.801 + db.forget(&sDatabase); 1.802 + } 1.803 + 1.804 + return sDatabase; 1.805 +} 1.806 + 1.807 +// static 1.808 +DOMStorageDBBridge* 1.809 +DOMStorageCache::GetDatabase() 1.810 +{ 1.811 + return sDatabase; 1.812 +} 1.813 + 1.814 +// static 1.815 +nsresult 1.816 +DOMStorageCache::StopDatabase() 1.817 +{ 1.818 + if (!sDatabase) { 1.819 + return NS_OK; 1.820 + } 1.821 + 1.822 + sDatabaseDown = true; 1.823 + 1.824 + nsresult rv = sDatabase->Shutdown(); 1.825 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.826 + delete sDatabase; 1.827 + } else { 1.828 + DOMStorageDBChild* child = static_cast<DOMStorageDBChild*>(sDatabase); 1.829 + NS_RELEASE(child); 1.830 + } 1.831 + 1.832 + sDatabase = nullptr; 1.833 + return rv; 1.834 +} 1.835 + 1.836 +} // ::dom 1.837 +} // ::mozilla