summaryrefslogtreecommitdiffstats
path: root/dom/storage
diff options
context:
space:
mode:
Diffstat (limited to 'dom/storage')
-rw-r--r--dom/storage/DOMStorage.cpp301
-rw-r--r--dom/storage/DOMStorage.h172
-rw-r--r--dom/storage/DOMStorageCache.cpp807
-rw-r--r--dom/storage/DOMStorageCache.h288
-rw-r--r--dom/storage/DOMStorageDBThread.cpp1486
-rw-r--r--dom/storage/DOMStorageDBThread.h407
-rw-r--r--dom/storage/DOMStorageDBUpdater.cpp417
-rw-r--r--dom/storage/DOMStorageDBUpdater.h22
-rw-r--r--dom/storage/DOMStorageIPC.cpp770
-rw-r--r--dom/storage/DOMStorageIPC.h218
-rw-r--r--dom/storage/DOMStorageManager.cpp661
-rw-r--r--dom/storage/DOMStorageManager.h155
-rw-r--r--dom/storage/DOMStorageObserver.cpp355
-rw-r--r--dom/storage/DOMStorageObserver.h67
-rw-r--r--dom/storage/PStorage.ipdl45
-rw-r--r--dom/storage/moz.build34
16 files changed, 6205 insertions, 0 deletions
diff --git a/dom/storage/DOMStorage.cpp b/dom/storage/DOMStorage.cpp
new file mode 100644
index 000000000..026959dce
--- /dev/null
+++ b/dom/storage/DOMStorage.cpp
@@ -0,0 +1,301 @@
+/* -*- 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/. */
+
+#include "DOMStorage.h"
+#include "DOMStorageCache.h"
+#include "DOMStorageManager.h"
+
+#include "nsIObserverService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrincipal.h"
+#include "nsICookiePermission.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/dom/StorageBinding.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/EnumSet.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMStorage, mManager, mPrincipal, mWindow)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMStorage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMStorage)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMStorage)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorage)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMStorage)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+DOMStorage::DOMStorage(nsPIDOMWindowInner* aWindow,
+ DOMStorageManager* aManager,
+ DOMStorageCache* aCache,
+ const nsAString& aDocumentURI,
+ nsIPrincipal* aPrincipal,
+ bool aIsPrivate)
+: mWindow(aWindow)
+, mManager(aManager)
+, mCache(aCache)
+, mDocumentURI(aDocumentURI)
+, mPrincipal(aPrincipal)
+, mIsPrivate(aIsPrivate)
+, mIsSessionOnly(false)
+{
+ mCache->Preload();
+}
+
+DOMStorage::~DOMStorage()
+{
+ mCache->KeepAlive();
+}
+
+/* virtual */ JSObject*
+DOMStorage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return StorageBinding::Wrap(aCx, this, aGivenProto);
+}
+
+uint32_t
+DOMStorage::GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return 0;
+ }
+
+ uint32_t length;
+ aRv = mCache->GetLength(this, &length);
+ return length;
+}
+
+void
+DOMStorage::Key(uint32_t aIndex, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aRv = mCache->GetKey(this, aIndex, aResult);
+}
+
+void
+DOMStorage::GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aRv = mCache->GetItem(this, aKey, aResult);
+}
+
+void
+DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsString data;
+ bool ok = data.Assign(aData, fallible);
+ if (!ok) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ nsString old;
+ aRv = mCache->SetItem(this, aKey, data, old);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
+ BroadcastChangeNotification(aKey, old, aData);
+ }
+}
+
+void
+DOMStorage::RemoveItem(const nsAString& aKey,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsAutoString old;
+ aRv = mCache->RemoveItem(this, aKey, old);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
+ BroadcastChangeNotification(aKey, old, NullString());
+ }
+}
+
+void
+DOMStorage::Clear(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+{
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ aRv = mCache->Clear(this);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
+ BroadcastChangeNotification(NullString(), NullString(), NullString());
+ }
+}
+
+namespace {
+
+class StorageNotifierRunnable : public Runnable
+{
+public:
+ StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aType)
+ : mSubject(aSubject), mType(aType)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+private:
+ nsCOMPtr<nsISupports> mSubject;
+ const char16_t* mType;
+};
+
+NS_IMETHODIMP
+StorageNotifierRunnable::Run()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(mSubject, "dom-storage2-changed", mType);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+void
+DOMStorage::BroadcastChangeNotification(const nsSubstring& aKey,
+ const nsSubstring& aOldValue,
+ const nsSubstring& aNewValue)
+{
+ StorageEventInit dict;
+ dict.mBubbles = false;
+ dict.mCancelable = false;
+ dict.mKey = aKey;
+ dict.mNewValue = aNewValue;
+ dict.mOldValue = aOldValue;
+ dict.mStorageArea = this;
+ dict.mUrl = mDocumentURI;
+
+ // Note, this DOM event should never reach JS. It is cloned later in
+ // nsGlobalWindow.
+ RefPtr<StorageEvent> event =
+ StorageEvent::Constructor(nullptr, NS_LITERAL_STRING("storage"), dict);
+
+ RefPtr<StorageNotifierRunnable> r =
+ new StorageNotifierRunnable(event,
+ GetType() == LocalStorage
+ ? u"localStorage"
+ : u"sessionStorage");
+ NS_DispatchToMainThread(r);
+}
+
+static const char kPermissionType[] = "cookie";
+static const char kStorageEnabled[] = "dom.storage.enabled";
+
+bool
+DOMStorage::CanUseStorage(nsIPrincipal& aSubjectPrincipal)
+{
+ // This method is responsible for correct setting of mIsSessionOnly.
+ // It doesn't work with mIsPrivate flag at all, since it is checked
+ // regardless mIsSessionOnly flag in DOMStorageCache code.
+
+ if (!mozilla::Preferences::GetBool(kStorageEnabled)) {
+ return false;
+ }
+
+ nsContentUtils::StorageAccess access =
+ nsContentUtils::StorageAllowedForPrincipal(mPrincipal);
+
+ if (access == nsContentUtils::StorageAccess::eDeny) {
+ return false;
+ }
+
+ mIsSessionOnly = access <= nsContentUtils::StorageAccess::eSessionScoped;
+ return CanAccess(&aSubjectPrincipal);
+}
+
+DOMStorage::StorageType
+DOMStorage::GetType() const
+{
+ return mManager->Type();
+}
+
+nsIPrincipal*
+DOMStorage::GetPrincipal()
+{
+ return mPrincipal;
+}
+
+// Defined in DOMStorageManager.cpp
+extern bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal);
+
+bool
+DOMStorage::PrincipalEquals(nsIPrincipal* aPrincipal)
+{
+ return PrincipalsEqual(mPrincipal, aPrincipal);
+}
+
+bool
+DOMStorage::CanAccess(nsIPrincipal* aPrincipal)
+{
+ return !aPrincipal || aPrincipal->Subsumes(mPrincipal);
+}
+
+void
+DOMStorage::GetSupportedNames(nsTArray<nsString>& aKeys)
+{
+ if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
+ // return just an empty array
+ aKeys.Clear();
+ return;
+ }
+
+ mCache->GetKeys(this, aKeys);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorage.h b/dom/storage/DOMStorage.h
new file mode 100644
index 000000000..d956ac3ed
--- /dev/null
+++ b/dom/storage/DOMStorage.h
@@ -0,0 +1,172 @@
+/* -*- 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 nsDOMStorage_h___
+#define nsDOMStorage_h___
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "nsIDOMStorage.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWeakReference.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageManager;
+class DOMStorageCache;
+
+class DOMStorage final
+ : public nsIDOMStorage
+ , public nsSupportsWeakReference
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(DOMStorage,
+ nsIDOMStorage)
+
+ enum StorageType {
+ LocalStorage = 1,
+ SessionStorage = 2
+ };
+
+ StorageType GetType() const;
+
+ DOMStorageManager* GetManager() const
+ {
+ return mManager;
+ }
+
+ DOMStorageCache const* GetCache() const
+ {
+ return mCache;
+ }
+
+ nsIPrincipal* GetPrincipal();
+ bool PrincipalEquals(nsIPrincipal* aPrincipal);
+ bool CanAccess(nsIPrincipal* aPrincipal);
+
+ DOMStorage(nsPIDOMWindowInner* aWindow,
+ DOMStorageManager* aManager,
+ DOMStorageCache* aCache,
+ const nsAString& aDocumentURI,
+ nsIPrincipal* aPrincipal,
+ bool aIsPrivate);
+
+ // WebIDL
+ JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ nsPIDOMWindowInner* GetParentObject() const
+ {
+ return mWindow;
+ }
+
+ uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void Key(uint32_t aIndex, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void GetSupportedNames(nsTArray<nsString>& aKeys);
+
+ void NamedGetter(const nsAString& aKey, bool& aFound, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+ {
+ GetItem(aKey, aResult, aSubjectPrincipal, aRv);
+ aFound = !aResult.IsVoid();
+ }
+
+ void SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void NamedSetter(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+ {
+ SetItem(aKey, aValue, aSubjectPrincipal, aRv);
+ }
+
+ void RemoveItem(const nsAString& aKey,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ void NamedDeleter(const nsAString& aKey, bool& aFound,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv)
+ {
+ RemoveItem(aKey, aSubjectPrincipal, aRv);
+
+ aFound = !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION);
+ }
+
+ void Clear(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ bool IsPrivate() const { return mIsPrivate; }
+ bool IsSessionOnly() const { return mIsSessionOnly; }
+
+ bool IsForkOf(const DOMStorage* aOther) const
+ {
+ MOZ_ASSERT(aOther);
+ return mCache == aOther->mCache;
+ }
+
+protected:
+ // The method checks whether the caller can use a storage.
+ // CanUseStorage is called before any DOM initiated operation
+ // on a storage is about to happen and ensures that the storage's
+ // session-only flag is properly set according the current settings.
+ // It is an optimization since the privileges check and session only
+ // state determination are complex and share the code (comes hand in
+ // hand together).
+ bool CanUseStorage(nsIPrincipal& aSubjectPrincipal);
+
+private:
+ ~DOMStorage();
+
+ friend class DOMStorageManager;
+ friend class DOMStorageCache;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<DOMStorageManager> mManager;
+ RefPtr<DOMStorageCache> mCache;
+ nsString mDocumentURI;
+
+ // Principal this DOMStorage (i.e. localStorage or sessionStorage) has
+ // been created for
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ // Whether this storage is running in private-browsing window.
+ bool mIsPrivate : 1;
+
+ // Whether storage is set to persist data only per session, may change
+ // dynamically and is set by CanUseStorage function that is called
+ // before any operation on the storage.
+ bool mIsSessionOnly : 1;
+
+ void BroadcastChangeNotification(const nsSubstring& aKey,
+ const nsSubstring& aOldValue,
+ const nsSubstring& aNewValue);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* nsDOMStorage_h___ */
diff --git a/dom/storage/DOMStorageCache.cpp b/dom/storage/DOMStorageCache.cpp
new file mode 100644
index 000000000..811f79fd3
--- /dev/null
+++ b/dom/storage/DOMStorageCache.cpp
@@ -0,0 +1,807 @@
+/* -*- 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/. */
+
+#include "DOMStorageCache.h"
+
+#include "DOMStorage.h"
+#include "DOMStorageDBThread.h"
+#include "DOMStorageIPC.h"
+#include "DOMStorageManager.h"
+
+#include "nsAutoPtr.h"
+#include "nsDOMString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
+
+// static
+DOMStorageDBBridge* DOMStorageCache::sDatabase = nullptr;
+bool DOMStorageCache::sDatabaseDown = false;
+
+namespace {
+
+const uint32_t kDefaultSet = 0;
+const uint32_t kPrivateSet = 1;
+const uint32_t kSessionSet = 2;
+
+inline uint32_t
+GetDataSetIndex(bool aPrivate, bool aSessionOnly)
+{
+ if (aPrivate) {
+ return kPrivateSet;
+ }
+
+ if (aSessionOnly) {
+ return kSessionSet;
+ }
+
+ return kDefaultSet;
+}
+
+inline uint32_t
+GetDataSetIndex(const DOMStorage* aStorage)
+{
+ return GetDataSetIndex(aStorage->IsPrivate(), aStorage->IsSessionOnly());
+}
+
+} // namespace
+
+// DOMStorageCacheBridge
+
+NS_IMPL_ADDREF(DOMStorageCacheBridge)
+
+// Since there is no consumer of return value of Release, we can turn this
+// method to void to make implementation of asynchronous DOMStorageCache::Release
+// much simpler.
+NS_IMETHODIMP_(void) DOMStorageCacheBridge::Release(void)
+{
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "DOMStorageCacheBridge");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(_class); */
+ delete (this);
+ }
+}
+
+// DOMStorageCache
+
+DOMStorageCache::DOMStorageCache(const nsACString* aOriginNoSuffix)
+: mOriginNoSuffix(*aOriginNoSuffix)
+, mMonitor("DOMStorageCache")
+, mLoaded(false)
+, mLoadResult(NS_OK)
+, mInitialized(false)
+, mPersistent(false)
+, mSessionOnlyDataSetActive(false)
+, mPreloadTelemetryRecorded(false)
+{
+ MOZ_COUNT_CTOR(DOMStorageCache);
+}
+
+DOMStorageCache::~DOMStorageCache()
+{
+ if (mManager) {
+ mManager->DropCache(this);
+ }
+
+ MOZ_COUNT_DTOR(DOMStorageCache);
+}
+
+NS_IMETHODIMP_(void)
+DOMStorageCache::Release(void)
+{
+ // We must actually release on the main thread since the cache removes it
+ // self from the manager's hash table. And we don't want to lock access to
+ // that hash table.
+ if (NS_IsMainThread()) {
+ DOMStorageCacheBridge::Release();
+ return;
+ }
+
+ RefPtr<nsRunnableMethod<DOMStorageCacheBridge, void, false> > event =
+ NewNonOwningRunnableMethod(static_cast<DOMStorageCacheBridge*>(this),
+ &DOMStorageCacheBridge::Release);
+
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("DOMStorageCache::Release() on a non-main thread");
+ DOMStorageCacheBridge::Release();
+ }
+}
+
+void
+DOMStorageCache::Init(DOMStorageManager* aManager,
+ bool aPersistent,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aQuotaOriginScope)
+{
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+ mPrincipal = aPrincipal;
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(mOriginSuffix);
+ mPersistent = aPersistent;
+ if (aQuotaOriginScope.IsEmpty()) {
+ mQuotaOriginScope = Origin();
+ } else {
+ mQuotaOriginScope = aQuotaOriginScope;
+ }
+
+ if (mPersistent) {
+ mManager = aManager;
+ Preload();
+ }
+
+ // Check the quota string has (or has not) the identical origin suffix as
+ // this storage cache is bound to.
+ MOZ_ASSERT(StringBeginsWith(mQuotaOriginScope, mOriginSuffix));
+ MOZ_ASSERT(mOriginSuffix.IsEmpty() != StringBeginsWith(mQuotaOriginScope,
+ NS_LITERAL_CSTRING("^")));
+
+ mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
+}
+
+inline bool
+DOMStorageCache::Persist(const DOMStorage* aStorage) const
+{
+ return mPersistent &&
+ !aStorage->IsSessionOnly() &&
+ !aStorage->IsPrivate();
+}
+
+const nsCString
+DOMStorageCache::Origin() const
+{
+ return DOMStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
+}
+
+DOMStorageCache::Data&
+DOMStorageCache::DataSet(const DOMStorage* aStorage)
+{
+ uint32_t index = GetDataSetIndex(aStorage);
+
+ if (index == kSessionSet && !mSessionOnlyDataSetActive) {
+ // Session only data set is demanded but not filled with
+ // current data set, copy to session only set now.
+
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_SESSIONONLY_PRELOAD_BLOCKING_MS);
+
+ Data& defaultSet = mData[kDefaultSet];
+ Data& sessionSet = mData[kSessionSet];
+
+ for (auto iter = defaultSet.mKeys.Iter(); !iter.Done(); iter.Next()) {
+ sessionSet.mKeys.Put(iter.Key(), iter.UserData());
+ }
+
+ mSessionOnlyDataSetActive = true;
+
+ // This updates sessionSet.mOriginQuotaUsage and also updates global usage
+ // for all session only data
+ ProcessUsageDelta(kSessionSet, defaultSet.mOriginQuotaUsage);
+ }
+
+ return mData[index];
+}
+
+bool
+DOMStorageCache::ProcessUsageDelta(const DOMStorage* aStorage, int64_t aDelta)
+{
+ return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta);
+}
+
+bool
+DOMStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta)
+{
+ // Check if we are in a low disk space situation
+ if (aDelta > 0 && mManager && mManager->IsLowDiskSpace()) {
+ return false;
+ }
+
+ // Check limit per this origin
+ Data& data = mData[aGetDataSetIndex];
+ uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
+ if (aDelta > 0 && newOriginUsage > DOMStorageManager::GetQuota()) {
+ return false;
+ }
+
+ // Now check eTLD+1 limit
+ if (mUsage && !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta)) {
+ return false;
+ }
+
+ // Update size in our data set
+ data.mOriginQuotaUsage = newOriginUsage;
+ return true;
+}
+
+void
+DOMStorageCache::Preload()
+{
+ if (mLoaded || !mPersistent) {
+ return;
+ }
+
+ if (!StartDatabase()) {
+ mLoaded = true;
+ mLoadResult = NS_ERROR_FAILURE;
+ return;
+ }
+
+ sDatabase->AsyncPreload(this);
+}
+
+namespace {
+
+// This class is passed to timer as a tick observer. It refers the cache
+// and keeps it alive for a time.
+class DOMStorageCacheHolder : public nsITimerCallback
+{
+ virtual ~DOMStorageCacheHolder() {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD
+ Notify(nsITimer* aTimer) override
+ {
+ mCache = nullptr;
+ return NS_OK;
+ }
+
+ RefPtr<DOMStorageCache> mCache;
+
+public:
+ explicit DOMStorageCacheHolder(DOMStorageCache* aCache) : mCache(aCache) {}
+};
+
+NS_IMPL_ISUPPORTS(DOMStorageCacheHolder, nsITimerCallback)
+
+} // namespace
+
+void
+DOMStorageCache::KeepAlive()
+{
+ // Missing reference back to the manager means the cache is not responsible
+ // for its lifetime. Used for keeping sessionStorage live forever.
+ if (!mManager) {
+ return;
+ }
+
+ if (!NS_IsMainThread()) {
+ // Timer and the holder must be initialized on the main thread.
+ NS_DispatchToMainThread(NewRunnableMethod(this, &DOMStorageCache::KeepAlive));
+ return;
+ }
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!timer) {
+ return;
+ }
+
+ RefPtr<DOMStorageCacheHolder> holder = new DOMStorageCacheHolder(this);
+ timer->InitWithCallback(holder, DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS,
+ nsITimer::TYPE_ONE_SHOT);
+
+ mKeepAliveTimer.swap(timer);
+}
+
+namespace {
+
+// The AutoTimer provided by telemetry headers is only using static,
+// i.e. compile time known ID, but here we know the ID only at run time.
+// Hence a new class.
+class TelemetryAutoTimer
+{
+public:
+ explicit TelemetryAutoTimer(Telemetry::ID aId)
+ : id(aId), start(TimeStamp::Now()) {}
+ ~TelemetryAutoTimer()
+ { Telemetry::AccumulateDelta_impl<Telemetry::Millisecond>::compute(id, start); }
+private:
+ Telemetry::ID id;
+ const TimeStamp start;
+};
+
+} // namespace
+
+void
+DOMStorageCache::WaitForPreload(Telemetry::ID aTelemetryID)
+{
+ if (!mPersistent) {
+ return;
+ }
+
+ bool loaded = mLoaded;
+
+ // Telemetry of rates of pending preloads
+ if (!mPreloadTelemetryRecorded) {
+ mPreloadTelemetryRecorded = true;
+ Telemetry::Accumulate(
+ Telemetry::LOCALDOMSTORAGE_PRELOAD_PENDING_ON_FIRST_ACCESS,
+ !loaded);
+ }
+
+ if (loaded) {
+ return;
+ }
+
+ // Measure which operation blocks and for how long
+ TelemetryAutoTimer timer(aTelemetryID);
+
+ // If preload already started (i.e. we got some first data, but not all)
+ // SyncPreload will just wait for it to finish rather then synchronously
+ // read from the database. It seems to me more optimal.
+
+ // TODO place for A/B testing (force main thread load vs. let preload finish)
+
+ // No need to check sDatabase for being non-null since preload is either
+ // done before we've shut the DB down or when the DB could not start,
+ // preload has not even be started.
+ sDatabase->SyncPreload(this);
+}
+
+nsresult
+DOMStorageCache::GetLength(const DOMStorage* aStorage, uint32_t* aRetval)
+{
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETLENGTH_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ return mLoadResult;
+ }
+ }
+
+ *aRetval = DataSet(aStorage).mKeys.Count();
+ return NS_OK;
+}
+
+nsresult
+DOMStorageCache::GetKey(const DOMStorage* aStorage, uint32_t aIndex, nsAString& aRetval)
+{
+ // XXX: This does a linear search for the key at index, which would
+ // suck if there's a large numer of indexes. Do we care? If so,
+ // maybe we need to have a lazily populated key array here or
+ // something?
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETKEY_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ return mLoadResult;
+ }
+ }
+
+ aRetval.SetIsVoid(true);
+ for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
+ if (aIndex == 0) {
+ aRetval = iter.Key();
+ break;
+ }
+ aIndex--;
+ }
+
+ return NS_OK;
+}
+
+void
+DOMStorageCache::GetKeys(const DOMStorage* aStorage, nsTArray<nsString>& aKeys)
+{
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
+ }
+
+ if (NS_FAILED(mLoadResult)) {
+ return;
+ }
+
+ for (auto iter = DataSet(aStorage).mKeys.Iter(); !iter.Done(); iter.Next()) {
+ aKeys.AppendElement(iter.Key());
+ }
+}
+
+nsresult
+DOMStorageCache::GetItem(const DOMStorage* aStorage, const nsAString& aKey,
+ nsAString& aRetval)
+{
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETVALUE_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ return mLoadResult;
+ }
+ }
+
+ // not using AutoString since we don't want to copy buffer to result
+ nsString value;
+ if (!DataSet(aStorage).mKeys.Get(aKey, &value)) {
+ SetDOMStringToNull(value);
+ }
+
+ aRetval = value;
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageCache::SetItem(const DOMStorage* aStorage, const nsAString& aKey,
+ const nsString& aValue, nsString& aOld)
+{
+ // Size of the cache that will change after this action.
+ int64_t delta = 0;
+
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_SETVALUE_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ return mLoadResult;
+ }
+ }
+
+ Data& data = DataSet(aStorage);
+ if (!data.mKeys.Get(aKey, &aOld)) {
+ SetDOMStringToNull(aOld);
+
+ // We only consider key size if the key doesn't exist before.
+ delta += static_cast<int64_t>(aKey.Length());
+ }
+
+ delta += static_cast<int64_t>(aValue.Length()) -
+ static_cast<int64_t>(aOld.Length());
+
+ if (!ProcessUsageDelta(aStorage, delta)) {
+ return NS_ERROR_DOM_QUOTA_REACHED;
+ }
+
+ if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ data.mKeys.Put(aKey, aValue);
+
+ if (Persist(aStorage)) {
+ if (!sDatabase) {
+ NS_ERROR("Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (DOMStringIsNull(aOld)) {
+ return sDatabase->AsyncAddItem(this, aKey, aValue);
+ }
+
+ return sDatabase->AsyncUpdateItem(this, aKey, aValue);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageCache::RemoveItem(const DOMStorage* aStorage, const nsAString& aKey,
+ nsString& aOld)
+{
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_REMOVEKEY_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ return mLoadResult;
+ }
+ }
+
+ Data& data = DataSet(aStorage);
+ if (!data.mKeys.Get(aKey, &aOld)) {
+ SetDOMStringToNull(aOld);
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Recalculate the cached data size
+ const int64_t delta = -(static_cast<int64_t>(aOld.Length()) +
+ static_cast<int64_t>(aKey.Length()));
+ Unused << ProcessUsageDelta(aStorage, delta);
+ data.mKeys.Remove(aKey);
+
+ if (Persist(aStorage)) {
+ if (!sDatabase) {
+ NS_ERROR("Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return sDatabase->AsyncRemoveItem(this, aKey);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageCache::Clear(const DOMStorage* aStorage)
+{
+ bool refresh = false;
+ if (Persist(aStorage)) {
+ // We need to preload all data (know the size) before we can proceeed
+ // to correctly decrease cached usage number.
+ // XXX as in case of unload, this is not technically needed now, but
+ // after super-scope quota introduction we have to do this. Get telemetry
+ // right now.
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_CLEAR_BLOCKING_MS);
+ if (NS_FAILED(mLoadResult)) {
+ // When we failed to load data from the database, force delete of the
+ // scope data and make use of the storage possible again.
+ refresh = true;
+ mLoadResult = NS_OK;
+ }
+ }
+
+ Data& data = DataSet(aStorage);
+ bool hadData = !!data.mKeys.Count();
+
+ if (hadData) {
+ Unused << ProcessUsageDelta(aStorage, -data.mOriginQuotaUsage);
+ data.mKeys.Clear();
+ }
+
+ if (Persist(aStorage) && (refresh || hadData)) {
+ if (!sDatabase) {
+ NS_ERROR("Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return sDatabase->AsyncClear(this);
+ }
+
+ return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+}
+
+void
+DOMStorageCache::CloneFrom(const DOMStorageCache* aThat)
+{
+ // This will never be called on anything else than SessionStorage.
+ // This means mData will never be touched on any other thread than
+ // the main thread and it never went through the loading process.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mPersistent);
+ MOZ_ASSERT(!(bool)aThat->mLoaded);
+
+ mLoaded = false;
+ mInitialized = aThat->mInitialized;
+ mPersistent = false;
+ mSessionOnlyDataSetActive = aThat->mSessionOnlyDataSetActive;
+
+ for (uint32_t i = 0; i < kDataSetCount; ++i) {
+ for (auto it = aThat->mData[i].mKeys.ConstIter(); !it.Done(); it.Next()) {
+ mData[i].mKeys.Put(it.Key(), it.UserData());
+ }
+ ProcessUsageDelta(i, aThat->mData[i].mOriginQuotaUsage);
+ }
+}
+
+// Defined in DOMStorageManager.cpp
+extern bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal);
+
+bool
+DOMStorageCache::CheckPrincipal(nsIPrincipal* aPrincipal) const
+{
+ return PrincipalsEqual(mPrincipal, aPrincipal);
+}
+
+void
+DOMStorageCache::UnloadItems(uint32_t aUnloadFlags)
+{
+ if (aUnloadFlags & kUnloadDefault) {
+ // Must wait for preload to pass correct usage to ProcessUsageDelta
+ // XXX this is not technically needed right now since there is just
+ // per-origin isolated quota handling, but when we introduce super-
+ // -scope quotas, we have to do this. Better to start getting
+ // telemetry right now.
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
+
+ mData[kDefaultSet].mKeys.Clear();
+ ProcessUsageDelta(kDefaultSet, -mData[kDefaultSet].mOriginQuotaUsage);
+ }
+
+ if (aUnloadFlags & kUnloadPrivate) {
+ mData[kPrivateSet].mKeys.Clear();
+ ProcessUsageDelta(kPrivateSet, -mData[kPrivateSet].mOriginQuotaUsage);
+ }
+
+ if (aUnloadFlags & kUnloadSession) {
+ mData[kSessionSet].mKeys.Clear();
+ ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
+ mSessionOnlyDataSetActive = false;
+ }
+
+#ifdef DOM_STORAGE_TESTS
+ if (aUnloadFlags & kTestReload) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_UNLOAD_BLOCKING_MS);
+
+ mData[kDefaultSet].mKeys.Clear();
+ mLoaded = false; // This is only used in testing code
+ Preload();
+ }
+#endif
+}
+
+// DOMStorageCacheBridge
+
+uint32_t
+DOMStorageCache::LoadedCount()
+{
+ MonitorAutoLock monitor(mMonitor);
+ Data& data = mData[kDefaultSet];
+ return data.mKeys.Count();
+}
+
+bool
+DOMStorageCache::LoadItem(const nsAString& aKey, const nsString& aValue)
+{
+ MonitorAutoLock monitor(mMonitor);
+ if (mLoaded) {
+ return false;
+ }
+
+ Data& data = mData[kDefaultSet];
+ if (data.mKeys.Get(aKey, nullptr)) {
+ return true; // don't stop, just don't override
+ }
+
+ data.mKeys.Put(aKey, aValue);
+ data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
+ return true;
+}
+
+void
+DOMStorageCache::LoadDone(nsresult aRv)
+{
+ // Keep the preloaded cache alive for a time
+ KeepAlive();
+
+ MonitorAutoLock monitor(mMonitor);
+ mLoadResult = aRv;
+ mLoaded = true;
+ monitor.Notify();
+}
+
+void
+DOMStorageCache::LoadWait()
+{
+ MonitorAutoLock monitor(mMonitor);
+ while (!mLoaded) {
+ monitor.Wait();
+ }
+}
+
+// DOMStorageUsage
+
+DOMStorageUsage::DOMStorageUsage(const nsACString& aOriginScope)
+ : mOriginScope(aOriginScope)
+{
+ mUsage[kDefaultSet] = mUsage[kPrivateSet] = mUsage[kSessionSet] = 0LL;
+}
+
+namespace {
+
+class LoadUsageRunnable : public Runnable
+{
+public:
+ LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
+ : mTarget(aUsage)
+ , mDelta(aDelta)
+ {}
+
+private:
+ int64_t* mTarget;
+ int64_t mDelta;
+
+ NS_IMETHOD Run() override { *mTarget = mDelta; return NS_OK; }
+};
+
+} // namespace
+
+void
+DOMStorageUsage::LoadUsage(const int64_t aUsage)
+{
+ // Using kDefaultSet index since it is the index for the persitent data
+ // stored in the database we have just loaded usage for.
+ if (!NS_IsMainThread()) {
+ // In single process scenario we get this call from the DB thread
+ RefPtr<LoadUsageRunnable> r =
+ new LoadUsageRunnable(mUsage + kDefaultSet, aUsage);
+ NS_DispatchToMainThread(r);
+ } else {
+ // On a child process we get this on the main thread already
+ mUsage[kDefaultSet] += aUsage;
+ }
+}
+
+bool
+DOMStorageUsage::CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, const int64_t aDelta)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
+ if (aDelta > 0 && newUsage > DOMStorageManager::GetQuota()) {
+ return false;
+ }
+
+ mUsage[aDataSetIndex] = newUsage;
+ return true;
+}
+
+
+// static
+DOMStorageDBBridge*
+DOMStorageCache::StartDatabase()
+{
+ if (sDatabase || sDatabaseDown) {
+ // When sDatabaseDown is at true, sDatabase is null.
+ // Checking sDatabaseDown flag here prevents reinitialization of
+ // the database after shutdown.
+ return sDatabase;
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsAutoPtr<DOMStorageDBThread> db(new DOMStorageDBThread());
+
+ nsresult rv = db->Init();
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ sDatabase = db.forget();
+ } else {
+ // Use DOMLocalStorageManager::Ensure in case we're called from
+ // DOMSessionStorageManager's initializer and we haven't yet initialized the
+ // local storage manager.
+ RefPtr<DOMStorageDBChild> db = new DOMStorageDBChild(
+ DOMLocalStorageManager::Ensure());
+
+ nsresult rv = db->Init();
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ db.forget(&sDatabase);
+ }
+
+ return sDatabase;
+}
+
+// static
+DOMStorageDBBridge*
+DOMStorageCache::GetDatabase()
+{
+ return sDatabase;
+}
+
+// static
+nsresult
+DOMStorageCache::StopDatabase()
+{
+ if (!sDatabase) {
+ return NS_OK;
+ }
+
+ sDatabaseDown = true;
+
+ nsresult rv = sDatabase->Shutdown();
+ if (XRE_IsParentProcess()) {
+ delete sDatabase;
+ } else {
+ DOMStorageDBChild* child = static_cast<DOMStorageDBChild*>(sDatabase);
+ NS_RELEASE(child);
+ }
+
+ sDatabase = nullptr;
+ return rv;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageCache.h b/dom/storage/DOMStorageCache.h
new file mode 100644
index 000000000..01cf6b3ea
--- /dev/null
+++ b/dom/storage/DOMStorageCache.h
@@ -0,0 +1,288 @@
+/* -*- 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 nsDOMStorageCache_h___
+#define nsDOMStorageCache_h___
+
+#include "nsIPrincipal.h"
+#include "nsITimer.h"
+
+#include "nsString.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorage;
+class DOMStorageUsage;
+class DOMStorageManager;
+class DOMStorageDBBridge;
+
+// Interface class on which only the database or IPC may call.
+// Used to populate the cache with DB data.
+class DOMStorageCacheBridge
+{
+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 nsString& 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 ~DOMStorageCacheBridge() {}
+
+ 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 DOMStorageCache : public DOMStorageCacheBridge
+{
+public:
+ NS_IMETHOD_(void) Release(void);
+
+ // Note: We pass aOriginNoSuffix through the ctor here, because
+ // DOMStorageCacheHashKey's ctor is creating this class and
+ // accepts reversed-origin-no-suffix as an argument - the hashing key.
+ explicit DOMStorageCache(const nsACString* aOriginNoSuffix);
+
+protected:
+ virtual ~DOMStorageCache();
+
+public:
+ void Init(DOMStorageManager* aManager, bool aPersistent, nsIPrincipal* aPrincipal,
+ const nsACString& aQuotaOriginScope);
+
+ // Copies all data from the other storage.
+ void CloneFrom(const DOMStorageCache* aThat);
+
+ // Starts async preload of this cache if it persistent and not loaded.
+ void Preload();
+
+ // Keeps the cache alive (i.e. present in the manager's hash table) for a time.
+ void KeepAlive();
+
+ // The set of methods that are invoked by DOM storage web API.
+ // We are passing the DOMStorage object just to let the cache
+ // read properties like mPrivate, mPrincipal and mSessionOnly.
+ // Get* methods return error when load from the database has failed.
+ nsresult GetLength(const DOMStorage* aStorage, uint32_t* aRetval);
+ nsresult GetKey(const DOMStorage* aStorage, uint32_t index, nsAString& aRetval);
+ nsresult GetItem(const DOMStorage* aStorage, const nsAString& aKey, nsAString& aRetval);
+ nsresult SetItem(const DOMStorage* aStorage, const nsAString& aKey, const nsString& aValue, nsString& aOld);
+ nsresult RemoveItem(const DOMStorage* aStorage, const nsAString& aKey, nsString& aOld);
+ nsresult Clear(const DOMStorage* aStorage);
+
+ void GetKeys(const DOMStorage* aStorage, nsTArray<nsString>& aKeys);
+
+ // Whether the principal equals principal the cache was created for
+ bool CheckPrincipal(nsIPrincipal* aPrincipal) const;
+ nsIPrincipal* Principal() const { return mPrincipal; }
+
+ // Starts the database engine thread or the IPC bridge
+ static DOMStorageDBBridge* StartDatabase();
+ static DOMStorageDBBridge* GetDatabase();
+
+ // Stops the thread and flushes all uncommited data
+ static nsresult StopDatabase();
+
+ // DOMStorageCacheBridge
+
+ virtual const nsCString Origin() const;
+ virtual const nsCString& OriginNoSuffix() const { return mOriginNoSuffix; }
+ virtual const nsCString& OriginSuffix() const { return mOriginSuffix; }
+ virtual bool Loaded() { return mLoaded; }
+ virtual uint32_t LoadedCount();
+ virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
+ virtual void LoadDone(nsresult aRv);
+ virtual void LoadWait();
+
+ // 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;
+ nsDataHashtable<nsStringHashKey, nsString> mKeys;
+ };
+
+public:
+ // Number of data sets we keep: default, private, session
+ static const uint32_t kDataSetCount = 3;
+
+private:
+ // API to clear the cache data, this is invoked by chrome operations
+ // like cookie deletion.
+ friend class DOMStorageManager;
+
+ static const uint32_t kUnloadDefault = 1 << 0;
+ static const uint32_t kUnloadPrivate = 1 << 1;
+ static const uint32_t kUnloadSession = 1 << 2;
+ static const uint32_t kUnloadComplete =
+ kUnloadDefault | kUnloadPrivate | 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::ID aTelemetryID);
+
+ // Helper to get one of the 3 data sets (regular, private, session)
+ Data& DataSet(const DOMStorage* aStorage);
+
+ // Whether the storage change is about to persist
+ bool Persist(const DOMStorage* 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.
+ bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta);
+ bool ProcessUsageDelta(const DOMStorage* aStorage, const int64_t aDelta);
+
+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<DOMStorageManager> mManager;
+
+ // Reference to the usage counter object we check on for eTLD+1 quota limit.
+ // Obtained from the manager during initialization (Init method).
+ RefPtr<DOMStorageUsage> mUsage;
+
+ // Timer that holds this cache alive for a while after it has been preloaded.
+ nsCOMPtr<nsITimer> mKeepAliveTimer;
+
+ // Principal the cache has been initially created for, this is used only
+ // for sessionStorage access checks since sessionStorage objects are strictly
+ // scoped by a principal. localStorage objects on the other hand are scoped by
+ // origin only.
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ // - False when the session-only data set was never used.
+ // - True after access to session-only data has been made for the first time.
+ // We also fill session-only data set with the default one at that moment.
+ // Drops back to false when session-only data are cleared from chrome.
+ bool mSessionOnlyDataSetActive : 1;
+
+ // Whether we have already captured state of the cache preload on our first access.
+ bool mPreloadTelemetryRecorded : 1;
+
+ // DOMStorageDBThread on the parent or single process,
+ // DOMStorageDBChild on the child process.
+ static DOMStorageDBBridge* sDatabase;
+
+ // False until we shut the database down.
+ static bool sDatabaseDown;
+};
+
+// DOMStorageUsage
+// Infrastructure to manage and check eTLD+1 quota
+class DOMStorageUsageBridge
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DOMStorageUsageBridge)
+
+ virtual const nsCString& OriginScope() = 0;
+ virtual void LoadUsage(const int64_t aUsage) = 0;
+
+protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~DOMStorageUsageBridge() {}
+};
+
+class DOMStorageUsage : public DOMStorageUsageBridge
+{
+public:
+ explicit DOMStorageUsage(const nsACString& aOriginScope);
+
+ bool CheckAndSetETLD1UsageDelta(uint32_t aDataSetIndex, int64_t aUsageDelta);
+
+private:
+ virtual const nsCString& OriginScope() { return mOriginScope; }
+ virtual void LoadUsage(const int64_t aUsage);
+
+ nsCString mOriginScope;
+ int64_t mUsage[DOMStorageCache::kDataSetCount];
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/storage/DOMStorageDBThread.cpp b/dom/storage/DOMStorageDBThread.cpp
new file mode 100644
index 000000000..183be5c5c
--- /dev/null
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -0,0 +1,1486 @@
+/* -*- 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/. */
+
+#include "DOMStorageDBThread.h"
+#include "DOMStorageDBUpdater.h"
+#include "DOMStorageCache.h"
+#include "DOMStorageManager.h"
+
+#include "nsIEffectiveTLDService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozStorageCID.h"
+#include "mozStorageHelper.h"
+#include "mozIStorageService.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsIObserverService.h"
+#include "nsVariant.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/Services.h"
+#include "mozilla/Tokenizer.h"
+
+// How long we collect write oprerations
+// before they are flushed to the database
+// In milliseconds.
+#define FLUSHING_INTERVAL_MS 5000
+
+// Write Ahead Log's maximum size is 512KB
+#define MAX_WAL_SIZE_BYTES 512 * 1024
+
+// Current version of the database schema
+#define CURRENT_SCHEMA_VERSION 1
+
+namespace mozilla {
+namespace dom {
+
+namespace { // anon
+
+// This is only a compatibility code for schema version 0. Returns the 'scope' key
+// in the schema version 0 format for the scope column.
+nsCString
+Scheme0Scope(DOMStorageCacheBridge* aCache)
+{
+ nsCString result;
+
+ nsCString suffix = aCache->OriginSuffix();
+
+ PrincipalOriginAttributes oa;
+ if (!suffix.IsEmpty()) {
+ DebugOnly<bool> success = oa.PopulateFromSuffix(suffix);
+ MOZ_ASSERT(success);
+ }
+
+ if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID || oa.mInIsolatedMozBrowser) {
+ result.AppendInt(oa.mAppId);
+ result.Append(':');
+ result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
+ result.Append(':');
+ }
+
+ // If there is more than just appid and/or inbrowser stored in origin
+ // attributes, put it to the schema 0 scope as well. We must do that
+ // to keep the scope column unique (same resolution as schema 1 has
+ // with originAttributes and originKey columns) so that switch between
+ // schema 1 and 0 always works in both ways.
+ nsAutoCString remaining;
+ oa.mAppId = 0;
+ oa.mInIsolatedMozBrowser = false;
+ oa.CreateSuffix(remaining);
+ if (!remaining.IsEmpty()) {
+ MOZ_ASSERT(!suffix.IsEmpty());
+
+ if (result.IsEmpty()) {
+ // Must contain the old prefix, otherwise we won't search for the whole
+ // origin attributes suffix.
+ result.Append(NS_LITERAL_CSTRING("0:f:"));
+ }
+ // Append the whole origin attributes suffix despite we have already stored
+ // appid and inbrowser. We are only looking for it when the scope string
+ // starts with "$appid:$inbrowser:" (with whatever valid values).
+ //
+ // The OriginAttributes suffix is a string in a form like:
+ // "^addonId=101&userContextId=5" and it's ensured it always starts with '^'
+ // and never contains ':'. See OriginAttributes::CreateSuffix.
+ result.Append(suffix);
+ result.Append(':');
+ }
+
+ result.Append(aCache->OriginNoSuffix());
+
+ return result;
+}
+
+} // anon
+
+
+DOMStorageDBBridge::DOMStorageDBBridge()
+{
+}
+
+
+DOMStorageDBThread::DOMStorageDBThread()
+: mThread(nullptr)
+, mThreadObserver(new ThreadObserver())
+, mStopIOThread(false)
+, mWALModeEnabled(false)
+, mDBReady(false)
+, mStatus(NS_OK)
+, mWorkerStatements(mWorkerConnection)
+, mReaderStatements(mReaderConnection)
+, mDirtyEpoch(0)
+, mFlushImmediately(false)
+, mPriorityCounter(0)
+{
+}
+
+nsresult
+DOMStorageDBThread::Init()
+{
+ nsresult rv;
+
+ // Need to determine location on the main thread, since
+ // NS_GetSpecialDirectory access the atom table that can
+ // be accessed only on the main thread.
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mDatabaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure mozIStorageService init on the main thread first.
+ nsCOMPtr<mozIStorageService> service =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Need to keep the lock to avoid setting mThread later then
+ // the thread body executes.
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+
+ mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
+ PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
+ 262144);
+ if (!mThread) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::Shutdown()
+{
+ if (!mThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
+
+ {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+
+ // After we stop, no other operations can be accepted
+ mFlushImmediately = true;
+ mStopIOThread = true;
+ monitor.Notify();
+ }
+
+ PR_JoinThread(mThread);
+ mThread = nullptr;
+
+ return mStatus;
+}
+
+void
+DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::STORAGE);
+ if (!aForceSync && aCache->LoadedCount()) {
+ // Preload already started for this cache, just wait for it to finish.
+ // LoadWait will exit after LoadDone on the cache has been called.
+ SetHigherPriority();
+ aCache->LoadWait();
+ SetDefaultPriority();
+ return;
+ }
+
+ // Bypass sync load when an update is pending in the queue to write, we would
+ // get incosistent data in the cache. Also don't allow sync main-thread preload
+ // when DB open and init is still pending on the background thread.
+ if (mDBReady && mWALModeEnabled) {
+ bool pendingTasks;
+ {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ pendingTasks = mPendingTasks.IsOriginUpdatePending(aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
+ mPendingTasks.IsOriginClearPending(aCache->OriginSuffix(), aCache->OriginNoSuffix());
+ }
+
+ if (!pendingTasks) {
+ // WAL is enabled, thus do the load synchronously on the main thread.
+ DBOperation preload(DBOperation::opPreload, aCache);
+ preload.PerformAndFinalize(this);
+ return;
+ }
+ }
+
+ // Need to go asynchronously since WAL is not allowed or scheduled updates
+ // need to be flushed first.
+ // Schedule preload for this cache as the first operation.
+ nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
+
+ // LoadWait exits after LoadDone of the cache has been called.
+ if (NS_SUCCEEDED(rv)) {
+ aCache->LoadWait();
+ }
+}
+
+void
+DOMStorageDBThread::AsyncFlush()
+{
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ mFlushImmediately = true;
+ monitor.Notify();
+}
+
+bool
+DOMStorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin)
+{
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ return mOriginsHavingData.Contains(aOrigin);
+}
+
+void
+DOMStorageDBThread::GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
+{
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ for (auto iter = mOriginsHavingData.Iter(); !iter.Done(); iter.Next()) {
+ aOrigins->AppendElement(iter.Get()->GetKey());
+ }
+}
+
+nsresult
+DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
+{
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+
+ // Sentinel to don't forget to delete the operation when we exit early.
+ nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
+
+ if (NS_FAILED(mStatus)) {
+ MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
+ aOperation->Finalize(mStatus);
+ return mStatus;
+ }
+
+ if (mStopIOThread) {
+ // Thread use after shutdown demanded.
+ MOZ_ASSERT(false);
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ switch (aOperation->Type()) {
+ case DBOperation::opPreload:
+ case DBOperation::opPreloadUrgent:
+ if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
+ // If there is a pending update operation for the scope first do the flush
+ // before we preload the cache. This may happen in an extremely rare case
+ // when a child process throws away its cache before flush on the parent
+ // has finished. If we would preloaded the cache as a priority operation
+ // before the pending flush, we would have got an inconsistent cache content.
+ mFlushImmediately = true;
+ } else if (mPendingTasks.IsOriginClearPending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
+ // The scope is scheduled to be cleared, so just quickly load as empty.
+ // We need to do this to prevent load of the DB data before the scope has
+ // actually been cleared from the database. Preloads are processed
+ // immediately before update and clear operations on the database that
+ // are flushed periodically in batches.
+ MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
+ aOperation->Finalize(NS_OK);
+ return NS_OK;
+ }
+ MOZ_FALLTHROUGH;
+
+ case DBOperation::opGetUsage:
+ if (aOperation->Type() == DBOperation::opPreloadUrgent) {
+ SetHigherPriority(); // Dropped back after urgent preload execution
+ mPreloads.InsertElementAt(0, aOperation);
+ } else {
+ mPreloads.AppendElement(aOperation);
+ }
+
+ // DB operation adopted, don't delete it.
+ opScope.forget();
+
+ // Immediately start executing this.
+ monitor.Notify();
+ break;
+
+ default:
+ // Update operations are first collected, coalesced and then flushed
+ // after a short time.
+ mPendingTasks.Add(aOperation);
+
+ // DB operation adopted, don't delete it.
+ opScope.forget();
+
+ ScheduleFlush();
+ break;
+ }
+
+ return NS_OK;
+}
+
+void
+DOMStorageDBThread::SetHigherPriority()
+{
+ ++mPriorityCounter;
+ PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
+}
+
+void
+DOMStorageDBThread::SetDefaultPriority()
+{
+ if (--mPriorityCounter <= 0) {
+ PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
+ }
+}
+
+void
+DOMStorageDBThread::ThreadFunc(void* aArg)
+{
+ PR_SetCurrentThreadName("localStorage DB");
+ mozilla::IOInterposer::RegisterCurrentThread();
+
+ DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
+ thread->ThreadFunc();
+ mozilla::IOInterposer::UnregisterCurrentThread();
+}
+
+void
+DOMStorageDBThread::ThreadFunc()
+{
+ nsresult rv = InitDatabase();
+
+ MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
+
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ mStopIOThread = true;
+ return;
+ }
+
+ // Create an nsIThread for the current PRThread, so we can observe runnables
+ // dispatched to it.
+ nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+ nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
+ MOZ_ASSERT(threadInternal); // Should always succeed.
+ threadInternal->SetObserver(mThreadObserver);
+
+ while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
+ mPendingTasks.HasTasks() ||
+ mThreadObserver->HasPendingEvents())) {
+ // Process xpcom events first.
+ while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
+ mThreadObserver->ClearPendingEvents();
+ MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
+ bool processedEvent;
+ do {
+ rv = thread->ProcessNextEvent(false, &processedEvent);
+ } while (NS_SUCCEEDED(rv) && processedEvent);
+ }
+
+ if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
+ // Flush time is up or flush has been forced, do it now.
+ UnscheduleFlush();
+ if (mPendingTasks.Prepare()) {
+ {
+ MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
+ rv = mPendingTasks.Execute(this);
+ }
+
+ if (!mPendingTasks.Finalize(rv)) {
+ mStatus = rv;
+ NS_WARNING("localStorage DB access broken");
+ }
+ }
+ NotifyFlushCompletion();
+ } else if (MOZ_LIKELY(mPreloads.Length())) {
+ nsAutoPtr<DBOperation> op(mPreloads[0]);
+ mPreloads.RemoveElementAt(0);
+ {
+ MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
+ op->PerformAndFinalize(this);
+ }
+
+ if (op->Type() == DBOperation::opPreloadUrgent) {
+ SetDefaultPriority(); // urgent preload unscheduled
+ }
+ } else if (MOZ_UNLIKELY(!mStopIOThread)) {
+ lockMonitor.Wait(TimeUntilFlush());
+ }
+ } // thread loop
+
+ mStatus = ShutdownDatabase();
+
+ if (threadInternal) {
+ threadInternal->SetObserver(nullptr);
+ }
+}
+
+
+NS_IMPL_ISUPPORTS(DOMStorageDBThread::ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+ MonitorAutoLock lock(mMonitor);
+ mHasPendingEvents = true;
+ lock.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
+ bool mayWait)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
+ bool eventWasProcessed)
+{
+ return NS_OK;
+}
+
+
+extern void
+ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
+
+nsresult
+DOMStorageDBThread::OpenDatabaseConnection()
+{
+ nsresult rv;
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageService> service
+ = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // delete the db and try opening again
+ rv = mDatabaseFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::OpenAndUpdateDatabase()
+{
+ nsresult rv;
+
+ // Here we are on the worker thread. This opens the worker connection.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ rv = OpenDatabaseConnection();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = TryJournalMode();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::InitDatabase()
+{
+ nsresult rv;
+
+ // Here we are on the worker thread. This opens the worker connection.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ rv = OpenAndUpdateDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = DOMStorageDBUpdater::Update(mWorkerConnection);
+ if (NS_FAILED(rv)) {
+ // Update has failed, rather throw the database away and try
+ // opening and setting it up again.
+ rv = mWorkerConnection->Close();
+ mWorkerConnection = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDatabaseFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = OpenAndUpdateDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Create a read-only clone
+ (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
+ NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
+
+ // Database open and all initiation operation are done. Switching this flag
+ // to true allow main thread to read directly from the database.
+ // If we would allow this sooner, we would have opened a window where main thread
+ // read might operate on a totaly broken and incosistent database.
+ mDBReady = true;
+
+ // List scopes having any stored data
+ nsCOMPtr<mozIStorageStatement> stmt;
+ // Note: result of this select must match DOMStorageManager::CreateOrigin()
+ rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT DISTINCT originAttributes || ':' || originKey FROM webappsstore2"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scope(stmt);
+
+ bool exists;
+ while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
+ nsAutoCString foundOrigin;
+ rv = stmt->GetUTF8String(0, foundOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ mOriginsHavingData.PutEntry(foundOrigin);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::SetJournalMode(bool aIsWal)
+{
+ nsresult rv;
+
+ nsAutoCString stmtString(
+ MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
+ if (aIsWal) {
+ stmtString.AppendLiteral("wal");
+ } else {
+ stmtString.AppendLiteral("truncate");
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scope(stmt);
+
+ bool hasResult = false;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasResult) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString journalMode;
+ rv = stmt->GetUTF8String(0, journalMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
+ (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::TryJournalMode()
+{
+ nsresult rv;
+
+ rv = SetJournalMode(true);
+ if (NS_FAILED(rv)) {
+ mWALModeEnabled = false;
+
+ rv = SetJournalMode(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mWALModeEnabled = true;
+
+ rv = ConfigureWALBehavior();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::ConfigureWALBehavior()
+{
+ // Get the DB's page size
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
+ MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult = false;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
+
+ int32_t pageSize = 0;
+ rv = stmt->GetInt32(0, &pageSize);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
+
+ // Set the threshold for auto-checkpointing the WAL.
+ // We don't want giant logs slowing down reads & shutdown.
+ int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
+ nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
+ thresholdPragma.AppendInt(thresholdInPages);
+ rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the maximum WAL log size to reduce footprint on mobile (large empty
+ // WAL files will be truncated)
+ nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
+ // bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold
+ journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
+ rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBThread::ShutdownDatabase()
+{
+ // Has to be called on the worker thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsresult rv = mStatus;
+
+ mDBReady = false;
+
+ // Finalize the cached statements.
+ mReaderStatements.FinalizeStatements();
+ mWorkerStatements.FinalizeStatements();
+
+ if (mReaderConnection) {
+ // No need to sync access to mReaderConnection since the main thread
+ // is right now joining this thread, unable to execute any events.
+ mReaderConnection->Close();
+ mReaderConnection = nullptr;
+ }
+
+ if (mWorkerConnection) {
+ rv = mWorkerConnection->Close();
+ mWorkerConnection = nullptr;
+ }
+
+ return rv;
+}
+
+void
+DOMStorageDBThread::ScheduleFlush()
+{
+ if (mDirtyEpoch) {
+ return; // Already scheduled
+ }
+
+ mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
+
+ // Wake the monitor from indefinite sleep...
+ (mThreadObserver->GetMonitor()).Notify();
+}
+
+void
+DOMStorageDBThread::UnscheduleFlush()
+{
+ // We are just about to do the flush, drop flags
+ mFlushImmediately = false;
+ mDirtyEpoch = 0;
+}
+
+PRIntervalTime
+DOMStorageDBThread::TimeUntilFlush()
+{
+ if (mFlushImmediately) {
+ return 0; // Do it now regardless the timeout.
+ }
+
+ static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
+ "PR_INTERVAL_NO_TIMEOUT must be non-zero");
+
+ if (!mDirtyEpoch) {
+ return PR_INTERVAL_NO_TIMEOUT; // No pending task...
+ }
+
+ static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
+
+ PRIntervalTime now = PR_IntervalNow() | 1;
+ PRIntervalTime age = now - mDirtyEpoch;
+ if (age > kMaxAge) {
+ return 0; // It is time.
+ }
+
+ return kMaxAge - age; // Time left, this is used to sleep the monitor
+}
+
+void
+DOMStorageDBThread::NotifyFlushCompletion()
+{
+#ifdef DOM_STORAGE_TESTS
+ if (!NS_IsMainThread()) {
+ RefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event =
+ NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion);
+ NS_DispatchToMainThread(event);
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
+ }
+#endif
+}
+
+// Helper SQL function classes
+
+namespace {
+
+class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ explicit OriginAttrsPatternMatchSQLFunction(OriginAttributesPattern const& aPattern)
+ : mPattern(aPattern) {}
+
+private:
+ OriginAttrsPatternMatchSQLFunction() = delete;
+ ~OriginAttrsPatternMatchSQLFunction() {}
+
+ OriginAttributesPattern mPattern;
+};
+
+NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString suffix;
+ rv = aFunctionArguments->GetUTF8String(0, suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrincipalOriginAttributes oa;
+ bool success = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+ bool result = mPattern.Matches(oa);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsBool(result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace
+
+// DOMStorageDBThread::DBOperation
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ DOMStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue)
+: mType(aType)
+, mCache(aCache)
+, mKey(aKey)
+, mValue(aValue)
+{
+ MOZ_ASSERT(mType == opPreload ||
+ mType == opPreloadUrgent ||
+ mType == opAddItem ||
+ mType == opUpdateItem ||
+ mType == opRemoveItem ||
+ mType == opClear ||
+ mType == opClearAll);
+ MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ DOMStorageUsageBridge* aUsage)
+: mType(aType)
+, mUsage(aUsage)
+{
+ MOZ_ASSERT(mType == opGetUsage);
+ MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ const nsACString& aOriginNoSuffix)
+: mType(aType)
+, mCache(nullptr)
+, mOrigin(aOriginNoSuffix)
+{
+ MOZ_ASSERT(mType == opClearMatchingOrigin);
+ MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ const OriginAttributesPattern& aOriginNoSuffix)
+: mType(aType)
+, mCache(nullptr)
+, mOriginPattern(aOriginNoSuffix)
+{
+ MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
+ MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
+}
+
+DOMStorageDBThread::DBOperation::~DBOperation()
+{
+ MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation);
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::OriginNoSuffix() const
+{
+ if (mCache) {
+ return mCache->OriginNoSuffix();
+ }
+
+ return EmptyCString();
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::OriginSuffix() const
+{
+ if (mCache) {
+ return mCache->OriginSuffix();
+ }
+
+ return EmptyCString();
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::Origin() const
+{
+ if (mCache) {
+ return mCache->Origin();
+ }
+
+ return mOrigin;
+}
+
+const nsCString
+DOMStorageDBThread::DBOperation::Target() const
+{
+ switch (mType) {
+ case opAddItem:
+ case opUpdateItem:
+ case opRemoveItem:
+ return Origin() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
+
+ default:
+ return Origin();
+ }
+}
+
+void
+DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread)
+{
+ Finalize(Perform(aThread));
+}
+
+nsresult
+DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
+{
+ nsresult rv;
+
+ switch (mType) {
+ case opPreload:
+ case opPreloadUrgent:
+ {
+ // Already loaded?
+ if (mCache->Loaded()) {
+ break;
+ }
+
+ StatementCache* statements;
+ if (MOZ_UNLIKELY(NS_IsMainThread())) {
+ statements = &aThread->mReaderStatements;
+ } else {
+ statements = &aThread->mWorkerStatements;
+ }
+
+ // OFFSET is an optimization when we have to do a sync load
+ // and cache has already loaded some parts asynchronously.
+ // It skips keys we have already loaded.
+ nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
+ "SELECT key, value FROM webappsstore2 "
+ "WHERE originAttributes = :originAttributes AND originKey = :originKey "
+ "ORDER BY key LIMIT -1 OFFSET :offset");
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+ mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
+ static_cast<int32_t>(mCache->LoadedCount()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
+ nsAutoString key;
+ rv = stmt->GetString(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString value;
+ rv = stmt->GetString(1, value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCache->LoadItem(key, value)) {
+ break;
+ }
+ }
+ // The loop condition's call to ExecuteStep() may have terminated because
+ // !NS_SUCCEEDED(), we need an early return to cover that case. This also
+ // covers success cases as well, but that's inductively safe.
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ case opGetUsage:
+ {
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
+ "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"
+ );
+ NS_ENSURE_STATE(stmt);
+
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("usageOrigin"),
+ mUsage->OriginScope());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = stmt->ExecuteStep(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t usage = 0;
+ if (exists) {
+ rv = stmt->GetInt64(0, &usage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mUsage->LoadUsage(usage);
+ break;
+ }
+
+ case opAddItem:
+ case opUpdateItem:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "INSERT OR REPLACE INTO webappsstore2 (originAttributes, originKey, scope, key, value) "
+ "VALUES (:originAttributes, :originKey, :scope, :key, :value) "
+ );
+ NS_ENSURE_STATE(stmt);
+
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+ mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Filling the 'scope' column just for downgrade compatibility reasons
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+ Scheme0Scope(mCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
+ mKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
+ mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
+ aThread->mOriginsHavingData.PutEntry(Origin());
+ break;
+ }
+
+ case opRemoveItem:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "DELETE FROM webappsstore2 "
+ "WHERE originAttributes = :originAttributes AND originKey = :originKey "
+ "AND key = :key "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+ mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
+ mKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+
+ case opClear:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "DELETE FROM webappsstore2 "
+ "WHERE originAttributes = :originAttributes AND originKey = :originKey"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
+ mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
+ aThread->mOriginsHavingData.RemoveEntry(Origin());
+ break;
+ }
+
+ case opClearAll:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "DELETE FROM webappsstore2"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
+ aThread->mOriginsHavingData.Clear();
+ break;
+ }
+
+ case opClearMatchingOrigin:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "DELETE FROM webappsstore2"
+ " WHERE originKey GLOB :scope"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scope(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+ mOrigin + NS_LITERAL_CSTRING("*"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No need to selectively clear mOriginsHavingData here. That hashtable only
+ // prevents preload for scopes with no data. Leaving a false record in it has
+ // a negligible effect on performance.
+ break;
+ }
+
+ case opClearMatchingOriginAttributes:
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the pattern
+ nsCOMPtr<mozIStorageFunction> patternMatchFunction(
+ new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
+
+ rv = aThread->mWorkerConnection->CreateFunction(
+ NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"), 1, patternMatchFunction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
+ "DELETE FROM webappsstore2"
+ " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"
+ );
+
+ if (stmt) {
+ mozStorageStatementScoper scope(stmt);
+ rv = stmt->Execute();
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ // Always remove the function
+ aThread->mWorkerConnection->RemoveFunction(
+ NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No need to selectively clear mOriginsHavingData here. That hashtable only
+ // prevents preload for scopes with no data. Leaving a false record in it has
+ // a negligible effect on performance.
+ break;
+ }
+
+ default:
+ NS_ERROR("Unknown task type");
+ break;
+ }
+
+ return NS_OK;
+}
+
+void
+DOMStorageDBThread::DBOperation::Finalize(nsresult aRv)
+{
+ switch (mType) {
+ case opPreloadUrgent:
+ case opPreload:
+ if (NS_FAILED(aRv)) {
+ // When we are here, something failed when loading from the database.
+ // Notify that the storage is loaded to prevent deadlock of the main thread,
+ // even though it is actually empty or incomplete.
+ NS_WARNING("Failed to preload localStorage");
+ }
+
+ mCache->LoadDone(aRv);
+ break;
+
+ case opGetUsage:
+ if (NS_FAILED(aRv)) {
+ mUsage->LoadUsage(0);
+ }
+
+ break;
+
+ default:
+ if (NS_FAILED(aRv)) {
+ NS_WARNING("localStorage update/clear operation failed,"
+ " data may not persist or clean up");
+ }
+
+ break;
+ }
+}
+
+// DOMStorageDBThread::PendingOperations
+
+DOMStorageDBThread::PendingOperations::PendingOperations()
+: mFlushFailureCount(0)
+{
+}
+
+bool
+DOMStorageDBThread::PendingOperations::HasTasks() const
+{
+ return !!mUpdates.Count() || !!mClears.Count();
+}
+
+namespace {
+
+bool OriginPatternMatches(const nsACString& aOriginSuffix, const OriginAttributesPattern& aPattern)
+{
+ PrincipalOriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
+ MOZ_ASSERT(rv);
+ return aPattern.Matches(oa);
+}
+
+} // namespace
+
+bool
+DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
+ DBOperation::OperationType aPendingType,
+ DBOperation::OperationType aNewType)
+{
+ if (aNewOp->Type() != aNewType) {
+ return false;
+ }
+
+ DOMStorageDBThread::DBOperation* pendingTask;
+ if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
+ return false;
+ }
+
+ if (pendingTask->Type() != aPendingType) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation)
+{
+ // Optimize: when a key to remove has never been written to disk
+ // just bypass this operation. A key is new when an operation scheduled
+ // to write it to the database is of type opAddItem.
+ if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) {
+ mUpdates.Remove(aOperation->Target());
+ delete aOperation;
+ return;
+ }
+
+ // Optimize: when changing a key that is new and has never been
+ // written to disk, keep type of the operation to store it at opAddItem.
+ // This allows optimization to just forget adding a new key when
+ // it is removed from the storage before flush.
+ if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) {
+ aOperation->mType = DBOperation::opAddItem;
+ }
+
+ // Optimize: to prevent lose of remove operation on a key when doing
+ // remove/set/remove on a previously existing key we have to change
+ // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
+ // pending for the key.
+ if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) {
+ aOperation->mType = DBOperation::opUpdateItem;
+ }
+
+ switch (aOperation->Type())
+ {
+ // Operations on single keys
+
+ case DBOperation::opAddItem:
+ case DBOperation::opUpdateItem:
+ case DBOperation::opRemoveItem:
+ // Override any existing operation for the target (=scope+key).
+ mUpdates.Put(aOperation->Target(), aOperation);
+ break;
+
+ // Clear operations
+
+ case DBOperation::opClear:
+ case DBOperation::opClearMatchingOrigin:
+ case DBOperation::opClearMatchingOriginAttributes:
+ // Drop all update (insert/remove) operations for equivavelent or matching scope.
+ // We do this as an optimization as well as a must based on the logic,
+ // if we would not delete the update tasks, changes would have been stored
+ // to the database after clear operations have been executed.
+ for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DBOperation>& pendingTask = iter.Data();
+
+ if (aOperation->Type() == DBOperation::opClear &&
+ (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() ||
+ pendingTask->OriginSuffix() != aOperation->OriginSuffix())) {
+ continue;
+ }
+
+ if (aOperation->Type() == DBOperation::opClearMatchingOrigin &&
+ !StringBeginsWith(pendingTask->OriginNoSuffix(), aOperation->Origin())) {
+ continue;
+ }
+
+ if (aOperation->Type() == DBOperation::opClearMatchingOriginAttributes &&
+ !OriginPatternMatches(pendingTask->OriginSuffix(), aOperation->OriginPattern())) {
+ continue;
+ }
+
+ iter.Remove();
+ }
+
+ mClears.Put(aOperation->Target(), aOperation);
+ break;
+
+ case DBOperation::opClearAll:
+ // Drop simply everything, this is a super-operation.
+ mUpdates.Clear();
+ mClears.Clear();
+ mClears.Put(aOperation->Target(), aOperation);
+ break;
+
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+}
+
+bool
+DOMStorageDBThread::PendingOperations::Prepare()
+{
+ // Called under the lock
+
+ // First collect clear operations and then updates, we can
+ // do this since whenever a clear operation for a scope is
+ // scheduled, we drop all updates matching that scope. So,
+ // all scope-related update operations we have here now were
+ // scheduled after the clear operations.
+ for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
+ mExecList.AppendElement(iter.Data().forget());
+ }
+ mClears.Clear();
+
+ for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
+ mExecList.AppendElement(iter.Data().forget());
+ }
+ mUpdates.Clear();
+
+ return !!mExecList.Length();
+}
+
+nsresult
+DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread)
+{
+ // Called outside the lock
+
+ mozStorageTransaction transaction(aThread->mWorkerConnection, false);
+
+ nsresult rv;
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ DOMStorageDBThread::DBOperation* task = mExecList[i];
+ rv = task->Perform(aThread);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ rv = transaction.Commit();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
+{
+ // Called under the lock
+
+ // The list is kept on a failure to retry it
+ if (NS_FAILED(aRv)) {
+ // XXX Followup: we may try to reopen the database and flush these
+ // pending tasks, however testing showed that even though I/O is actually
+ // broken some amount of operations is left in sqlite+system buffers and
+ // seems like successfully flushed to disk.
+ // Tested by removing a flash card and disconnecting from network while
+ // using a network drive on Windows system.
+ NS_WARNING("Flush operation on localStorage database failed");
+
+ ++mFlushFailureCount;
+
+ return mFlushFailureCount >= 5;
+ }
+
+ mFlushFailureCount = 0;
+ mExecList.Clear();
+ return true;
+}
+
+namespace {
+
+bool
+FindPendingClearForOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ DOMStorageDBThread::DBOperation* aPendingOperation)
+{
+ if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
+ aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
+ aOriginSuffix == aPendingOperation->OriginSuffix()) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOrigin &&
+ StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
+ OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+bool
+DOMStorageDBThread::PendingOperations::IsOriginClearPending(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix) const
+{
+ // Called under the lock
+
+ for (auto iter = mClears.ConstIter(); !iter.Done(); iter.Next()) {
+ if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace {
+
+bool
+FindPendingUpdateForOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ DOMStorageDBThread::DBOperation* aPendingOperation)
+{
+ if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
+ aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
+ aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
+ aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
+ aOriginSuffix == aPendingOperation->OriginSuffix()) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+bool
+DOMStorageDBThread::PendingOperations::IsOriginUpdatePending(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix) const
+{
+ // Called under the lock
+
+ for (auto iter = mUpdates.ConstIter(); !iter.Done(); iter.Next()) {
+ if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageDBThread.h b/dom/storage/DOMStorageDBThread.h
new file mode 100644
index 000000000..0efaebaa3
--- /dev/null
+++ b/dom/storage/DOMStorageDBThread.h
@@ -0,0 +1,407 @@
+/* -*- 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 DOMStorageDBThread_h___
+#define DOMStorageDBThread_h___
+
+#include "prthread.h"
+#include "prinrval.h"
+#include "nsTArray.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/storage/StatementCache.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIFile.h"
+#include "nsIThreadInternal.h"
+
+class mozIStorageConnection;
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageCacheBridge;
+class DOMStorageUsageBridge;
+class DOMStorageUsage;
+
+typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
+
+// Interface used by the cache to post operations to the asynchronous
+// database thread or process.
+class DOMStorageDBBridge
+{
+public:
+ DOMStorageDBBridge();
+ virtual ~DOMStorageDBBridge() {}
+
+ // Ensures the database engine is started
+ virtual nsresult Init() = 0;
+
+ // Releases the database and disallows its usage
+ virtual nsresult Shutdown() = 0;
+
+ // Asynchronously fills the cache with data from the database for first use.
+ // When |aPriority| is true, the preload operation is scheduled as the first one.
+ // This method is responsible to keep hard reference to the cache for the time of
+ // the preload or, when preload cannot be performed, call LoadDone() immediately.
+ virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false) = 0;
+
+ // Asynchronously fill the |usage| object with actual usage of data by its scope.
+ // The scope is eTLD+1 tops, never deeper subdomains.
+ virtual void AsyncGetUsage(DOMStorageUsageBridge* aUsage) = 0;
+
+ // Synchronously fills the cache, when |aForceSync| is false and cache already got some
+ // data before, the method waits for the running preload to finish
+ virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync = false) = 0;
+
+ // Called when an existing key is modified in the storage, schedules update to the database
+ virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0;
+
+ // Called when an existing key is modified in the storage, schedules update to the database
+ virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue) = 0;
+
+ // Called when an item is removed from the storage, schedules delete of the key
+ virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey) = 0;
+
+ // Called when the whole storage is cleared by the DOM API, schedules delete of the scope
+ virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache) = 0;
+
+ // Called when chrome deletes e.g. cookies, schedules delete of the whole database
+ virtual void AsyncClearAll() = 0;
+
+ // Called when only a domain and its subdomains is about to clear
+ virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) = 0;
+
+ // Called when data matching an origin pattern have to be cleared
+ virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern) = 0;
+
+ // Forces scheduled DB operations to be early flushed to the disk
+ virtual void AsyncFlush() = 0;
+
+ // Check whether the scope has any data stored on disk and is thus allowed to preload
+ virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix) = 0;
+
+ // Get the complete list of scopes having data
+ virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins) = 0;
+};
+
+// The implementation of the the database engine, this directly works
+// with the sqlite or any other db API we are based on
+// This class is resposible for collecting and processing asynchronous
+// DB operations over caches (DOMStorageCache) communicating though
+// DOMStorageCacheBridge interface class
+class DOMStorageDBThread final : public DOMStorageDBBridge
+{
+public:
+ class PendingOperations;
+
+ // Representation of a singe database task, like adding and removing keys,
+ // (pre)loading the whole origin data, cleaning.
+ class DBOperation
+ {
+ public:
+ typedef enum {
+ // Only operation that reads data from the database
+ opPreload,
+ // The same as opPreload, just executed with highest priority
+ opPreloadUrgent,
+
+ // Load usage of a scope
+ opGetUsage,
+
+ // Operations invoked by the DOM content API
+ opAddItem,
+ opUpdateItem,
+ opRemoveItem,
+ // Clears a specific single origin data
+ opClear,
+
+ // Operations invoked by chrome
+
+ // Clear all the data stored in the database, for all scopes, no exceptions
+ opClearAll,
+ // Clear data under a domain and all its subdomains regardless OriginAttributes value
+ opClearMatchingOrigin,
+ // Clear all data matching an OriginAttributesPattern regardless a domain
+ opClearMatchingOriginAttributes,
+ } OperationType;
+
+ explicit DBOperation(const OperationType aType,
+ DOMStorageCacheBridge* aCache = nullptr,
+ const nsAString& aKey = EmptyString(),
+ const nsAString& aValue = EmptyString());
+ DBOperation(const OperationType aType,
+ DOMStorageUsageBridge* aUsage);
+ DBOperation(const OperationType aType,
+ const nsACString& aOriginNoSuffix);
+ DBOperation(const OperationType aType,
+ const OriginAttributesPattern& aOriginNoSuffix);
+ ~DBOperation();
+
+ // Executes the operation, doesn't necessarity have to be called on the I/O thread
+ void PerformAndFinalize(DOMStorageDBThread* aThread);
+
+ // Finalize the operation, i.e. do any internal cleanup and finish calls
+ void Finalize(nsresult aRv);
+
+ // The operation type
+ OperationType Type() const { return mType; }
+
+ // The origin in the database usage format (reversed)
+ const nsCString OriginNoSuffix() const;
+
+ // The origin attributes suffix
+ const nsCString OriginSuffix() const;
+
+ // |origin suffix + origin key| the operation is working with
+ // or a scope pattern to delete with simple SQL's "LIKE %" from the database.
+ const nsCString Origin() const;
+
+ // |origin suffix + origin key + key| the operation is working with
+ const nsCString Target() const;
+
+ // Pattern to delete matching data with this op
+ const OriginAttributesPattern& OriginPattern() const { return mOriginPattern; }
+
+ private:
+ // The operation implementation body
+ nsresult Perform(DOMStorageDBThread* aThread);
+
+ friend class PendingOperations;
+ OperationType mType;
+ RefPtr<DOMStorageCacheBridge> mCache;
+ RefPtr<DOMStorageUsageBridge> mUsage;
+ nsString const mKey;
+ nsString const mValue;
+ nsCString const mOrigin;
+ OriginAttributesPattern const mOriginPattern;
+ };
+
+ // Encapsulation of collective and coalescing logic for all pending operations
+ // except preloads that are handled separately as priority operations
+ class PendingOperations {
+ public:
+ PendingOperations();
+
+ // Method responsible for coalescing redundant update operations with the same
+ // |Target()| or clear operations with the same or matching |Origin()|
+ void Add(DBOperation* aOperation);
+
+ // True when there are some scheduled operations to flush on disk
+ bool HasTasks() const;
+
+ // Moves collected operations to a local flat list to allow execution of the operation
+ // list out of the thread lock
+ bool Prepare();
+
+ // Executes the previously |Prepared()'ed| list of operations, retuns result, but doesn't
+ // handle it in any way in case of a failure
+ nsresult Execute(DOMStorageDBThread* aThread);
+
+ // Finalizes the pending operation list, returns false when too many operations failed
+ // to flush what indicates a long standing issue with the database access.
+ bool Finalize(nsresult aRv);
+
+ // true when a clear that deletes the given origin attr pattern and/or origin key
+ // is among the pending operations; when a preload for that scope is being scheduled,
+ // it must be finished right away
+ bool IsOriginClearPending(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const;
+
+ // Checks whether there is a pending update operation for this scope.
+ bool IsOriginUpdatePending(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const;
+
+ private:
+ // Returns true iff new operation is of type newType and there is a pending
+ // operation of type pendingType for the same key (target).
+ bool CheckForCoalesceOpportunity(DBOperation* aNewOp,
+ DBOperation::OperationType aPendingType,
+ DBOperation::OperationType aNewType);
+
+ // List of all clearing operations, executed first
+ nsClassHashtable<nsCStringHashKey, DBOperation> mClears;
+
+ // List of all update/insert operations, executed as second
+ nsClassHashtable<nsCStringHashKey, DBOperation> mUpdates;
+
+ // Collection of all tasks, valid only between Prepare() and Execute()
+ nsTArray<nsAutoPtr<DBOperation> > mExecList;
+
+ // Number of failing flush attempts
+ uint32_t mFlushFailureCount;
+ };
+
+ class ThreadObserver final : public nsIThreadObserver
+ {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ ThreadObserver()
+ : mHasPendingEvents(false)
+ , mMonitor("DOMStorageThreadMonitor")
+ {
+ }
+
+ bool HasPendingEvents() {
+ mMonitor.AssertCurrentThreadOwns();
+ return mHasPendingEvents;
+ }
+ void ClearPendingEvents() {
+ mMonitor.AssertCurrentThreadOwns();
+ mHasPendingEvents = false;
+ }
+ Monitor& GetMonitor() { return mMonitor; }
+
+ private:
+ virtual ~ThreadObserver() {}
+ bool mHasPendingEvents;
+ // The monitor we drive the thread with
+ Monitor mMonitor;
+ };
+
+public:
+ DOMStorageDBThread();
+ virtual ~DOMStorageDBThread() {}
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false)
+ { InsertDBOp(new DBOperation(aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload, aCache)); }
+
+ virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForce = false);
+
+ virtual void AsyncGetUsage(DOMStorageUsageBridge * aUsage)
+ { InsertDBOp(new DBOperation(DBOperation::opGetUsage, aUsage)); }
+
+ virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue)
+ { return InsertDBOp(new DBOperation(DBOperation::opAddItem, aCache, aKey, aValue)); }
+
+ virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue)
+ { return InsertDBOp(new DBOperation(DBOperation::opUpdateItem, aCache, aKey, aValue)); }
+
+ virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey)
+ { return InsertDBOp(new DBOperation(DBOperation::opRemoveItem, aCache, aKey)); }
+
+ virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache)
+ { return InsertDBOp(new DBOperation(DBOperation::opClear, aCache)); }
+
+ virtual void AsyncClearAll()
+ { InsertDBOp(new DBOperation(DBOperation::opClearAll)); }
+
+ virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
+ { InsertDBOp(new DBOperation(DBOperation::opClearMatchingOrigin, aOriginNoSuffix)); }
+
+ virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
+ { InsertDBOp(new DBOperation(DBOperation::opClearMatchingOriginAttributes, aPattern)); }
+
+ virtual void AsyncFlush();
+
+ virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
+ virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins);
+
+private:
+ nsCOMPtr<nsIFile> mDatabaseFile;
+ PRThread* mThread;
+
+ // Used to observe runnables dispatched to our thread and to monitor it.
+ RefPtr<ThreadObserver> mThreadObserver;
+
+ // Flag to stop, protected by the monitor returned by
+ // mThreadObserver->GetMonitor().
+ bool mStopIOThread;
+
+ // Whether WAL is enabled
+ bool mWALModeEnabled;
+
+ // Whether DB has already been open, avoid races between main thread reads
+ // and pending DB init in the background I/O thread
+ Atomic<bool, ReleaseAcquire> mDBReady;
+
+ // State of the database initiation
+ nsresult mStatus;
+
+ // List of origins (including origin attributes suffix) having data, for optimization purposes only
+ nsTHashtable<nsCStringHashKey> mOriginsHavingData;
+
+ // Connection used by the worker thread for all read and write ops
+ nsCOMPtr<mozIStorageConnection> mWorkerConnection;
+
+ // Connection used only on the main thread for sync read operations
+ nsCOMPtr<mozIStorageConnection> mReaderConnection;
+
+ StatementCache mWorkerStatements;
+ StatementCache mReaderStatements;
+
+ // Time the first pending operation has been added to the pending operations
+ // list
+ PRIntervalTime mDirtyEpoch;
+
+ // Flag to force immediate flush of all pending operations
+ bool mFlushImmediately;
+
+ // List of preloading operations, in chronological or priority order.
+ // Executed prioritly over pending update operations.
+ nsTArray<DBOperation*> mPreloads;
+
+ // Collector of pending update operations
+ PendingOperations mPendingTasks;
+
+ // Counter of calls for thread priority rising.
+ int32_t mPriorityCounter;
+
+ // Helper to direct an operation to one of the arrays above;
+ // also checks IsOriginClearPending for preloads
+ nsresult InsertDBOp(DBOperation* aOperation);
+
+ // Opens the database, first thing we do after start of the thread.
+ nsresult OpenDatabaseConnection();
+ nsresult OpenAndUpdateDatabase();
+ nsresult InitDatabase();
+ nsresult ShutdownDatabase();
+
+ // Tries to establish WAL mode
+ nsresult SetJournalMode(bool aIsWal);
+ nsresult TryJournalMode();
+
+ // Sets the threshold for auto-checkpointing the WAL.
+ nsresult ConfigureWALBehavior();
+
+ void SetHigherPriority();
+ void SetDefaultPriority();
+
+ // Ensures we flush pending tasks in some reasonble time
+ void ScheduleFlush();
+
+ // Called when flush of pending tasks is being executed
+ void UnscheduleFlush();
+
+ // This method is used for two purposes:
+ // 1. as a value passed to monitor.Wait() method
+ // 2. as in indicator that flush has to be performed
+ //
+ // Return:
+ // - PR_INTERVAL_NO_TIMEOUT when no pending tasks are scheduled
+ // - larger then zero when tasks have been scheduled, but it is
+ // still not time to perform the flush ; it is actual interval
+ // time to wait until the flush has to happen
+ // - 0 when it is time to do the flush
+ PRIntervalTime TimeUntilFlush();
+
+ // Notifies to the main thread that flush has completed
+ void NotifyFlushCompletion();
+
+ // Thread loop
+ static void ThreadFunc(void* aArg);
+ void ThreadFunc();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* DOMStorageDBThread_h___ */
diff --git a/dom/storage/DOMStorageDBUpdater.cpp b/dom/storage/DOMStorageDBUpdater.cpp
new file mode 100644
index 000000000..8e3f07d7c
--- /dev/null
+++ b/dom/storage/DOMStorageDBUpdater.cpp
@@ -0,0 +1,417 @@
+/* -*- 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/. */
+
+#include "DOMStorageManager.h"
+
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsVariant.h"
+#include "mozilla/Services.h"
+#include "mozilla/Tokenizer.h"
+
+// Current version of the database schema
+#define CURRENT_SCHEMA_VERSION 1
+
+namespace mozilla {
+namespace dom {
+
+extern void
+ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
+
+namespace {
+
+class nsReverseStringSQLFunction final : public mozIStorageFunction
+{
+ ~nsReverseStringSQLFunction() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+nsReverseStringSQLFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString stringToReverse;
+ rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString result;
+ ReverseString(stringToReverse, result);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsAUTF8String(result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+// "scope" to "origin attributes suffix" and "origin key" convertor
+
+class ExtractOriginData : protected mozilla::Tokenizer
+{
+public:
+ ExtractOriginData(const nsACString& scope, nsACString& suffix, nsACString& origin)
+ : mozilla::Tokenizer(scope)
+ {
+ using mozilla::OriginAttributes;
+
+ // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
+ // we don't find it, the scope is our new origin key and suffix
+ // is empty.
+ suffix.Truncate();
+ origin.Assign(scope);
+
+ // Bail out if it isn't appId.
+ uint32_t appId;
+ if (!ReadInteger(&appId)) {
+ return;
+ }
+
+ // Should be followed by a colon.
+ if (!CheckChar(':')) {
+ return;
+ }
+
+ // Bail out if it isn't 'isolatedBrowserFlag'.
+ nsDependentCSubstring isolatedBrowserFlag;
+ if (!ReadWord(isolatedBrowserFlag)) {
+ return;
+ }
+
+ bool inIsolatedMozBrowser = isolatedBrowserFlag == "t";
+ bool notInIsolatedBrowser = isolatedBrowserFlag == "f";
+ if (!inIsolatedMozBrowser && !notInIsolatedBrowser) {
+ return;
+ }
+
+ // Should be followed by a colon.
+ if (!CheckChar(':')) {
+ return;
+ }
+
+ // OK, we have found appId and inIsolatedMozBrowser flag, create the suffix
+ // from it and take the rest as the origin key.
+
+ // If the profile went through schema 1 -> schema 0 -> schema 1 switching
+ // we may have stored the full attributes origin suffix when there were
+ // more than just appId and inIsolatedMozBrowser set on storage principal's
+ // OriginAttributes.
+ //
+ // To preserve full uniqueness we store this suffix to the scope key.
+ // Schema 0 code will just ignore it while keeping the scoping unique.
+ //
+ // The whole scope string is in one of the following forms (when we are here):
+ //
+ // "1001:f:^appId=1001&inBrowser=false&addonId=101:gro.allizom.rxd.:https:443"
+ // "1001:f:gro.allizom.rxd.:https:443"
+ // |
+ // +- the parser cursor position.
+ //
+ // If there is '^', the full origin attributes suffix follows. We search
+ // for ':' since it is the delimiter used in the scope string and is never
+ // contained in the origin attributes suffix. Remaining string after
+ // the comma is the reversed-domain+schema+port tuple.
+ Record();
+ if (CheckChar('^')) {
+ Token t;
+ while (Next(t)) {
+ if (t.Equals(Token::Char(':'))) {
+ Claim(suffix);
+ break;
+ }
+ }
+ } else {
+ PrincipalOriginAttributes attrs(appId, inIsolatedMozBrowser);
+ attrs.CreateSuffix(suffix);
+ }
+
+ // Consume the rest of the input as "origin".
+ origin.Assign(Substring(mCursor, mEnd));
+ }
+};
+
+class GetOriginParticular final : public mozIStorageFunction
+{
+public:
+ enum EParticular {
+ ORIGIN_ATTRIBUTES_SUFFIX,
+ ORIGIN_KEY
+ };
+
+ explicit GetOriginParticular(EParticular aParticular)
+ : mParticular(aParticular) {}
+
+private:
+ GetOriginParticular() = delete;
+ ~GetOriginParticular() {}
+
+ EParticular mParticular;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(GetOriginParticular, mozIStorageFunction)
+
+NS_IMETHODIMP
+GetOriginParticular::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
+{
+ nsresult rv;
+
+ nsAutoCString scope;
+ rv = aFunctionArguments->GetUTF8String(0, scope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix, origin;
+ ExtractOriginData(scope, suffix, origin);
+
+ nsCOMPtr<nsIWritableVariant> outVar(new nsVariant());
+
+ switch (mParticular) {
+ case EParticular::ORIGIN_ATTRIBUTES_SUFFIX:
+ rv = outVar->SetAsAUTF8String(suffix);
+ break;
+ case EParticular::ORIGIN_KEY:
+ rv = outVar->SetAsAUTF8String(origin);
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace
+
+namespace DOMStorageDBUpdater {
+
+nsresult CreateSchema1Tables(mozIStorageConnection *aWorkerConnection)
+{
+ nsresult rv;
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE IF NOT EXISTS webappsstore2 ("
+ "originAttributes TEXT, "
+ "originKey TEXT, "
+ "scope TEXT, " // Only for schema0 downgrade compatibility
+ "key TEXT, "
+ "value TEXT)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
+ " ON webappsstore2(originAttributes, originKey, key)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult Update(mozIStorageConnection *aWorkerConnection)
+{
+ nsresult rv;
+
+ mozStorageTransaction transaction(aWorkerConnection, false);
+
+ bool doVacuum = false;
+
+ int32_t schemaVer;
+ rv = aWorkerConnection->GetSchemaVersion(&schemaVer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // downgrade (v0) -> upgrade (v1+) specific code
+ if (schemaVer >= 1) {
+ bool schema0IndexExists;
+ rv = aWorkerConnection->IndexExists(NS_LITERAL_CSTRING("scope_key_index"),
+ &schema0IndexExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (schema0IndexExists) {
+ // If this index exists, the database (already updated to schema >1)
+ // has been run again on schema 0 code. That recreated that index
+ // and might store some new rows while updating only the 'scope' column.
+ // For such added rows we must fill the new 'origin*' columns correctly
+ // otherwise there would be a data loss. The safest way to do it is to
+ // simply run the whole update to schema 1 again.
+ schemaVer = 0;
+ }
+ }
+
+ switch (schemaVer) {
+ case 0: {
+ bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
+
+ rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore2"),
+ &webappsstore2Exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
+ &webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
+ &moz_webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!webappsstore2Exists && !webappsstoreExists && !moz_webappsstoreExists) {
+ // The database is empty, this is the first start. Just create the schema table
+ // and break to the next version to update to, i.e. bypass update from the old version.
+
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+
+ doVacuum = true;
+
+ // Ensure Gecko 1.9.1 storage table
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE IF NOT EXISTS webappsstore2 ("
+ "scope TEXT, "
+ "key TEXT, "
+ "value TEXT, "
+ "secure INTEGER, "
+ "owner TEXT)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
+ " ON webappsstore2(scope, key)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
+ NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
+ // to actual webappsstore2 table and drop the obsolete table. First process
+ // this newer table upgrade to priority potential duplicates from older
+ // storage table.
+ if (webappsstoreExists) {
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
+ "webappsstore2(scope, key, value, secure, owner) "
+ "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
+ "FROM webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
+ // to actual webappsstore2 table and drop the obsolete table. Potential
+ // duplicates will be ignored.
+ if (moz_webappsstoreExists) {
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
+ "webappsstore2(scope, key, value, secure, owner) "
+ "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
+ "FROM moz_webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("REVERSESTRING"));
+
+ // Update the scoping to match the new implememntation: split to oa suffix and origin key
+ // First rename the old table, we want to remove some columns no longer needed,
+ // but even before that drop all indexes from it (CREATE IF NOT EXISTS for index on the
+ // new table would falsely find the index!)
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> oaSuffixFunc(
+ new GetOriginParticular(GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
+ rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"), 1, oaSuffixFunc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> originKeyFunc(
+ new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
+ rv = aWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"), 1, originKeyFunc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Here we ensure this schema tables when we are updating.
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO "
+ "webappsstore2 (originAttributes, originKey, scope, key, value) "
+ "SELECT GET_ORIGIN_SUFFIX(scope), GET_ORIGIN_KEY(scope), scope, key, value "
+ "FROM webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DROP TABLE webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_SUFFIX"));
+ aWorkerConnection->RemoveFunction(NS_LITERAL_CSTRING("GET_ORIGIN_KEY"));
+
+ rv = aWorkerConnection->SetSchemaVersion(1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_FALLTHROUGH;
+ }
+ case CURRENT_SCHEMA_VERSION:
+ // Ensure the tables and indexes are up. This is mostly a no-op
+ // in common scenarios.
+ rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Nothing more to do here, this is the current schema version
+ break;
+
+ default:
+ MOZ_ASSERT(false);
+ break;
+ } // switch
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (doVacuum) {
+ // In some cases this can make the disk file of the database significantly smaller.
+ // VACUUM cannot be executed inside a transaction.
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("VACUUM"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+} // namespace DOMStorageDBUpdater
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageDBUpdater.h b/dom/storage/DOMStorageDBUpdater.h
new file mode 100644
index 000000000..6ab4622bc
--- /dev/null
+++ b/dom/storage/DOMStorageDBUpdater.h
@@ -0,0 +1,22 @@
+/* -*- 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 DOMStorageDBUpdater_h___
+#define DOMStorageDBUpdater_h___
+
+namespace mozilla {
+namespace dom {
+
+namespace DOMStorageDBUpdater {
+
+nsresult Update(mozIStorageConnection *aWorkerConnection);
+
+} // DOMStorageDBUpdater
+
+} // dom
+} // mozilla
+
+#endif
diff --git a/dom/storage/DOMStorageIPC.cpp b/dom/storage/DOMStorageIPC.cpp
new file mode 100644
index 000000000..a8cd745f1
--- /dev/null
+++ b/dom/storage/DOMStorageIPC.cpp
@@ -0,0 +1,770 @@
+/* -*- 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/. */
+
+#include "DOMStorageIPC.h"
+
+#include "DOMStorageManager.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Unused.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+// ----------------------------------------------------------------------------
+// Child
+// ----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DOMStorageDBChild)
+
+NS_IMETHODIMP_(MozExternalRefCountType) DOMStorageDBChild::Release(void)
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "DOMStorageDBChild");
+ if (count == 1 && mIPCOpen) {
+ Send__delete__(this);
+ return 0;
+ }
+ if (count == 0) {
+ mRefCnt = 1;
+ delete this;
+ return 0;
+ }
+ return count;
+}
+
+void
+DOMStorageDBChild::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+DOMStorageDBChild::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+DOMStorageDBChild::DOMStorageDBChild(DOMLocalStorageManager* aManager)
+ : mManager(aManager)
+ , mStatus(NS_OK)
+ , mIPCOpen(false)
+{
+}
+
+DOMStorageDBChild::~DOMStorageDBChild()
+{
+}
+
+nsTHashtable<nsCStringHashKey>&
+DOMStorageDBChild::OriginsHavingData()
+{
+ if (!mOriginsHavingData) {
+ mOriginsHavingData = new nsTHashtable<nsCStringHashKey>;
+ }
+
+ return *mOriginsHavingData;
+}
+
+nsresult
+DOMStorageDBChild::Init()
+{
+ ContentChild* child = ContentChild::GetSingleton();
+ AddIPDLReference();
+ child->SendPStorageConstructor(this);
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::Shutdown()
+{
+ // There is nothing to do here, IPC will release automatically and
+ // the actual thread running on the parent process will also stop
+ // automatically in profile-before-change topic observer.
+ return NS_OK;
+}
+
+void
+DOMStorageDBChild::AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority)
+{
+ if (mIPCOpen) {
+ // Adding ref to cache for the time of preload. This ensures a reference to
+ // to the cache and that all keys will load into this cache object.
+ mLoadingCaches.PutEntry(aCache);
+ SendAsyncPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aPriority);
+ } else {
+ // No IPC, no love. But the LoadDone call is expected.
+ aCache->LoadDone(NS_ERROR_UNEXPECTED);
+ }
+}
+
+void
+DOMStorageDBChild::AsyncGetUsage(DOMStorageUsageBridge* aUsage)
+{
+ if (mIPCOpen) {
+ SendAsyncGetUsage(aUsage->OriginScope());
+ }
+}
+
+void
+DOMStorageDBChild::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
+{
+ if (NS_FAILED(mStatus)) {
+ aCache->LoadDone(mStatus);
+ return;
+ }
+
+ if (!mIPCOpen) {
+ aCache->LoadDone(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ // There is no way to put the child process to a wait state to receive all
+ // incoming async responses from the parent, hence we have to do a sync preload
+ // instead. We are smart though, we only demand keys that are left to load in
+ // case the async preload has already loaded some keys.
+ InfallibleTArray<nsString> keys, values;
+ nsresult rv;
+ SendPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aCache->LoadedCount(),
+ &keys, &values, &rv);
+
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ aCache->LoadItem(keys[i], values[i]);
+ }
+
+ aCache->LoadDone(rv);
+}
+
+nsresult
+DOMStorageDBChild::AsyncAddItem(DOMStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue)
+{
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncAddItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey), nsString(aValue));
+ OriginsHavingData().PutEntry(aCache->Origin());
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncUpdateItem(DOMStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue)
+{
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncUpdateItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey), nsString(aValue));
+ OriginsHavingData().PutEntry(aCache->Origin());
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncRemoveItem(DOMStorageCacheBridge* aCache,
+ const nsAString& aKey)
+{
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncRemoveItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey));
+ return NS_OK;
+}
+
+nsresult
+DOMStorageDBChild::AsyncClear(DOMStorageCacheBridge* aCache)
+{
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncClear(aCache->OriginSuffix(), aCache->OriginNoSuffix());
+ OriginsHavingData().RemoveEntry(aCache->Origin());
+ return NS_OK;
+}
+
+bool
+DOMStorageDBChild::ShouldPreloadOrigin(const nsACString& aOrigin)
+{
+ // Return true if we didn't receive the origins list yet.
+ // I tend to rather preserve a bit of early-after-start performance
+ // than a bit of memory here.
+ return !mOriginsHavingData || mOriginsHavingData->Contains(aOrigin);
+}
+
+bool
+DOMStorageDBChild::RecvObserve(const nsCString& aTopic,
+ const nsString& aOriginAttributesPattern,
+ const nsCString& aOriginScope)
+{
+ DOMStorageObserver::Self()->Notify(
+ aTopic.get(), aOriginAttributesPattern, aOriginScope);
+ return true;
+}
+
+bool
+DOMStorageDBChild::RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins)
+{
+ for (uint32_t i = 0; i < aOrigins.Length(); ++i) {
+ OriginsHavingData().PutEntry(aOrigins[i]);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadItem(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsString& aKey,
+ const nsString& aValue)
+{
+ DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
+ if (aCache) {
+ aCache->LoadItem(aKey, aValue);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadDone(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsresult& aRv)
+{
+ DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
+ if (aCache) {
+ aCache->LoadDone(aRv);
+
+ // Just drop reference to this cache now since the load is done.
+ mLoadingCaches.RemoveEntry(static_cast<DOMStorageCacheBridge*>(aCache));
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBChild::RecvLoadUsage(const nsCString& aOriginNoSuffix, const int64_t& aUsage)
+{
+ RefPtr<DOMStorageUsageBridge> scopeUsage = mManager->GetOriginUsage(aOriginNoSuffix);
+ scopeUsage->LoadUsage(aUsage);
+ return true;
+}
+
+bool
+DOMStorageDBChild::RecvError(const nsresult& aRv)
+{
+ mStatus = aRv;
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// Parent
+// ----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(DOMStorageDBParent)
+NS_IMPL_RELEASE(DOMStorageDBParent)
+
+void
+DOMStorageDBParent::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void
+DOMStorageDBParent::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+namespace {
+
+class SendInitialChildDataRunnable : public Runnable
+{
+public:
+ explicit SendInitialChildDataRunnable(DOMStorageDBParent* aParent)
+ : mParent(aParent)
+ {}
+
+private:
+ NS_IMETHOD Run() override
+ {
+ if (!mParent->IPCOpen()) {
+ return NS_OK;
+ }
+
+ DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+ if (db) {
+ InfallibleTArray<nsCString> scopes;
+ db->GetOriginsHavingData(&scopes);
+ mozilla::Unused << mParent->SendOriginsHavingData(scopes);
+ }
+
+ // We need to check if the device is in a low disk space situation, so
+ // we can forbid in that case any write in localStorage.
+ nsCOMPtr<nsIDiskSpaceWatcher> diskSpaceWatcher =
+ do_GetService("@mozilla.org/toolkit/disk-space-watcher;1");
+ if (!diskSpaceWatcher) {
+ return NS_OK;
+ }
+
+ bool lowDiskSpace = false;
+ diskSpaceWatcher->GetIsDiskFull(&lowDiskSpace);
+
+ if (lowDiskSpace) {
+ mozilla::Unused << mParent->SendObserve(
+ nsDependentCString("low-disk-space"), EmptyString(), EmptyCString());
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<DOMStorageDBParent> mParent;
+};
+
+} // namespace
+
+DOMStorageDBParent::DOMStorageDBParent()
+: mIPCOpen(false)
+{
+ DOMStorageObserver* observer = DOMStorageObserver::Self();
+ if (observer) {
+ observer->AddSink(this);
+ }
+
+ // We are always open by IPC only
+ AddIPDLReference();
+
+ // Cannot send directly from here since the channel
+ // is not completely built at this moment.
+ RefPtr<SendInitialChildDataRunnable> r =
+ new SendInitialChildDataRunnable(this);
+ NS_DispatchToCurrentThread(r);
+}
+
+DOMStorageDBParent::~DOMStorageDBParent()
+{
+ DOMStorageObserver* observer = DOMStorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+}
+
+DOMStorageDBParent::CacheParentBridge*
+DOMStorageDBParent::NewCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
+{
+ return new CacheParentBridge(this, aOriginSuffix, aOriginNoSuffix);
+}
+
+void
+DOMStorageDBParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+ // Implement me! Bug 1005169
+}
+
+bool
+DOMStorageDBParent::RecvAsyncPreload(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const bool& aPriority)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ db->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix), aPriority);
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aOriginNoSuffix)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ // The object releases it self in LoadUsage method
+ RefPtr<UsageParentBridge> usage = new UsageParentBridge(this, aOriginNoSuffix);
+ db->AsyncGetUsage(usage);
+ return true;
+}
+
+namespace {
+
+// We need another implementation of DOMStorageCacheBridge to do
+// synchronous IPC preload. This class just receives Load* notifications
+// and fills the returning arguments of RecvPreload with the database
+// values for us.
+class SyncLoadCacheHelper : public DOMStorageCacheBridge
+{
+public:
+ SyncLoadCacheHelper(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ uint32_t aAlreadyLoadedCount,
+ InfallibleTArray<nsString>* aKeys,
+ InfallibleTArray<nsString>* aValues,
+ nsresult* rv)
+ : mMonitor("DOM Storage SyncLoad IPC")
+ , mSuffix(aOriginSuffix)
+ , mOrigin(aOriginNoSuffix)
+ , mKeys(aKeys)
+ , mValues(aValues)
+ , mRv(rv)
+ , mLoaded(false)
+ , mLoadedCount(aAlreadyLoadedCount)
+ {
+ // Precaution
+ *mRv = NS_ERROR_UNEXPECTED;
+ }
+
+ virtual const nsCString Origin() const
+ {
+ return DOMStorageManager::CreateOrigin(mSuffix, mOrigin);
+ }
+ virtual const nsCString& OriginNoSuffix() const { return mOrigin; }
+ virtual const nsCString& OriginSuffix() const { return mSuffix; }
+ virtual bool Loaded() { return mLoaded; }
+ virtual uint32_t LoadedCount() { return mLoadedCount; }
+ virtual bool LoadItem(const nsAString& aKey, const nsString& aValue)
+ {
+ // Called on the aCache background thread
+ MOZ_ASSERT(!mLoaded);
+ if (mLoaded) {
+ return false;
+ }
+
+ ++mLoadedCount;
+ mKeys->AppendElement(aKey);
+ mValues->AppendElement(aValue);
+ return true;
+ }
+
+ virtual void LoadDone(nsresult aRv)
+ {
+ // Called on the aCache background thread
+ MonitorAutoLock monitor(mMonitor);
+ MOZ_ASSERT(!mLoaded && mRv);
+ mLoaded = true;
+ if (mRv) {
+ *mRv = aRv;
+ mRv = nullptr;
+ }
+ monitor.Notify();
+ }
+
+ virtual void LoadWait()
+ {
+ // Called on the main thread, exits after LoadDone() call
+ MonitorAutoLock monitor(mMonitor);
+ while (!mLoaded) {
+ monitor.Wait();
+ }
+ }
+
+private:
+ Monitor mMonitor;
+ nsCString mSuffix, mOrigin;
+ InfallibleTArray<nsString>* mKeys;
+ InfallibleTArray<nsString>* mValues;
+ nsresult* mRv;
+ bool mLoaded;
+ uint32_t mLoadedCount;
+};
+
+} // namespace
+
+bool
+DOMStorageDBParent::RecvPreload(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const uint32_t& aAlreadyLoadedCount,
+ InfallibleTArray<nsString>* aKeys,
+ InfallibleTArray<nsString>* aValues,
+ nsresult* aRv)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ RefPtr<SyncLoadCacheHelper> cache(
+ new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount, aKeys, aValues, aRv));
+
+ db->SyncPreload(cache, true);
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsString& aKey,
+ const nsString& aValue)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ nsresult rv = db->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsString& aKey,
+ const nsString& aValue)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ nsresult rv = db->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsString& aKey)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ nsresult rv = db->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncClear(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix)
+{
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (!db) {
+ return false;
+ }
+
+ nsresult rv = db->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return true;
+}
+
+bool
+DOMStorageDBParent::RecvAsyncFlush()
+{
+ DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+ if (!db) {
+ return false;
+ }
+
+ db->AsyncFlush();
+ return true;
+}
+
+// DOMStorageObserverSink
+
+nsresult
+DOMStorageDBParent::Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope)
+{
+ if (mIPCOpen) {
+ mozilla::Unused << SendObserve(nsDependentCString(aTopic),
+ nsString(aOriginAttributesPattern),
+ nsCString(aOriginScope));
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+// Results must be sent back on the main thread
+class LoadRunnable : public Runnable
+{
+public:
+ enum TaskType {
+ loadItem,
+ loadDone
+ };
+
+ LoadRunnable(DOMStorageDBParent* aParent,
+ TaskType aType,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey = EmptyString(),
+ const nsAString& aValue = EmptyString())
+ : mParent(aParent)
+ , mType(aType)
+ , mSuffix(aOriginSuffix)
+ , mOrigin(aOriginNoSuffix)
+ , mKey(aKey)
+ , mValue(aValue)
+ { }
+
+ LoadRunnable(DOMStorageDBParent* aParent,
+ TaskType aType,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ nsresult aRv)
+ : mParent(aParent)
+ , mType(aType)
+ , mSuffix(aOriginSuffix)
+ , mOrigin(aOriginNoSuffix)
+ , mRv(aRv)
+ { }
+
+private:
+ RefPtr<DOMStorageDBParent> mParent;
+ TaskType mType;
+ nsCString mSuffix, mOrigin;
+ nsString mKey;
+ nsString mValue;
+ nsresult mRv;
+
+ NS_IMETHOD Run() override
+ {
+ if (!mParent->IPCOpen()) {
+ return NS_OK;
+ }
+
+ switch (mType)
+ {
+ case loadItem:
+ mozilla::Unused << mParent->SendLoadItem(mSuffix, mOrigin, mKey, mValue);
+ break;
+ case loadDone:
+ mozilla::Unused << mParent->SendLoadDone(mSuffix, mOrigin, mRv);
+ break;
+ }
+
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+// DOMStorageDBParent::CacheParentBridge
+
+const nsCString
+DOMStorageDBParent::CacheParentBridge::Origin() const
+{
+ return DOMStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
+}
+
+bool
+DOMStorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey, const nsString& aValue)
+{
+ if (mLoaded) {
+ return false;
+ }
+
+ ++mLoadedCount;
+
+ RefPtr<LoadRunnable> r =
+ new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix, mOriginNoSuffix, aKey, aValue);
+ NS_DispatchToMainThread(r);
+ return true;
+}
+
+void
+DOMStorageDBParent::CacheParentBridge::LoadDone(nsresult aRv)
+{
+ // Prevent send of duplicate LoadDone.
+ if (mLoaded) {
+ return;
+ }
+
+ mLoaded = true;
+
+ RefPtr<LoadRunnable> r =
+ new LoadRunnable(mParent, LoadRunnable::loadDone, mOriginSuffix, mOriginNoSuffix, aRv);
+ NS_DispatchToMainThread(r);
+}
+
+void
+DOMStorageDBParent::CacheParentBridge::LoadWait()
+{
+ // Should never be called on this implementation
+ MOZ_ASSERT(false);
+}
+
+// DOMStorageDBParent::UsageParentBridge
+
+namespace {
+
+class UsageRunnable : public Runnable
+{
+public:
+ UsageRunnable(DOMStorageDBParent* aParent, const nsACString& aOriginScope, const int64_t& aUsage)
+ : mParent(aParent)
+ , mOriginScope(aOriginScope)
+ , mUsage(aUsage)
+ {}
+
+private:
+ NS_IMETHOD Run() override
+ {
+ if (!mParent->IPCOpen()) {
+ return NS_OK;
+ }
+
+ mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage);
+ return NS_OK;
+ }
+
+ RefPtr<DOMStorageDBParent> mParent;
+ nsCString mOriginScope;
+ int64_t mUsage;
+};
+
+} // namespace
+
+void
+DOMStorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage)
+{
+ RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mOriginScope, aUsage);
+ NS_DispatchToMainThread(r);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageIPC.h b/dom/storage/DOMStorageIPC.h
new file mode 100644
index 000000000..b3508a588
--- /dev/null
+++ b/dom/storage/DOMStorageIPC.h
@@ -0,0 +1,218 @@
+/* -*- 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 nsDOMStorageIPC_h___
+#define nsDOMStorageIPC_h___
+
+#include "mozilla/dom/PStorageChild.h"
+#include "mozilla/dom/PStorageParent.h"
+#include "DOMStorageDBThread.h"
+#include "DOMStorageCache.h"
+#include "DOMStorageObserver.h"
+#include "mozilla/Mutex.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace dom {
+
+class DOMLocalStorageManager;
+
+// Child side of the IPC protocol, exposes as DB interface but
+// is responsible to send all requests to the parent process
+// and expects asynchronous answers. Those are then transparently
+// forwarded back to consumers on the child process.
+class DOMStorageDBChild final : public DOMStorageDBBridge
+ , public PStorageChild
+{
+ virtual ~DOMStorageDBChild();
+
+public:
+ explicit DOMStorageDBChild(DOMLocalStorageManager* aManager);
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+ NS_IMETHOD_(MozExternalRefCountType) Release(void);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false);
+ virtual void AsyncGetUsage(DOMStorageUsageBridge* aUsage);
+
+ virtual void SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync = false);
+
+ virtual nsresult AsyncAddItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue);
+ virtual nsresult AsyncUpdateItem(DOMStorageCacheBridge* aCache, const nsAString& aKey, const nsAString& aValue);
+ virtual nsresult AsyncRemoveItem(DOMStorageCacheBridge* aCache, const nsAString& aKey);
+ virtual nsresult AsyncClear(DOMStorageCacheBridge* aCache);
+
+ virtual void AsyncClearAll()
+ {
+ if (mOriginsHavingData) {
+ mOriginsHavingData->Clear(); /* NO-OP on the child process otherwise */
+ }
+ }
+
+ virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix)
+ { /* NO-OP on the child process */ }
+
+ virtual void AsyncClearMatchingOriginAttributes(const OriginAttributesPattern& aPattern)
+ { /* NO-OP on the child process */ }
+
+ virtual void AsyncFlush()
+ { SendAsyncFlush(); }
+
+ virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix);
+ virtual void GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
+ { NS_NOTREACHED("Not implemented for child process"); }
+
+private:
+ bool RecvObserve(const nsCString& aTopic,
+ const nsString& aOriginAttributesPattern,
+ const nsCString& aOriginScope);
+ bool RecvLoadItem(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsString& aKey,
+ const nsString& aValue);
+ bool RecvLoadDone(const nsCString& aOriginSuffix,
+ const nsCString& aOriginNoSuffix,
+ const nsresult& aRv);
+ bool RecvOriginsHavingData(nsTArray<nsCString>&& aOrigins);
+ bool RecvLoadUsage(const nsCString& aOriginNoSuffix,
+ const int64_t& aUsage);
+ bool RecvError(const nsresult& aRv);
+
+ nsTHashtable<nsCStringHashKey>& OriginsHavingData();
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ // Held to get caches to forward answers to.
+ RefPtr<DOMLocalStorageManager> mManager;
+
+ // Origins having data hash, for optimization purposes only
+ nsAutoPtr<nsTHashtable<nsCStringHashKey>> mOriginsHavingData;
+
+ // List of caches waiting for preload. This ensures the contract that
+ // AsyncPreload call references the cache for time of the preload.
+ nsTHashtable<nsRefPtrHashKey<DOMStorageCacheBridge>> mLoadingCaches;
+
+ // Status of the remote database
+ nsresult mStatus;
+
+ bool mIPCOpen;
+};
+
+
+// Receives async requests from child processes and is responsible
+// to send back responses from the DB thread. Exposes as a fake
+// DOMStorageCache consumer.
+// Also responsible for forwardning all chrome operation notifications
+// such as cookie cleaning etc to the child process.
+class DOMStorageDBParent final : public PStorageParent
+ , public DOMStorageObserverSink
+{
+ virtual ~DOMStorageDBParent();
+
+public:
+ DOMStorageDBParent();
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+ NS_IMETHOD_(MozExternalRefCountType) Release(void);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ bool IPCOpen() { return mIPCOpen; }
+
+public:
+ // Fake cache class receiving async callbacks from DB thread, sending
+ // them back to appropriate cache object on the child process.
+ class CacheParentBridge : public DOMStorageCacheBridge {
+ public:
+ CacheParentBridge(DOMStorageDBParent* aParentDB,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix)
+ : mParent(aParentDB)
+ , mOriginSuffix(aOriginSuffix), mOriginNoSuffix(aOriginNoSuffix)
+ , mLoaded(false), mLoadedCount(0) {}
+ virtual ~CacheParentBridge() {}
+
+ // DOMStorageCacheBridge
+ virtual const nsCString Origin() const;
+ virtual const nsCString& OriginNoSuffix() const
+ { return mOriginNoSuffix; }
+ virtual const nsCString& OriginSuffix() const
+ { return mOriginSuffix; }
+ virtual bool Loaded()
+ { return mLoaded; }
+ virtual uint32_t LoadedCount()
+ { return mLoadedCount; }
+
+ virtual bool LoadItem(const nsAString& aKey, const nsString& aValue);
+ virtual void LoadDone(nsresult aRv);
+ virtual void LoadWait();
+
+ private:
+ RefPtr<DOMStorageDBParent> mParent;
+ nsCString mOriginSuffix, mOriginNoSuffix;
+ bool mLoaded;
+ uint32_t mLoadedCount;
+ };
+
+ // Fake usage class receiving async callbacks from DB thread
+ class UsageParentBridge : public DOMStorageUsageBridge
+ {
+ public:
+ UsageParentBridge(DOMStorageDBParent* aParentDB, const nsACString& aOriginScope)
+ : mParent(aParentDB), mOriginScope(aOriginScope) {}
+ virtual ~UsageParentBridge() {}
+
+ // DOMStorageUsageBridge
+ virtual const nsCString& OriginScope() { return mOriginScope; }
+ virtual void LoadUsage(const int64_t usage);
+
+ private:
+ RefPtr<DOMStorageDBParent> mParent;
+ nsCString mOriginScope;
+ };
+
+private:
+ // IPC
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool RecvAsyncPreload(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const bool& aPriority) override;
+ bool RecvPreload(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const uint32_t& aAlreadyLoadedCount,
+ InfallibleTArray<nsString>* aKeys, InfallibleTArray<nsString>* aValues,
+ nsresult* aRv) override;
+ bool RecvAsyncGetUsage(const nsCString& aOriginNoSuffix) override;
+ bool RecvAsyncAddItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& aValue) override;
+ bool RecvAsyncUpdateItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& aValue) override;
+ bool RecvAsyncRemoveItem(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey) override;
+ bool RecvAsyncClear(const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix) override;
+ bool RecvAsyncFlush() override;
+
+ // DOMStorageObserverSink
+ virtual nsresult Observe(const char* aTopic, const nsAString& aOriginAttrPattern, const nsACString& aOriginScope) override;
+
+private:
+ CacheParentBridge* NewCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ // True when IPC channel is open and Send*() methods are OK to use.
+ bool mIPCOpen;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/storage/DOMStorageManager.cpp b/dom/storage/DOMStorageManager.cpp
new file mode 100644
index 000000000..156e846ba
--- /dev/null
+++ b/dom/storage/DOMStorageManager.cpp
@@ -0,0 +1,661 @@
+/* -*- 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/. */
+
+#include "DOMStorageManager.h"
+#include "DOMStorage.h"
+#include "DOMStorageDBThread.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIEffectiveTLDService.h"
+
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+
+// Only allow relatively small amounts of data since performance of
+// the synchronous IO is very bad.
+// We are enforcing simple per-origin quota only.
+#define DEFAULT_QUOTA_LIMIT (5 * 1024)
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+int32_t gQuotaLimit = DEFAULT_QUOTA_LIMIT;
+
+} // namespace
+
+DOMLocalStorageManager*
+DOMLocalStorageManager::sSelf = nullptr;
+
+// static
+uint32_t
+DOMStorageManager::GetQuota()
+{
+ static bool preferencesInitialized = false;
+ if (!preferencesInitialized) {
+ mozilla::Preferences::AddIntVarCache(&gQuotaLimit, "dom.storage.default_quota",
+ DEFAULT_QUOTA_LIMIT);
+ preferencesInitialized = true;
+ }
+
+ return gQuotaLimit * 1024; // pref is in kBs
+}
+
+void
+ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult)
+{
+ nsACString::const_iterator sourceBegin, sourceEnd;
+ aSource.BeginReading(sourceBegin);
+ aSource.EndReading(sourceEnd);
+
+ aResult.SetLength(aSource.Length());
+ nsACString::iterator destEnd;
+ aResult.EndWriting(destEnd);
+
+ while (sourceBegin != sourceEnd) {
+ *(--destEnd) = *sourceBegin;
+ ++sourceBegin;
+ }
+}
+
+nsresult
+CreateReversedDomain(const nsACString& aAsciiDomain,
+ nsACString& aKey)
+{
+ if (aAsciiDomain.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ ReverseString(aAsciiDomain, aKey);
+
+ aKey.Append('.');
+ return NS_OK;
+}
+
+bool
+PrincipalsEqual(nsIPrincipal* aObjectPrincipal, nsIPrincipal* aSubjectPrincipal)
+{
+ if (!aSubjectPrincipal) {
+ return true;
+ }
+
+ if (!aObjectPrincipal) {
+ return false;
+ }
+
+ return aSubjectPrincipal->Equals(aObjectPrincipal);
+}
+
+NS_IMPL_ISUPPORTS(DOMStorageManager,
+ nsIDOMStorageManager)
+
+DOMStorageManager::DOMStorageManager(DOMStorage::StorageType aType)
+ : mCaches(8)
+ , mType(aType)
+ , mLowDiskSpace(false)
+{
+ DOMStorageObserver* observer = DOMStorageObserver::Self();
+ NS_ASSERTION(observer, "No DOMStorageObserver, cannot observe private data delete notifications!");
+
+ if (observer) {
+ observer->AddSink(this);
+ }
+}
+
+DOMStorageManager::~DOMStorageManager()
+{
+ DOMStorageObserver* observer = DOMStorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+}
+
+namespace {
+
+nsresult
+AppendOriginNoSuffix(nsIPrincipal* aPrincipal,
+ nsACString& aKey)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString domainOrigin;
+ rv = uri->GetAsciiHost(domainOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (domainOrigin.IsEmpty()) {
+ // For the file:/// protocol use the exact directory as domain.
+ bool isScheme = false;
+ if (NS_SUCCEEDED(uri->SchemeIs("file", &isScheme)) && isScheme) {
+ nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->GetDirectory(domainOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Append reversed domain
+ nsAutoCString reverseDomain;
+ rv = CreateReversedDomain(domainOrigin, reverseDomain);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aKey.Append(reverseDomain);
+
+ // Append scheme
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aKey.Append(':');
+ aKey.Append(scheme);
+
+ // Append port if any
+ int32_t port = NS_GetRealPort(uri);
+ if (port != -1) {
+ aKey.Append(nsPrintfCString(":%d", port));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+CreateQuotaDBKey(nsIPrincipal* aPrincipal,
+ nsACString& aKey)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService(do_GetService(
+ NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString eTLDplusOne;
+ rv = eTLDService->GetBaseDomain(uri, 0, eTLDplusOne);
+ if (NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS == rv) {
+ // XXX bug 357323 - what to do for localhost/file exactly?
+ rv = uri->GetAsciiHost(eTLDplusOne);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aKey.Truncate();
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(aKey);
+
+ nsAutoCString subdomainsDBKey;
+ CreateReversedDomain(eTLDplusOne, subdomainsDBKey);
+
+ aKey.Append(':');
+ aKey.Append(subdomainsDBKey);
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+nsCString
+DOMStorageManager::CreateOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
+{
+ // Note: some hard-coded sqlite statements are dependent on the format this
+ // method returns. Changing this without updating those sqlite statements
+ // will cause malfunction.
+
+ nsAutoCString scope;
+ scope.Append(aOriginSuffix);
+ scope.Append(':');
+ scope.Append(aOriginNoSuffix);
+ return scope;
+}
+
+DOMStorageCache*
+DOMStorageManager::GetCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix)
+{
+ CacheOriginHashtable* table = mCaches.LookupOrAdd(aOriginSuffix);
+ DOMStorageCacheHashKey* entry = table->GetEntry(aOriginNoSuffix);
+ if (!entry) {
+ return nullptr;
+ }
+
+ return entry->cache();
+}
+
+already_AddRefed<DOMStorageUsage>
+DOMStorageManager::GetOriginUsage(const nsACString& aOriginNoSuffix)
+{
+ RefPtr<DOMStorageUsage> usage;
+ if (mUsages.Get(aOriginNoSuffix, &usage)) {
+ return usage.forget();
+ }
+
+ usage = new DOMStorageUsage(aOriginNoSuffix);
+
+ if (mType == LocalStorage) {
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ if (db) {
+ db->AsyncGetUsage(usage);
+ }
+ }
+
+ mUsages.Put(aOriginNoSuffix, usage);
+
+ return usage.forget();
+}
+
+already_AddRefed<DOMStorageCache>
+DOMStorageManager::PutCache(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ nsIPrincipal* aPrincipal)
+{
+ CacheOriginHashtable* table = mCaches.LookupOrAdd(aOriginSuffix);
+ DOMStorageCacheHashKey* entry = table->PutEntry(aOriginNoSuffix);
+ RefPtr<DOMStorageCache> cache = entry->cache();
+
+ nsAutoCString quotaOrigin;
+ CreateQuotaDBKey(aPrincipal, quotaOrigin);
+
+ switch (mType) {
+ case SessionStorage:
+ // Lifetime handled by the manager, don't persist
+ entry->HardRef();
+ cache->Init(this, false, aPrincipal, quotaOrigin);
+ break;
+
+ case LocalStorage:
+ // Lifetime handled by the cache, do persist
+ cache->Init(this, true, aPrincipal, quotaOrigin);
+ break;
+
+ default:
+ MOZ_ASSERT(false);
+ }
+
+ return cache.forget();
+}
+
+void
+DOMStorageManager::DropCache(DOMStorageCache* aCache)
+{
+ if (!NS_IsMainThread()) {
+ NS_WARNING("DOMStorageManager::DropCache called on a non-main thread, shutting down?");
+ }
+
+ CacheOriginHashtable* table = mCaches.LookupOrAdd(aCache->OriginSuffix());
+ table->RemoveEntry(aCache->OriginNoSuffix());
+}
+
+nsresult
+DOMStorageManager::GetStorageInternal(bool aCreate,
+ mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate,
+ nsIDOMStorage** aRetval)
+{
+ nsresult rv;
+
+ nsAutoCString originAttrSuffix;
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(originAttrSuffix);
+
+ nsAutoCString originKey;
+ rv = AppendOriginNoSuffix(aPrincipal, originKey);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<DOMStorageCache> cache = GetCache(originAttrSuffix, originKey);
+
+ // Get or create a cache for the given scope
+ if (!cache) {
+ if (!aCreate) {
+ *aRetval = nullptr;
+ return NS_OK;
+ }
+
+ if (!aRetval) {
+ // This is a demand to just preload the cache, if the scope has
+ // no data stored, bypass creation and preload of the cache.
+ DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+ if (db) {
+ if (!db->ShouldPreloadOrigin(DOMStorageManager::CreateOrigin(originAttrSuffix, originKey))) {
+ return NS_OK;
+ }
+ } else {
+ if (originKey.EqualsLiteral("knalb.:about")) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // There is always a single instance of a cache per scope
+ // in a single instance of a DOM storage manager.
+ cache = PutCache(originAttrSuffix, originKey, aPrincipal);
+ } else if (mType == SessionStorage) {
+ if (!cache->CheckPrincipal(aPrincipal)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ if (aRetval) {
+ nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
+
+ nsCOMPtr<nsIDOMStorage> storage = new DOMStorage(
+ inner, this, cache, aDocumentURI, aPrincipal, aPrivate);
+ storage.forget(aRetval);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal)
+{
+ return GetStorageInternal(true, nullptr, aPrincipal, EmptyString(), false,
+ nullptr);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CreateStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate,
+ nsIDOMStorage** aRetval)
+{
+ return GetStorageInternal(true, aWindow, aPrincipal, aDocumentURI, aPrivate,
+ aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::GetStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ bool aPrivate,
+ nsIDOMStorage** aRetval)
+{
+ return GetStorageInternal(false, aWindow, aPrincipal, EmptyString(), aPrivate,
+ aRetval);
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CloneStorage(nsIDOMStorage* aStorage)
+{
+ if (mType != SessionStorage) {
+ // Cloning is supported only for sessionStorage
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage);
+ if (!storage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const DOMStorageCache* origCache = storage->GetCache();
+
+ DOMStorageCache* existingCache = GetCache(origCache->OriginSuffix(),
+ origCache->OriginNoSuffix());
+ if (existingCache) {
+ // Do not replace an existing sessionStorage.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Since this manager is sessionStorage manager, PutCache hard references
+ // the cache in our hashtable.
+ RefPtr<DOMStorageCache> newCache = PutCache(origCache->OriginSuffix(),
+ origCache->OriginNoSuffix(),
+ origCache->Principal());
+
+ newCache->CloneFrom(origCache);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageManager::CheckStorage(nsIPrincipal* aPrincipal,
+ nsIDOMStorage* aStorage,
+ bool* aRetval)
+{
+ nsresult rv;
+
+ RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage);
+ if (!storage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *aRetval = false;
+
+ if (!aPrincipal) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString suffix;
+ BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix);
+
+ nsAutoCString origin;
+ rv = AppendOriginNoSuffix(aPrincipal, origin);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ DOMStorageCache* cache = GetCache(suffix, origin);
+ if (cache != storage->GetCache()) {
+ return NS_OK;
+ }
+
+ if (!storage->PrincipalEquals(aPrincipal)) {
+ return NS_OK;
+ }
+
+ *aRetval = true;
+ return NS_OK;
+}
+
+// Obsolete nsIDOMStorageManager methods
+
+NS_IMETHODIMP
+DOMStorageManager::GetLocalStorageForPrincipal(nsIPrincipal* aPrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate,
+ nsIDOMStorage** aRetval)
+{
+ if (mType != LocalStorage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return CreateStorage(nullptr, aPrincipal, aDocumentURI, aPrivate, aRetval);
+}
+
+void
+DOMStorageManager::ClearCaches(uint32_t aUnloadFlags,
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope)
+{
+ for (auto iter1 = mCaches.Iter(); !iter1.Done(); iter1.Next()) {
+ PrincipalOriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(iter1.Key());
+ MOZ_ASSERT(rv);
+ if (!aPattern.Matches(oa)) {
+ // This table doesn't match the given origin attributes pattern
+ continue;
+ }
+
+ CacheOriginHashtable* table = iter1.Data();
+
+ for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
+ DOMStorageCache* cache = iter2.Get()->cache();
+
+ if (aOriginScope.IsEmpty() ||
+ StringBeginsWith(cache->OriginNoSuffix(), aOriginScope)) {
+ cache->UnloadItems(aUnloadFlags);
+ }
+ }
+ }
+}
+
+nsresult
+DOMStorageManager::Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope)
+{
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(aOriginAttributesPattern)) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Clear everything, caches + database
+ if (!strcmp(aTopic, "cookie-cleared") ||
+ !strcmp(aTopic, "extension:purge-localStorage-caches")) {
+ ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
+ return NS_OK;
+ }
+
+ // Clear from caches everything that has been stored
+ // while in session-only mode
+ if (!strcmp(aTopic, "session-only-cleared")) {
+ ClearCaches(DOMStorageCache::kUnloadSession, pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ // Clear everything (including so and pb data) from caches and database
+ // for the gived domain and subdomains.
+ if (!strcmp(aTopic, "domain-data-cleared")) {
+ ClearCaches(DOMStorageCache::kUnloadComplete, pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ // Clear all private-browsing caches
+ if (!strcmp(aTopic, "private-browsing-data-cleared")) {
+ ClearCaches(DOMStorageCache::kUnloadPrivate, pattern, EmptyCString());
+ return NS_OK;
+ }
+
+ // Clear localStorage data beloging to an origin pattern
+ if (!strcmp(aTopic, "origin-attr-pattern-cleared")) {
+ // sessionStorage is expected to stay
+ if (mType == SessionStorage) {
+ return NS_OK;
+ }
+
+ ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-change")) {
+ // For case caches are still referenced - clear them completely
+ ClearCaches(DOMStorageCache::kUnloadComplete, pattern, EmptyCString());
+ mCaches.Clear();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "low-disk-space")) {
+ if (mType == LocalStorage) {
+ mLowDiskSpace = true;
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "no-low-disk-space")) {
+ if (mType == LocalStorage) {
+ mLowDiskSpace = false;
+ }
+
+ return NS_OK;
+ }
+
+#ifdef DOM_STORAGE_TESTS
+ if (!strcmp(aTopic, "test-reload")) {
+ if (mType != LocalStorage) {
+ return NS_OK;
+ }
+
+ // This immediately completely reloads all caches from the database.
+ ClearCaches(DOMStorageCache::kTestReload, pattern, EmptyCString());
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "test-flushed")) {
+ if (!XRE_IsParentProcess()) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
+ }
+ }
+
+ return NS_OK;
+ }
+#endif
+
+ NS_ERROR("Unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+}
+
+// DOMLocalStorageManager
+
+DOMLocalStorageManager::DOMLocalStorageManager()
+ : DOMStorageManager(LocalStorage)
+{
+ NS_ASSERTION(!sSelf, "Somebody is trying to do_CreateInstance(\"@mozilla/dom/localStorage-manager;1\"");
+ sSelf = this;
+
+ if (!XRE_IsParentProcess()) {
+ // Do this only on the child process. The thread IPC bridge
+ // is also used to communicate chrome observer notifications.
+ // Note: must be called after we set sSelf
+ DOMStorageCache::StartDatabase();
+ }
+}
+
+DOMLocalStorageManager::~DOMLocalStorageManager()
+{
+ sSelf = nullptr;
+}
+
+DOMLocalStorageManager*
+DOMLocalStorageManager::Ensure()
+{
+ if (sSelf) {
+ return sSelf;
+ }
+
+ // Cause sSelf to be populated.
+ nsCOMPtr<nsIDOMStorageManager> initializer =
+ do_GetService("@mozilla.org/dom/localStorage-manager;1");
+ MOZ_ASSERT(sSelf, "Didn't initialize?");
+
+ return sSelf;
+}
+
+// DOMSessionStorageManager
+
+DOMSessionStorageManager::DOMSessionStorageManager()
+ : DOMStorageManager(SessionStorage)
+{
+ if (!XRE_IsParentProcess()) {
+ // Do this only on the child process. The thread IPC bridge
+ // is also used to communicate chrome observer notifications.
+ DOMStorageCache::StartDatabase();
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageManager.h b/dom/storage/DOMStorageManager.h
new file mode 100644
index 000000000..666e16a6f
--- /dev/null
+++ b/dom/storage/DOMStorageManager.h
@@ -0,0 +1,155 @@
+/* -*- 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 nsDOMStorageManager_h__
+#define nsDOMStorageManager_h__
+
+#include "nsIDOMStorageManager.h"
+#include "DOMStorageObserver.h"
+
+#include "DOMStorageCache.h"
+#include "mozilla/dom/DOMStorage.h"
+
+#include "nsTHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace dom {
+
+const DOMStorage::StorageType SessionStorage = DOMStorage::SessionStorage;
+const DOMStorage::StorageType LocalStorage = DOMStorage::LocalStorage;
+
+class DOMStorageManager : public nsIDOMStorageManager
+ , public DOMStorageObserverSink
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMSTORAGEMANAGER
+
+public:
+ virtual DOMStorage::StorageType Type() { return mType; }
+
+ // Reads the preference for DOM storage quota
+ static uint32_t GetQuota();
+ // Gets (but not ensures) cache for the given scope
+ DOMStorageCache* GetCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
+ // Returns object keeping usage cache for the scope.
+ already_AddRefed<DOMStorageUsage> GetOriginUsage(const nsACString& aOriginNoSuffix);
+
+ static nsCString CreateOrigin(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix);
+
+protected:
+ explicit DOMStorageManager(DOMStorage::StorageType aType);
+ virtual ~DOMStorageManager();
+
+private:
+ // DOMStorageObserverSink, handler to various chrome clearing notification
+ virtual nsresult Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) override;
+
+ // Since nsTHashtable doesn't like multiple inheritance, we have to aggregate
+ // DOMStorageCache into the entry.
+ class DOMStorageCacheHashKey : public nsCStringHashKey
+ {
+ public:
+ explicit DOMStorageCacheHashKey(const nsACString* aKey)
+ : nsCStringHashKey(aKey)
+ , mCache(new DOMStorageCache(aKey))
+ {}
+
+ DOMStorageCacheHashKey(const DOMStorageCacheHashKey& aOther)
+ : nsCStringHashKey(aOther)
+ {
+ NS_ERROR("Shouldn't be called");
+ }
+
+ DOMStorageCache* cache() { return mCache; }
+ // Keep the cache referenced forever, used for sessionStorage.
+ void HardRef() { mCacheRef = mCache; }
+
+ private:
+ // weak ref only since cache references its manager.
+ DOMStorageCache* mCache;
+ // hard ref when this is sessionStorage to keep it alive forever.
+ RefPtr<DOMStorageCache> mCacheRef;
+ };
+
+ // Ensures cache for a scope, when it doesn't exist it is created and initalized,
+ // this also starts preload of persistent data.
+ already_AddRefed<DOMStorageCache> PutCache(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ nsIPrincipal* aPrincipal);
+
+ // Helper for creation of DOM storage objects
+ nsresult GetStorageInternal(bool aCreate,
+ mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate,
+ nsIDOMStorage** aRetval);
+
+ // Suffix->origin->cache map
+ typedef nsTHashtable<DOMStorageCacheHashKey> CacheOriginHashtable;
+ nsClassHashtable<nsCStringHashKey, CacheOriginHashtable> mCaches;
+
+ const DOMStorage::StorageType mType;
+
+ // If mLowDiskSpace is true it indicates a low device storage situation and
+ // so no localStorage writes are allowed. sessionStorage writes are still
+ // allowed.
+ bool mLowDiskSpace;
+ bool IsLowDiskSpace() const { return mLowDiskSpace; };
+
+ void ClearCaches(uint32_t aUnloadFlags,
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aKeyPrefix);
+
+protected:
+ // Keeps usage cache objects for eTLD+1 scopes we have touched.
+ nsDataHashtable<nsCStringHashKey, RefPtr<DOMStorageUsage> > mUsages;
+
+ friend class DOMStorageCache;
+ // Releases cache since it is no longer referrered by any DOMStorage object.
+ virtual void DropCache(DOMStorageCache* aCache);
+};
+
+// Derived classes to allow two different contract ids, one for localStorage and
+// one for sessionStorage management. localStorage manager is used as service
+// scoped to the application while sessionStorage managers are instantiated by each
+// top doc shell in the application since sessionStorages are isolated per top level
+// browsing context. The code may easily by shared by both.
+
+class DOMLocalStorageManager final : public DOMStorageManager
+{
+public:
+ DOMLocalStorageManager();
+ virtual ~DOMLocalStorageManager();
+
+ // Global getter of localStorage manager service
+ static DOMLocalStorageManager* Self() { return sSelf; }
+
+ // Like Self, but creates an instance if we're not yet initialized.
+ static DOMLocalStorageManager* Ensure();
+
+private:
+ static DOMLocalStorageManager* sSelf;
+};
+
+class DOMSessionStorageManager final : public DOMStorageManager
+{
+public:
+ DOMSessionStorageManager();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* nsDOMStorageManager_h__ */
diff --git a/dom/storage/DOMStorageObserver.cpp b/dom/storage/DOMStorageObserver.cpp
new file mode 100644
index 000000000..a2b3f1da8
--- /dev/null
+++ b/dom/storage/DOMStorageObserver.cpp
@@ -0,0 +1,355 @@
+/* -*- 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/. */
+
+#include "DOMStorageObserver.h"
+
+#include "DOMStorageDBThread.h"
+#include "DOMStorageCache.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsIURL.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIPermission.h"
+#include "nsIIDNService.h"
+#include "nsICookiePermission.h"
+
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+static const char kStartupTopic[] = "sessionstore-windows-restored";
+static const uint32_t kStartupDelay = 0;
+
+NS_IMPL_ISUPPORTS(DOMStorageObserver,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+DOMStorageObserver* DOMStorageObserver::sSelf = nullptr;
+
+extern nsresult
+CreateReversedDomain(const nsACString& aAsciiDomain, nsACString& aKey);
+
+// static
+nsresult
+DOMStorageObserver::Init()
+{
+ if (sSelf) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ sSelf = new DOMStorageObserver();
+ NS_ADDREF(sSelf);
+
+ // Chrome clear operations.
+ obs->AddObserver(sSelf, kStartupTopic, true);
+ obs->AddObserver(sSelf, "cookie-changed", true);
+ obs->AddObserver(sSelf, "perm-changed", true);
+ obs->AddObserver(sSelf, "browser:purge-domain-data", true);
+ obs->AddObserver(sSelf, "last-pb-context-exited", true);
+ obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
+ obs->AddObserver(sSelf, "extension:purge-localStorage", true);
+
+ // Shutdown
+ obs->AddObserver(sSelf, "profile-after-change", true);
+ obs->AddObserver(sSelf, "profile-before-change", true);
+ obs->AddObserver(sSelf, "xpcom-shutdown", true);
+
+ // Observe low device storage notifications.
+ obs->AddObserver(sSelf, "disk-space-watcher", true);
+
+#ifdef DOM_STORAGE_TESTS
+ // Testing
+ obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
+ if (XRE_IsParentProcess()) {
+ // Only to forward to child process.
+ obs->AddObserver(sSelf, "domstorage-test-flushed", true);
+ }
+
+ obs->AddObserver(sSelf, "domstorage-test-reload", true);
+#endif
+
+ return NS_OK;
+}
+
+// static
+nsresult
+DOMStorageObserver::Shutdown()
+{
+ if (!sSelf) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_RELEASE(sSelf);
+ return NS_OK;
+}
+
+void
+DOMStorageObserver::AddSink(DOMStorageObserverSink* aObs)
+{
+ mSinks.AppendElement(aObs);
+}
+
+void
+DOMStorageObserver::RemoveSink(DOMStorageObserverSink* aObs)
+{
+ mSinks.RemoveElement(aObs);
+}
+
+void
+DOMStorageObserver::Notify(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope)
+{
+ for (uint32_t i = 0; i < mSinks.Length(); ++i) {
+ DOMStorageObserverSink* sink = mSinks[i];
+ sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
+ }
+}
+
+NS_IMETHODIMP
+DOMStorageObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ nsresult rv;
+
+ // Start the thread that opens the database.
+ if (!strcmp(aTopic, kStartupTopic)) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kStartupTopic);
+
+ mDBThreadStartDelayTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (!mDBThreadStartDelayTimer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mDBThreadStartDelayTimer->Init(this, nsITimer::TYPE_ONE_SHOT, kStartupDelay);
+
+ return NS_OK;
+ }
+
+ // Timer callback used to start the database a short timer after startup
+ if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
+ if (!timer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (timer == mDBThreadStartDelayTimer) {
+ mDBThreadStartDelayTimer = nullptr;
+
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+ }
+
+ // Clear everything, caches + database
+ if (!strcmp(aTopic, "cookie-changed")) {
+ if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
+ return NS_OK;
+ }
+
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+ db->AsyncClearAll();
+
+ Notify("cookie-cleared");
+
+ return NS_OK;
+ }
+
+ // Clear from caches everything that has been stored
+ // while in session-only mode
+ if (!strcmp(aTopic, "perm-changed")) {
+ // Check for cookie permission change
+ nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
+ if (!perm) {
+ return NS_OK;
+ }
+
+ nsAutoCString type;
+ perm->GetType(type);
+ if (type != NS_LITERAL_CSTRING("cookie")) {
+ return NS_OK;
+ }
+
+ uint32_t cap = 0;
+ perm->GetCapability(&cap);
+ if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
+ !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ perm->GetPrincipal(getter_AddRefs(principal));
+ if (!principal) {
+ return NS_OK;
+ }
+
+ nsAutoCString originSuffix;
+ BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(originSuffix);
+
+ nsCOMPtr<nsIURI> origin;
+ principal->GetURI(getter_AddRefs(origin));
+ if (!origin) {
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ origin->GetHost(host);
+ if (host.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoCString originScope;
+ rv = CreateReversedDomain(host, originScope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix), originScope);
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "extension:purge-localStorage")) {
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+ db->AsyncClearAll();
+
+ Notify("extension:purge-localStorage-caches");
+
+ return NS_OK;
+ }
+
+ // Clear everything (including so and pb data) from caches and database
+ // for the gived domain and subdomains.
+ if (!strcmp(aTopic, "browser:purge-domain-data")) {
+ // Convert the domain name to the ACE format
+ nsAutoCString aceDomain;
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (converter) {
+ rv = converter->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData), aceDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // In case the IDN service is not available, this is the best we can come up with!
+ rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(aData),
+ esc_OnlyNonASCII | esc_AlwaysCopy,
+ aceDomain,
+ fallible);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString originScope;
+ rv = CreateReversedDomain(aceDomain, originScope);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+ db->AsyncClearMatchingOrigin(originScope);
+
+ Notify("domain-data-cleared", EmptyString(), originScope);
+
+ return NS_OK;
+ }
+
+ // Clear all private-browsing caches
+ if (!strcmp(aTopic, "last-pb-context-exited")) {
+ Notify("private-browsing-data-cleared");
+
+ return NS_OK;
+ }
+
+ // Clear data of the origins whose prefixes will match the suffix.
+ if (!strcmp(aTopic, "clear-origin-attributes-data")) {
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(nsDependentString(aData))) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ DOMStorageDBBridge* db = DOMStorageCache::StartDatabase();
+ NS_ENSURE_TRUE(db, NS_ERROR_FAILURE);
+
+ db->AsyncClearMatchingOriginAttributes(pattern);
+
+ Notify("origin-attr-pattern-cleared", nsDependentString(aData));
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-after-change")) {
+ Notify("profile-change");
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-before-change") ||
+ !strcmp(aTopic, "xpcom-shutdown")) {
+ rv = DOMStorageCache::StopDatabase();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error while stopping DOMStorage DB background thread");
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "disk-space-watcher")) {
+ if (NS_LITERAL_STRING("full").Equals(aData)) {
+ Notify("low-disk-space");
+ } else if (NS_LITERAL_STRING("free").Equals(aData)) {
+ Notify("no-low-disk-space");
+ }
+
+ return NS_OK;
+ }
+
+#ifdef DOM_STORAGE_TESTS
+ if (!strcmp(aTopic, "domstorage-test-flush-force")) {
+ DOMStorageDBBridge* db = DOMStorageCache::GetDatabase();
+ if (db) {
+ db->AsyncFlush();
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "domstorage-test-flushed")) {
+ // Only used to propagate to IPC children
+ Notify("test-flushed");
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "domstorage-test-reload")) {
+ Notify("test-reload");
+
+ return NS_OK;
+ }
+#endif
+
+ NS_ERROR("Unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/DOMStorageObserver.h b/dom/storage/DOMStorageObserver.h
new file mode 100644
index 000000000..2efce96a3
--- /dev/null
+++ b/dom/storage/DOMStorageObserver.h
@@ -0,0 +1,67 @@
+/* -*- 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 nsIDOMStorageObserver_h__
+#define nsIDOMStorageObserver_h__
+
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class DOMStorageObserver;
+
+// Implementers are DOMStorageManager and DOMStorageDBParent to forward to
+// child processes.
+class DOMStorageObserverSink
+{
+public:
+ virtual ~DOMStorageObserverSink() {}
+
+private:
+ friend class DOMStorageObserver;
+ virtual nsresult Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) = 0;
+};
+
+// Statically (though layout statics) initialized observer receiving and processing
+// chrome clearing notifications, such as cookie deletion etc.
+class DOMStorageObserver : public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static DOMStorageObserver* Self() { return sSelf; }
+
+ void AddSink(DOMStorageObserverSink* aObs);
+ void RemoveSink(DOMStorageObserverSink* aObs);
+ void Notify(const char* aTopic,
+ const nsAString& aOriginAttributesPattern = EmptyString(),
+ const nsACString& aOriginScope = EmptyCString());
+
+private:
+ virtual ~DOMStorageObserver() {}
+
+ static DOMStorageObserver* sSelf;
+
+ // Weak references
+ nsTArray<DOMStorageObserverSink*> mSinks;
+ nsCOMPtr<nsITimer> mDBThreadStartDelayTimer;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/storage/PStorage.ipdl b/dom/storage/PStorage.ipdl
new file mode 100644
index 000000000..22b85f795
--- /dev/null
+++ b/dom/storage/PStorage.ipdl
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* 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/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+
+/* This protocol bridges async access to the database thread running on the parent process
+ * and caches running on the child process.
+ */
+nested(upto inside_cpow) sync protocol PStorage
+{
+ manager PContent;
+
+parent:
+ async __delete__();
+
+ nested(inside_cpow) sync Preload(nsCString originSuffix, nsCString originNoSuffix, uint32_t alreadyLoadedCount)
+ returns (nsString[] keys, nsString[] values, nsresult rv);
+
+ async AsyncPreload(nsCString originSuffix, nsCString originNoSuffix, bool priority);
+ async AsyncGetUsage(nsCString scope);
+ async AsyncAddItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
+ async AsyncUpdateItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
+ async AsyncRemoveItem(nsCString originSuffix, nsCString originNoSuffix, nsString key);
+ async AsyncClear(nsCString originSuffix, nsCString originNoSuffix);
+ async AsyncFlush();
+
+child:
+ async Observe(nsCString topic,
+ nsString originAttributesPattern,
+ nsCString originScope);
+ async OriginsHavingData(nsCString[] origins);
+ async LoadItem(nsCString originSuffix, nsCString originNoSuffix, nsString key, nsString value);
+ async LoadDone(nsCString originSuffix, nsCString originNoSuffix, nsresult rv);
+ async LoadUsage(nsCString scope, int64_t usage);
+ async Error(nsresult rv);
+};
+
+}
+}
diff --git a/dom/storage/moz.build b/dom/storage/moz.build
new file mode 100644
index 000000000..47c2fb85f
--- /dev/null
+++ b/dom/storage/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'DOMStorage.h',
+ 'DOMStorageIPC.h',
+]
+
+UNIFIED_SOURCES += [
+ 'DOMStorage.cpp',
+ 'DOMStorageCache.cpp',
+ 'DOMStorageDBThread.cpp',
+ 'DOMStorageDBUpdater.cpp',
+ 'DOMStorageIPC.cpp',
+ 'DOMStorageManager.cpp',
+ 'DOMStorageObserver.cpp',
+]
+
+IPDL_SOURCES += [
+ 'PStorage.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/dom/base',
+]
+
+if CONFIG['ENABLE_TESTS']:
+ DEFINES['DOM_STORAGE_TESTS'] = True