diff options
Diffstat (limited to 'dom/storage/LocalStorageCache.h')
-rw-r--r-- | dom/storage/LocalStorageCache.h | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/dom/storage/LocalStorageCache.h b/dom/storage/LocalStorageCache.h new file mode 100644 index 0000000000..9df56cd475 --- /dev/null +++ b/dom/storage/LocalStorageCache.h @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_LocalStorageCache_h +#define mozilla_dom_LocalStorageCache_h + +#include "nsIPrincipal.h" + +#include "nsString.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "mozilla/Monitor.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Atomics.h" + +namespace mozilla::dom { + +class LocalStorage; +class LocalStorageCacheChild; +class LocalStorageManager; +class StorageUsage; +class StorageDBBridge; + +// Interface class on which only the database or IPC may call. +// Used to populate the cache with DB data. +class LocalStorageCacheBridge { + public: + NS_IMETHOD_(MozExternalRefCountType) AddRef(void); + NS_IMETHOD_(void) Release(void); + + // The origin of the cache, result is concatenation of OriginNoSuffix() and + // OriginSuffix(), see below. + virtual const nsCString Origin() const = 0; + + // The origin attributes suffix alone, this is usually passed as an + // |aOriginSuffix| argument to various methods + virtual const nsCString& OriginSuffix() const = 0; + + // The origin in the database usage format (reversed) and without the suffix + virtual const nsCString& OriginNoSuffix() const = 0; + + // Whether the cache is already fully loaded + virtual bool Loaded() = 0; + + // How many items has so far been loaded into the cache, used + // for optimization purposes + virtual uint32_t LoadedCount() = 0; + + // Called by the database to load a key and its value to the cache + virtual bool LoadItem(const nsAString& aKey, const nsAString& aValue) = 0; + + // Called by the database after all keys and values has been loaded + // to this cache + virtual void LoadDone(nsresult aRv) = 0; + + // Use to synchronously wait until the cache gets fully loaded with data, + // this method exits after LoadDone has been called + virtual void LoadWait() = 0; + + protected: + virtual ~LocalStorageCacheBridge() = default; + + ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD +}; + +// Implementation of scope cache that is responsible for preloading data +// for persistent storage (localStorage) and hold data for non-private, +// private and session-only cookie modes. It is also responsible for +// persisting data changes using the database, works as a write-back cache. +class LocalStorageCache : public LocalStorageCacheBridge { + public: + void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LocalStorage); } + + void SetActor(LocalStorageCacheChild* aActor); + + void ClearActor() { + AssertIsOnOwningThread(); + + mActor = nullptr; + } + + NS_IMETHOD_(void) Release(void) override; + + enum MutationSource { + // The mutation is a result of an explicit JS mutation in this process. + // The mutation should be sent to the sDatabase. Quota will be checked and + // QuotaExceededError may be returned without the mutation being applied. + ContentMutation, + // The mutation initially was triggered in a different process and is being + // propagated to this cache via LocalStorage::ApplyEvent. The mutation + // should + // not be sent to the sDatabase because the originating process is already + // doing that. (In addition to the redundant writes being wasteful, there + // is the potential for other processes to see inconsistent state from the + // database while preloading.) Quota will be updated but not checked + // because it's assumed it was checked in another process and data-coherency + // is more important than slightly exceeding quota. + E10sPropagated + }; + + // Note: We pass aOriginNoSuffix through the ctor here, because + // LocalStorageCacheHashKey's ctor is creating this class and + // accepts reversed-origin-no-suffix as an argument - the hashing key. + explicit LocalStorageCache(const nsACString* aOriginNoSuffix); + + protected: + virtual ~LocalStorageCache(); + + public: + void Init(LocalStorageManager* aManager, bool aPersistent, + nsIPrincipal* aPrincipal, const nsACString& aQuotaOriginScope); + + // Get size of per-origin data. + int64_t GetOriginQuotaUsage(const LocalStorage* aStorage) const; + + // Starts async preload of this cache if it persistent and not loaded. + void Preload(); + + // The set of methods that are invoked by DOM storage web API. + // We are passing the LocalStorage object just to let the cache + // read properties like mPrivate and mSessionOnly. + // Get* methods return error when load from the database has failed. + nsresult GetLength(const LocalStorage* aStorage, uint32_t* aRetval); + nsresult GetKey(const LocalStorage* aStorage, uint32_t index, + nsAString& aRetval); + nsresult GetItem(const LocalStorage* aStorage, const nsAString& aKey, + nsAString& aRetval); + nsresult SetItem(const LocalStorage* aStorage, const nsAString& aKey, + const nsAString& aValue, nsString& aOld, + const MutationSource aSource = ContentMutation); + nsresult RemoveItem(const LocalStorage* aStorage, const nsAString& aKey, + nsString& aOld, + const MutationSource aSource = ContentMutation); + nsresult Clear(const LocalStorage* aStorage, + const MutationSource aSource = ContentMutation); + + void GetKeys(const LocalStorage* aStorage, nsTArray<nsString>& aKeys); + + // LocalStorageCacheBridge + + const nsCString Origin() const override; + const nsCString& OriginNoSuffix() const override { return mOriginNoSuffix; } + const nsCString& OriginSuffix() const override { return mOriginSuffix; } + bool Loaded() override { return mLoaded; } + uint32_t LoadedCount() override; + bool LoadItem(const nsAString& aKey, const nsAString& aValue) override; + void LoadDone(nsresult aRv) override; + void LoadWait() override; + + // Cache keeps 3 sets of data: regular, private and session-only. + // This class keeps keys and values for a set and also caches + // size of the data for quick per-origin quota checking. + class Data { + public: + Data() : mOriginQuotaUsage(0) {} + int64_t mOriginQuotaUsage; + nsTHashMap<nsStringHashKey, nsString> mKeys; + }; + + public: + // Number of data sets we keep: default, session + static const uint32_t kDataSetCount = 2; + + private: + // API to clear the cache data, this is invoked by chrome operations + // like cookie deletion. + friend class LocalStorageManager; + + static const uint32_t kUnloadDefault = 1 << 0; + static const uint32_t kUnloadSession = 1 << 1; + static const uint32_t kUnloadComplete = kUnloadDefault | kUnloadSession; + +#ifdef DOM_STORAGE_TESTS + static const uint32_t kTestReload = 1 << 15; +#endif + + void UnloadItems(uint32_t aUnloadFlags); + + private: + // Synchronously blocks until the cache is fully loaded from the database + void WaitForPreload(mozilla::Telemetry::HistogramID aTelemetryID); + + // Helper to get one of the 3 data sets (regular, private, session) + Data& DataSet(const LocalStorage* aStorage); + + // Used for firing storage events and synchronization of caches in other + // content processes. + void NotifyObservers(const LocalStorage* aStorage, const nsAString& aKey, + const nsAString& aOldValue, const nsAString& aNewValue); + + // Whether the storage change is about to persist + bool Persist(const LocalStorage* aStorage) const; + + // Changes the quota usage on the given data set if it fits the quota. + // If not, then false is returned and no change to the set must be done. + // A special case is if aSource==E10sPropagated, then we will return true even + // if the change would put us over quota. This is done to ensure coherency of + // caches between processes in the face of races. It does allow an attacker + // to potentially use N multiples of the quota storage limit if they can + // arrange for their origin to execute code in N processes. However, this is + // not considered a particularly concerning threat model because it's already + // very possible for a rogue page to attempt to intentionally fill up the + // user's storage through the use of multiple domains. + bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta, + const MutationSource aSource = ContentMutation); + bool ProcessUsageDelta(const LocalStorage* aStorage, const int64_t aDelta, + const MutationSource aSource = ContentMutation); + + private: + // When a cache is reponsible for its life time (in case of localStorage data + // cache) we need to refer our manager since removal of the cache from the + // hash table is handled in the destructor by call to the manager. Cache + // could potentially overlive the manager, hence the hard ref. + RefPtr<LocalStorageManager> mManager; + + // Reference to the usage counter object we check on for eTLD+1 quota limit. + // Obtained from the manager during initialization (Init method). + RefPtr<StorageUsage> mUsage; + + // The LocalStorageCacheChild is created at the same time of this class. + // In normal operation, the actor will be synchronously cleared in our + // destructor when we tell it to delete itself. In a shutdown-related edge + // case in the parent process for JSM's, it is possible for the actor to be + // destroyed while this class remains alive, in which case it will be nulled + // out. + LocalStorageCacheChild* mActor; + + // The origin this cache belongs to in the "DB format", i.e. reversed + nsCString mOriginNoSuffix; + + // The origin attributes suffix + nsCString mOriginSuffix; + + // The eTLD+1 scope used to count quota usage. It is in the reversed format + // and contains the origin attributes suffix. + nsCString mQuotaOriginScope; + + // Non-private Browsing, Private Browsing and Session Only sets. + Data mData[kDataSetCount]; + + // This monitor is used to wait for full load of data. + mozilla::Monitor mMonitor MOZ_UNANNOTATED; + + // Flag that is initially false. When the cache is about to work with + // the database (i.e. it is persistent) this flags is set to true after + // all keys and coresponding values are loaded from the database. + // This flag never goes from true back to false. Since this flag is + // critical for mData hashtable synchronization, it's made atomic. + Atomic<bool, ReleaseAcquire> mLoaded; + + // Result of load from the database. Valid after mLoaded flag has been set. + nsresult mLoadResult; + + // Expected to be only 0 or 1. + uint32_t mPrivateBrowsingId; + + // Init() method has been called + bool mInitialized : 1; + + // This cache is about to be bound with the database (i.e. it has + // to load from the DB first and has to persist when modifying the + // default data set.) + bool mPersistent : 1; + + // Whether we have already captured state of the cache preload on our first + // access. + bool mPreloadTelemetryRecorded : 1; +}; + +// StorageUsage +// Infrastructure to manage and check eTLD+1 quota +class StorageUsageBridge { + public: + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(StorageUsageBridge) + + virtual const nsCString& OriginScope() = 0; + virtual void LoadUsage(const int64_t aUsage) = 0; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~StorageUsageBridge() = default; +}; + +class StorageUsage : public StorageUsageBridge { + public: + explicit StorageUsage(const nsACString& aOriginScope); + + bool CheckAndSetETLD1UsageDelta( + uint32_t aDataSetIndex, int64_t aUsageDelta, + const LocalStorageCache::MutationSource aSource); + + private: + const nsCString& OriginScope() override { return mOriginScope; } + void LoadUsage(const int64_t aUsage) override; + + nsCString mOriginScope; + int64_t mUsage[LocalStorageCache::kDataSetCount]; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_LocalStorageCache_h |