dom/src/storage/DOMStorageCache.cpp

changeset 0
6474c204b198
     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

mercurial