summaryrefslogtreecommitdiffstats
path: root/dom/storage
diff options
context:
space:
mode:
Diffstat (limited to 'dom/storage')
-rw-r--r--dom/storage/BackgroundSessionStorageServiceParent.cpp29
-rw-r--r--dom/storage/BackgroundSessionStorageServiceParent.h28
-rw-r--r--dom/storage/LocalStorage.cpp216
-rw-r--r--dom/storage/LocalStorage.h83
-rw-r--r--dom/storage/LocalStorageCache.cpp615
-rw-r--r--dom/storage/LocalStorageCache.h306
-rw-r--r--dom/storage/LocalStorageManager.cpp454
-rw-r--r--dom/storage/LocalStorageManager.h140
-rw-r--r--dom/storage/PBackgroundLocalStorageCache.ipdl43
-rw-r--r--dom/storage/PBackgroundSessionStorageCache.ipdl75
-rw-r--r--dom/storage/PBackgroundSessionStorageManager.ipdl37
-rw-r--r--dom/storage/PBackgroundSessionStorageService.ipdl22
-rw-r--r--dom/storage/PBackgroundStorage.ipdl70
-rw-r--r--dom/storage/PSessionStorageObserver.ipdl42
-rw-r--r--dom/storage/PartitionedLocalStorage.cpp133
-rw-r--r--dom/storage/PartitionedLocalStorage.h64
-rw-r--r--dom/storage/SessionStorage.cpp258
-rw-r--r--dom/storage/SessionStorage.h86
-rw-r--r--dom/storage/SessionStorageCache.cpp292
-rw-r--r--dom/storage/SessionStorageCache.h99
-rw-r--r--dom/storage/SessionStorageManager.cpp987
-rw-r--r--dom/storage/SessionStorageManager.h285
-rw-r--r--dom/storage/SessionStorageObserver.cpp55
-rw-r--r--dom/storage/SessionStorageObserver.h63
-rw-r--r--dom/storage/SessionStorageService.cpp134
-rw-r--r--dom/storage/SessionStorageService.h55
-rw-r--r--dom/storage/Storage.cpp156
-rw-r--r--dom/storage/Storage.h180
-rw-r--r--dom/storage/StorageActivityService.cpp302
-rw-r--r--dom/storage/StorageActivityService.h62
-rw-r--r--dom/storage/StorageCommon.h18
-rw-r--r--dom/storage/StorageDBThread.cpp1626
-rw-r--r--dom/storage/StorageDBThread.h493
-rw-r--r--dom/storage/StorageDBUpdater.cpp524
-rw-r--r--dom/storage/StorageDBUpdater.h20
-rw-r--r--dom/storage/StorageIPC.cpp1598
-rw-r--r--dom/storage/StorageIPC.h617
-rw-r--r--dom/storage/StorageNotifierService.cpp129
-rw-r--r--dom/storage/StorageNotifierService.h76
-rw-r--r--dom/storage/StorageObserver.cpp532
-rw-r--r--dom/storage/StorageObserver.h79
-rw-r--r--dom/storage/StorageUtils.cpp110
-rw-r--r--dom/storage/StorageUtils.h28
-rw-r--r--dom/storage/components.conf16
-rw-r--r--dom/storage/moz.build74
-rw-r--r--dom/storage/nsISessionStorageService.idl21
46 files changed, 11332 insertions, 0 deletions
diff --git a/dom/storage/BackgroundSessionStorageServiceParent.cpp b/dom/storage/BackgroundSessionStorageServiceParent.cpp
new file mode 100644
index 0000000000..4182a7a718
--- /dev/null
+++ b/dom/storage/BackgroundSessionStorageServiceParent.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "mozilla/dom/BackgroundSessionStorageServiceParent.h"
+
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::ipc;
+
+mozilla::ipc::IPCResult
+BackgroundSessionStorageServiceParent::RecvClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) {
+ AssertIsInMainProcess();
+ AssertIsOnBackgroundThread();
+
+ if (!mozilla::dom::RecvClearStoragesForOrigin(aOriginAttrs, aOriginKey)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/BackgroundSessionStorageServiceParent.h b/dom/storage/BackgroundSessionStorageServiceParent.h
new file mode 100644
index 0000000000..ee25dc5c5e
--- /dev/null
+++ b/dom/storage/BackgroundSessionStorageServiceParent.h
@@ -0,0 +1,28 @@
+/* -*- 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 DOM_STORAGE_BACKGROUNDSESSIONSTORAGESERVICEPARENT_H_
+#define DOM_STORAGE_BACKGROUNDSESSIONSTORAGESERVICEPARENT_H_
+
+#include "mozilla/dom/PBackgroundSessionStorageServiceParent.h"
+
+namespace mozilla::dom {
+
+class BackgroundSessionStorageServiceParent final
+ : public PBackgroundSessionStorageServiceParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(BackgroundSessionStorageServiceParent, override)
+
+ mozilla::ipc::IPCResult RecvClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey);
+
+ private:
+ ~BackgroundSessionStorageServiceParent() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_STORAGE_BACKGROUNDSESSIONSTORAGESERVICEPARENT_H_
diff --git a/dom/storage/LocalStorage.cpp b/dom/storage/LocalStorage.cpp
new file mode 100644
index 0000000000..ec75566242
--- /dev/null
+++ b/dom/storage/LocalStorage.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 "LocalStorage.h"
+#include "LocalStorageCache.h"
+#include "LocalStorageManager.h"
+#include "StorageUtils.h"
+
+#include "nsIPrincipal.h"
+
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/StorageBinding.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/EnumSet.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+using namespace ipc;
+
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(LocalStorage)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(LocalStorage, Storage)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mManager)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(LocalStorage, Storage)
+ CycleCollectionNoteChild(
+ cb, NS_ISUPPORTS_CAST(nsIDOMStorageManager*, tmp->mManager.get()),
+ "mManager");
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalStorage)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(Storage)
+
+NS_IMPL_ADDREF_INHERITED(LocalStorage, Storage)
+NS_IMPL_RELEASE_INHERITED(LocalStorage, Storage)
+
+LocalStorage::LocalStorage(nsPIDOMWindowInner* aWindow,
+ LocalStorageManager* aManager,
+ LocalStorageCache* aCache,
+ const nsAString& aDocumentURI,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, bool aIsPrivate)
+ : Storage(aWindow, aPrincipal, aStoragePrincipal),
+ mManager(aManager),
+ mCache(aCache),
+ mDocumentURI(aDocumentURI),
+ mIsPrivate(aIsPrivate) {
+ mCache->Preload();
+}
+
+LocalStorage::~LocalStorage() = default;
+
+int64_t LocalStorage::GetOriginQuotaUsage() const {
+ return mCache->GetOriginQuotaUsage(this);
+}
+
+uint32_t LocalStorage::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 LocalStorage::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 LocalStorage::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 LocalStorage::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)) {
+ OnChange(aKey, old, aData);
+ }
+}
+
+void LocalStorage::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)) {
+ OnChange(aKey, old, VoidString());
+ }
+}
+
+void LocalStorage::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)) {
+ OnChange(VoidString(), VoidString(), VoidString());
+ }
+}
+
+void LocalStorage::OnChange(const nsAString& aKey, const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ NotifyChange(/* aStorage */ this, StoragePrincipal(), aKey, aOldValue,
+ aNewValue, /* aStorageType */ u"localStorage", mDocumentURI,
+ mIsPrivate, /* aImmediateDispatch */ false);
+}
+
+void LocalStorage::ApplyEvent(StorageEvent* aStorageEvent) {
+ MOZ_ASSERT(aStorageEvent);
+
+ nsAutoString key;
+ nsAutoString old;
+ nsAutoString value;
+
+ aStorageEvent->GetKey(key);
+ aStorageEvent->GetNewValue(value);
+
+ // No key means clearing the full storage.
+ if (key.IsVoid()) {
+ MOZ_ASSERT(value.IsVoid());
+ mCache->Clear(this, LocalStorageCache::E10sPropagated);
+ return;
+ }
+
+ // No new value means removing the key.
+ if (value.IsVoid()) {
+ mCache->RemoveItem(this, key, old, LocalStorageCache::E10sPropagated);
+ return;
+ }
+
+ // Otherwise, we set the new value.
+ mCache->SetItem(this, key, value, old, LocalStorageCache::E10sPropagated);
+}
+
+void LocalStorage::GetSupportedNames(nsTArray<nsString>& aKeys) {
+ if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
+ // return just an empty array
+ aKeys.Clear();
+ return;
+ }
+
+ mCache->GetKeys(this, aKeys);
+}
+
+bool LocalStorage::IsForkOf(const Storage* aOther) const {
+ MOZ_ASSERT(aOther);
+ if (aOther->Type() != eLocalStorage) {
+ return false;
+ }
+
+ return mCache == static_cast<const LocalStorage*>(aOther)->mCache;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/LocalStorage.h b/dom/storage/LocalStorage.h
new file mode 100644
index 0000000000..4df24c5e63
--- /dev/null
+++ b/dom/storage/LocalStorage.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_LocalStorage_h
+#define mozilla_dom_LocalStorage_h
+
+#include "Storage.h"
+#include "nsWeakReference.h"
+
+namespace mozilla::dom {
+
+class LocalStorageCache;
+class LocalStorageManager;
+class StorageEvent;
+
+class LocalStorage final : public Storage, public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(LocalStorage, Storage)
+
+ StorageType Type() const override { return eLocalStorage; }
+
+ LocalStorageManager* GetManager() const { return mManager; }
+
+ LocalStorageCache const* GetCache() const { return mCache; }
+
+ const nsString& DocumentURI() const { return mDocumentURI; }
+
+ LocalStorage(nsPIDOMWindowInner* aWindow, LocalStorageManager* aManager,
+ LocalStorageCache* aCache, const nsAString& aDocumentURI,
+ nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
+ bool aIsPrivate);
+
+ bool IsForkOf(const Storage* aOther) const override;
+
+ // WebIDL
+
+ int64_t GetOriginQuotaUsage() const override;
+
+ uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Key(uint32_t aIndex, nsAString& aResult, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void GetSupportedNames(nsTArray<nsString>& aKeys) override;
+
+ void SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void ApplyEvent(StorageEvent* aStorageEvent);
+
+ private:
+ ~LocalStorage();
+
+ friend class LocalStorageManager;
+ friend class LocalStorageCache;
+
+ RefPtr<LocalStorageManager> mManager;
+ RefPtr<LocalStorageCache> mCache;
+ nsString mDocumentURI;
+
+ // Whether this storage is running in private-browsing window.
+ bool mIsPrivate : 1;
+
+ void OnChange(const nsAString& aKey, const nsAString& aOldValue,
+ const nsAString& aNewValue);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_LocalStorage_h
diff --git a/dom/storage/LocalStorageCache.cpp b/dom/storage/LocalStorageCache.cpp
new file mode 100644
index 0000000000..4b55482441
--- /dev/null
+++ b/dom/storage/LocalStorageCache.cpp
@@ -0,0 +1,615 @@
+/* -*- 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 "LocalStorageCache.h"
+
+#include "Storage.h"
+#include "StorageDBThread.h"
+#include "StorageIPC.h"
+#include "StorageUtils.h"
+#include "LocalStorageManager.h"
+
+#include "nsDOMString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Unused.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+#define DOM_STORAGE_CACHE_KEEP_ALIVE_TIME_MS 20000
+
+namespace {
+
+const uint32_t kDefaultSet = 0;
+const uint32_t kSessionSet = 1;
+
+inline uint32_t GetDataSetIndex(bool aPrivateBrowsing,
+ bool aSessionScopedOrLess) {
+ if (!aPrivateBrowsing && aSessionScopedOrLess) {
+ return kSessionSet;
+ }
+
+ return kDefaultSet;
+}
+
+inline uint32_t GetDataSetIndex(const LocalStorage* aStorage) {
+ return GetDataSetIndex(aStorage->IsPrivateBrowsing(),
+ aStorage->IsSessionScopedOrLess());
+}
+
+} // namespace
+
+// LocalStorageCacheBridge
+
+NS_IMPL_ADDREF(LocalStorageCacheBridge)
+
+// Since there is no consumer of return value of Release, we can turn this
+// method to void to make implementation of asynchronous
+// LocalStorageCache::Release much simpler.
+NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(_class); */
+ delete (this);
+ }
+}
+
+// LocalStorageCache
+
+LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
+ : mActor(nullptr),
+ mOriginNoSuffix(*aOriginNoSuffix),
+ mMonitor("LocalStorageCache"),
+ mLoaded(false),
+ mLoadResult(NS_OK),
+ mInitialized(false),
+ mPersistent(false),
+ mPreloadTelemetryRecorded(false) {
+ MOZ_COUNT_CTOR(LocalStorageCache);
+}
+
+LocalStorageCache::~LocalStorageCache() {
+ if (mActor) {
+ mActor->SendDeleteMeInternal();
+ MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+ }
+
+ if (mManager) {
+ mManager->DropCache(this);
+ }
+
+ MOZ_COUNT_DTOR(LocalStorageCache);
+}
+
+void LocalStorageCache::SetActor(LocalStorageCacheChild* aActor) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+
+ mActor = aActor;
+}
+
+NS_IMETHODIMP_(void)
+LocalStorageCache::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()) {
+ LocalStorageCacheBridge::Release();
+ return;
+ }
+
+ RefPtr<nsRunnableMethod<LocalStorageCacheBridge, void, false>> event =
+ NewNonOwningRunnableMethod("dom::LocalStorageCacheBridge::Release",
+ static_cast<LocalStorageCacheBridge*>(this),
+ &LocalStorageCacheBridge::Release);
+
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("LocalStorageCache::Release() on a non-main thread");
+ LocalStorageCacheBridge::Release();
+ }
+}
+
+void LocalStorageCache::Init(LocalStorageManager* aManager, bool aPersistent,
+ nsIPrincipal* aPrincipal,
+ const nsACString& aQuotaOriginScope) {
+ MOZ_ASSERT(!aQuotaOriginScope.IsEmpty());
+
+ if (mInitialized) {
+ return;
+ }
+
+ mInitialized = true;
+ aPrincipal->OriginAttributesRef().CreateSuffix(mOriginSuffix);
+ mPrivateBrowsingId = aPrincipal->GetPrivateBrowsingId();
+ mPersistent = aPersistent;
+ 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));
+
+ mUsage = aManager->GetOriginUsage(mQuotaOriginScope, mPrivateBrowsingId);
+}
+
+void LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
+ const nsAString& aKey,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aStorage);
+
+ if (!mActor) {
+ return;
+ }
+
+ // We want to send a message to the parent in order to broadcast the
+ // StorageEvent correctly to any child process.
+
+ Unused << mActor->SendNotify(aStorage->DocumentURI(), aKey, aOldValue,
+ aNewValue);
+}
+
+inline bool LocalStorageCache::Persist(const LocalStorage* aStorage) const {
+ return mPersistent &&
+ (aStorage->IsPrivateBrowsing() || !aStorage->IsSessionScopedOrLess());
+}
+
+const nsCString LocalStorageCache::Origin() const {
+ return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
+}
+
+LocalStorageCache::Data& LocalStorageCache::DataSet(
+ const LocalStorage* aStorage) {
+ return mData[GetDataSetIndex(aStorage)];
+}
+
+bool LocalStorageCache::ProcessUsageDelta(const LocalStorage* aStorage,
+ int64_t aDelta,
+ const MutationSource aSource) {
+ return ProcessUsageDelta(GetDataSetIndex(aStorage), aDelta, aSource);
+}
+
+bool LocalStorageCache::ProcessUsageDelta(uint32_t aGetDataSetIndex,
+ const int64_t aDelta,
+ const MutationSource aSource) {
+ // Check limit per this origin
+ Data& data = mData[aGetDataSetIndex];
+ uint64_t newOriginUsage = data.mOriginQuotaUsage + aDelta;
+ if (aSource == ContentMutation && aDelta > 0 &&
+ newOriginUsage > LocalStorageManager::GetOriginQuota()) {
+ return false;
+ }
+
+ // Now check eTLD+1 limit
+ if (mUsage &&
+ !mUsage->CheckAndSetETLD1UsageDelta(aGetDataSetIndex, aDelta, aSource)) {
+ return false;
+ }
+
+ // Update size in our data set
+ data.mOriginQuotaUsage = newOriginUsage;
+ return true;
+}
+
+void LocalStorageCache::Preload() {
+ if (mLoaded || !mPersistent) {
+ return;
+ }
+
+ StorageDBChild* storageChild =
+ StorageDBChild::GetOrCreate(mPrivateBrowsingId);
+ if (!storageChild) {
+ mLoaded = true;
+ mLoadResult = NS_ERROR_FAILURE;
+ return;
+ }
+
+ storageChild->AsyncPreload(this);
+}
+
+void LocalStorageCache::WaitForPreload(Telemetry::HistogramID 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
+ Telemetry::RuntimeAutoTimer 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.
+ StorageDBChild::Get(mPrivateBrowsingId)->SyncPreload(this);
+}
+
+nsresult LocalStorageCache::GetLength(const LocalStorage* 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 LocalStorageCache::GetKey(const LocalStorage* 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 LocalStorageCache::GetKeys(const LocalStorage* aStorage,
+ nsTArray<nsString>& aKeys) {
+ if (Persist(aStorage)) {
+ WaitForPreload(Telemetry::LOCALDOMSTORAGE_GETALLKEYS_BLOCKING_MS);
+ }
+
+ if (NS_FAILED(mLoadResult)) {
+ return;
+ }
+
+ AppendToArray(aKeys, DataSet(aStorage).mKeys.Keys());
+}
+
+nsresult LocalStorageCache::GetItem(const LocalStorage* 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 LocalStorageCache::SetItem(const LocalStorage* aStorage,
+ const nsAString& aKey,
+ const nsAString& aValue, nsString& aOld,
+ const MutationSource aSource) {
+ // 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, aSource)) {
+ return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+ }
+
+ if (aValue == aOld && DOMStringIsNull(aValue) == DOMStringIsNull(aOld)) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ data.mKeys.InsertOrUpdate(aKey, aValue);
+
+ if (aSource != ContentMutation) {
+ return NS_OK;
+ }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+ NotifyObservers(aStorage, aKey, aOld, aValue);
+#endif
+
+ if (Persist(aStorage)) {
+ StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
+ if (!storageChild) {
+ NS_ERROR(
+ "Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (DOMStringIsNull(aOld)) {
+ return storageChild->AsyncAddItem(this, aKey, aValue);
+ }
+
+ return storageChild->AsyncUpdateItem(this, aKey, aValue);
+ }
+
+ return NS_OK;
+}
+
+nsresult LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
+ const nsAString& aKey, nsString& aOld,
+ const MutationSource aSource) {
+ 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, aSource);
+ data.mKeys.Remove(aKey);
+
+ if (aSource != ContentMutation) {
+ return NS_OK;
+ }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+ NotifyObservers(aStorage, aKey, aOld, VoidString());
+#endif
+
+ if (Persist(aStorage)) {
+ StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
+ if (!storageChild) {
+ NS_ERROR(
+ "Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return storageChild->AsyncRemoveItem(this, aKey);
+ }
+
+ return NS_OK;
+}
+
+nsresult LocalStorageCache::Clear(const LocalStorage* aStorage,
+ const MutationSource aSource) {
+ 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, aSource);
+ data.mKeys.Clear();
+ }
+
+ if (aSource != ContentMutation) {
+ return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+ if (hadData) {
+ NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
+ }
+#endif
+
+ if (Persist(aStorage) && (refresh || hadData)) {
+ StorageDBChild* storageChild = StorageDBChild::Get(mPrivateBrowsingId);
+ if (!storageChild) {
+ NS_ERROR(
+ "Writing to localStorage after the database has been shut down"
+ ", data lose!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return storageChild->AsyncClear(this);
+ }
+
+ return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
+}
+
+int64_t LocalStorageCache::GetOriginQuotaUsage(
+ const LocalStorage* aStorage) const {
+ return mData[GetDataSetIndex(aStorage)].mOriginQuotaUsage;
+}
+
+void LocalStorageCache::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 & kUnloadSession) {
+ mData[kSessionSet].mKeys.Clear();
+ ProcessUsageDelta(kSessionSet, -mData[kSessionSet].mOriginQuotaUsage);
+ }
+
+#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
+}
+
+// LocalStorageCacheBridge
+
+uint32_t LocalStorageCache::LoadedCount() {
+ MonitorAutoLock monitor(mMonitor);
+ Data& data = mData[kDefaultSet];
+ return data.mKeys.Count();
+}
+
+bool LocalStorageCache::LoadItem(const nsAString& aKey,
+ const nsAString& aValue) {
+ MonitorAutoLock monitor(mMonitor);
+ if (mLoaded) {
+ return false;
+ }
+
+ Data& data = mData[kDefaultSet];
+ data.mKeys.LookupOrInsertWith(aKey, [&] {
+ data.mOriginQuotaUsage += aKey.Length() + aValue.Length();
+ return nsString(aValue);
+ });
+ return true;
+}
+
+void LocalStorageCache::LoadDone(nsresult aRv) {
+ MonitorAutoLock monitor(mMonitor);
+ mLoadResult = aRv;
+ mLoaded = true;
+ monitor.Notify();
+}
+
+void LocalStorageCache::LoadWait() {
+ MonitorAutoLock monitor(mMonitor);
+ while (!mLoaded) {
+ monitor.Wait();
+ }
+}
+
+// StorageUsage
+
+StorageUsage::StorageUsage(const nsACString& aOriginScope)
+ : mOriginScope(aOriginScope) {
+ mUsage[kDefaultSet] = mUsage[kSessionSet] = 0LL;
+}
+
+namespace {
+
+class LoadUsageRunnable : public Runnable {
+ public:
+ LoadUsageRunnable(int64_t* aUsage, const int64_t aDelta)
+ : Runnable("dom::LoadUsageRunnable"), mTarget(aUsage), mDelta(aDelta) {}
+
+ private:
+ int64_t* mTarget;
+ int64_t mDelta;
+
+ NS_IMETHOD Run() override {
+ *mTarget = mDelta;
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+void StorageUsage::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 StorageUsage::CheckAndSetETLD1UsageDelta(
+ uint32_t aDataSetIndex, const int64_t aDelta,
+ const LocalStorageCache::MutationSource aSource) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int64_t newUsage = mUsage[aDataSetIndex] + aDelta;
+ if (aSource == LocalStorageCache::ContentMutation && aDelta > 0 &&
+ newUsage > LocalStorageManager::GetSiteQuota()) {
+ return false;
+ }
+
+ mUsage[aDataSetIndex] = newUsage;
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/LocalStorageCache.h b/dom/storage/LocalStorageCache.h
new file mode 100644
index 0000000000..9df56cd475
--- /dev/null
+++ b/dom/storage/LocalStorageCache.h
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_LocalStorageCache_h
+#define mozilla_dom_LocalStorageCache_h
+
+#include "nsIPrincipal.h"
+
+#include "nsString.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla::dom {
+
+class LocalStorage;
+class LocalStorageCacheChild;
+class LocalStorageManager;
+class StorageUsage;
+class StorageDBBridge;
+
+// Interface class on which only the database or IPC may call.
+// Used to populate the cache with DB data.
+class LocalStorageCacheBridge {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+ NS_IMETHOD_(void) Release(void);
+
+ // The origin of the cache, result is concatenation of OriginNoSuffix() and
+ // OriginSuffix(), see below.
+ virtual const nsCString Origin() const = 0;
+
+ // The origin attributes suffix alone, this is usually passed as an
+ // |aOriginSuffix| argument to various methods
+ virtual const nsCString& OriginSuffix() const = 0;
+
+ // The origin in the database usage format (reversed) and without the suffix
+ virtual const nsCString& OriginNoSuffix() const = 0;
+
+ // Whether the cache is already fully loaded
+ virtual bool Loaded() = 0;
+
+ // How many items has so far been loaded into the cache, used
+ // for optimization purposes
+ virtual uint32_t LoadedCount() = 0;
+
+ // Called by the database to load a key and its value to the cache
+ virtual bool LoadItem(const nsAString& aKey, const nsAString& aValue) = 0;
+
+ // Called by the database after all keys and values has been loaded
+ // to this cache
+ virtual void LoadDone(nsresult aRv) = 0;
+
+ // Use to synchronously wait until the cache gets fully loaded with data,
+ // this method exits after LoadDone has been called
+ virtual void LoadWait() = 0;
+
+ protected:
+ virtual ~LocalStorageCacheBridge() = default;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+// Implementation of scope cache that is responsible for preloading data
+// for persistent storage (localStorage) and hold data for non-private,
+// private and session-only cookie modes. It is also responsible for
+// persisting data changes using the database, works as a write-back cache.
+class LocalStorageCache : public LocalStorageCacheBridge {
+ public:
+ void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LocalStorage); }
+
+ void SetActor(LocalStorageCacheChild* aActor);
+
+ void ClearActor() {
+ AssertIsOnOwningThread();
+
+ mActor = nullptr;
+ }
+
+ NS_IMETHOD_(void) Release(void) override;
+
+ enum MutationSource {
+ // The mutation is a result of an explicit JS mutation in this process.
+ // The mutation should be sent to the sDatabase. Quota will be checked and
+ // QuotaExceededError may be returned without the mutation being applied.
+ ContentMutation,
+ // The mutation initially was triggered in a different process and is being
+ // propagated to this cache via LocalStorage::ApplyEvent. The mutation
+ // should
+ // not be sent to the sDatabase because the originating process is already
+ // doing that. (In addition to the redundant writes being wasteful, there
+ // is the potential for other processes to see inconsistent state from the
+ // database while preloading.) Quota will be updated but not checked
+ // because it's assumed it was checked in another process and data-coherency
+ // is more important than slightly exceeding quota.
+ E10sPropagated
+ };
+
+ // Note: We pass aOriginNoSuffix through the ctor here, because
+ // LocalStorageCacheHashKey's ctor is creating this class and
+ // accepts reversed-origin-no-suffix as an argument - the hashing key.
+ explicit LocalStorageCache(const nsACString* aOriginNoSuffix);
+
+ protected:
+ virtual ~LocalStorageCache();
+
+ public:
+ void Init(LocalStorageManager* aManager, bool aPersistent,
+ nsIPrincipal* aPrincipal, const nsACString& aQuotaOriginScope);
+
+ // Get size of per-origin data.
+ int64_t GetOriginQuotaUsage(const LocalStorage* aStorage) const;
+
+ // Starts async preload of this cache if it persistent and not loaded.
+ void Preload();
+
+ // The set of methods that are invoked by DOM storage web API.
+ // We are passing the LocalStorage object just to let the cache
+ // read properties like mPrivate and mSessionOnly.
+ // Get* methods return error when load from the database has failed.
+ nsresult GetLength(const LocalStorage* aStorage, uint32_t* aRetval);
+ nsresult GetKey(const LocalStorage* aStorage, uint32_t index,
+ nsAString& aRetval);
+ nsresult GetItem(const LocalStorage* aStorage, const nsAString& aKey,
+ nsAString& aRetval);
+ nsresult SetItem(const LocalStorage* aStorage, const nsAString& aKey,
+ const nsAString& aValue, nsString& aOld,
+ const MutationSource aSource = ContentMutation);
+ nsresult RemoveItem(const LocalStorage* aStorage, const nsAString& aKey,
+ nsString& aOld,
+ const MutationSource aSource = ContentMutation);
+ nsresult Clear(const LocalStorage* aStorage,
+ const MutationSource aSource = ContentMutation);
+
+ void GetKeys(const LocalStorage* aStorage, nsTArray<nsString>& aKeys);
+
+ // LocalStorageCacheBridge
+
+ const nsCString Origin() const override;
+ const nsCString& OriginNoSuffix() const override { return mOriginNoSuffix; }
+ const nsCString& OriginSuffix() const override { return mOriginSuffix; }
+ bool Loaded() override { return mLoaded; }
+ uint32_t LoadedCount() override;
+ bool LoadItem(const nsAString& aKey, const nsAString& aValue) override;
+ void LoadDone(nsresult aRv) override;
+ void LoadWait() override;
+
+ // Cache keeps 3 sets of data: regular, private and session-only.
+ // This class keeps keys and values for a set and also caches
+ // size of the data for quick per-origin quota checking.
+ class Data {
+ public:
+ Data() : mOriginQuotaUsage(0) {}
+ int64_t mOriginQuotaUsage;
+ nsTHashMap<nsStringHashKey, nsString> mKeys;
+ };
+
+ public:
+ // Number of data sets we keep: default, session
+ static const uint32_t kDataSetCount = 2;
+
+ private:
+ // API to clear the cache data, this is invoked by chrome operations
+ // like cookie deletion.
+ friend class LocalStorageManager;
+
+ static const uint32_t kUnloadDefault = 1 << 0;
+ static const uint32_t kUnloadSession = 1 << 1;
+ static const uint32_t kUnloadComplete = kUnloadDefault | kUnloadSession;
+
+#ifdef DOM_STORAGE_TESTS
+ static const uint32_t kTestReload = 1 << 15;
+#endif
+
+ void UnloadItems(uint32_t aUnloadFlags);
+
+ private:
+ // Synchronously blocks until the cache is fully loaded from the database
+ void WaitForPreload(mozilla::Telemetry::HistogramID aTelemetryID);
+
+ // Helper to get one of the 3 data sets (regular, private, session)
+ Data& DataSet(const LocalStorage* aStorage);
+
+ // Used for firing storage events and synchronization of caches in other
+ // content processes.
+ void NotifyObservers(const LocalStorage* aStorage, const nsAString& aKey,
+ const nsAString& aOldValue, const nsAString& aNewValue);
+
+ // Whether the storage change is about to persist
+ bool Persist(const LocalStorage* aStorage) const;
+
+ // Changes the quota usage on the given data set if it fits the quota.
+ // If not, then false is returned and no change to the set must be done.
+ // A special case is if aSource==E10sPropagated, then we will return true even
+ // if the change would put us over quota. This is done to ensure coherency of
+ // caches between processes in the face of races. It does allow an attacker
+ // to potentially use N multiples of the quota storage limit if they can
+ // arrange for their origin to execute code in N processes. However, this is
+ // not considered a particularly concerning threat model because it's already
+ // very possible for a rogue page to attempt to intentionally fill up the
+ // user's storage through the use of multiple domains.
+ bool ProcessUsageDelta(uint32_t aGetDataSetIndex, const int64_t aDelta,
+ const MutationSource aSource = ContentMutation);
+ bool ProcessUsageDelta(const LocalStorage* aStorage, const int64_t aDelta,
+ const MutationSource aSource = ContentMutation);
+
+ private:
+ // When a cache is reponsible for its life time (in case of localStorage data
+ // cache) we need to refer our manager since removal of the cache from the
+ // hash table is handled in the destructor by call to the manager. Cache
+ // could potentially overlive the manager, hence the hard ref.
+ RefPtr<LocalStorageManager> mManager;
+
+ // Reference to the usage counter object we check on for eTLD+1 quota limit.
+ // Obtained from the manager during initialization (Init method).
+ RefPtr<StorageUsage> mUsage;
+
+ // The LocalStorageCacheChild is created at the same time of this class.
+ // In normal operation, the actor will be synchronously cleared in our
+ // destructor when we tell it to delete itself. In a shutdown-related edge
+ // case in the parent process for JSM's, it is possible for the actor to be
+ // destroyed while this class remains alive, in which case it will be nulled
+ // out.
+ LocalStorageCacheChild* mActor;
+
+ // The origin this cache belongs to in the "DB format", i.e. reversed
+ nsCString mOriginNoSuffix;
+
+ // The origin attributes suffix
+ nsCString mOriginSuffix;
+
+ // The eTLD+1 scope used to count quota usage. It is in the reversed format
+ // and contains the origin attributes suffix.
+ nsCString mQuotaOriginScope;
+
+ // Non-private Browsing, Private Browsing and Session Only sets.
+ Data mData[kDataSetCount];
+
+ // This monitor is used to wait for full load of data.
+ mozilla::Monitor mMonitor MOZ_UNANNOTATED;
+
+ // Flag that is initially false. When the cache is about to work with
+ // the database (i.e. it is persistent) this flags is set to true after
+ // all keys and coresponding values are loaded from the database.
+ // This flag never goes from true back to false. Since this flag is
+ // critical for mData hashtable synchronization, it's made atomic.
+ Atomic<bool, ReleaseAcquire> mLoaded;
+
+ // Result of load from the database. Valid after mLoaded flag has been set.
+ nsresult mLoadResult;
+
+ // Expected to be only 0 or 1.
+ uint32_t mPrivateBrowsingId;
+
+ // Init() method has been called
+ bool mInitialized : 1;
+
+ // This cache is about to be bound with the database (i.e. it has
+ // to load from the DB first and has to persist when modifying the
+ // default data set.)
+ bool mPersistent : 1;
+
+ // Whether we have already captured state of the cache preload on our first
+ // access.
+ bool mPreloadTelemetryRecorded : 1;
+};
+
+// StorageUsage
+// Infrastructure to manage and check eTLD+1 quota
+class StorageUsageBridge {
+ public:
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(StorageUsageBridge)
+
+ virtual const nsCString& OriginScope() = 0;
+ virtual void LoadUsage(const int64_t aUsage) = 0;
+
+ protected:
+ // Protected destructor, to discourage deletion outside of Release():
+ virtual ~StorageUsageBridge() = default;
+};
+
+class StorageUsage : public StorageUsageBridge {
+ public:
+ explicit StorageUsage(const nsACString& aOriginScope);
+
+ bool CheckAndSetETLD1UsageDelta(
+ uint32_t aDataSetIndex, int64_t aUsageDelta,
+ const LocalStorageCache::MutationSource aSource);
+
+ private:
+ const nsCString& OriginScope() override { return mOriginScope; }
+ void LoadUsage(const int64_t aUsage) override;
+
+ nsCString mOriginScope;
+ int64_t mUsage[LocalStorageCache::kDataSetCount];
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_LocalStorageCache_h
diff --git a/dom/storage/LocalStorageManager.cpp b/dom/storage/LocalStorageManager.cpp
new file mode 100644
index 0000000000..58b57df76a
--- /dev/null
+++ b/dom/storage/LocalStorageManager.cpp
@@ -0,0 +1,454 @@
+/* -*- 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 "LocalStorageManager.h"
+#include "LocalStorage.h"
+#include "StorageDBThread.h"
+#include "StorageIPC.h"
+#include "StorageUtils.h"
+
+#include "nsIEffectiveTLDService.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+
+namespace mozilla::dom {
+
+using namespace StorageUtils;
+
+LocalStorageManager* LocalStorageManager::sSelf = nullptr;
+
+// static
+uint32_t LocalStorageManager::GetOriginQuota() {
+ return StaticPrefs::dom_storage_default_quota() * 1024; // pref is in kBs
+}
+
+// static
+uint32_t LocalStorageManager::GetSiteQuota() {
+ return std::max(StaticPrefs::dom_storage_default_quota(),
+ StaticPrefs::dom_storage_default_site_quota()) *
+ 1024; // pref is in kBs
+}
+
+NS_IMPL_ISUPPORTS(LocalStorageManager, nsIDOMStorageManager,
+ nsILocalStorageManager)
+
+LocalStorageManager::LocalStorageManager() : mCaches(8) {
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ StorageObserver* observer = StorageObserver::Self();
+ NS_ASSERTION(
+ observer,
+ "No StorageObserver, cannot observe private data delete notifications!");
+
+ if (observer) {
+ observer->AddSink(this);
+ }
+
+ 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
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild::GetOrCreate(id);
+ }
+ }
+}
+
+LocalStorageManager::~LocalStorageManager() {
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+
+ sSelf = nullptr;
+}
+
+// static
+nsAutoCString LocalStorageManager::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;
+}
+
+LocalStorageCache* LocalStorageManager::GetCache(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) {
+ CacheOriginHashtable* table = mCaches.GetOrInsertNew(aOriginSuffix);
+ LocalStorageCacheHashKey* entry = table->GetEntry(aOriginNoSuffix);
+ if (!entry) {
+ return nullptr;
+ }
+
+ return entry->cache();
+}
+
+already_AddRefed<StorageUsage> LocalStorageManager::GetOriginUsage(
+ const nsACString& aOriginNoSuffix, const uint32_t aPrivateBrowsingId) {
+ return do_AddRef(mUsages.LookupOrInsertWith(aOriginNoSuffix, [&] {
+ auto usage = MakeRefPtr<StorageUsage>(aOriginNoSuffix);
+
+ StorageDBChild* storageChild =
+ StorageDBChild::GetOrCreate(aPrivateBrowsingId);
+ if (storageChild) {
+ storageChild->AsyncGetUsage(usage);
+ }
+
+ return usage;
+ }));
+}
+
+already_AddRefed<LocalStorageCache> LocalStorageManager::PutCache(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsACString& aQuotaKey, nsIPrincipal* aPrincipal) {
+ CacheOriginHashtable* table = mCaches.GetOrInsertNew(aOriginSuffix);
+ LocalStorageCacheHashKey* entry = table->PutEntry(aOriginNoSuffix);
+ RefPtr<LocalStorageCache> cache = entry->cache();
+
+ // Lifetime handled by the cache, do persist
+ cache->Init(this, true, aPrincipal, aQuotaKey);
+ return cache.forget();
+}
+
+void LocalStorageManager::DropCache(LocalStorageCache* aCache) {
+ if (!NS_IsMainThread()) {
+ NS_WARNING(
+ "StorageManager::DropCache called on a non-main thread, shutting "
+ "down?");
+ }
+
+ CacheOriginHashtable* table = mCaches.GetOrInsertNew(aCache->OriginSuffix());
+ table->RemoveEntry(aCache->OriginNoSuffix());
+}
+
+nsresult LocalStorageManager::GetStorageInternal(
+ CreateMode aCreateMode, mozIDOMWindow* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, const nsAString& aDocumentURI,
+ bool aPrivate, Storage** aRetval) {
+ nsAutoCString originAttrSuffix;
+ nsAutoCString originKey;
+ nsAutoCString quotaKey;
+
+ aStoragePrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
+
+ nsresult rv = aStoragePrincipal->GetStorageOriginKey(originKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = aStoragePrincipal->GetLocalStorageQuotaKey(quotaKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<LocalStorageCache> cache = GetCache(originAttrSuffix, originKey);
+
+ // Get or create a cache for the given scope
+ if (!cache) {
+ if (aCreateMode == CreateMode::UseIfExistsNeverCreate) {
+ *aRetval = nullptr;
+ return NS_OK;
+ }
+
+ if (aCreateMode == CreateMode::CreateIfShouldPreload) {
+ const uint32_t privateBrowsingId =
+ aStoragePrincipal->GetPrivateBrowsingId();
+
+ // This is a demand to just preload the cache, if the scope has
+ // no data stored, bypass creation and preload of the cache.
+ StorageDBChild* db = StorageDBChild::Get(privateBrowsingId);
+ if (db) {
+ if (!db->ShouldPreloadOrigin(LocalStorageManager::CreateOrigin(
+ originAttrSuffix, originKey))) {
+ return NS_OK;
+ }
+ } else {
+ if (originKey.EqualsLiteral("knalb.:about")) {
+ return NS_OK;
+ }
+ }
+ }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ::mozilla::ipc::PrincipalInfo principalInfo;
+ rv = mozilla::ipc::PrincipalToPrincipalInfo(aStoragePrincipal,
+ &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ uint32_t privateBrowsingId;
+ rv = aStoragePrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ // There is always a single instance of a cache per scope
+ // in a single instance of a DOM storage manager.
+ cache = PutCache(originAttrSuffix, originKey, quotaKey, aStoragePrincipal);
+
+#if !defined(MOZ_WIDGET_ANDROID)
+ LocalStorageCacheChild* actor = new LocalStorageCacheChild(cache);
+
+ MOZ_ALWAYS_TRUE(
+ backgroundActor->SendPBackgroundLocalStorageCacheConstructor(
+ actor, principalInfo, originKey, privateBrowsingId));
+
+ cache->SetActor(actor);
+#endif
+ }
+
+ if (aRetval) {
+ nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
+
+ RefPtr<Storage> storage =
+ new LocalStorage(inner, this, cache, aDocumentURI, aPrincipal,
+ aStoragePrincipal, aPrivate);
+ storage.forget(aRetval);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ Storage** aRetval) {
+ return GetStorageInternal(CreateMode::CreateIfShouldPreload, nullptr,
+ aPrincipal, aStoragePrincipal, u""_ns, false,
+ aRetval);
+}
+
+NS_IMETHODIMP
+LocalStorageManager::CreateStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ const nsAString& aDocumentURI, bool aPrivate,
+ Storage** aRetval) {
+ return GetStorageInternal(CreateMode::CreateAlways, aWindow, aPrincipal,
+ aStoragePrincipal, aDocumentURI, aPrivate, aRetval);
+}
+
+NS_IMETHODIMP
+LocalStorageManager::GetStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, bool aPrivate,
+ Storage** aRetval) {
+ return GetStorageInternal(CreateMode::UseIfExistsNeverCreate, aWindow,
+ aPrincipal, aStoragePrincipal, u""_ns, aPrivate,
+ aRetval);
+}
+
+NS_IMETHODIMP
+LocalStorageManager::CloneStorage(Storage* aStorage) {
+ // Cloning is supported only for sessionStorage
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage,
+ bool* aRetval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aStorage);
+ MOZ_ASSERT(aRetval);
+
+ // Only used by sessionStorage.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::GetNextGenLocalStorageEnabled(bool* aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aResult);
+
+ *aResult = NextGenLocalStorageEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::Preload(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::IsPreloaded(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStorageManager::GetState(nsIPrincipal* aPrincipal, JSContext* aContext,
+ Promise** _retval) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(_retval);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void LocalStorageManager::ClearCaches(uint32_t aUnloadFlags,
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope) {
+ for (const auto& cacheEntry : mCaches) {
+ OriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(cacheEntry.GetKey());
+ MOZ_ASSERT(rv);
+ if (!aPattern.Matches(oa)) {
+ // This table doesn't match the given origin attributes pattern
+ continue;
+ }
+
+ CacheOriginHashtable* table = cacheEntry.GetWeak();
+
+ for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
+ LocalStorageCache* cache = iter2.Get()->cache();
+
+ if (aOriginScope.IsEmpty() ||
+ StringBeginsWith(cache->OriginNoSuffix(), aOriginScope)) {
+ cache->UnloadItems(aUnloadFlags);
+ }
+ }
+ }
+}
+
+nsresult LocalStorageManager::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")) {
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
+ return NS_OK;
+ }
+
+ // Clear everything, caches + database
+ if (!strcmp(aTopic, "extension:purge-localStorage-caches")) {
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
+ // This is only meant for SessionStorageManager.
+ return NS_OK;
+ }
+
+ // Clear from caches everything that has been stored
+ // while in session-only mode
+ if (!strcmp(aTopic, "session-only-cleared")) {
+ ClearCaches(LocalStorageCache::kUnloadSession, pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ // Clear all private-browsing caches
+ if (!strcmp(aTopic, "private-browsing-data-cleared")) {
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
+ return NS_OK;
+ }
+
+ // Clear localStorage data belonging to an origin pattern
+ if (!strcmp(aTopic, "clear-origin-attributes-data") ||
+ !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-change")) {
+ // For case caches are still referenced - clear them completely
+ ClearCaches(LocalStorageCache::kUnloadComplete, pattern, ""_ns);
+ mCaches.Clear();
+ return NS_OK;
+ }
+
+#ifdef DOM_STORAGE_TESTS
+ if (!strcmp(aTopic, "test-reload")) {
+ // This immediately completely reloads all caches from the database.
+ ClearCaches(LocalStorageCache::kTestReload, pattern, ""_ns);
+ 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;
+}
+
+// static
+LocalStorageManager* LocalStorageManager::Self() {
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ return sSelf;
+}
+
+LocalStorageManager* LocalStorageManager::Ensure() {
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ 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;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/LocalStorageManager.h b/dom/storage/LocalStorageManager.h
new file mode 100644
index 0000000000..cffb9c0d76
--- /dev/null
+++ b/dom/storage/LocalStorageManager.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageManager_h
+#define mozilla_dom_StorageManager_h
+
+#include "nsIDOMStorageManager.h"
+#include "nsILocalStorageManager.h"
+#include "StorageObserver.h"
+
+#include "LocalStorage.h"
+#include "LocalStorageCache.h"
+#include "mozilla/dom/Storage.h"
+
+#include "nsTHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace dom {
+
+class LocalStorageManager final : public nsIDOMStorageManager,
+ public nsILocalStorageManager,
+ public StorageObserverSink {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMSTORAGEMANAGER
+ NS_DECL_NSILOCALSTORAGEMANAGER
+
+ public:
+ LocalStorageManager();
+
+ // Reads the preference for DOM storage quota
+ static uint32_t GetOriginQuota();
+
+ // Reads the preference for DOM storage site quota
+ static uint32_t GetSiteQuota();
+
+ // Gets (but not ensures) cache for the given scope
+ LocalStorageCache* GetCache(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix);
+
+ // Returns object keeping usage cache for the scope.
+ already_AddRefed<StorageUsage> GetOriginUsage(
+ const nsACString& aOriginNoSuffix, uint32_t aPrivateBrowsingId);
+
+ static nsAutoCString CreateOrigin(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix);
+
+ private:
+ ~LocalStorageManager();
+
+ // StorageObserverSink, handler to various chrome clearing notification
+ nsresult Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) override;
+
+ // Since nsTHashtable doesn't like multiple inheritance, we have to aggregate
+ // LocalStorageCache into the entry.
+ class LocalStorageCacheHashKey : public nsCStringHashKey {
+ public:
+ explicit LocalStorageCacheHashKey(const nsACString* aKey)
+ : nsCStringHashKey(aKey), mCache(new LocalStorageCache(aKey)) {}
+
+ LocalStorageCacheHashKey(LocalStorageCacheHashKey&& aOther)
+ : nsCStringHashKey(std::move(aOther)),
+ mCache(std::move(aOther.mCache)),
+ mCacheRef(std::move(aOther.mCacheRef)) {
+ NS_ERROR("Shouldn't be called");
+ }
+
+ LocalStorageCache* cache() { return mCache; }
+ // Keep the cache referenced forever, used for sessionStorage.
+ void HardRef() { mCacheRef = mCache; }
+
+ private:
+ // weak ref only since cache references its manager.
+ LocalStorageCache* mCache;
+ // hard ref when this is sessionStorage to keep it alive forever.
+ RefPtr<LocalStorageCache> 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<LocalStorageCache> PutCache(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsACString& aQuotaKey, nsIPrincipal* aPrincipal);
+
+ enum class CreateMode {
+ // GetStorage: do not create if it's not already in memory.
+ UseIfExistsNeverCreate,
+ // CreateStorage: Create it if it's not already in memory.
+ CreateAlways,
+ // PrecacheStorage: Create only if the database says we ShouldPreloadOrigin.
+ CreateIfShouldPreload
+ };
+
+ // Helper for creation of DOM storage objects
+ nsresult GetStorageInternal(CreateMode aCreate, mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ const nsAString& aDocumentURI, bool aPrivate,
+ Storage** aRetval);
+
+ // Suffix->origin->cache map
+ using CacheOriginHashtable = nsTHashtable<LocalStorageCacheHashKey>;
+ nsClassHashtable<nsCStringHashKey, CacheOriginHashtable> mCaches;
+
+ void ClearCaches(uint32_t aUnloadFlags,
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aKeyPrefix);
+
+ // Global getter of localStorage manager service
+ static LocalStorageManager* Self();
+
+ // Like Self, but creates an instance if we're not yet initialized.
+ static LocalStorageManager* Ensure();
+
+ private:
+ // Keeps usage cache objects for eTLD+1 scopes we have touched.
+ nsTHashMap<nsCString, RefPtr<StorageUsage> > mUsages;
+
+ friend class LocalStorageCache;
+ friend class StorageDBChild;
+ // Releases cache since it is no longer referrered by any Storage object.
+ virtual void DropCache(LocalStorageCache* aCache);
+
+ static LocalStorageManager* sSelf;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StorageManager_h
diff --git a/dom/storage/PBackgroundLocalStorageCache.ipdl b/dom/storage/PBackgroundLocalStorageCache.ipdl
new file mode 100644
index 0000000000..1a98224016
--- /dev/null
+++ b/dom/storage/PBackgroundLocalStorageCache.ipdl
@@ -0,0 +1,43 @@
+/* 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 PBackground;
+
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PBackgroundLocalStorageCache
+{
+ manager PBackground;
+
+parent:
+ async DeleteMe();
+
+ async Notify(nsString documentURI,
+ nsString key,
+ nsString oldValue,
+ nsString newValue);
+
+child:
+ // The principalInfo and privateBrowsingId could instead be retained by the
+ // LocalStorageCacheChild/LocalStorageCache instead of being re-transmitted.
+ // However, these changes are a temporary optimization intended for uplift,
+ // and this constant factor overhead is very small compared to the upside of
+ // filtering.
+ async Observe(PrincipalInfo principalInfo,
+ PrincipalInfo cachePrincipalInfo,
+ uint32_t privateBrowsingId,
+ nsString documentURI,
+ nsString key,
+ nsString oldValue,
+ nsString newValue);
+
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/PBackgroundSessionStorageCache.ipdl b/dom/storage/PBackgroundSessionStorageCache.ipdl
new file mode 100644
index 0000000000..35f06f43ec
--- /dev/null
+++ b/dom/storage/PBackgroundSessionStorageCache.ipdl
@@ -0,0 +1,75 @@
+/* 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 PBackground;
+include protocol PBackgroundSessionStorageManager;
+include PBackgroundSharedTypes;
+
+namespace mozilla {
+namespace dom {
+
+struct SSSetItemInfo
+{
+ nsString key;
+ nsString value;
+};
+
+struct SSRemoveItemInfo
+{
+ nsString key;
+};
+
+struct SSClearInfo
+{
+};
+
+/**
+ * Union of SessionStorage mutation types.
+ */
+union SSWriteInfo
+{
+ SSSetItemInfo;
+ SSRemoveItemInfo;
+ SSClearInfo;
+};
+
+struct SSCacheCopy {
+ nsCString originKey;
+ PrincipalInfo principalInfo;
+ SSSetItemInfo[] data;
+};
+
+[ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PBackgroundSessionStorageCache
+{
+ manager PBackgroundSessionStorageManager;
+
+ parent:
+ async DeleteMe();
+
+ /**
+ * Copy SessionStorageCache from the parent process to the content process.
+ * See SessionStorageManager documentation for more details.
+ *
+ * This needs to be synchronous because SessionStorage's semantics are
+ * synchronous. Note that the BackgroundSessionStorageManager in the
+ * PBackground parent already has the answers to this request immediately
+ * available without needing to consult any other threads or perform any I/O.
+ */
+ sync Load()
+ returns (SSSetItemInfo[] aData);
+
+ /**
+ * Send changes for SessionStorageCache from a content process to the parent
+ * process so that the data in the parent can be updated to be in sync with
+ * the content. See SessionStorageManager documentation for more details.
+ */
+ async Checkpoint(SSWriteInfo[] aWriteInfos);
+
+ child:
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/PBackgroundSessionStorageManager.ipdl b/dom/storage/PBackgroundSessionStorageManager.ipdl
new file mode 100644
index 0000000000..50041c78f7
--- /dev/null
+++ b/dom/storage/PBackgroundSessionStorageManager.ipdl
@@ -0,0 +1,37 @@
+/* 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 PBackground;
+include protocol PBackgroundSessionStorageCache;
+include PBackgroundSharedTypes;
+
+include "mozilla/dom/quota/SerializationHelpers.h";
+
+using mozilla::OriginAttributesPattern
+ from "mozilla/OriginAttributes.h";
+
+namespace mozilla {
+namespace dom {
+
+[ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PBackgroundSessionStorageManager
+{
+ manager PBackground;
+ manages PBackgroundSessionStorageCache;
+
+ parent:
+ async PBackgroundSessionStorageCache(PrincipalInfo aPrincipalInfo, nsCString aOriginKey);
+
+ async ClearStorages(OriginAttributesPattern aPattern, nsCString aOriginScope);
+
+ async DeleteMe();
+
+ child:
+ async __delete__();
+
+ async ClearStoragesForOrigin(nsCString aOriginAttrs, nsCString aOriginKey);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/PBackgroundSessionStorageService.ipdl b/dom/storage/PBackgroundSessionStorageService.ipdl
new file mode 100644
index 0000000000..ef6087f0c3
--- /dev/null
+++ b/dom/storage/PBackgroundSessionStorageService.ipdl
@@ -0,0 +1,22 @@
+/* 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 PBackground;
+
+namespace mozilla {
+namespace dom {
+
+[ChildImpl="SessionStorageService"]
+protocol PBackgroundSessionStorageService
+{
+ manager PBackground;
+
+ parent:
+ async __delete__();
+
+ async ClearStoragesForOrigin(nsCString originAttrs, nsCString originKey);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/storage/PBackgroundStorage.ipdl b/dom/storage/PBackgroundStorage.ipdl
new file mode 100644
index 0000000000..44e7c8a612
--- /dev/null
+++ b/dom/storage/PBackgroundStorage.ipdl
@@ -0,0 +1,70 @@
+/* -*- 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 PBackground;
+
+include "mozilla/dom/quota/SerializationHelpers.h";
+
+using mozilla::OriginAttributesPattern
+ from "mozilla/OriginAttributes.h";
+
+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.
+ */
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PBackgroundStorage
+{
+ manager PBackground;
+
+parent:
+ async DeleteMe();
+
+ 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();
+
+ // These are privileged operations for use only by the observer API for
+ // delayed initialization and clearing origins and will never be used from
+ // content process children. Ideally these would be guarded by checks or
+ // exist on a separate, privileged interface, but PBackgroundStorage is
+ // already insecure.
+ async Startup();
+ async ClearAll();
+ async ClearMatchingOrigin(nsCString originNoSuffix);
+ async ClearMatchingOriginAttributes(OriginAttributesPattern pattern);
+
+child:
+ async __delete__();
+
+ 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/PSessionStorageObserver.ipdl b/dom/storage/PSessionStorageObserver.ipdl
new file mode 100644
index 0000000000..ad223346db
--- /dev/null
+++ b/dom/storage/PSessionStorageObserver.ipdl
@@ -0,0 +1,42 @@
+/* -*- 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 {
+
+/**
+ * Protocol used to relay chrome observer notifications related to clearing data
+ * to SessionStorageManager instances in content processes. A single instance is
+ * created by each content process when LocalStorage NextGen is enabled. When
+ * LSNG is disabled, the notifications are instead propagated via
+ * PBackgroundStorageChild. This does mean there are potentially slight ordering
+ * differences in when the notification will be received and processed. It's
+ * okay for this protocol to be managed by PContent rather than PBackground
+ * because these notifications are both rare and to-child-only. (Legacy
+ * LocalStorage was moved to PBackground from PContent because of parent-process
+ * main-thread contention for the processing of "parent:" messages in a very
+ * performance-sensitive context!)
+ */
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PSessionStorageObserver
+{
+ manager PContent;
+
+parent:
+ async DeleteMe();
+
+child:
+ async __delete__();
+
+ async Observe(nsCString topic,
+ nsString originAttributesPattern,
+ nsCString originScope);
+};
+
+}
+}
diff --git a/dom/storage/PartitionedLocalStorage.cpp b/dom/storage/PartitionedLocalStorage.cpp
new file mode 100644
index 0000000000..847d578668
--- /dev/null
+++ b/dom/storage/PartitionedLocalStorage.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 "PartitionedLocalStorage.h"
+#include "SessionStorageCache.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/dom/StorageBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PartitionedLocalStorage, Storage);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PartitionedLocalStorage)
+NS_INTERFACE_MAP_END_INHERITING(Storage)
+
+NS_IMPL_ADDREF_INHERITED(PartitionedLocalStorage, Storage)
+NS_IMPL_RELEASE_INHERITED(PartitionedLocalStorage, Storage)
+
+PartitionedLocalStorage::PartitionedLocalStorage(
+ nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, SessionStorageCache* aCache)
+ : Storage(aWindow, aPrincipal, aStoragePrincipal), mCache(aCache) {}
+
+PartitionedLocalStorage::~PartitionedLocalStorage() = default;
+
+int64_t PartitionedLocalStorage::GetOriginQuotaUsage() const {
+ return mCache->GetOriginQuotaUsage();
+}
+
+uint32_t PartitionedLocalStorage::GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return 0;
+ }
+
+ return mCache->Length();
+}
+
+void PartitionedLocalStorage::Key(uint32_t aIndex, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ mCache->Key(aIndex, aResult);
+}
+
+void PartitionedLocalStorage::GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ mCache->GetItem(aKey, aResult);
+}
+
+void PartitionedLocalStorage::GetSupportedNames(nsTArray<nsString>& aKeys) {
+ if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
+ // return just an empty array
+ aKeys.Clear();
+ return;
+ }
+
+ mCache->GetKeys(aKeys);
+}
+
+void PartitionedLocalStorage::SetItem(const nsAString& aKey,
+ const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsString oldValue;
+ nsresult rv = mCache->SetItem(aKey, aValue, oldValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return;
+ }
+}
+
+void PartitionedLocalStorage::RemoveItem(const nsAString& aKey,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsString oldValue;
+ nsresult rv = mCache->RemoveItem(aKey, oldValue);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return;
+ }
+}
+
+void PartitionedLocalStorage::Clear(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ uint32_t length = GetLength(aSubjectPrincipal, aRv);
+ if (!length) {
+ return;
+ }
+
+ mCache->Clear();
+}
+
+bool PartitionedLocalStorage::IsForkOf(const Storage* aOther) const {
+ MOZ_ASSERT(aOther);
+ if (aOther->Type() != eLocalStorage) {
+ return false;
+ }
+
+ return mCache == static_cast<const PartitionedLocalStorage*>(aOther)->mCache;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/PartitionedLocalStorage.h b/dom/storage/PartitionedLocalStorage.h
new file mode 100644
index 0000000000..6be4a6a7ab
--- /dev/null
+++ b/dom/storage/PartitionedLocalStorage.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PartitionedLocalStorage_h
+#define mozilla_dom_PartitionedLocalStorage_h
+
+#include "Storage.h"
+
+class nsIPrincipal;
+
+namespace mozilla::dom {
+
+class SessionStorageCache;
+
+// PartitionedLocalStorage is a in-memory-only storage exposed to trackers. It
+// doesn't share data with other contexts.
+
+class PartitionedLocalStorage final : public Storage {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PartitionedLocalStorage, Storage)
+
+ PartitionedLocalStorage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ SessionStorageCache* aCache);
+
+ StorageType Type() const override { return ePartitionedLocalStorage; }
+
+ int64_t GetOriginQuotaUsage() const override;
+
+ bool IsForkOf(const Storage* aStorage) const override;
+
+ // WebIDL
+ uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Key(uint32_t aIndex, nsAString& aResult, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void GetSupportedNames(nsTArray<nsString>& aKeys) override;
+
+ void SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ private:
+ ~PartitionedLocalStorage();
+
+ RefPtr<SessionStorageCache> mCache;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_PartitionedLocalStorage_h
diff --git a/dom/storage/SessionStorage.cpp b/dom/storage/SessionStorage.cpp
new file mode 100644
index 0000000000..4f13370338
--- /dev/null
+++ b/dom/storage/SessionStorage.cpp
@@ -0,0 +1,258 @@
+/* -*- 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 "SessionStorage.h"
+
+#include "SessionStorageCache.h"
+#include "SessionStorageManager.h"
+
+#include "mozilla/dom/StorageBinding.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+#include "nsIPrincipal.h"
+#include "nsPIDOMWindow.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(SessionStorage, Storage, mManager);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStorage)
+NS_INTERFACE_MAP_END_INHERITING(Storage)
+
+NS_IMPL_ADDREF_INHERITED(SessionStorage, Storage)
+NS_IMPL_RELEASE_INHERITED(SessionStorage, Storage)
+
+SessionStorage::SessionStorage(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ SessionStorageCache* aCache,
+ SessionStorageManager* aManager,
+ const nsAString& aDocumentURI, bool aIsPrivate)
+ : Storage(aWindow, aPrincipal, aStoragePrincipal),
+ mCache(aCache),
+ mManager(aManager),
+ mDocumentURI(aDocumentURI),
+ mIsPrivate(aIsPrivate),
+ mHasPendingStableStateCallback(false) {
+ MOZ_ASSERT(aCache);
+}
+
+SessionStorage::~SessionStorage() = default;
+
+int64_t SessionStorage::GetOriginQuotaUsage() const {
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 0;
+ }
+
+ return mCache->GetOriginQuotaUsage();
+}
+
+uint32_t SessionStorage::GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return 0;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return 0;
+ }
+
+ return mCache->Length();
+}
+
+void SessionStorage::Key(uint32_t aIndex, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ mCache->Key(aIndex, aResult);
+}
+
+void SessionStorage::GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ mCache->GetItem(aKey, aResult);
+}
+
+void SessionStorage::GetSupportedNames(nsTArray<nsString>& aKeys) {
+ if (!CanUseStorage(*nsContentUtils::SubjectPrincipal())) {
+ // return just an empty array
+ aKeys.Clear();
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // return just an empty array
+ aKeys.Clear();
+ return;
+ }
+
+ mCache->GetKeys(aKeys);
+}
+
+void SessionStorage::SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsString oldValue;
+ rv = mCache->SetItem(aKey, aValue, oldValue);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return;
+ }
+
+ BroadcastChangeNotification(aKey, oldValue, aValue);
+}
+
+void SessionStorage::RemoveItem(const nsAString& aKey,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ if (!CanUseStorage(aSubjectPrincipal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsString oldValue;
+ rv = mCache->RemoveItem(aKey, oldValue);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+ return;
+ }
+
+ BroadcastChangeNotification(aKey, oldValue, VoidString());
+}
+
+void SessionStorage::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ uint32_t length = GetLength(aSubjectPrincipal, aRv);
+ if (!length) {
+ return;
+ }
+
+ nsresult rv = EnsureCacheLoadedOrCloned();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ mCache->Clear();
+ BroadcastChangeNotification(VoidString(), VoidString(), VoidString());
+}
+
+void SessionStorage::BroadcastChangeNotification(const nsAString& aKey,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ NotifyChange(this, StoragePrincipal(), aKey, aOldValue, aNewValue,
+ u"sessionStorage", mDocumentURI, mIsPrivate, false);
+
+ // Sync changes on SessionStorageCache at the next statble state.
+ if (mManager->CanLoadData()) {
+ MaybeScheduleStableStateCallback();
+ }
+}
+
+bool SessionStorage::IsForkOf(const Storage* aOther) const {
+ MOZ_ASSERT(aOther);
+ if (aOther->Type() != eSessionStorage) {
+ return false;
+ }
+
+ return mCache == static_cast<const SessionStorage*>(aOther)->mCache;
+}
+
+void SessionStorage::MaybeScheduleStableStateCallback() {
+ AssertIsOnOwningThread();
+
+ if (!mHasPendingStableStateCallback) {
+ nsContentUtils::RunInStableState(
+ NewRunnableMethod("SessionStorage::StableStateCallback", this,
+ &SessionStorage::StableStateCallback));
+
+ mHasPendingStableStateCallback = true;
+ }
+}
+
+void SessionStorage::StableStateCallback() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mHasPendingStableStateCallback);
+ MOZ_ASSERT(mManager);
+ MOZ_ASSERT(mCache);
+
+ mHasPendingStableStateCallback = false;
+
+ if (mManager->CanLoadData()) {
+ mManager->CheckpointData(*StoragePrincipal(), *mCache);
+ }
+}
+
+nsresult SessionStorage::EnsureCacheLoadedOrCloned() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mManager);
+
+ if (!mManager->CanLoadData()) {
+ return NS_OK;
+ }
+
+ // Ensure manager actor.
+ nsresult rv = mManager->EnsureManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Ensure cache is loaded or cloned.
+ if (mCache->WasLoadedOrCloned()) {
+ return NS_OK;
+ }
+
+ return mManager->LoadData(*StoragePrincipal(), *mCache);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/SessionStorage.h b/dom/storage/SessionStorage.h
new file mode 100644
index 0000000000..de20282276
--- /dev/null
+++ b/dom/storage/SessionStorage.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStorage_h
+#define mozilla_dom_SessionStorage_h
+
+#include "Storage.h"
+
+class nsIPrincipal;
+
+namespace mozilla::dom {
+
+class SessionStorageCache;
+class SessionStorageManager;
+
+class SessionStorage final : public Storage {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SessionStorage, Storage)
+
+ SessionStorage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal, SessionStorageCache* aCache,
+ SessionStorageManager* aManager, const nsAString& aDocumentURI,
+ bool aIsPrivate);
+
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(SessionStorage);
+ }
+
+ StorageType Type() const override { return eSessionStorage; }
+
+ SessionStorageManager* Manager() const { return mManager; }
+
+ SessionStorageCache* Cache() const { return mCache; }
+
+ int64_t GetOriginQuotaUsage() const override;
+
+ bool IsForkOf(const Storage* aStorage) const override;
+
+ // WebIDL
+ uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Key(uint32_t aIndex, nsAString& aResult, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void GetSupportedNames(nsTArray<nsString>& aKeys) override;
+
+ void SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ void RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) override;
+
+ void Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) override;
+
+ private:
+ ~SessionStorage();
+
+ void BroadcastChangeNotification(const nsAString& aKey,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue);
+
+ void MaybeScheduleStableStateCallback();
+
+ void StableStateCallback();
+
+ nsresult EnsureCacheLoadedOrCloned() const;
+
+ RefPtr<SessionStorageCache> mCache;
+ RefPtr<SessionStorageManager> mManager;
+
+ nsString mDocumentURI;
+ bool mIsPrivate;
+ bool mHasPendingStableStateCallback;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStorage_h
diff --git a/dom/storage/SessionStorageCache.cpp b/dom/storage/SessionStorageCache.cpp
new file mode 100644
index 0000000000..40f29ebc26
--- /dev/null
+++ b/dom/storage/SessionStorageCache.cpp
@@ -0,0 +1,292 @@
+/* -*- 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 "SessionStorageCache.h"
+
+#include "LocalStorageManager.h"
+#include "StorageIPC.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/LSWriteOptimizer.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "nsDOMString.h"
+
+namespace mozilla::dom {
+
+void SSWriteOptimizer::Enumerate(nsTArray<SSWriteInfo>& aWriteInfos) {
+ AssertIsOnOwningThread();
+
+ // The mWriteInfos hash table contains all write infos, but it keeps them in
+ // an arbitrary order, which means write infos need to be sorted before being
+ // processed.
+
+ nsTArray<NotNull<WriteInfo*>> writeInfos;
+ GetSortedWriteInfos(writeInfos);
+
+ for (const auto& writeInfo : writeInfos) {
+ switch (writeInfo->GetType()) {
+ case WriteInfo::InsertItem: {
+ const auto& insertItemInfo =
+ static_cast<const InsertItemInfo&>(*writeInfo);
+
+ aWriteInfos.AppendElement(
+ SSSetItemInfo{nsString{insertItemInfo.GetKey()},
+ nsString{insertItemInfo.GetValue()}});
+
+ break;
+ }
+
+ case WriteInfo::UpdateItem: {
+ const auto& updateItemInfo =
+ static_cast<const UpdateItemInfo&>(*writeInfo);
+
+ if (updateItemInfo.UpdateWithMove()) {
+ // See the comment in LSWriteOptimizer::InsertItem for more details
+ // about the UpdateWithMove flag.
+
+ aWriteInfos.AppendElement(
+ SSRemoveItemInfo{nsString{updateItemInfo.GetKey()}});
+ }
+
+ aWriteInfos.AppendElement(
+ SSSetItemInfo{nsString{updateItemInfo.GetKey()},
+ nsString{updateItemInfo.GetValue()}});
+
+ break;
+ }
+
+ case WriteInfo::DeleteItem: {
+ const auto& deleteItemInfo =
+ static_cast<const DeleteItemInfo&>(*writeInfo);
+
+ aWriteInfos.AppendElement(
+ SSRemoveItemInfo{nsString{deleteItemInfo.GetKey()}});
+
+ break;
+ }
+
+ case WriteInfo::Truncate: {
+ aWriteInfos.AppendElement(SSClearInfo{});
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type!");
+ }
+ }
+}
+
+SessionStorageCache::SessionStorageCache()
+ : mActor(nullptr), mLoadedOrCloned(false) {}
+
+SessionStorageCache::~SessionStorageCache() {
+ if (mActor) {
+ mActor->SendDeleteMeInternal();
+ MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+ }
+}
+
+int64_t SessionStorageCache::GetOriginQuotaUsage() {
+ return mDataSet.mOriginQuotaUsage;
+}
+
+uint32_t SessionStorageCache::Length() { return mDataSet.mKeys.Count(); }
+
+void SessionStorageCache::Key(uint32_t aIndex, nsAString& aResult) {
+ aResult.SetIsVoid(true);
+ for (auto iter = mDataSet.mKeys.ConstIter(); !iter.Done(); iter.Next()) {
+ if (aIndex == 0) {
+ aResult = iter.Key();
+ return;
+ }
+ aIndex--;
+ }
+}
+
+void SessionStorageCache::GetItem(const nsAString& aKey, nsAString& aResult) {
+ // not using AutoString since we don't want to copy buffer to result
+ nsString value;
+ if (!mDataSet.mKeys.Get(aKey, &value)) {
+ SetDOMStringToNull(value);
+ }
+ aResult = value;
+}
+
+void SessionStorageCache::GetKeys(nsTArray<nsString>& aKeys) {
+ AppendToArray(aKeys, mDataSet.mKeys.Keys());
+}
+
+nsresult SessionStorageCache::SetItem(const nsAString& aKey,
+ const nsAString& aValue,
+ nsString& aOldValue,
+ bool aRecordWriteInfo) {
+ int64_t delta = 0;
+
+ if (!mDataSet.mKeys.Get(aKey, &aOldValue)) {
+ SetDOMStringToNull(aOldValue);
+
+ // 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>(aOldValue.Length());
+
+ if (aValue == aOldValue &&
+ DOMStringIsNull(aValue) == DOMStringIsNull(aOldValue)) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ if (!mDataSet.ProcessUsageDelta(delta)) {
+ return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+ }
+
+ if (aRecordWriteInfo && XRE_IsContentProcess()) {
+ if (DOMStringIsNull(aOldValue)) {
+ mDataSet.mWriteOptimizer.InsertItem(aKey, aValue);
+ } else {
+ mDataSet.mWriteOptimizer.UpdateItem(aKey, aValue);
+ }
+ }
+
+ mDataSet.mKeys.InsertOrUpdate(aKey, nsString(aValue));
+ return NS_OK;
+}
+
+nsresult SessionStorageCache::RemoveItem(const nsAString& aKey,
+ nsString& aOldValue,
+ bool aRecordWriteInfo) {
+ if (!mDataSet.mKeys.Get(aKey, &aOldValue)) {
+ return NS_SUCCESS_DOM_NO_OPERATION;
+ }
+
+ // Recalculate the cached data size
+ mDataSet.ProcessUsageDelta(-(static_cast<int64_t>(aOldValue.Length()) +
+ static_cast<int64_t>(aKey.Length())));
+
+ if (aRecordWriteInfo && XRE_IsContentProcess()) {
+ mDataSet.mWriteOptimizer.DeleteItem(aKey);
+ }
+
+ mDataSet.mKeys.Remove(aKey);
+ return NS_OK;
+}
+
+void SessionStorageCache::Clear(bool aByUserInteraction,
+ bool aRecordWriteInfo) {
+ mDataSet.ProcessUsageDelta(-mDataSet.mOriginQuotaUsage);
+
+ if (aRecordWriteInfo && XRE_IsContentProcess()) {
+ mDataSet.mWriteOptimizer.Truncate();
+ }
+
+ mDataSet.mKeys.Clear();
+}
+
+void SessionStorageCache::ResetWriteInfos() {
+ mDataSet.mWriteOptimizer.Reset();
+}
+
+already_AddRefed<SessionStorageCache> SessionStorageCache::Clone() const {
+ RefPtr<SessionStorageCache> cache = new SessionStorageCache();
+
+ cache->mDataSet.mOriginQuotaUsage = mDataSet.mOriginQuotaUsage;
+ for (const auto& keyEntry : mDataSet.mKeys) {
+ cache->mDataSet.mKeys.InsertOrUpdate(keyEntry.GetKey(), keyEntry.GetData());
+ cache->mDataSet.mWriteOptimizer.InsertItem(keyEntry.GetKey(),
+ keyEntry.GetData());
+ }
+
+ return cache.forget();
+}
+
+nsTArray<SSSetItemInfo> SessionStorageCache::SerializeData() {
+ nsTArray<SSSetItemInfo> data;
+ for (const auto& keyEntry : mDataSet.mKeys) {
+ data.EmplaceBack(nsString{keyEntry.GetKey()}, keyEntry.GetData());
+ }
+ return data;
+}
+
+nsTArray<SSWriteInfo> SessionStorageCache::SerializeWriteInfos() {
+ nsTArray<SSWriteInfo> writeInfos;
+ mDataSet.mWriteOptimizer.Enumerate(writeInfos);
+ return writeInfos;
+}
+
+void SessionStorageCache::DeserializeData(
+ const nsTArray<SSSetItemInfo>& aData) {
+ Clear(false, /* aRecordWriteInfo */ false);
+ for (const auto& keyValuePair : aData) {
+ nsString oldValue;
+ SetItem(keyValuePair.key(), keyValuePair.value(), oldValue, false);
+ }
+}
+
+void SessionStorageCache::DeserializeWriteInfos(
+ const nsTArray<SSWriteInfo>& aInfos) {
+ for (const auto& writeInfo : aInfos) {
+ switch (writeInfo.type()) {
+ case SSWriteInfo::TSSSetItemInfo: {
+ const SSSetItemInfo& info = writeInfo.get_SSSetItemInfo();
+
+ nsString oldValue;
+ SetItem(info.key(), info.value(), oldValue,
+ /* aRecordWriteInfo */ false);
+
+ break;
+ }
+
+ case SSWriteInfo::TSSRemoveItemInfo: {
+ const SSRemoveItemInfo& info = writeInfo.get_SSRemoveItemInfo();
+
+ nsString oldValue;
+ RemoveItem(info.key(), oldValue,
+ /* aRecordWriteInfo */ false);
+
+ break;
+ }
+
+ case SSWriteInfo::TSSClearInfo: {
+ Clear(false, /* aRecordWriteInfo */ false);
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+ }
+}
+
+void SessionStorageCache::SetActor(SessionStorageCacheChild* aActor) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+
+ mActor = aActor;
+}
+
+void SessionStorageCache::ClearActor() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+bool SessionStorageCache::DataSet::ProcessUsageDelta(int64_t aDelta) {
+ // Check limit per this origin
+ uint64_t newOriginUsage = mOriginQuotaUsage + aDelta;
+ if (aDelta > 0 && newOriginUsage > LocalStorageManager::GetOriginQuota()) {
+ return false;
+ }
+
+ // Update size in our data set
+ mOriginQuotaUsage = newOriginUsage;
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/SessionStorageCache.h b/dom/storage/SessionStorageCache.h
new file mode 100644
index 0000000000..6e5a57d3da
--- /dev/null
+++ b/dom/storage/SessionStorageCache.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStorageCache_h
+#define mozilla_dom_SessionStorageCache_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/LSWriteOptimizerImpl.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom {
+
+class SSSetItemInfo;
+class SSWriteInfo;
+class SessionStorageCacheChild;
+
+/**
+ * Coalescing manipulation queue used by `SessionStorageCache`. Used by
+ * `SessionStorageCache` to buffer and coalesce manipulations before they
+ * are sent to the parent process.
+ */
+class SSWriteOptimizer final : public LSWriteOptimizer<nsAString, nsString> {
+ public:
+ void Enumerate(nsTArray<SSWriteInfo>& aWriteInfos);
+};
+
+class SessionStorageCache final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(SessionStorageCache)
+
+ SessionStorageCache();
+
+ int64_t GetOriginQuotaUsage();
+
+ uint32_t Length();
+
+ void Key(uint32_t aIndex, nsAString& aResult);
+
+ void GetItem(const nsAString& aKey, nsAString& aResult);
+
+ void GetKeys(nsTArray<nsString>& aKeys);
+
+ nsresult SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsString& aOldValue, bool aRecordWriteInfo = true);
+
+ nsresult RemoveItem(const nsAString& aKey, nsString& aOldValue,
+ bool aRecordWriteInfo = true);
+
+ void Clear(bool aByUserInteraction = true, bool aRecordWriteInfo = true);
+
+ void ResetWriteInfos();
+
+ already_AddRefed<SessionStorageCache> Clone() const;
+
+ nsTArray<SSSetItemInfo> SerializeData();
+
+ nsTArray<SSWriteInfo> SerializeWriteInfos();
+
+ void DeserializeData(const nsTArray<SSSetItemInfo>& aData);
+
+ void DeserializeWriteInfos(const nsTArray<SSWriteInfo>& aInfos);
+
+ void SetActor(SessionStorageCacheChild* aActor);
+
+ SessionStorageCacheChild* Actor() const { return mActor; }
+
+ void ClearActor();
+
+ void SetLoadedOrCloned() { mLoadedOrCloned = true; }
+
+ bool WasLoadedOrCloned() const { return mLoadedOrCloned; }
+
+ private:
+ ~SessionStorageCache();
+
+ struct DataSet {
+ DataSet() : mOriginQuotaUsage(0) {}
+
+ bool ProcessUsageDelta(int64_t aDelta);
+
+ nsTHashMap<nsStringHashKey, nsString> mKeys;
+
+ SSWriteOptimizer mWriteOptimizer;
+
+ int64_t mOriginQuotaUsage;
+ };
+
+ DataSet mDataSet;
+
+ SessionStorageCacheChild* mActor;
+ bool mLoadedOrCloned;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStorageCache_h
diff --git a/dom/storage/SessionStorageManager.cpp b/dom/storage/SessionStorageManager.cpp
new file mode 100644
index 0000000000..020083730a
--- /dev/null
+++ b/dom/storage/SessionStorageManager.cpp
@@ -0,0 +1,987 @@
+/* -*- 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 "SessionStorageManager.h"
+
+#include "StorageIPC.h"
+#include "SessionStorage.h"
+#include "SessionStorageCache.h"
+#include "SessionStorageObserver.h"
+#include "StorageUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/PrincipalHashKey.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/dom/PBackgroundSessionStorageManager.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "nsTHashMap.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+using namespace StorageUtils;
+
+// Parent process, background thread hashmap that stores top context id and
+// manager pair.
+static StaticAutoPtr<
+ nsRefPtrHashtable<nsUint64HashKey, BackgroundSessionStorageManager>>
+ sManagers;
+
+bool RecvShutdownBackgroundSessionStorageManagers() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ sManagers = nullptr;
+ return true;
+}
+
+void RecvPropagateBackgroundSessionStorageManager(
+ uint64_t aCurrentTopContextId, uint64_t aTargetTopContextId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (sManagers) {
+ if (RefPtr<BackgroundSessionStorageManager> mgr =
+ sManagers->Get(aCurrentTopContextId)) {
+ mgr->MaybeDispatchSessionStoreUpdate();
+ mgr->SetCurrentBrowsingContextId(aTargetTopContextId);
+ // Because of bfcache, we may re-register aTargetTopContextId in
+ // CanonicalBrowsingContext::ReplacedBy.
+ // XXXBFCache do we want to tweak this behavior and ensure this is
+ // called only once?
+ sManagers->InsertOrUpdate(aTargetTopContextId, std::move(mgr));
+ }
+ }
+}
+
+bool RecvRemoveBackgroundSessionStorageManager(uint64_t aTopContextId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (sManagers) {
+ RefPtr<BackgroundSessionStorageManager> mgr;
+ sManagers->Remove(aTopContextId, getter_AddRefs(mgr));
+
+ if (mgr) {
+ mgr->CancelSessionStoreUpdate();
+ }
+ }
+
+ return true;
+}
+
+bool RecvLoadSessionStorageData(
+ uint64_t aTopContextId,
+ nsTArray<mozilla::dom::SSCacheCopy>&& aCacheCopyList) {
+ if (aCacheCopyList.IsEmpty()) {
+ return true;
+ }
+
+ RefPtr<BackgroundSessionStorageManager> manager =
+ BackgroundSessionStorageManager::GetOrCreate(aTopContextId);
+
+ if (!manager) {
+ return true;
+ }
+
+ for (const auto& cacheInit : aCacheCopyList) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(cacheInit.principalInfo(),
+ attrs);
+
+ nsAutoCString originAttrs;
+ attrs.CreateSuffix(originAttrs);
+
+ manager->UpdateData(originAttrs, cacheInit.originKey(), cacheInit.data());
+ }
+
+ return true;
+}
+
+bool RecvGetSessionStorageData(
+ uint64_t aTopContextId, uint32_t aSizeLimit, bool aCancelSessionStoreTimer,
+ ::mozilla::ipc::PBackgroundParent::GetSessionStorageManagerDataResolver&&
+ aResolver) {
+ nsTArray<mozilla::dom::SSCacheCopy> data;
+ auto resolve = MakeScopeExit([&]() { aResolver(std::move(data)); });
+
+ if (!sManagers) {
+ return true;
+ }
+
+ RefPtr<BackgroundSessionStorageManager> manager =
+ sManagers->Get(aTopContextId);
+ if (!manager) {
+ return true;
+ }
+
+ if (aCancelSessionStoreTimer) {
+ manager->CancelSessionStoreUpdate();
+ }
+
+ manager->GetData(aSizeLimit, data);
+
+ return true;
+}
+
+bool RecvClearStoragesForOrigin(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey) {
+ mozilla::ipc::AssertIsInMainProcess();
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!sManagers) {
+ return true;
+ }
+
+ for (auto& entry : *sManagers) {
+ entry.GetData()->ClearStoragesForOrigin(aOriginAttrs, aOriginKey);
+ }
+
+ return true;
+}
+
+void SessionStorageManagerBase::ClearStoragesInternal(
+ const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) {
+ for (const auto& oaEntry : mOATable) {
+ OriginAttributes oa;
+ DebugOnly<bool> ok = oa.PopulateFromSuffix(oaEntry.GetKey());
+ MOZ_ASSERT(ok);
+ if (!aPattern.Matches(oa)) {
+ // This table doesn't match the given origin attributes pattern
+ continue;
+ }
+
+ OriginKeyHashTable* table = oaEntry.GetWeak();
+ for (const auto& originKeyEntry : *table) {
+ if (aOriginScope.IsEmpty() ||
+ StringBeginsWith(originKeyEntry.GetKey(), aOriginScope)) {
+ const auto cache = originKeyEntry.GetData()->mCache;
+ cache->Clear(false);
+ cache->ResetWriteInfos();
+ }
+ }
+ }
+}
+
+void SessionStorageManagerBase::ClearStoragesForOriginInternal(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) {
+ for (const auto& oaEntry : mOATable) {
+ // Filter tables which match the given origin attrs.
+ if (oaEntry.GetKey() != aOriginAttrs) {
+ continue;
+ }
+
+ OriginKeyHashTable* table = oaEntry.GetWeak();
+ for (const auto& originKeyEntry : *table) {
+ // Match exact origin (without origin attrs).
+ if (originKeyEntry.GetKey() != aOriginKey) {
+ continue;
+ }
+
+ const auto cache = originKeyEntry.GetData()->mCache;
+ cache->Clear(false);
+ cache->ResetWriteInfos();
+ }
+ }
+}
+
+SessionStorageManagerBase::OriginRecord*
+SessionStorageManagerBase::GetOriginRecord(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ const bool aMakeIfNeeded, SessionStorageCache* const aCloneFrom) {
+ // XXX It seems aMakeIfNeeded is always known at compile-time, so this could
+ // be split into two functions.
+
+ if (aMakeIfNeeded) {
+ return mOATable.GetOrInsertNew(aOriginAttrs)
+ ->LookupOrInsertWith(
+ aOriginKey,
+ [&] {
+ auto newOriginRecord = MakeUnique<OriginRecord>();
+ if (aCloneFrom) {
+ newOriginRecord->mCache = aCloneFrom->Clone();
+ } else {
+ newOriginRecord->mCache = new SessionStorageCache();
+ }
+ return newOriginRecord;
+ })
+ .get();
+ }
+
+ auto* const table = mOATable.Get(aOriginAttrs);
+ if (!table) return nullptr;
+
+ return table->Get(aOriginKey);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SessionStorageManager)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMSessionStorageManager)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(SessionStorageManager, mBrowsingContext)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SessionStorageManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SessionStorageManager)
+
+SessionStorageManager::SessionStorageManager(
+ RefPtr<BrowsingContext> aBrowsingContext)
+ : mBrowsingContext(std::move(aBrowsingContext)), mActor(nullptr) {
+ AssertIsOnMainThread();
+
+ StorageObserver* observer = StorageObserver::Self();
+ NS_ASSERTION(
+ observer,
+ "No StorageObserver, cannot observe private data delete notifications!");
+
+ if (observer) {
+ observer->AddSink(this);
+ }
+
+ if (!XRE_IsParentProcess() && NextGenLocalStorageEnabled()) {
+ // When LSNG is enabled the thread IPC bridge doesn't exist, so we have to
+ // create own protocol to distribute chrome observer notifications to
+ // content processes.
+ mObserver = SessionStorageObserver::Get();
+
+ if (!mObserver) {
+ ContentChild* contentActor = ContentChild::GetSingleton();
+ MOZ_ASSERT(contentActor);
+
+ RefPtr<SessionStorageObserver> observer = new SessionStorageObserver();
+
+ SessionStorageObserverChild* actor =
+ new SessionStorageObserverChild(observer);
+
+ MOZ_ALWAYS_TRUE(
+ contentActor->SendPSessionStorageObserverConstructor(actor));
+
+ observer->SetActor(actor);
+
+ mObserver = std::move(observer);
+ }
+ }
+}
+
+SessionStorageManager::~SessionStorageManager() {
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+
+ if (mActor) {
+ mActor->SendDeleteMeInternal();
+ MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+ }
+}
+
+bool SessionStorageManager::CanLoadData() {
+ AssertIsOnMainThread();
+
+ return mBrowsingContext && !mBrowsingContext->IsDiscarded();
+}
+
+void SessionStorageManager::SetActor(SessionStorageManagerChild* aActor) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+
+ mActor = aActor;
+}
+
+bool SessionStorageManager::ActorExists() const {
+ AssertIsOnMainThread();
+
+ return mActor;
+}
+
+void SessionStorageManager::ClearActor() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+}
+
+nsresult SessionStorageManager::EnsureManager() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(CanLoadData());
+
+ if (ActorExists()) {
+ return NS_OK;
+ }
+
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<SessionStorageManagerChild> actor =
+ new SessionStorageManagerChild(this);
+
+ if (!backgroundActor->SendPBackgroundSessionStorageManagerConstructor(
+ actor, mBrowsingContext->Top()->Id())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SetActor(actor);
+
+ return NS_OK;
+}
+
+SessionStorageCacheChild* SessionStorageManager::EnsureCache(
+ nsIPrincipal& aPrincipal, const nsACString& aOriginKey,
+ SessionStorageCache& aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(CanLoadData());
+ MOZ_ASSERT(ActorExists());
+
+ if (aCache.Actor()) {
+ return aCache.Actor();
+ }
+
+ mozilla::ipc::PrincipalInfo info;
+ nsresult rv = PrincipalToPrincipalInfo(&aPrincipal, &info);
+
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ RefPtr<SessionStorageCacheChild> actor =
+ new SessionStorageCacheChild(&aCache);
+ if (!mActor->SendPBackgroundSessionStorageCacheConstructor(actor, info,
+ aOriginKey)) {
+ return nullptr;
+ }
+
+ aCache.SetActor(actor);
+
+ return actor;
+}
+
+nsresult SessionStorageManager::LoadData(nsIPrincipal& aPrincipal,
+ SessionStorageCache& aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+
+ nsAutoCString originKey;
+ nsresult rv = aPrincipal.GetStorageOriginKey(originKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString originAttributes;
+ aPrincipal.OriginAttributesRef().CreateSuffix(originAttributes);
+
+ auto* const originRecord =
+ GetOriginRecord(originAttributes, originKey, true, nullptr);
+ MOZ_ASSERT(originRecord);
+
+ if (originRecord->mLoaded) {
+ return NS_OK;
+ }
+
+ RefPtr<SessionStorageCacheChild> cacheActor =
+ EnsureCache(aPrincipal, originKey, aCache);
+
+ if (!cacheActor) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<SSSetItemInfo> data;
+ if (!cacheActor->SendLoad(&data)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ originRecord->mCache->DeserializeData(data);
+
+ originRecord->mLoaded.Flip();
+ aCache.SetLoadedOrCloned();
+
+ return NS_OK;
+}
+
+void SessionStorageManager::CheckpointData(nsIPrincipal& aPrincipal,
+ SessionStorageCache& aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+
+ nsAutoCString originKey;
+ nsresult rv = aPrincipal.GetStorageOriginKey(originKey);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ return CheckpointDataInternal(aPrincipal, originKey, aCache);
+}
+
+void SessionStorageManager::CheckpointDataInternal(
+ nsIPrincipal& aPrincipal, const nsACString& aOriginKey,
+ SessionStorageCache& aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mActor);
+
+ nsTArray<SSWriteInfo> writeInfos = aCache.SerializeWriteInfos();
+
+ if (writeInfos.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<SessionStorageCacheChild> cacheActor =
+ EnsureCache(aPrincipal, aOriginKey, aCache);
+
+ if (!cacheActor) {
+ return;
+ }
+
+ Unused << cacheActor->SendCheckpoint(writeInfos);
+
+ aCache.ResetWriteInfos();
+}
+
+nsresult SessionStorageManager::ClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) {
+ AssertIsOnMainThread();
+
+ ClearStoragesForOriginInternal(aOriginAttrs, aOriginKey);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::PrecacheStorage(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ Storage** aRetval) {
+ // Nothing to preload.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::GetSessionStorageCache(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aStoragePrincipal,
+ RefPtr<SessionStorageCache>* aRetVal) {
+ return GetSessionStorageCacheHelper(aStoragePrincipal, true, nullptr,
+ aRetVal);
+}
+
+nsresult SessionStorageManager::GetSessionStorageCacheHelper(
+ nsIPrincipal* aPrincipal, bool aMakeIfNeeded,
+ SessionStorageCache* aCloneFrom, RefPtr<SessionStorageCache>* aRetVal) {
+ nsAutoCString originKey;
+ nsAutoCString originAttributes;
+ nsresult rv = aPrincipal->GetStorageOriginKey(originKey);
+ aPrincipal->OriginAttributesRef().CreateSuffix(originAttributes);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return GetSessionStorageCacheHelper(originAttributes, originKey,
+ aMakeIfNeeded, aCloneFrom, aRetVal);
+}
+
+nsresult SessionStorageManager::GetSessionStorageCacheHelper(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ bool aMakeIfNeeded, SessionStorageCache* aCloneFrom,
+ RefPtr<SessionStorageCache>* aRetVal) {
+ if (OriginRecord* const originRecord = GetOriginRecord(
+ aOriginAttrs, aOriginKey, aMakeIfNeeded, aCloneFrom)) {
+ *aRetVal = originRecord->mCache;
+ } else {
+ *aRetVal = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::CreateStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ const nsAString& aDocumentURI,
+ bool aPrivate, Storage** aRetval) {
+ RefPtr<SessionStorageCache> cache;
+ nsresult rv = GetSessionStorageCache(aPrincipal, aStoragePrincipal, &cache);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
+
+ RefPtr<SessionStorage> storage =
+ new SessionStorage(inner, aPrincipal, aStoragePrincipal, cache, this,
+ aDocumentURI, aPrivate);
+
+ storage.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::GetStorage(mozIDOMWindow* aWindow,
+ nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal,
+ bool aPrivate, Storage** aRetval) {
+ *aRetval = nullptr;
+
+ RefPtr<SessionStorageCache> cache;
+ nsresult rv =
+ GetSessionStorageCacheHelper(aStoragePrincipal, false, nullptr, &cache);
+ if (NS_FAILED(rv) || !cache) {
+ return rv;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
+
+ RefPtr<SessionStorage> storage = new SessionStorage(
+ inner, aPrincipal, aStoragePrincipal, cache, this, u""_ns, aPrivate);
+
+ storage.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::CloneStorage(Storage* aStorage) {
+ if (NS_WARN_IF(!aStorage)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (aStorage->Type() != Storage::eSessionStorage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // ToDo: At the moment, we clone the cache on the child process and then
+ // send the checkpoint. It would be nicer if we either serailizing all the
+ // data and sync to the parent process directly or clonig storage on the
+ // parnet process and sync it to the child process on demand.
+
+ RefPtr<SessionStorageCache> cache;
+ nsresult rv = GetSessionStorageCacheHelper(
+ aStorage->StoragePrincipal(), true,
+ static_cast<SessionStorage*>(aStorage)->Cache(), &cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // If cache was cloned from other storage, then we shouldn't load the cache
+ // at the first access.
+ cache->SetLoadedOrCloned();
+
+ if (CanLoadData()) {
+ rv = EnsureManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ CheckpointData(*aStorage->StoragePrincipal(), *cache);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+SessionStorageManager::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage,
+ bool* aRetval) {
+ if (NS_WARN_IF(!aStorage)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!aPrincipal) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aRetval = false;
+
+ RefPtr<SessionStorageCache> cache;
+ nsresult rv =
+ GetSessionStorageCacheHelper(aPrincipal, false, nullptr, &cache);
+ if (NS_FAILED(rv) || !cache) {
+ return rv;
+ }
+
+ if (aStorage->Type() != Storage::eSessionStorage) {
+ return NS_OK;
+ }
+
+ RefPtr<SessionStorage> sessionStorage =
+ static_cast<SessionStorage*>(aStorage);
+ if (sessionStorage->Cache() != cache) {
+ return NS_OK;
+ }
+
+ if (!StorageUtils::PrincipalsEqual(aStorage->StoragePrincipal(),
+ aPrincipal)) {
+ return NS_OK;
+ }
+
+ *aRetval = true;
+ return NS_OK;
+}
+
+void SessionStorageManager::ClearStorages(
+ const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) {
+ if (CanLoadData()) {
+ nsresult rv = EnsureManager();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ mActor->SendClearStorages(aPattern, nsCString(aOriginScope));
+ }
+
+ ClearStoragesInternal(aPattern, aOriginScope);
+}
+
+nsresult SessionStorageManager::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")) {
+ ClearStorages(pattern, ""_ns);
+ return NS_OK;
+ }
+
+ // Clear from caches everything that has been stored
+ // while in session-only mode
+ if (!strcmp(aTopic, "session-only-cleared")) {
+ ClearStorages(pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ // Clear everything (including so and pb data) from caches and database
+ // for the given domain and subdomains.
+ if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
+ ClearStorages(pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ // Clear entries which match an OriginAttributesPattern.
+ if (!strcmp(aTopic, "dom-storage:clear-origin-attributes-data") ||
+ !strcmp(aTopic, "session-storage:clear-origin-attributes-data")) {
+ ClearStorages(pattern, aOriginScope);
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-change")) {
+ // For case caches are still referenced - clear them completely
+ ClearStorages(pattern, ""_ns);
+ mOATable.Clear();
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+SessionStorageManager::OriginRecord::~OriginRecord() = default;
+
+// static
+void BackgroundSessionStorageManager::RemoveManager(uint64_t aTopContextId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ AssertIsOnMainThread();
+
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ if (NS_WARN_IF(!backgroundActor->SendRemoveBackgroundSessionStorageManager(
+ aTopContextId))) {
+ return;
+ }
+}
+
+// static
+void BackgroundSessionStorageManager::PropagateManager(
+ uint64_t aCurrentTopContextId, uint64_t aTargetTopContextId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aCurrentTopContextId != aTargetTopContextId);
+
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ if (NS_WARN_IF(!backgroundActor->SendPropagateBackgroundSessionStorageManager(
+ aCurrentTopContextId, aTargetTopContextId))) {
+ return;
+ }
+}
+
+// static
+void BackgroundSessionStorageManager::LoadData(
+ uint64_t aTopContextId,
+ const nsTArray<mozilla::dom::SSCacheCopy>& aCacheCopyList) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ AssertIsOnMainThread();
+
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ if (NS_WARN_IF(!backgroundActor->SendLoadSessionStorageManagerData(
+ aTopContextId, aCacheCopyList))) {
+ return;
+ }
+}
+
+// static
+BackgroundSessionStorageManager* BackgroundSessionStorageManager::GetOrCreate(
+ uint64_t aTopContextId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (!sManagers) {
+ sManagers = new nsRefPtrHashtable<nsUint64HashKey,
+ BackgroundSessionStorageManager>();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "dom::BackgroundSessionStorageManager::GetOrCreate", [] {
+ RunOnShutdown(
+ [] {
+ ::mozilla::ipc::PBackgroundChild* backgroundActor = ::mozilla::
+ ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return;
+ }
+
+ if (NS_WARN_IF(
+ !backgroundActor
+ ->SendShutdownBackgroundSessionStorageManagers())) {
+ return;
+ }
+ },
+ ShutdownPhase::XPCOMShutdown);
+ }));
+ }
+
+ return sManagers
+ ->LookupOrInsertWith(
+ aTopContextId,
+ [aTopContextId] {
+ return new BackgroundSessionStorageManager(aTopContextId);
+ })
+ .get();
+}
+
+BackgroundSessionStorageManager::BackgroundSessionStorageManager(
+ uint64_t aBrowsingContextId)
+ : mCurrentBrowsingContextId(aBrowsingContextId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+BackgroundSessionStorageManager::~BackgroundSessionStorageManager() = default;
+
+void BackgroundSessionStorageManager::CopyDataToContentProcess(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ nsTArray<SSSetItemInfo>& aData) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ auto* const originRecord =
+ GetOriginRecord(aOriginAttrs, aOriginKey, false, nullptr);
+ if (!originRecord) {
+ return;
+ }
+
+ aData = originRecord->mCache->SerializeData();
+}
+
+/* static */
+RefPtr<BackgroundSessionStorageManager::DataPromise>
+BackgroundSessionStorageManager::GetData(BrowsingContext* aContext,
+ uint32_t aSizeLimit,
+ bool aClearSessionStoreTimer) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aContext->IsTop());
+
+ AssertIsOnMainThread();
+
+ ::mozilla::ipc::PBackgroundChild* backgroundActor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!backgroundActor)) {
+ return DataPromise::CreateAndReject(
+ ::mozilla::ipc::ResponseRejectReason::SendError, __func__);
+ }
+
+ return backgroundActor->SendGetSessionStorageManagerData(
+ aContext->Id(), aSizeLimit, aClearSessionStoreTimer);
+}
+
+void BackgroundSessionStorageManager::GetData(
+ uint32_t aSizeLimit, nsTArray<SSCacheCopy>& aCacheCopyList) {
+ for (auto& managerActor : mParticipatingActors) {
+ for (auto* cacheActor :
+ managerActor->ManagedPBackgroundSessionStorageCacheParent()) {
+ auto* cache = static_cast<SessionStorageCacheParent*>(cacheActor);
+ ::mozilla::ipc::PrincipalInfo info = cache->PrincipalInfo();
+
+ OriginAttributes attributes;
+ StoragePrincipalHelper::GetOriginAttributes(cache->PrincipalInfo(),
+ attributes);
+
+ nsAutoCString originAttrs;
+ attributes.CreateSuffix(originAttrs);
+
+ auto* record =
+ GetOriginRecord(originAttrs, cache->OriginKey(), false, nullptr);
+
+ if (!record) {
+ continue;
+ }
+
+ if (record->mCache->GetOriginQuotaUsage() > aSizeLimit) {
+ continue;
+ }
+
+ nsTArray<SSSetItemInfo> data = record->mCache->SerializeData();
+ if (data.IsEmpty()) {
+ continue;
+ }
+
+ SSCacheCopy& cacheCopy = *aCacheCopyList.AppendElement();
+ cacheCopy.originKey() = cache->OriginKey();
+ cacheCopy.principalInfo() = info;
+ cacheCopy.data().SwapElements(data);
+ }
+ }
+}
+
+void BackgroundSessionStorageManager::UpdateData(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ const nsTArray<SSWriteInfo>& aWriteInfos) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ auto* const originRecord =
+ GetOriginRecord(aOriginAttrs, aOriginKey, true, nullptr);
+ MOZ_ASSERT(originRecord);
+
+ MaybeScheduleSessionStoreUpdate();
+
+ originRecord->mCache->DeserializeWriteInfos(aWriteInfos);
+}
+
+void BackgroundSessionStorageManager::UpdateData(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ const nsTArray<SSSetItemInfo>& aData) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ auto* const originRecord =
+ GetOriginRecord(aOriginAttrs, aOriginKey, true, nullptr);
+ MOZ_ASSERT(originRecord);
+
+ originRecord->mCache->DeserializeData(aData);
+}
+
+void BackgroundSessionStorageManager::ClearStorages(
+ const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ ClearStoragesInternal(aPattern, aOriginScope);
+}
+
+void BackgroundSessionStorageManager::ClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) {
+ ::mozilla::ipc::AssertIsInMainProcess();
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ for (auto& managerActor : mParticipatingActors) {
+ QM_WARNONLY_TRY(OkIf(managerActor->SendClearStoragesForOrigin(
+ nsCString(aOriginAttrs), nsCString(aOriginKey))));
+ }
+
+ ClearStoragesForOriginInternal(aOriginAttrs, aOriginKey);
+}
+
+void BackgroundSessionStorageManager::SetCurrentBrowsingContextId(
+ uint64_t aBrowsingContextId) {
+ MOZ_DIAGNOSTIC_ASSERT(aBrowsingContextId != mCurrentBrowsingContextId);
+ mCurrentBrowsingContextId = aBrowsingContextId;
+}
+
+void BackgroundSessionStorageManager::MaybeScheduleSessionStoreUpdate() {
+ if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ return;
+ }
+
+ if (mSessionStoreCallbackTimer) {
+ return;
+ }
+
+ if (StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
+ DispatchSessionStoreUpdate();
+ return;
+ }
+
+ auto result = NS_NewTimerWithFuncCallback(
+ [](nsITimer*, void* aClosure) {
+ auto* mgr = static_cast<BackgroundSessionStorageManager*>(aClosure);
+ mgr->DispatchSessionStoreUpdate();
+ },
+ this, StaticPrefs::browser_sessionstore_interval(),
+ nsITimer::TYPE_ONE_SHOT,
+ "BackgroundSessionStorageManager::DispatchSessionStoreUpdate");
+
+ if (result.isErr()) {
+ return;
+ }
+
+ mSessionStoreCallbackTimer = result.unwrap();
+}
+
+void BackgroundSessionStorageManager::MaybeDispatchSessionStoreUpdate() {
+ if (mSessionStoreCallbackTimer) {
+ BackgroundSessionStorageManager::DispatchSessionStoreUpdate();
+ }
+}
+
+void BackgroundSessionStorageManager::DispatchSessionStoreUpdate() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "CanonicalBrowsingContext::UpdateSessionStore",
+ [targetBrowsingContextId = mCurrentBrowsingContextId]() {
+ CanonicalBrowsingContext::UpdateSessionStoreForStorage(
+ targetBrowsingContextId);
+ }));
+
+ CancelSessionStoreUpdate();
+}
+
+void BackgroundSessionStorageManager::CancelSessionStoreUpdate() {
+ if (mSessionStoreCallbackTimer) {
+ mSessionStoreCallbackTimer->Cancel();
+ mSessionStoreCallbackTimer = nullptr;
+ }
+}
+
+void BackgroundSessionStorageManager::AddParticipatingActor(
+ SessionStorageManagerParent* aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ mParticipatingActors.AppendElement(aActor);
+}
+
+void BackgroundSessionStorageManager::RemoveParticipatingActor(
+ SessionStorageManagerParent* aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ mParticipatingActors.RemoveElement(aActor);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/SessionStorageManager.h b/dom/storage/SessionStorageManager.h
new file mode 100644
index 0000000000..4880f13270
--- /dev/null
+++ b/dom/storage/SessionStorageManager.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStorageManager_h
+#define mozilla_dom_SessionStorageManager_h
+
+#include "StorageObserver.h"
+
+#include "mozilla/dom/FlippedOnce.h"
+#include "nsIDOMStorageManager.h"
+#include "nsClassHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+
+class nsIPrincipal;
+class nsITimer;
+
+namespace mozilla {
+class OriginAttributesPattern;
+
+namespace dom {
+
+class SSCacheCopy;
+
+bool RecvShutdownBackgroundSessionStorageManagers();
+void RecvPropagateBackgroundSessionStorageManager(uint64_t aCurrentTopContextId,
+ uint64_t aTargetTopContextId);
+bool RecvRemoveBackgroundSessionStorageManager(uint64_t aTopContextId);
+
+bool RecvGetSessionStorageData(
+ uint64_t aTopContextId, uint32_t aSizeLimit, bool aCancelSessionStoreTimer,
+ ::mozilla::ipc::PBackgroundParent::GetSessionStorageManagerDataResolver&&
+ aResolver);
+
+bool RecvLoadSessionStorageData(
+ uint64_t aTopContextId,
+ nsTArray<mozilla::dom::SSCacheCopy>&& aCacheCopyList);
+
+bool RecvClearStoragesForOrigin(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey);
+
+class BrowsingContext;
+class ContentParent;
+class SSSetItemInfo;
+class SSWriteInfo;
+class SessionStorageCache;
+class SessionStorageCacheChild;
+class SessionStorageManagerChild;
+class SessionStorageManagerParent;
+class SessionStorageObserver;
+struct OriginRecord;
+
+// sessionStorage is a data store that's unique to each tab (i.e. top-level
+// browsing context) and origin. Before the advent of Fission all the data
+// for a given tab could be stored in a single content process; now each
+// site-specific process stores only its portion of the data. As content
+// processes terminate, their sessionStorage data needs to be saved in the
+// parent process, in case the same origin appears again in the tab (e.g.
+// by navigating an iframe element). Therefore SessionStorageManager
+// objects exist in both the parent and content processes.
+//
+// Whenever a write operation for SessionStorage executes, the content process
+// sends the changes to the parent process at the next stable state. Whenever a
+// content process navigates to an origin for the first time in a given tab, the
+// parent process sends it the saved data. SessionStorageCache has a flag
+// (mLoadedOrCloned) to ensure that it's only be loaded or cloned once.
+//
+// Note: the current implementation is expected to be replaced by a new
+// implementation using LSNG.
+class SessionStorageManagerBase {
+ public:
+ SessionStorageManagerBase() = default;
+
+ protected:
+ ~SessionStorageManagerBase() = default;
+
+ struct OriginRecord {
+ OriginRecord() = default;
+ OriginRecord(OriginRecord&&) = default;
+ OriginRecord& operator=(OriginRecord&&) = default;
+ ~OriginRecord();
+
+ RefPtr<SessionStorageCache> mCache;
+
+ // A flag to ensure that cache is only loaded once.
+ FlippedOnce<false> mLoaded;
+ };
+
+ void ClearStoragesInternal(const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope);
+
+ void ClearStoragesForOriginInternal(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey);
+
+ OriginRecord* GetOriginRecord(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey,
+ bool aMakeIfNeeded,
+ SessionStorageCache* aCloneFrom);
+
+ using OriginKeyHashTable = nsClassHashtable<nsCStringHashKey, OriginRecord>;
+ nsClassHashtable<nsCStringHashKey, OriginKeyHashTable> mOATable;
+};
+
+class SessionStorageManager final : public SessionStorageManagerBase,
+ public nsIDOMSessionStorageManager,
+ public StorageObserverSink {
+ public:
+ explicit SessionStorageManager(RefPtr<BrowsingContext> aBrowsingContext);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDOMSTORAGEMANAGER
+ NS_DECL_NSIDOMSESSIONSTORAGEMANAGER
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(SessionStorageManager)
+
+ bool CanLoadData();
+
+ void SetActor(SessionStorageManagerChild* aActor);
+
+ bool ActorExists() const;
+
+ void ClearActor();
+
+ nsresult EnsureManager();
+
+ nsresult LoadData(nsIPrincipal& aPrincipal, SessionStorageCache& aCache);
+
+ void CheckpointData(nsIPrincipal& aPrincipal, SessionStorageCache& aCache);
+
+ nsresult ClearStoragesForOrigin(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey);
+
+ private:
+ ~SessionStorageManager();
+
+ // StorageObserverSink, handler to various chrome clearing notification
+ nsresult Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) override;
+
+ nsresult GetSessionStorageCacheHelper(nsIPrincipal* aPrincipal,
+ bool aMakeIfNeeded,
+ SessionStorageCache* aCloneFrom,
+ RefPtr<SessionStorageCache>* aRetVal);
+
+ nsresult GetSessionStorageCacheHelper(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey,
+ bool aMakeIfNeeded,
+ SessionStorageCache* aCloneFrom,
+ RefPtr<SessionStorageCache>* aRetVal);
+
+ void ClearStorages(const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope);
+
+ SessionStorageCacheChild* EnsureCache(nsIPrincipal& aPrincipal,
+ const nsACString& aOriginKey,
+ SessionStorageCache& aCache);
+
+ void CheckpointDataInternal(nsIPrincipal& aPrincipal,
+ const nsACString& aOriginKey,
+ SessionStorageCache& aCache);
+
+ RefPtr<SessionStorageObserver> mObserver;
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+
+ SessionStorageManagerChild* mActor;
+};
+
+/**
+ * A specialized SessionStorageManager class that lives on the parent process
+ * background thread. It is a shadow copy of SessionStorageManager and it's used
+ * to preserve SessionStorageCaches for the other SessionStorageManagers.
+ */
+class BackgroundSessionStorageManager final : public SessionStorageManagerBase {
+ public:
+ // Parent process getter function.
+ static BackgroundSessionStorageManager* GetOrCreate(uint64_t aTopContextId);
+
+ NS_INLINE_DECL_REFCOUNTING(BackgroundSessionStorageManager);
+
+ // Only called by CanonicalBrowsingContext::ReplaceBy on the parent process.
+ static void PropagateManager(uint64_t aCurrentTopContextId,
+ uint64_t aTargetTopContextId);
+
+ // Only called by CanonicalBrowsingContext::CanonicalDiscard on parent
+ // process.
+ static void RemoveManager(uint64_t aTopContextId);
+
+ static void LoadData(
+ uint64_t aTopContextId,
+ const nsTArray<mozilla::dom::SSCacheCopy>& aCacheCopyList);
+
+ using DataPromise =
+ ::mozilla::ipc::PBackgroundChild::GetSessionStorageManagerDataPromise;
+ static RefPtr<DataPromise> GetData(BrowsingContext* aContext,
+ uint32_t aSizeLimit,
+ bool aClearSessionStoreTimer = false);
+
+ void GetData(uint32_t aSizeLimit, nsTArray<SSCacheCopy>& aCacheCopyList);
+
+ void CopyDataToContentProcess(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey,
+ nsTArray<SSSetItemInfo>& aData);
+
+ void UpdateData(const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ const nsTArray<SSWriteInfo>& aWriteInfos);
+
+ void UpdateData(const nsACString& aOriginAttrs, const nsACString& aOriginKey,
+ const nsTArray<SSSetItemInfo>& aData);
+
+ void ClearStorages(const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope);
+
+ void ClearStoragesForOrigin(const nsACString& aOriginAttrs,
+ const nsACString& aOriginKey);
+
+ void SetCurrentBrowsingContextId(uint64_t aBrowsingContextId);
+
+ void MaybeDispatchSessionStoreUpdate();
+
+ void CancelSessionStoreUpdate();
+
+ void AddParticipatingActor(SessionStorageManagerParent* aActor);
+
+ void RemoveParticipatingActor(SessionStorageManagerParent* aActor);
+
+ private:
+ // Only be called by GetOrCreate() on the parent process.
+ explicit BackgroundSessionStorageManager(uint64_t aBrowsingContextId);
+
+ ~BackgroundSessionStorageManager();
+
+ // Sets a timer for notifying main thread that the cache has been
+ // updated. May do nothing if we're coalescing notifications.
+ void MaybeScheduleSessionStoreUpdate();
+
+ void DispatchSessionStoreUpdate();
+
+ // The most current browsing context using this manager
+ uint64_t mCurrentBrowsingContextId;
+
+ // Callback for notifying main thread of calls to `UpdateData`.
+ //
+ // A timer that is held whenever this manager has dirty state that
+ // has not yet been reflected to the main thread. The timer is used
+ // to delay notifying the main thread to ask for changes, thereby
+ // coalescing/throttling changes. (Note that SessionStorage, like
+ // LocalStorage, treats attempts to set a value to its current value
+ // as a no-op.)
+ //
+
+ // The timer is initialized with a fixed delay as soon as the state
+ // becomes dirty; additional mutations to our state will not reset
+ // the timer because then we might never flush to the main
+ // thread. The timer is cleared only when a new set of data is sent
+ // to the main thread and therefore this manager no longer has any
+ // dirty state. This means that there is a period of time after the
+ // nsITimer fires where this value is non-null but there is no
+ // scheduled timer while we wait for the main thread to request the
+ // new state. Callers of GetData can also optionally cancel the
+ // current timer to reduce the amounts of notifications.
+ //
+ // When this manager is moved to a new top-level browsing context id
+ // via a PropagateBackgroundSessionStorageManager message, the
+ // behavior of the timer doesn't change because the main thread knows
+ // about the renaming and is initiating it (and any in-flight
+ // GetSessionStorageManagerData requests will be unaffected because
+ // they use async-returns so the response is inherently matched up via
+ // the issued promise).
+ nsCOMPtr<nsITimer> mSessionStoreCallbackTimer;
+
+ nsTArray<RefPtr<SessionStorageManagerParent>> mParticipatingActors;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SessionStorageManager_h
diff --git a/dom/storage/SessionStorageObserver.cpp b/dom/storage/SessionStorageObserver.cpp
new file mode 100644
index 0000000000..2963a046be
--- /dev/null
+++ b/dom/storage/SessionStorageObserver.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "SessionStorageObserver.h"
+#include "StorageIPC.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+SessionStorageObserver* gSessionStorageObserver = nullptr;
+
+} // namespace
+
+SessionStorageObserver::SessionStorageObserver() : mActor(nullptr) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NextGenLocalStorageEnabled());
+
+ MOZ_ASSERT(!gSessionStorageObserver);
+ gSessionStorageObserver = this;
+}
+
+SessionStorageObserver::~SessionStorageObserver() {
+ AssertIsOnOwningThread();
+
+ if (mActor) {
+ mActor->SendDeleteMeInternal();
+ MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
+ }
+
+ MOZ_ASSERT(gSessionStorageObserver);
+ gSessionStorageObserver = nullptr;
+}
+
+// static
+SessionStorageObserver* SessionStorageObserver::Get() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NextGenLocalStorageEnabled());
+
+ return gSessionStorageObserver;
+}
+
+void SessionStorageObserver::SetActor(SessionStorageObserverChild* aActor) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+
+ mActor = aActor;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/SessionStorageObserver.h b/dom/storage/SessionStorageObserver.h
new file mode 100644
index 0000000000..dee472cc9c
--- /dev/null
+++ b/dom/storage/SessionStorageObserver.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionStorageObserver_h
+#define mozilla_dom_SessionStorageObserver_h
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class SessionStorageObserverChild;
+
+/**
+ * Effectively just a refcounted life-cycle management wrapper around
+ * SessionStorageObserverChild which exists to receive chrome observer
+ * notifications from the main process.
+ *
+ * ## Lifecycle ##
+ * - Created by SessionStorageManager::SessionStorageManager. Placed in the
+ * gSessionStorageObserver variable for subsequent SessionStorageManager's via
+ * SessionStorageObserver::Get lookup.
+ * - The SessionStorageObserverChild directly handles "Observe" messages,
+ * shunting them directly to StorageObserver::Notify which distributes them to
+ * individual observer sinks.
+ * - Destroyed when refcount goes to zero due to all owning
+ * SessionStorageManager being destroyed.
+ */
+class SessionStorageObserver final {
+ friend class SessionStorageManager;
+
+ SessionStorageObserverChild* mActor;
+
+ public:
+ static SessionStorageObserver* Get();
+
+ NS_INLINE_DECL_REFCOUNTING(SessionStorageObserver)
+
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(SessionStorageObserver);
+ }
+
+ void SetActor(SessionStorageObserverChild* aActor);
+
+ void ClearActor() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mActor);
+
+ mActor = nullptr;
+ }
+
+ private:
+ // Only created by SessionStorageManager.
+ SessionStorageObserver();
+
+ ~SessionStorageObserver();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_SessionStorageObserver_h
diff --git a/dom/storage/SessionStorageService.cpp b/dom/storage/SessionStorageService.cpp
new file mode 100644
index 0000000000..fe42354c09
--- /dev/null
+++ b/dom/storage/SessionStorageService.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "SessionStorageService.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+StaticRefPtr<SessionStorageService> gSessionStorageService;
+
+bool gShutdown(false);
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(SessionStorageService, nsISessionStorageService)
+
+NS_IMETHODIMP
+SessionStorageService::ClearStoragesForOrigin(nsIPrincipal* aPrincipal) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ QM_TRY_INSPECT(const auto& originAttrs,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, aPrincipal,
+ GetOriginSuffix));
+
+ QM_TRY_INSPECT(const auto& originKey,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, aPrincipal,
+ GetStorageOriginKey));
+
+ QM_TRY(OkIf(SendClearStoragesForOrigin(originAttrs, originKey)),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+SessionStorageService::SessionStorageService() { AssertIsOnMainThread(); }
+
+SessionStorageService::~SessionStorageService() {
+ AssertIsOnMainThread();
+ MOZ_ASSERT_IF(mInitialized, mActorDestroyed);
+}
+
+// static
+Result<RefPtr<SessionStorageService>, nsresult> SessionStorageService::Acquire(
+ const CreateIfNonExistent&) {
+ AssertIsOnMainThread();
+
+ QM_TRY(OkIf(!gShutdown), Err(NS_ERROR_ILLEGAL_DURING_SHUTDOWN));
+
+ if (gSessionStorageService) {
+ return RefPtr<SessionStorageService>(gSessionStorageService);
+ }
+
+ auto sessionStorageService = MakeRefPtr<SessionStorageService>();
+
+ QM_TRY(sessionStorageService->Init());
+
+ gSessionStorageService = sessionStorageService;
+
+ RunOnShutdown(
+ [] {
+ gShutdown = true;
+
+ gSessionStorageService->Shutdown();
+
+ gSessionStorageService = nullptr;
+ },
+ ShutdownPhase::XPCOMShutdown);
+
+ return sessionStorageService;
+}
+
+// static
+RefPtr<SessionStorageService> SessionStorageService::Acquire() {
+ AssertIsOnMainThread();
+
+ return gSessionStorageService;
+}
+
+Result<Ok, nsresult> SessionStorageService::Init() {
+ AssertIsOnMainThread();
+
+ PBackgroundChild* backgroundActor =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ QM_TRY(OkIf(backgroundActor), Err(NS_ERROR_FAILURE));
+
+ QM_TRY(OkIf(backgroundActor->SendPBackgroundSessionStorageServiceConstructor(
+ this)),
+ Err(NS_ERROR_FAILURE));
+
+ mInitialized.Flip();
+
+ return Ok{};
+}
+
+void SessionStorageService::Shutdown() {
+ AssertIsOnMainThread();
+
+ if (!mActorDestroyed) {
+ QM_WARNONLY_TRY(OkIf(Send__delete__(this)));
+ }
+}
+
+void SessionStorageService::ActorDestroy(ActorDestroyReason /* aWhy */) {
+ AssertIsOnMainThread();
+
+ mActorDestroyed.Flip();
+}
+
+} // namespace mozilla::dom
+
+NS_IMPL_COMPONENT_FACTORY(nsISessionStorageService) {
+ mozilla::AssertIsOnMainThread();
+
+ QM_TRY_UNWRAP(auto sessionStorageService,
+ mozilla::dom::SessionStorageService::Acquire(
+ mozilla::CreateIfNonExistent{}),
+ nullptr);
+
+ return sessionStorageService.forget().downcast<nsISupports>();
+}
diff --git a/dom/storage/SessionStorageService.h b/dom/storage/SessionStorageService.h
new file mode 100644
index 0000000000..70b780a0ee
--- /dev/null
+++ b/dom/storage/SessionStorageService.h
@@ -0,0 +1,55 @@
+/* -*- 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 DOM_STORAGE_SESSIONSTORAGESERVICE_H_
+#define DOM_STORAGE_SESSIONSTORAGESERVICE_H_
+
+#include "nsISessionStorageService.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PBackgroundSessionStorageServiceChild.h"
+
+namespace mozilla {
+
+struct CreateIfNonExistent;
+
+namespace dom {
+
+class SessionStorageService final
+ : public nsISessionStorageService,
+ public PBackgroundSessionStorageServiceChild {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISESSIONSTORAGESERVICE
+
+ SessionStorageService();
+
+ // Singleton Boilerplate
+ static mozilla::Result<RefPtr<SessionStorageService>, nsresult> Acquire(
+ const CreateIfNonExistent&);
+
+ // Can return null if the service hasn't be created yet or after
+ // XPCOMShutdown.
+ static RefPtr<SessionStorageService> Acquire();
+
+ private:
+ ~SessionStorageService();
+
+ mozilla::Result<Ok, nsresult> Init();
+
+ void Shutdown();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ FlippedOnce<false> mInitialized;
+ FlippedOnce<false> mActorDestroyed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* DOM_STORAGE_SESSIONSTORAGESERVICE_H_ */
diff --git a/dom/storage/Storage.cpp b/dom/storage/Storage.cpp
new file mode 100644
index 0000000000..cf0f4f32d7
--- /dev/null
+++ b/dom/storage/Storage.cpp
@@ -0,0 +1,156 @@
+/* -*- 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 "Storage.h"
+#include "StorageNotifierService.h"
+
+#include "mozilla/dom/StorageBinding.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StorageAccess.h"
+#include "nsIObserverService.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Storage, mWindow, mPrincipal,
+ mStoragePrincipal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Storage)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(Storage, LastRelease())
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Storage)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Storage::Storage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal)
+ : mWindow(aWindow),
+ mPrincipal(aPrincipal),
+ mStoragePrincipal(aStoragePrincipal),
+ mPrivateBrowsing(false),
+ mSessionScopedOrLess(false) {
+ MOZ_ASSERT(aPrincipal);
+
+ if (mPrincipal->IsSystemPrincipal()) {
+ mPrivateBrowsing = false;
+ mSessionScopedOrLess = false;
+ } else if (mWindow) {
+ uint32_t rejectedReason = 0;
+ StorageAccess access = StorageAllowedForWindow(mWindow, &rejectedReason);
+
+ mPrivateBrowsing = access == StorageAccess::ePrivateBrowsing;
+ mSessionScopedOrLess = access <= StorageAccess::eSessionScoped;
+ }
+}
+
+Storage::~Storage() = default;
+
+/* static */
+bool Storage::StoragePrefIsEnabled() {
+ return StaticPrefs::dom_storage_enabled();
+}
+
+int64_t Storage::GetSnapshotUsage(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return 0;
+}
+
+bool Storage::CanUseStorage(nsIPrincipal& aSubjectPrincipal) {
+ if (!StoragePrefIsEnabled()) {
+ return false;
+ }
+
+ return aSubjectPrincipal.Subsumes(mPrincipal);
+}
+
+/* virtual */
+JSObject* Storage::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Storage_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace {
+
+class StorageNotifierRunnable : public Runnable {
+ public:
+ StorageNotifierRunnable(nsISupports* aSubject, const char16_t* aStorageType,
+ bool aPrivateBrowsing)
+ : Runnable("StorageNotifierRunnable"),
+ mSubject(aSubject),
+ mStorageType(aStorageType),
+ mPrivateBrowsing(aPrivateBrowsing) {}
+
+ NS_IMETHOD
+ Run() override {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(mSubject,
+ mPrivateBrowsing
+ ? "dom-private-storage2-changed"
+ : "dom-storage2-changed",
+ mStorageType);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsISupports> mSubject;
+ const char16_t* mStorageType;
+ const bool mPrivateBrowsing;
+};
+
+} // namespace
+
+/* static */
+void Storage::NotifyChange(Storage* aStorage, nsIPrincipal* aPrincipal,
+ const nsAString& aKey, const nsAString& aOldValue,
+ const nsAString& aNewValue,
+ const char16_t* aStorageType,
+ const nsAString& aDocumentURI, bool aIsPrivate,
+ bool aImmediateDispatch) {
+ StorageEventInit dict;
+ dict.mBubbles = false;
+ dict.mCancelable = false;
+ dict.mKey = aKey;
+ dict.mNewValue = aNewValue;
+ dict.mOldValue = aOldValue;
+ dict.mStorageArea = aStorage;
+ dict.mUrl = aDocumentURI;
+
+ // Note, this DOM event should never reach JS. It is cloned later in
+ // nsGlobalWindow.
+ RefPtr<StorageEvent> event =
+ StorageEvent::Constructor(nullptr, u"storage"_ns, dict);
+
+ event->SetPrincipal(aPrincipal);
+
+ // This will send the event to any registered window.
+ StorageNotifierService::Broadcast(event, aStorageType, aIsPrivate,
+ aImmediateDispatch);
+
+ // This runnable is mainly used by devtools. Windows receive notification by
+ // StorageNotifierService.
+
+ RefPtr<StorageNotifierRunnable> r =
+ new StorageNotifierRunnable(event, aStorageType, aIsPrivate);
+
+ if (aImmediateDispatch) {
+ Unused << r->Run();
+ } else {
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/Storage.h b/dom/storage/Storage.h
new file mode 100644
index 0000000000..81546a0b0e
--- /dev/null
+++ b/dom/storage/Storage.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Storage_h
+#define mozilla_dom_Storage_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsISupports.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsString.h"
+
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class Storage : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Storage)
+
+ Storage(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ nsIPrincipal* aStoragePrincipal);
+
+ static bool StoragePrefIsEnabled();
+
+ enum StorageType {
+ eSessionStorage,
+ eLocalStorage,
+ ePartitionedLocalStorage,
+ };
+
+ virtual StorageType Type() const = 0;
+
+ virtual bool IsForkOf(const Storage* aStorage) const = 0;
+
+ virtual int64_t GetOriginQuotaUsage() const = 0;
+
+ virtual void Disconnect() {}
+
+ nsIPrincipal* Principal() const { return mPrincipal; }
+
+ nsIPrincipal* StoragePrincipal() const { return mStoragePrincipal; }
+
+ bool IsPrivateBrowsing() const { return mPrivateBrowsing; }
+
+ bool IsSessionScopedOrLess() const { return mSessionScopedOrLess; }
+
+ // WebIDL
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ virtual uint32_t GetLength(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) = 0;
+
+ virtual void Key(uint32_t aIndex, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
+
+ virtual void GetItem(const nsAString& aKey, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
+
+ virtual void GetSupportedNames(nsTArray<nsString>& aKeys) = 0;
+
+ void NamedGetter(const nsAString& aKey, bool& aFound, nsAString& aResult,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ GetItem(aKey, aResult, aSubjectPrincipal, aRv);
+ aFound = !aResult.IsVoid();
+ }
+
+ virtual void SetItem(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
+
+ void NamedSetter(const nsAString& aKey, const nsAString& aValue,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ SetItem(aKey, aValue, aSubjectPrincipal, aRv);
+ }
+
+ virtual void RemoveItem(const nsAString& aKey,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) = 0;
+
+ void NamedDeleter(const nsAString& aKey, bool& aFound,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
+ RemoveItem(aKey, aSubjectPrincipal, aRv);
+
+ aFound = !aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION);
+ }
+
+ virtual void Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) = 0;
+
+ // The attribute in the WebIDL interface has rather confusing name. So we
+ // shouldn't use this method internally. IsSessionScopedOrLess should be used
+ // directly.
+ bool IsSessionOnly() const { return IsSessionScopedOrLess(); }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Testing Methods:
+ //
+ // These methods are exposed on the `Storage` WebIDL interface behind a
+ // preference for the benefit of automated-tests. They are not exposed to
+ // content. See `Storage.webidl` for more details.
+
+ virtual void Open(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {}
+
+ virtual void Close(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {}
+
+ virtual void BeginExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {}
+
+ virtual void CheckpointExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {}
+
+ virtual void EndExplicitSnapshot(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {}
+
+ virtual bool GetHasSnapshot(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv) {
+ return false;
+ }
+
+ virtual int64_t GetSnapshotUsage(nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aRv);
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Dispatch storage notification events on all impacted pages in the current
+ // process as well as for consumption by devtools. Pages receive the
+ // notification via StorageNotifierService (not observers like in the past),
+ // while devtools does receive the notification via the observer service.
+ //
+ // aStorage can be null if this method is called by LocalStorageCacheChild.
+ //
+ // aImmediateDispatch is for use by child IPC code (LocalStorageCacheChild)
+ // so that PBackground ordering can be maintained. Without this, the event
+ // would be enqueued and run in a future turn of the event loop, potentially
+ // allowing other PBackground Recv* methods to trigger script that wants to
+ // assume our localstorage changes have already been applied. This is the
+ // case for message manager messages which are used by ContentTask testing
+ // logic and webextensions.
+ static void NotifyChange(Storage* aStorage, nsIPrincipal* aPrincipal,
+ const nsAString& aKey, const nsAString& aOldValue,
+ const nsAString& aNewValue,
+ const char16_t* aStorageType,
+ const nsAString& aDocumentURI, bool aIsPrivate,
+ bool aImmediateDispatch);
+
+ protected:
+ virtual ~Storage();
+
+ // The method checks whether the caller can use a storage.
+ bool CanUseStorage(nsIPrincipal& aSubjectPrincipal);
+
+ virtual void LastRelease() {}
+
+ private:
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCOMPtr<nsIPrincipal> mStoragePrincipal;
+
+ bool mPrivateBrowsing : 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 mSessionScopedOrLess : 1;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Storage_h
diff --git a/dom/storage/StorageActivityService.cpp b/dom/storage/StorageActivityService.cpp
new file mode 100644
index 0000000000..8652d82653
--- /dev/null
+++ b/dom/storage/StorageActivityService.cpp
@@ -0,0 +1,302 @@
+/* -*- 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 "StorageActivityService.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIUserIdleService.h"
+#include "nsSupportsPrimitives.h"
+#include "nsXPCOM.h"
+
+// This const is used to know when origin activities should be purged because
+// too old. This value should be in sync with what the UI needs.
+#define TIME_MAX_SECS 86400 /* 24 hours */
+
+namespace mozilla::dom {
+
+static StaticRefPtr<StorageActivityService> gStorageActivityService;
+static bool gStorageActivityShutdown = false;
+
+/* static */
+void StorageActivityService::SendActivity(nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aPrincipal || BasePrincipal::Cast(aPrincipal)->Kind() !=
+ BasePrincipal::eContentPrincipal) {
+ // Only content principals.
+ return;
+ }
+
+ RefPtr<StorageActivityService> service = GetOrCreate();
+ if (NS_WARN_IF(!service)) {
+ return;
+ }
+
+ service->SendActivityInternal(aPrincipal);
+}
+
+/* static */
+void StorageActivityService::SendActivity(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
+ if (aPrincipalInfo.type() !=
+ mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
+ // only content principal.
+ return;
+ }
+
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ "StorageActivityService::SendActivity", [aPrincipalInfo]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto principalOrErr =
+ mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo);
+
+ if (principalOrErr.isOk()) {
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ StorageActivityService::SendActivity(principal);
+ } else {
+ NS_WARNING(
+ "Could not obtain principal from "
+ "mozilla::ipc::PrincipalInfoToPrincipal");
+ }
+ });
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+}
+
+/* static */
+void StorageActivityService::SendActivity(const nsACString& aOrigin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCString origin;
+ origin.Assign(aOrigin);
+
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ "StorageActivityService::SendActivity", [origin]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<StorageActivityService> service = GetOrCreate();
+ if (NS_WARN_IF(!service)) {
+ return;
+ }
+
+ service->SendActivityInternal(origin);
+ });
+
+ if (NS_IsMainThread()) {
+ Unused << r->Run();
+ } else {
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget());
+ }
+}
+
+/* static */
+already_AddRefed<StorageActivityService> StorageActivityService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gStorageActivityService && !gStorageActivityShutdown) {
+ RefPtr<StorageActivityService> service = new StorageActivityService();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return nullptr;
+ }
+
+ nsresult rv =
+ obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ gStorageActivityService = service;
+ }
+
+ RefPtr<StorageActivityService> service = gStorageActivityService;
+ return service.forget();
+}
+
+StorageActivityService::StorageActivityService() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+StorageActivityService::~StorageActivityService() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void StorageActivityService::SendActivityInternal(nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(BasePrincipal::Cast(aPrincipal)->Kind() ==
+ BasePrincipal::eContentPrincipal);
+
+ if (!XRE_IsParentProcess()) {
+ SendActivityToParent(aPrincipal);
+ return;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = aPrincipal->GetOrigin(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ SendActivityInternal(origin);
+}
+
+void StorageActivityService::SendActivityInternal(const nsACString& aOrigin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ bool shouldAddObserver = mActivities.Count() == 0;
+ mActivities.InsertOrUpdate(aOrigin, PR_Now());
+
+ if (shouldAddObserver) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return;
+ }
+
+ obs->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, true);
+ }
+}
+
+void StorageActivityService::SendActivityToParent(nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ ::mozilla::ipc::PBackgroundChild* actor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actor)) {
+ return;
+ }
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ nsresult rv =
+ mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ actor->SendStorageActivity(principalInfo);
+}
+
+NS_IMETHODIMP
+StorageActivityService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
+ CleanUp();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+
+ gStorageActivityShutdown = true;
+ gStorageActivityService = nullptr;
+ return NS_OK;
+}
+
+void StorageActivityService::CleanUp() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint64_t now = PR_Now();
+
+ for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
+ if ((now - iter.UserData()) / PR_USEC_PER_SEC > TIME_MAX_SECS) {
+ iter.Remove();
+ }
+ }
+
+ // If no activities, remove the observer.
+ if (mActivities.Count() == 0) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
+ }
+ }
+}
+
+NS_IMETHODIMP
+StorageActivityService::GetActiveOrigins(PRTime aFrom, PRTime aTo,
+ nsIArray** aRetval) {
+ uint64_t now = PR_Now();
+ if (((now - aFrom) / PR_USEC_PER_SEC) > TIME_MAX_SECS || aFrom >= aTo) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Remove expired entries first.
+ CleanUp();
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> devices =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (const auto& activityEntry : mActivities) {
+ if (activityEntry.GetData() >= aFrom && activityEntry.GetData() <= aTo) {
+ RefPtr<BasePrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(activityEntry.GetKey());
+ MOZ_ASSERT(principal);
+
+ rv = devices->AppendElement(principal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ devices.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageActivityService::MoveOriginInTime(nsIPrincipal* aPrincipal,
+ PRTime aWhen) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString origin;
+ nsresult rv = aPrincipal->GetOrigin(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mActivities.InsertOrUpdate(origin, aWhen / PR_USEC_PER_SEC);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageActivityService::TestOnlyReset() {
+ mActivities.Clear();
+ return NS_OK;
+}
+
+NS_INTERFACE_MAP_BEGIN(StorageActivityService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStorageActivityService)
+ NS_INTERFACE_MAP_ENTRY(nsIStorageActivityService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(StorageActivityService)
+NS_IMPL_RELEASE(StorageActivityService)
+
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageActivityService.h b/dom/storage/StorageActivityService.h
new file mode 100644
index 0000000000..0b61bd128a
--- /dev/null
+++ b/dom/storage/StorageActivityService.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageActivityService_h
+#define mozilla_dom_StorageActivityService_h
+
+#include "nsTHashMap.h"
+#include "nsIObserver.h"
+#include "nsIStorageActivityService.h"
+#include "nsWeakReference.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class StorageActivityService final : public nsIStorageActivityService,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTORAGEACTIVITYSERVICE
+ NS_DECL_NSIOBSERVER
+
+ // Main-thread only.
+ static void SendActivity(nsIPrincipal* aPrincipal);
+
+ // Thread-safe.
+ static void SendActivity(const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
+
+ // Thread-safe but for parent process only!
+ static void SendActivity(const nsACString& aOrigin);
+
+ // Used by XPCOM. Don't use it, use SendActivity() instead.
+ static already_AddRefed<StorageActivityService> GetOrCreate();
+
+ private:
+ StorageActivityService();
+ ~StorageActivityService();
+
+ void SendActivityInternal(nsIPrincipal* aPrincipal);
+
+ void SendActivityInternal(const nsACString& aOrigin);
+
+ void SendActivityToParent(nsIPrincipal* aPrincipal);
+
+ void CleanUp();
+
+ // Activities grouped by origin (+OriginAttributes).
+ nsTHashMap<nsCStringHashKey, PRTime> mActivities;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StorageActivityService_h
diff --git a/dom/storage/StorageCommon.h b/dom/storage/StorageCommon.h
new file mode 100644
index 0000000000..633a97805e
--- /dev/null
+++ b/dom/storage/StorageCommon.h
@@ -0,0 +1,18 @@
+/* -*- 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 DOM_STORAGE_STORAGECOMMON_H_
+#define DOM_STORAGE_STORAGECOMMON_H_
+
+#include "mozilla/Types.h"
+
+namespace mozilla::dom {
+
+constexpr uint32_t kPrivateBrowsingIdCount = 2;
+
+} // namespace mozilla::dom
+
+#endif // DOM_STORAGE_STORAGECOMMON_H_
diff --git a/dom/storage/StorageDBThread.cpp b/dom/storage/StorageDBThread.cpp
new file mode 100644
index 0000000000..840fb219b9
--- /dev/null
+++ b/dom/storage/StorageDBThread.cpp
@@ -0,0 +1,1626 @@
+/* -*- 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 "StorageDBThread.h"
+
+#include "StorageCommon.h"
+#include "StorageDBUpdater.h"
+#include "StorageUtils.h"
+#include "LocalStorageCache.h"
+#include "LocalStorageManager.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozStorageCID.h"
+#include "mozStorageHelper.h"
+#include "mozIStorageService.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsIObserverService.h"
+#include "nsThread.h"
+#include "nsThreadManager.h"
+#include "nsVariant.h"
+#include "mozilla/EventQueue.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/Services.h"
+#include "mozilla/Tokenizer.h"
+#include "GeckoProfiler.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 2
+
+namespace mozilla::dom {
+
+using namespace StorageUtils;
+
+namespace { // anon
+
+StorageDBThread* sStorageThread[kPrivateBrowsingIdCount] = {nullptr, nullptr};
+
+// False until we shut the storage thread down.
+bool sStorageThreadDown[kPrivateBrowsingIdCount] = {false, false};
+
+} // namespace
+
+// XXX Fix me!
+#if 0
+StorageDBBridge::StorageDBBridge()
+{
+}
+#endif
+
+class StorageDBThread::InitHelper final : public Runnable {
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+ mozilla::CondVar mCondVar;
+ nsString mProfilePath;
+ nsresult mMainThreadResultCode;
+ bool mWaiting;
+
+ public:
+ InitHelper()
+ : Runnable("dom::StorageDBThread::InitHelper"),
+ mOwningThread(GetCurrentSerialEventTarget()),
+ mMutex("InitHelper::mMutex"),
+ mCondVar(mMutex, "InitHelper::mCondVar"),
+ mMainThreadResultCode(NS_OK),
+ mWaiting(true) {}
+
+ // Because of the `sync Preload` IPC, we need to be able to synchronously
+ // initialize, which includes consulting and initializing
+ // some main-thread-only APIs. Bug 1386441 discusses improving this situation.
+ nsresult SyncDispatchAndReturnProfilePath(nsAString& aProfilePath);
+
+ private:
+ ~InitHelper() override = default;
+
+ nsresult RunOnMainThread();
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class StorageDBThread::NoteBackgroundThreadRunnable final : public Runnable {
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+ nsCOMPtr<nsIEventTarget> mOwningThread;
+
+ public:
+ explicit NoteBackgroundThreadRunnable(const uint32_t aPrivateBrowsingId)
+ : Runnable("dom::StorageDBThread::NoteBackgroundThreadRunnable"),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mOwningThread(GetCurrentSerialEventTarget()) {
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ }
+
+ private:
+ ~NoteBackgroundThreadRunnable() override = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+StorageDBThread::StorageDBThread(const uint32_t aPrivateBrowsingId)
+ : mThread(nullptr),
+ mThreadObserver(new ThreadObserver()),
+ mStopIOThread(false),
+ mWALModeEnabled(false),
+ mDBReady(false),
+ mStatus(NS_OK),
+ mWorkerStatements(mWorkerConnection),
+ mReaderStatements(mReaderConnection),
+ mFlushImmediately(false),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mPriorityCounter(0) {
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+}
+
+// static
+StorageDBThread* StorageDBThread::Get(const uint32_t aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+
+ return sStorageThread[aPrivateBrowsingId];
+}
+
+// static
+StorageDBThread* StorageDBThread::GetOrCreate(
+ const nsString& aProfilePath, const uint32_t aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+
+ StorageDBThread*& storageThread = sStorageThread[aPrivateBrowsingId];
+ if (storageThread || sStorageThreadDown[aPrivateBrowsingId]) {
+ // When sStorageThreadDown is at true, sStorageThread is null.
+ // Checking sStorageThreadDown flag here prevents reinitialization of
+ // the storage thread after shutdown.
+ return storageThread;
+ }
+
+ auto newStorageThread = MakeUnique<StorageDBThread>(aPrivateBrowsingId);
+
+ nsresult rv = newStorageThread->Init(aProfilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ storageThread = newStorageThread.release();
+
+ return storageThread;
+}
+
+// static
+nsresult StorageDBThread::GetProfilePath(nsString& aProfilePath) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Need to determine location on the main thread, since
+ // NS_GetSpecialDirectory accesses the atom table that can
+ // only be accessed on the main thread.
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = profileDir->GetPath(aProfilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // This service has to be started on the main thread currently.
+ nsCOMPtr<mozIStorageService> ss =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::Init(const nsString& aProfilePath) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mPrivateBrowsingId == 0) {
+ nsresult rv;
+
+ nsString profilePath;
+ if (aProfilePath.IsEmpty()) {
+ RefPtr<InitHelper> helper = new InitHelper();
+
+ rv = helper->SyncDispatchAndReturnProfilePath(profilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ profilePath = aProfilePath;
+ }
+
+ mDatabaseFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mDatabaseFile->InitWithPath(profilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mDatabaseFile->Append(u"webappsstore.sqlite"_ns);
+ 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, &StorageDBThread::ThreadFunc, this,
+ PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD, 262144);
+ if (!mThread) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ RefPtr<NoteBackgroundThreadRunnable> runnable =
+ new NoteBackgroundThreadRunnable(mPrivateBrowsingId);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::Shutdown() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ 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 StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache,
+ bool aForceSync) {
+ AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", OTHER);
+ 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(MakeUnique<DBOperation>(DBOperation::opPreloadUrgent, aCache));
+
+ // LoadWait exits after LoadDone of the cache has been called.
+ if (NS_SUCCEEDED(rv)) {
+ aCache->LoadWait();
+ }
+}
+
+void StorageDBThread::AsyncFlush() {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ mFlushImmediately = true;
+ monitor.Notify();
+}
+
+bool StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin) {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ return mOriginsHavingData.Contains(aOrigin);
+}
+
+void StorageDBThread::GetOriginsHavingData(nsTArray<nsCString>* aOrigins) {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+ AppendToArray(*aOrigins, mOriginsHavingData);
+}
+
+nsresult StorageDBThread::InsertDBOp(
+ UniquePtr<StorageDBThread::DBOperation> aOperation) {
+ MonitorAutoLock monitor(mThreadObserver->GetMonitor());
+
+ 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;
+ }
+ [[fallthrough]];
+
+ case DBOperation::opGetUsage:
+ if (aOperation->Type() == DBOperation::opPreloadUrgent) {
+ SetHigherPriority(); // Dropped back after urgent preload execution
+ mPreloads.InsertElementAt(0, aOperation.release());
+ } else {
+ mPreloads.AppendElement(aOperation.release());
+ }
+
+ // Immediately start executing this.
+ monitor.Notify();
+ break;
+
+ default:
+ // Update operations are first collected, coalesced and then flushed
+ // after a short time.
+ mPendingTasks.Add(std::move(aOperation));
+
+ ScheduleFlush();
+ break;
+ }
+
+ return NS_OK;
+}
+
+void StorageDBThread::SetHigherPriority() {
+ ++mPriorityCounter;
+ PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
+}
+
+void StorageDBThread::SetDefaultPriority() {
+ if (--mPriorityCounter <= 0) {
+ PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
+ }
+}
+
+void StorageDBThread::ThreadFunc(void* aArg) {
+ {
+ auto queue = MakeRefPtr<ThreadEventQueue>(MakeUnique<EventQueue>());
+ Unused << nsThreadManager::get().CreateCurrentThread(
+ queue, nsThread::NOT_MAIN_THREAD);
+ }
+
+ AUTO_PROFILER_REGISTER_THREAD("localStorage DB");
+ NS_SetCurrentThreadName("localStorage DB");
+ mozilla::IOInterposer::RegisterCurrentThread();
+
+ StorageDBThread* thread = static_cast<StorageDBThread*>(aArg);
+ thread->ThreadFunc();
+ mozilla::IOInterposer::UnregisterCurrentThread();
+}
+
+void StorageDBThread::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);
+ }
+
+ TimeDuration timeUntilFlush = TimeUntilFlush();
+ if (MOZ_UNLIKELY(timeUntilFlush.IsZero())) {
+ // 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())) {
+ UniquePtr<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)) {
+ AUTO_PROFILER_LABEL("StorageDBThread::ThreadFunc::Wait", IDLE);
+ lockMonitor.Wait(timeUntilFlush);
+ }
+ } // thread loop
+
+ mStatus = ShutdownDatabase();
+
+ if (threadInternal) {
+ threadInternal->SetObserver(nullptr);
+ }
+}
+
+NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+StorageDBThread::ThreadObserver::OnDispatchedEvent() {
+ MonitorAutoLock lock(mMonitor);
+ mHasPendingEvents = true;
+ lock.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
+ bool mayWait) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::ThreadObserver::AfterProcessNextEvent(
+ nsIThreadInternal* aThread, bool eventWasProcessed) {
+ return NS_OK;
+}
+
+nsresult StorageDBThread::OpenDatabaseConnection() {
+ nsresult rv;
+
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageService> service =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mPrivateBrowsingId == 0) {
+ MOZ_ASSERT(mDatabaseFile);
+
+ rv = service->OpenUnsharedDatabase(mDatabaseFile,
+ mozIStorageService::CONNECTION_DEFAULT,
+ 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,
+ mozIStorageService::CONNECTION_DEFAULT,
+ getter_AddRefs(mWorkerConnection));
+ }
+ } else {
+ MOZ_ASSERT(mPrivateBrowsingId == 1);
+
+ rv = service->OpenSpecialDatabase(kMozStorageMemoryStorageKey,
+ "lsprivatedb"_ns,
+ mozIStorageService::CONNECTION_DEFAULT,
+ getter_AddRefs(mWorkerConnection));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::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);
+
+ // SQLite doesn't support WAL journals for in-memory databases.
+ if (mPrivateBrowsingId == 0) {
+ rv = TryJournalMode();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::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 = StorageDBUpdater::Update(mWorkerConnection);
+ if (NS_FAILED(rv)) {
+ if (mPrivateBrowsingId == 0) {
+ // 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 totally broken and incosistent database.
+ mDBReady = true;
+
+ // List scopes having any stored data
+ nsCOMPtr<mozIStorageStatement> stmt;
+ // Note: result of this select must match StorageManager::CreateOrigin()
+ rv = mWorkerConnection->CreateStatement(
+ nsLiteralCString("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.Insert(foundOrigin);
+ }
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::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 StorageDBThread::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 StorageDBThread::ConfigureWALBehavior() {
+ // Get the DB's page size
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mWorkerConnection->CreateStatement(
+ nsLiteralCString(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.
+ // Note there is a default journal_size_limit set by mozStorage.
+ 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);
+
+ return NS_OK;
+}
+
+nsresult StorageDBThread::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 StorageDBThread::ScheduleFlush() {
+ if (mDirtyEpoch) {
+ return; // Already scheduled
+ }
+
+ // Must be non-zero to indicate we are scheduled
+ mDirtyEpoch = TimeStamp::Now();
+
+ // Wake the monitor from indefinite sleep...
+ (mThreadObserver->GetMonitor()).Notify();
+}
+
+void StorageDBThread::UnscheduleFlush() {
+ // We are just about to do the flush, drop flags
+ mFlushImmediately = false;
+ mDirtyEpoch = TimeStamp();
+}
+
+TimeDuration StorageDBThread::TimeUntilFlush() {
+ if (mFlushImmediately) {
+ return 0; // Do it now regardless the timeout.
+ }
+
+ if (!mDirtyEpoch) {
+ return TimeDuration::Forever(); // No pending task...
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration age = now - mDirtyEpoch;
+ static const TimeDuration kMaxAge =
+ TimeDuration::FromMilliseconds(FLUSHING_INTERVAL_MS);
+ if (age > kMaxAge) {
+ return 0; // It is time.
+ }
+
+ return kMaxAge - age; // Time left. This is used to sleep the monitor.
+}
+
+void StorageDBThread::NotifyFlushCompletion() {
+#ifdef DOM_STORAGE_TESTS
+ if (!NS_IsMainThread()) {
+ RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event =
+ NewNonOwningRunnableMethod(
+ "dom::StorageDBThread::NotifyFlushCompletion", this,
+ &StorageDBThread::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() = default;
+
+ 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);
+
+ OriginAttributes 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
+
+// StorageDBThread::DBOperation
+
+StorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ LocalStorageCacheBridge* 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(StorageDBThread::DBOperation);
+}
+
+StorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ StorageUsageBridge* aUsage)
+ : mType(aType), mUsage(aUsage) {
+ MOZ_ASSERT(mType == opGetUsage);
+ MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
+}
+
+StorageDBThread::DBOperation::DBOperation(const OperationType aType,
+ const nsACString& aOriginNoSuffix)
+ : mType(aType), mCache(nullptr), mOrigin(aOriginNoSuffix) {
+ MOZ_ASSERT(mType == opClearMatchingOrigin);
+ MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
+}
+
+StorageDBThread::DBOperation::DBOperation(
+ const OperationType aType, const OriginAttributesPattern& aOriginNoSuffix)
+ : mType(aType), mCache(nullptr), mOriginPattern(aOriginNoSuffix) {
+ MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
+ MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
+}
+
+StorageDBThread::DBOperation::~DBOperation() {
+ MOZ_COUNT_DTOR(StorageDBThread::DBOperation);
+}
+
+const nsCString StorageDBThread::DBOperation::OriginNoSuffix() const {
+ if (mCache) {
+ return mCache->OriginNoSuffix();
+ }
+
+ return ""_ns;
+}
+
+const nsCString StorageDBThread::DBOperation::OriginSuffix() const {
+ if (mCache) {
+ return mCache->OriginSuffix();
+ }
+
+ return ""_ns;
+}
+
+const nsCString StorageDBThread::DBOperation::Origin() const {
+ if (mCache) {
+ return mCache->Origin();
+ }
+
+ return mOrigin;
+}
+
+const nsCString StorageDBThread::DBOperation::Target() const {
+ switch (mType) {
+ case opAddItem:
+ case opUpdateItem:
+ case opRemoveItem:
+ return Origin() + "|"_ns + NS_ConvertUTF16toUTF8(mKey);
+
+ default:
+ return Origin();
+ }
+}
+
+void StorageDBThread::DBOperation::PerformAndFinalize(
+ StorageDBThread* aThread) {
+ Finalize(Perform(aThread));
+}
+
+nsresult StorageDBThread::DBOperation::Perform(StorageDBThread* aThread) {
+ nsresult rv;
+
+ switch (mType) {
+ case opPreload:
+ case opPreloadUrgent: {
+ // Already loaded?
+ if (mCache->Loaded()) {
+ break;
+ }
+
+ StatementCache* statements;
+ if (MOZ_UNLIKELY(::mozilla::ipc::IsOnBackgroundThread())) {
+ 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("originAttributes"_ns,
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName("offset"_ns,
+ 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: {
+ // Bug 1676410 fixed a regression caused by bug 1165214. However, it
+ // turns out that 100% correct checking of the eTLD+1 usage is not
+ // possible to recover easily, see bug 1683299.
+#if 0
+ // This is how it should be done, but due to other problems like lack
+ // of usage synchronization between content processes, we temporarily
+ // disabled the matching using "%".
+
+ nsCOMPtr<mozIStorageStatement> stmt =
+ aThread->mWorkerStatements.GetCachedStatement(
+ "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
+ "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin "
+ "ESCAPE '\\'");
+ NS_ENSURE_STATE(stmt);
+
+ mozStorageStatementScoper scope(stmt);
+
+ // The database schema is built around cleverly reversing domain names
+ // (the "originKey") so that we can efficiently group usage by eTLD+1.
+ // "foo.example.org" has an eTLD+1 of ".example.org". They reverse to
+ // "gro.elpmaxe.oof" and "gro.elpmaxe." respectively, noting that the
+ // reversed eTLD+1 is a prefix of its reversed sub-domain. To this end,
+ // we can calculate all of the usage for an eTLD+1 by summing up all the
+ // rows which have the reversed eTLD+1 as a prefix. In SQL we can
+ // accomplish this using LIKE which provides for case-insensitive
+ // matching with "_" as a single-character wildcard match and "%" any
+ // sequence of zero or more characters. So by suffixing the reversed
+ // eTLD+1 and using "%" we get our case-insensitive (domain names are
+ // case-insensitive) matching. Note that although legal domain names
+ // don't include "_" or "%", file origins can include them, so we need
+ // to escape our OriginScope for correctness.
+ nsAutoCString originScopeEscaped;
+ rv = stmt->EscapeUTF8StringForLIKE(mUsage->OriginScope(), '\\',
+ originScopeEscaped);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName("usageOrigin"_ns,
+ originScopeEscaped + "%"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+#else
+ // This is the code before bug 1676410 and bug 1676973. The returned
+ // usage will be zero in most of the cases, but due to lack of usage
+ // synchronization between content processes we have to live with this
+ // semi-broken behaviour because it causes less harm than the matching
+ // using "%".
+
+ 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("usageOrigin"_ns, mUsage->OriginScope());
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ 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("originAttributes"_ns,
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Filling the 'scope' column just for downgrade compatibility reasons
+ rv = stmt->BindUTF8StringByName(
+ "scope"_ns,
+ Scheme0Scope(mCache->OriginSuffix(), mCache->OriginNoSuffix()));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName("key"_ns, mKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName("value"_ns, mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
+ aThread->mOriginsHavingData.Insert(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("originAttributes"_ns,
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName("key"_ns, 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("originAttributes"_ns,
+ mCache->OriginSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName("originKey"_ns, mCache->OriginNoSuffix());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
+ aThread->mOriginsHavingData.Remove(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("scope"_ns, mOrigin + "*"_ns);
+ 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(
+ "ORIGIN_ATTRS_PATTERN_MATCH"_ns, 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(
+ "ORIGIN_ATTRS_PATTERN_MATCH"_ns);
+
+ 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 StorageDBThread::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;
+ }
+}
+
+// StorageDBThread::PendingOperations
+
+StorageDBThread::PendingOperations::PendingOperations()
+ : mFlushFailureCount(0) {}
+
+bool StorageDBThread::PendingOperations::HasTasks() const {
+ return !!mUpdates.Count() || !!mClears.Count();
+}
+
+namespace {
+
+bool OriginPatternMatches(const nsACString& aOriginSuffix,
+ const OriginAttributesPattern& aPattern) {
+ OriginAttributes oa;
+ DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
+ MOZ_ASSERT(rv);
+ return aPattern.Matches(oa);
+}
+
+} // namespace
+
+bool StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(
+ DBOperation* aNewOp, DBOperation::OperationType aPendingType,
+ DBOperation::OperationType aNewType) {
+ if (aNewOp->Type() != aNewType) {
+ return false;
+ }
+
+ StorageDBThread::DBOperation* pendingTask;
+ if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
+ return false;
+ }
+
+ if (pendingTask->Type() != aPendingType) {
+ return false;
+ }
+
+ return true;
+}
+
+void StorageDBThread::PendingOperations::Add(
+ UniquePtr<StorageDBThread::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.get(), DBOperation::opAddItem,
+ DBOperation::opRemoveItem)) {
+ mUpdates.Remove(aOperation->Target());
+ 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.get(), 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.get(), 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.InsertOrUpdate(aOperation->Target(), std::move(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()) {
+ const auto& 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.InsertOrUpdate(aOperation->Target(), std::move(aOperation));
+ break;
+
+ case DBOperation::opClearAll:
+ // Drop simply everything, this is a super-operation.
+ mUpdates.Clear();
+ mClears.Clear();
+ mClears.InsertOrUpdate(aOperation->Target(), std::move(aOperation));
+ break;
+
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+}
+
+bool StorageDBThread::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(std::move(iter.Data()));
+ }
+ mClears.Clear();
+
+ for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
+ mExecList.AppendElement(std::move(iter.Data()));
+ }
+ mUpdates.Clear();
+
+ return !!mExecList.Length();
+}
+
+nsresult StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread) {
+ // Called outside the lock
+
+ mozStorageTransaction transaction(aThread->mWorkerConnection, false);
+
+ nsresult rv = transaction.Start();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ const auto& 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 StorageDBThread::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,
+ StorageDBThread::DBOperation* aPendingOperation) {
+ if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear &&
+ aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
+ aOriginSuffix == aPendingOperation->OriginSuffix()) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() ==
+ StorageDBThread::DBOperation::opClearMatchingOrigin &&
+ StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
+ return true;
+ }
+
+ if (aPendingOperation->Type() ==
+ StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
+ OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+bool StorageDBThread::PendingOperations::IsOriginClearPending(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const {
+ // Called under the lock
+
+ for (const auto& clear : mClears.Values()) {
+ if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
+ clear.get())) {
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix,
+ mExecList[i].get())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace {
+
+bool FindPendingUpdateForOrigin(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ StorageDBThread::DBOperation* aPendingOperation) {
+ if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem ||
+ aPendingOperation->Type() ==
+ StorageDBThread::DBOperation::opUpdateItem ||
+ aPendingOperation->Type() ==
+ StorageDBThread::DBOperation::opRemoveItem) &&
+ aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
+ aOriginSuffix == aPendingOperation->OriginSuffix()) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace
+
+bool StorageDBThread::PendingOperations::IsOriginUpdatePending(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) const {
+ // Called under the lock
+
+ for (const auto& update : mUpdates.Values()) {
+ if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
+ update.get())) {
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < mExecList.Length(); ++i) {
+ if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix,
+ mExecList[i].get())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult StorageDBThread::InitHelper::SyncDispatchAndReturnProfilePath(
+ nsAString& aProfilePath) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ mozilla::MutexAutoLock autolock(mMutex);
+ while (mWaiting) {
+ mCondVar.Wait();
+ }
+
+ if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
+ return mMainThreadResultCode;
+ }
+
+ aProfilePath = mProfilePath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::InitHelper::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = GetProfilePath(mProfilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mMainThreadResultCode = rv;
+ }
+
+ mozilla::MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mWaiting);
+
+ mWaiting = false;
+ mCondVar.Notify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::NoteBackgroundThreadRunnable::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StorageObserver* observer = StorageObserver::Self();
+ MOZ_ASSERT(observer);
+
+ observer->NoteBackgroundThread(mPrivateBrowsingId, mOwningThread);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageDBThread::ShutdownRunnable::Run() {
+ if (NS_IsMainThread()) {
+ mDone = true;
+
+ return NS_OK;
+ }
+
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_RELEASE_ASSERT(mPrivateBrowsingId < kPrivateBrowsingIdCount);
+
+ StorageDBThread*& storageThread = sStorageThread[mPrivateBrowsingId];
+ if (storageThread) {
+ sStorageThreadDown[mPrivateBrowsingId] = true;
+
+ storageThread->Shutdown();
+
+ delete storageThread;
+ storageThread = nullptr;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageDBThread.h b/dom/storage/StorageDBThread.h
new file mode 100644
index 0000000000..7708de5a97
--- /dev/null
+++ b/dom/storage/StorageDBThread.h
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageDBThread_h
+#define mozilla_dom_StorageDBThread_h
+
+#include "prthread.h"
+#include "prinrval.h"
+#include "nsTArray.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/storage/StatementCache.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsIFile.h"
+#include "nsIThreadInternal.h"
+#include "nsThreadUtils.h"
+#include "nsTHashSet.h"
+
+class mozIStorageConnection;
+
+namespace mozilla::dom {
+
+class LocalStorageCacheBridge;
+class StorageUsageBridge;
+class StorageUsage;
+
+using StatementCache = mozilla::storage::StatementCache<mozIStorageStatement>;
+
+// XXX Fix me!
+// 1. Move comments to StorageDBThread/StorageDBChild.
+// 2. Devirtualize relevant methods in StorageDBThread/StorageDBChild.
+// 3. Remove relevant methods in StorageDBThread/StorageDBChild that are
+// unused.
+// 4. Remove this class completely.
+//
+// See bug 1387636 for more details.
+#if 0
+// Interface used by the cache to post operations to the asynchronous
+// database thread or process.
+class StorageDBBridge
+{
+public:
+ StorageDBBridge();
+ virtual ~StorageDBBridge() {}
+
+ // 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(LocalStorageCacheBridge* 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(StorageUsageBridge* 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(LocalStorageCacheBridge* aCache,
+ bool aForceSync = false) = 0;
+
+ // Called when an existing key is modified in the storage, schedules update to
+ // the database
+ virtual nsresult AsyncAddItem(LocalStorageCacheBridge* 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(LocalStorageCacheBridge* 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(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey) = 0;
+
+ // Called when the whole storage is cleared by the DOM API, schedules delete
+ // of the scope
+ virtual nsresult AsyncClear(LocalStorageCacheBridge* 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;
+};
+#endif
+
+// 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 (LocalStorageCache) communicating though
+// LocalStorageCacheBridge interface class
+class StorageDBThread final {
+ public:
+ class PendingOperations;
+
+ // Representation of a singe database task, like adding and removing keys,
+ // (pre)loading the whole origin data, cleaning.
+ class DBOperation {
+ public:
+ enum OperationType {
+ // 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,
+ };
+
+ explicit DBOperation(const OperationType aType,
+ LocalStorageCacheBridge* aCache = nullptr,
+ const nsAString& aKey = u""_ns,
+ const nsAString& aValue = u""_ns);
+ DBOperation(const OperationType aType, StorageUsageBridge* 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(StorageDBThread* 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(StorageDBThread* aThread);
+
+ friend class PendingOperations;
+ OperationType mType;
+ RefPtr<LocalStorageCacheBridge> mCache;
+ RefPtr<StorageUsageBridge> 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(UniquePtr<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, returns
+ // result, but doesn't handle it in any way in case of a failure
+ nsresult Execute(StorageDBThread* 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<UniquePtr<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("StorageThreadMonitor") {}
+
+ bool HasPendingEvents() {
+ mMonitor.AssertCurrentThreadOwns();
+ return mHasPendingEvents;
+ }
+ void ClearPendingEvents() {
+ mMonitor.AssertCurrentThreadOwns();
+ mHasPendingEvents = false;
+ }
+ Monitor& GetMonitor() { return mMonitor; }
+
+ private:
+ virtual ~ThreadObserver() = default;
+ bool mHasPendingEvents;
+ // The monitor we drive the thread with
+ Monitor mMonitor MOZ_UNANNOTATED;
+ };
+
+ class InitHelper;
+
+ class NoteBackgroundThreadRunnable;
+
+ class ShutdownRunnable : public Runnable {
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+ // Only touched on the main thread.
+ bool& mDone;
+
+ public:
+ explicit ShutdownRunnable(const uint32_t aPrivateBrowsingId, bool& aDone)
+ : Runnable("dom::StorageDBThread::ShutdownRunnable"),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mDone(aDone) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ private:
+ ~ShutdownRunnable() = default;
+
+ NS_DECL_NSIRUNNABLE
+ };
+
+ public:
+ explicit StorageDBThread(uint32_t aPrivateBrowsingId);
+ virtual ~StorageDBThread() = default;
+
+ static StorageDBThread* Get(uint32_t aPrivateBrowsingId);
+
+ static StorageDBThread* GetOrCreate(const nsString& aProfilePath,
+ uint32_t aPrivateBrowsingId);
+
+ static nsresult GetProfilePath(nsString& aProfilePath);
+
+ virtual nsresult Init(const nsString& aProfilePath);
+
+ // Flushes all uncommited data and stops the I/O thread.
+ virtual nsresult Shutdown();
+
+ virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
+ bool aPriority = false) {
+ InsertDBOp(MakeUnique<DBOperation>(
+ aPriority ? DBOperation::opPreloadUrgent : DBOperation::opPreload,
+ aCache));
+ }
+
+ virtual void SyncPreload(LocalStorageCacheBridge* aCache,
+ bool aForce = false);
+
+ virtual void AsyncGetUsage(StorageUsageBridge* aUsage) {
+ InsertDBOp(MakeUnique<DBOperation>(DBOperation::opGetUsage, aUsage));
+ }
+
+ virtual nsresult AsyncAddItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue) {
+ return InsertDBOp(
+ MakeUnique<DBOperation>(DBOperation::opAddItem, aCache, aKey, aValue));
+ }
+
+ virtual nsresult AsyncUpdateItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue) {
+ return InsertDBOp(MakeUnique<DBOperation>(DBOperation::opUpdateItem, aCache,
+ aKey, aValue));
+ }
+
+ virtual nsresult AsyncRemoveItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey) {
+ return InsertDBOp(
+ MakeUnique<DBOperation>(DBOperation::opRemoveItem, aCache, aKey));
+ }
+
+ virtual nsresult AsyncClear(LocalStorageCacheBridge* aCache) {
+ return InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClear, aCache));
+ }
+
+ virtual void AsyncClearAll() {
+ InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClearAll));
+ }
+
+ virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) {
+ InsertDBOp(MakeUnique<DBOperation>(DBOperation::opClearMatchingOrigin,
+ aOriginNoSuffix));
+ }
+
+ virtual void AsyncClearMatchingOriginAttributes(
+ const OriginAttributesPattern& aPattern) {
+ InsertDBOp(MakeUnique<DBOperation>(
+ DBOperation::opClearMatchingOriginAttributes, aPattern));
+ }
+
+ virtual void AsyncFlush();
+
+ virtual bool ShouldPreloadOrigin(const nsACString& aOrigin);
+
+ // Get the complete list of scopes having data.
+ void GetOriginsHavingData(nsTArray<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
+ nsTHashSet<nsCString> 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
+ TimeStamp 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;
+
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+
+ // 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(UniquePtr<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:
+ // - TimeDuration::Forever() when no pending tasks are scheduled
+ // - Non-zero TimeDuration when tasks have been scheduled, but it
+ // is still not time to perform the flush ; it is actual time to
+ // wait until the flush has to happen.
+ // - 0 TimeDuration when it is time to do the flush
+ TimeDuration TimeUntilFlush();
+
+ // Notifies to the main thread that flush has completed
+ void NotifyFlushCompletion();
+
+ // Thread loop
+ static void ThreadFunc(void* aArg);
+ void ThreadFunc();
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_StorageDBThread_h
diff --git a/dom/storage/StorageDBUpdater.cpp b/dom/storage/StorageDBUpdater.cpp
new file mode 100644
index 0000000000..7dec6425e2
--- /dev/null
+++ b/dom/storage/StorageDBUpdater.cpp
@@ -0,0 +1,524 @@
+/* -*- 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 "LocalStorageManager.h"
+#include "StorageUtils.h"
+
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageValueArray.h"
+#include "mozIStorageFunction.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsVariant.h"
+#include "mozilla/Tokenizer.h"
+#include "mozIStorageConnection.h"
+#include "mozStorageHelper.h"
+
+// Current version of the database schema
+#define CURRENT_SCHEMA_VERSION 2
+
+namespace mozilla::dom {
+
+using namespace StorageUtils;
+
+namespace {
+
+class nsReverseStringSQLFunction final : public mozIStorageFunction {
+ ~nsReverseStringSQLFunction() = default;
+
+ 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.
+ // AppId doesn't exist any more but we could have old storage data...
+ 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 {
+ OriginAttributes attrs(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() = default;
+
+ 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 extractor(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;
+}
+
+class StripOriginAddonId final : public mozIStorageFunction {
+ public:
+ explicit StripOriginAddonId() = default;
+
+ private:
+ ~StripOriginAddonId() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(StripOriginAddonId, mozIStorageFunction)
+
+NS_IMETHODIMP
+StripOriginAddonId::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
+ nsIVariant** aResult) {
+ nsresult rv;
+
+ nsAutoCString suffix;
+ rv = aFunctionArguments->GetUTF8String(0, suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Deserialize and re-serialize to automatically drop any obsolete origin
+ // attributes.
+ OriginAttributes oa;
+ bool ok = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+ nsAutoCString newSuffix;
+ oa.CreateSuffix(newSuffix);
+
+ nsCOMPtr<nsIWritableVariant> outVar = new nsVariant();
+ rv = outVar->SetAsAUTF8String(newSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+nsresult CreateSchema1Tables(mozIStorageConnection* aWorkerConnection) {
+ nsresult rv;
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "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(
+ nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS origin_key_index"
+ " ON webappsstore2(originAttributes, originKey, key)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult TablesExist(mozIStorageConnection* aWorkerConnection,
+ bool* aWebappsstore2Exists, bool* aWebappsstoreExists,
+ bool* aMoz_webappsstoreExists) {
+ nsresult rv =
+ aWorkerConnection->TableExists("webappsstore2"_ns, aWebappsstore2Exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists("webappsstore"_ns, aWebappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aWorkerConnection->TableExists("moz_webappsstore"_ns,
+ aMoz_webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult CreateCurrentSchemaOnEmptyTableInternal(
+ mozIStorageConnection* aWorkerConnection) {
+ nsresult rv = CreateSchema1Tables(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->SetSchemaVersion(CURRENT_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace
+
+namespace StorageDBUpdater {
+
+nsresult CreateCurrentSchema(mozIStorageConnection* aConnection) {
+ mozStorageTransaction transaction(aConnection, false);
+
+ nsresult rv = transaction.Start();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ {
+ int32_t schemaVer;
+ nsresult rv = aConnection->GetSchemaVersion(&schemaVer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_DIAGNOSTIC_ASSERT(0 == schemaVer);
+
+ bool webappsstore2Exists, webappsstoreExists, moz_webappsstoreExists;
+ rv = TablesExist(aConnection, &webappsstore2Exists, &webappsstoreExists,
+ &moz_webappsstoreExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_DIAGNOSTIC_ASSERT(!webappsstore2Exists && !webappsstoreExists &&
+ !moz_webappsstoreExists);
+ }
+#endif
+
+ rv = CreateCurrentSchemaOnEmptyTableInternal(aConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult Update(mozIStorageConnection* aWorkerConnection) {
+ mozStorageTransaction transaction(aWorkerConnection, false);
+
+ nsresult rv = transaction.Start();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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("scope_key_index"_ns,
+ &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 = TablesExist(aWorkerConnection, &webappsstore2Exists,
+ &webappsstoreExists, &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.
+
+ // XXX What does "break to the next version to update to" mean here? It
+ // seems to refer to the 'break' statement below, but that breaks out of
+ // the 'switch' statement and continues with committing the transaction.
+ // Either this is wrong, or the comment above is misleading.
+
+ rv = CreateCurrentSchemaOnEmptyTableInternal(aWorkerConnection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+
+ doVacuum = true;
+
+ // Ensure Gecko 1.9.1 storage table
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE IF NOT EXISTS webappsstore2 ("
+ "scope TEXT, "
+ "key TEXT, "
+ "value TEXT, "
+ "secure INTEGER, "
+ "owner TEXT)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(
+ nsLiteralCString("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("REVERSESTRING"_ns, 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(nsLiteralCString(
+ "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("DROP TABLE webappsstore"_ns);
+ 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(nsLiteralCString(
+ "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(
+ "DROP TABLE moz_webappsstore"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aWorkerConnection->RemoveFunction("REVERSESTRING"_ns);
+
+ // 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(nsLiteralCString(
+ "DROP INDEX IF EXISTS webappsstore2.origin_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "DROP INDEX IF EXISTS webappsstore2.scope_key_index"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "ALTER TABLE webappsstore2 RENAME TO webappsstore2_old"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> oaSuffixFunc(new GetOriginParticular(
+ GetOriginParticular::ORIGIN_ATTRIBUTES_SUFFIX));
+ rv = aWorkerConnection->CreateFunction("GET_ORIGIN_SUFFIX"_ns, 1,
+ oaSuffixFunc);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageFunction> originKeyFunc(
+ new GetOriginParticular(GetOriginParticular::ORIGIN_KEY));
+ rv = aWorkerConnection->CreateFunction("GET_ORIGIN_KEY"_ns, 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(nsLiteralCString(
+ "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(
+ "DROP TABLE webappsstore2_old"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aWorkerConnection->RemoveFunction("GET_ORIGIN_SUFFIX"_ns);
+ aWorkerConnection->RemoveFunction("GET_ORIGIN_KEY"_ns);
+
+ rv = aWorkerConnection->SetSchemaVersion(1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ [[fallthrough]];
+ }
+ case 1: {
+ nsCOMPtr<mozIStorageFunction> oaStripAddonId(new StripOriginAddonId());
+ rv = aWorkerConnection->CreateFunction("STRIP_ADDON_ID"_ns, 1,
+ oaStripAddonId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aWorkerConnection->ExecuteSimpleSQL(nsLiteralCString(
+ "UPDATE webappsstore2 "
+ "SET originAttributes = STRIP_ADDON_ID(originAttributes) "
+ "WHERE originAttributes LIKE '^%'"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aWorkerConnection->RemoveFunction("STRIP_ADDON_ID"_ns);
+
+ rv = aWorkerConnection->SetSchemaVersion(2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ [[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("VACUUM"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+} // namespace StorageDBUpdater
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageDBUpdater.h b/dom/storage/StorageDBUpdater.h
new file mode 100644
index 0000000000..6745e7e8cb
--- /dev/null
+++ b/dom/storage/StorageDBUpdater.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageDBUpdater_h
+#define mozilla_dom_StorageDBUpdater_h
+
+namespace mozilla::dom::StorageDBUpdater {
+
+// Must only be called on an empty database.
+nsresult CreateCurrentSchema(mozIStorageConnection* aWorkerConnection);
+
+// XXX Rename to MaybeUpdate or EnsureCurrentSchemaVersion.
+nsresult Update(mozIStorageConnection* aWorkerConnection);
+
+} // namespace mozilla::dom::StorageDBUpdater
+
+#endif // mozilla_dom_StorageDBUpdater_h
diff --git a/dom/storage/StorageIPC.cpp b/dom/storage/StorageIPC.cpp
new file mode 100644
index 0000000000..811575f471
--- /dev/null
+++ b/dom/storage/StorageIPC.cpp
@@ -0,0 +1,1598 @@
+/* -*- 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 "StorageIPC.h"
+
+#include "StorageCommon.h"
+#include "StorageUtils.h"
+#include "LocalStorageManager.h"
+#include "SessionStorageObserver.h"
+#include "SessionStorageManager.h"
+#include "SessionStorageCache.h"
+
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsIPrincipal.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+using LocalStorageCacheParentHashtable =
+ nsClassHashtable<nsCStringHashKey, nsTArray<LocalStorageCacheParent*>>;
+
+StaticAutoPtr<LocalStorageCacheParentHashtable> gLocalStorageCacheParents;
+
+StorageDBChild* sStorageChild[kPrivateBrowsingIdCount] = {nullptr, nullptr};
+
+// False until we shut the storage child down.
+bool sStorageChildDown[kPrivateBrowsingIdCount] = {false, false};
+
+} // namespace
+
+LocalStorageCacheChild::LocalStorageCacheChild(LocalStorageCache* aCache)
+ : mCache(aCache) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCache);
+ aCache->AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(LocalStorageCacheChild);
+}
+
+LocalStorageCacheChild::~LocalStorageCacheChild() {
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(LocalStorageCacheChild);
+}
+
+void LocalStorageCacheChild::SendDeleteMeInternal() {
+ AssertIsOnOwningThread();
+
+ if (mCache) {
+ mCache->ClearActor();
+ mCache = nullptr;
+
+ MOZ_ALWAYS_TRUE(PBackgroundLocalStorageCacheChild::SendDeleteMe());
+ }
+}
+
+void LocalStorageCacheChild::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ if (mCache) {
+ mCache->ClearActor();
+ mCache = nullptr;
+ }
+}
+
+mozilla::ipc::IPCResult LocalStorageCacheChild::RecvObserve(
+ const PrincipalInfo& aPrincipalInfo,
+ const PrincipalInfo& aCachePrincipalInfo,
+ const uint32_t& aPrivateBrowsingId, const nsAString& aDocumentURI,
+ const nsAString& aKey, const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ AssertIsOnOwningThread();
+
+ auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
+ if (NS_WARN_IF(principalOrErr.isErr())) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ auto cachePrincipalOrErr = PrincipalInfoToPrincipal(aCachePrincipalInfo);
+ if (NS_WARN_IF(cachePrincipalOrErr.isErr())) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+ nsCOMPtr<nsIPrincipal> cachePrincipal = cachePrincipalOrErr.unwrap();
+
+ if (StorageUtils::PrincipalsEqual(principal, cachePrincipal)) {
+ Storage::NotifyChange(/* aStorage */ nullptr, principal, aKey, aOldValue,
+ aNewValue,
+ /* aStorageType */ u"localStorage", aDocumentURI,
+ /* aIsPrivate */ !!aPrivateBrowsingId,
+ /* aImmediateDispatch */ true);
+ }
+
+ return IPC_OK();
+}
+
+// ----------------------------------------------------------------------------
+// Child
+// ----------------------------------------------------------------------------
+
+class StorageDBChild::ShutdownObserver final : public nsIObserver {
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+
+ public:
+ explicit ShutdownObserver(const uint32_t aPrivateBrowsingId)
+ : mPrivateBrowsingId(aPrivateBrowsingId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ ~ShutdownObserver() { MOZ_ASSERT(NS_IsMainThread()); }
+};
+
+void StorageDBChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void StorageDBChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+StorageDBChild::StorageDBChild(LocalStorageManager* aManager,
+ const uint32_t aPrivateBrowsingId)
+ : mManager(aManager),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mStatus(NS_OK),
+ mIPCOpen(false) {
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+}
+
+StorageDBChild::~StorageDBChild() = default;
+
+// static
+StorageDBChild* StorageDBChild::Get(const uint32_t aPrivateBrowsingId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ return sStorageChild[aPrivateBrowsingId];
+}
+
+// static
+StorageDBChild* StorageDBChild::GetOrCreate(const uint32_t aPrivateBrowsingId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ StorageDBChild*& storageChild = sStorageChild[aPrivateBrowsingId];
+ if (storageChild || sStorageChildDown[aPrivateBrowsingId]) {
+ // When sStorageChildDown is at true, sStorageChild is null.
+ // Checking sStorageChildDown flag here prevents reinitialization of
+ // the storage child after shutdown.
+ return storageChild;
+ }
+
+ // Use LocalStorageManager::Ensure in case we're called from
+ // DOMSessionStorageManager's initializer and we haven't yet initialized the
+ // local storage manager.
+ RefPtr<StorageDBChild> newStorageChild =
+ new StorageDBChild(LocalStorageManager::Ensure(), aPrivateBrowsingId);
+
+ nsresult rv = newStorageChild->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ newStorageChild.forget(&storageChild);
+
+ return storageChild;
+}
+
+nsTHashSet<nsCString>& StorageDBChild::OriginsHavingData() {
+ if (!mOriginsHavingData) {
+ mOriginsHavingData = MakeUnique<nsTHashSet<nsCString>>();
+ }
+
+ return *mOriginsHavingData;
+}
+
+nsresult StorageDBChild::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ::mozilla::ipc::PBackgroundChild* actor =
+ ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actor)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString profilePath;
+ if (XRE_IsParentProcess() && mPrivateBrowsingId == 0) {
+ nsresult rv = StorageDBThread::GetProfilePath(profilePath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ AddIPDLReference();
+
+ actor->SendPBackgroundStorageConstructor(this, profilePath,
+ mPrivateBrowsingId);
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ MOZ_ASSERT(observerService);
+
+ nsCOMPtr<nsIObserver> observer = new ShutdownObserver(mPrivateBrowsingId);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ observerService->AddObserver(observer, "xpcom-shutdown", false));
+
+ return NS_OK;
+}
+
+nsresult StorageDBChild::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 StorageDBChild::AsyncPreload(LocalStorageCacheBridge* 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.Insert(aCache);
+ SendAsyncPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ aPriority);
+ } else {
+ // No IPC, no love. But the LoadDone call is expected.
+ aCache->LoadDone(NS_ERROR_UNEXPECTED);
+ }
+}
+
+void StorageDBChild::AsyncGetUsage(StorageUsageBridge* aUsage) {
+ if (mIPCOpen) {
+ SendAsyncGetUsage(aUsage->OriginScope());
+ }
+}
+
+void StorageDBChild::SyncPreload(LocalStorageCacheBridge* 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.
+ nsTArray<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 StorageDBChild::AsyncAddItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue) {
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncAddItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey), nsString(aValue));
+ OriginsHavingData().Insert(aCache->Origin());
+ return NS_OK;
+}
+
+nsresult StorageDBChild::AsyncUpdateItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue) {
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncUpdateItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey), nsString(aValue));
+ OriginsHavingData().Insert(aCache->Origin());
+ return NS_OK;
+}
+
+nsresult StorageDBChild::AsyncRemoveItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey) {
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncRemoveItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(),
+ nsString(aKey));
+ return NS_OK;
+}
+
+nsresult StorageDBChild::AsyncClear(LocalStorageCacheBridge* aCache) {
+ if (NS_FAILED(mStatus) || !mIPCOpen) {
+ return mStatus;
+ }
+
+ SendAsyncClear(aCache->OriginSuffix(), aCache->OriginNoSuffix());
+ OriginsHavingData().Remove(aCache->Origin());
+ return NS_OK;
+}
+
+bool StorageDBChild::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);
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvObserve(
+ const nsACString& aTopic, const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ if (StorageObserver* obs = StorageObserver::Self()) {
+ obs->Notify(PromiseFlatCString(aTopic).get(), aOriginAttributesPattern,
+ aOriginScope);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvOriginsHavingData(
+ nsTArray<nsCString>&& aOrigins) {
+ // Force population of mOriginsHavingData even if there are no origins so that
+ // ShouldPreloadOrigin does not generate false positives for all origins.
+ if (!aOrigins.Length()) {
+ Unused << OriginsHavingData();
+ }
+
+ for (uint32_t i = 0; i < aOrigins.Length(); ++i) {
+ OriginsHavingData().Insert(aOrigins[i]);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvLoadItem(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsAString& aKey, const nsAString& aValue) {
+ LocalStorageCache* aCache =
+ mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
+ if (aCache) {
+ aCache->LoadItem(aKey, aValue);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvLoadDone(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsresult& aRv) {
+ LocalStorageCache* aCache =
+ mManager->GetCache(aOriginSuffix, aOriginNoSuffix);
+ if (aCache) {
+ aCache->LoadDone(aRv);
+
+ // Just drop reference to this cache now since the load is done.
+ mLoadingCaches.Remove(static_cast<LocalStorageCacheBridge*>(aCache));
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvLoadUsage(
+ const nsACString& aOriginNoSuffix, const int64_t& aUsage) {
+ RefPtr<StorageUsageBridge> scopeUsage =
+ mManager->GetOriginUsage(aOriginNoSuffix, mPrivateBrowsingId);
+ scopeUsage->LoadUsage(aUsage);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBChild::RecvError(const nsresult& aRv) {
+ mStatus = aRv;
+ return IPC_OK();
+}
+
+NS_IMPL_ISUPPORTS(StorageDBChild::ShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+StorageDBChild::ShutdownObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Unused << observerService->RemoveObserver(this, "xpcom-shutdown");
+
+ StorageDBChild*& storageChild = sStorageChild[mPrivateBrowsingId];
+ if (storageChild) {
+ sStorageChildDown[mPrivateBrowsingId] = true;
+
+ MOZ_ALWAYS_TRUE(storageChild->PBackgroundStorageChild::SendDeleteMe());
+
+ NS_RELEASE(storageChild);
+ storageChild = nullptr;
+ }
+
+ return NS_OK;
+}
+
+SessionStorageObserverChild::SessionStorageObserverChild(
+ SessionStorageObserver* aObserver)
+ : mObserver(aObserver) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(NextGenLocalStorageEnabled());
+ MOZ_ASSERT(aObserver);
+ aObserver->AssertIsOnOwningThread();
+
+ MOZ_COUNT_CTOR(SessionStorageObserverChild);
+}
+
+SessionStorageObserverChild::~SessionStorageObserverChild() {
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(SessionStorageObserverChild);
+}
+
+void SessionStorageObserverChild::SendDeleteMeInternal() {
+ AssertIsOnOwningThread();
+
+ if (mObserver) {
+ mObserver->ClearActor();
+ mObserver = nullptr;
+
+ // Don't check result here since IPC may no longer be available due to
+ // SessionStorageManager (which holds a strong reference to
+ // SessionStorageObserver) being destroyed very late in the game.
+ PSessionStorageObserverChild::SendDeleteMe();
+ }
+}
+
+void SessionStorageObserverChild::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ if (mObserver) {
+ mObserver->ClearActor();
+ mObserver = nullptr;
+ }
+}
+
+mozilla::ipc::IPCResult SessionStorageObserverChild::RecvObserve(
+ const nsACString& aTopic, const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ AssertIsOnOwningThread();
+
+ if (StorageObserver* obs = StorageObserver::Self()) {
+ obs->Notify(PromiseFlatCString(aTopic).get(), aOriginAttributesPattern,
+ aOriginScope);
+ }
+
+ return IPC_OK();
+}
+
+SessionStorageCacheChild::SessionStorageCacheChild(SessionStorageCache* aCache)
+ : mCache(aCache) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mCache);
+
+ MOZ_COUNT_CTOR(SessionStorageCacheChild);
+}
+
+SessionStorageCacheChild::~SessionStorageCacheChild() {
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(SessionStorageCacheChild);
+}
+
+void SessionStorageCacheChild::SendDeleteMeInternal() {
+ AssertIsOnOwningThread();
+
+ if (mCache) {
+ mCache->ClearActor();
+ mCache = nullptr;
+
+ MOZ_ALWAYS_TRUE(PBackgroundSessionStorageCacheChild::SendDeleteMe());
+ }
+}
+
+void SessionStorageCacheChild::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ if (mCache) {
+ mCache->ClearActor();
+ mCache = nullptr;
+ }
+}
+
+SessionStorageManagerChild::SessionStorageManagerChild(
+ SessionStorageManager* aSSManager)
+ : mSSManager(aSSManager) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mSSManager);
+
+ MOZ_COUNT_CTOR(SessionStorageManagerChild);
+}
+
+SessionStorageManagerChild::~SessionStorageManagerChild() {
+ AssertIsOnOwningThread();
+
+ MOZ_COUNT_DTOR(SessionStorageManagerChild);
+}
+
+void SessionStorageManagerChild::SendDeleteMeInternal() {
+ AssertIsOnOwningThread();
+
+ if (mSSManager) {
+ mSSManager->ClearActor();
+ mSSManager = nullptr;
+
+ MOZ_ALWAYS_TRUE(PBackgroundSessionStorageManagerChild::SendDeleteMe());
+ }
+}
+
+void SessionStorageManagerChild::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ if (mSSManager) {
+ mSSManager->ClearActor();
+ mSSManager = nullptr;
+ }
+}
+
+mozilla::ipc::IPCResult SessionStorageManagerChild::RecvClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) {
+ AssertIsOnOwningThread();
+
+ if (mSSManager) {
+ mSSManager->ClearStoragesForOrigin(aOriginAttrs, aOriginKey);
+ }
+
+ return IPC_OK();
+}
+
+LocalStorageCacheParent::LocalStorageCacheParent(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, uint32_t aPrivateBrowsingId)
+ : mPrincipalInfo(aPrincipalInfo),
+ mOriginKey(aOriginKey),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mActorDestroyed(false) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+LocalStorageCacheParent::~LocalStorageCacheParent() {
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+void LocalStorageCacheParent::ActorDestroy(ActorDestroyReason aWhy) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ MOZ_ASSERT(gLocalStorageCacheParents);
+
+ nsTArray<LocalStorageCacheParent*>* array;
+ gLocalStorageCacheParents->Get(mOriginKey, &array);
+ MOZ_ASSERT(array);
+
+ array->RemoveElement(this);
+
+ if (array->IsEmpty()) {
+ gLocalStorageCacheParents->Remove(mOriginKey);
+ }
+
+ if (!gLocalStorageCacheParents->Count()) {
+ gLocalStorageCacheParents = nullptr;
+ }
+}
+
+mozilla::ipc::IPCResult LocalStorageCacheParent::RecvDeleteMe() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundLocalStorageCacheParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult LocalStorageCacheParent::RecvNotify(
+ const nsAString& aDocumentURI, const nsAString& aKey,
+ const nsAString& aOldValue, const nsAString& aNewValue) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(gLocalStorageCacheParents);
+
+ nsTArray<LocalStorageCacheParent*>* array;
+ gLocalStorageCacheParents->Get(mOriginKey, &array);
+ MOZ_ASSERT(array);
+
+ for (LocalStorageCacheParent* localStorageCacheParent : *array) {
+ if (localStorageCacheParent != this) {
+ // When bug 1443925 is fixed, we can compare mPrincipalInfo against
+ // localStorageCacheParent->PrincipalInfo() here on the background thread
+ // instead of posting it to the main thread. The advantage of doing so is
+ // that it would save an IPC message in the case where the principals do
+ // not match.
+ Unused << localStorageCacheParent->SendObserve(
+ mPrincipalInfo, localStorageCacheParent->PrincipalInfo(),
+ mPrivateBrowsingId, aDocumentURI, aKey, aOldValue, aNewValue);
+ }
+ }
+
+ return IPC_OK();
+}
+
+// ----------------------------------------------------------------------------
+// Parent
+// ----------------------------------------------------------------------------
+
+class StorageDBParent::ObserverSink : public StorageObserverSink {
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ // Only touched on the PBackground thread.
+ StorageDBParent* MOZ_NON_OWNING_REF mActor;
+
+ public:
+ explicit ObserverSink(StorageDBParent* aActor)
+ : mOwningEventTarget(GetCurrentSerialEventTarget()), mActor(aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageDBParent::ObserverSink);
+
+ void Start();
+
+ void Stop();
+
+ private:
+ ~ObserverSink() = default;
+
+ void AddSink();
+
+ void RemoveSink();
+
+ void Notify(const nsACString& aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope);
+
+ // StorageObserverSink
+ nsresult Observe(const char* aTopic, const nsAString& aOriginAttrPattern,
+ const nsACString& aOriginScope) override;
+};
+
+NS_IMPL_ADDREF(StorageDBParent)
+NS_IMPL_RELEASE(StorageDBParent)
+
+void StorageDBParent::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void StorageDBParent::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+namespace {} // namespace
+
+StorageDBParent::StorageDBParent(const nsAString& aProfilePath,
+ const uint32_t aPrivateBrowsingId)
+ : mProfilePath(aProfilePath),
+ mPrivateBrowsingId(aPrivateBrowsingId),
+ mIPCOpen(false) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // We are always open by IPC only
+ AddIPDLReference();
+}
+
+StorageDBParent::~StorageDBParent() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mObserverSink) {
+ mObserverSink->Stop();
+ mObserverSink = nullptr;
+ }
+}
+
+void StorageDBParent::Init() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ PBackgroundParent* actor = Manager();
+ MOZ_ASSERT(actor);
+
+ if (::mozilla::ipc::BackgroundParent::IsOtherProcessActor(actor)) {
+ mObserverSink = new ObserverSink(this);
+ mObserverSink->Start();
+ }
+
+ StorageDBThread* storageThread = StorageDBThread::Get(mPrivateBrowsingId);
+ if (storageThread) {
+ nsTArray<nsCString> scopes;
+ storageThread->GetOriginsHavingData(&scopes);
+ mozilla::Unused << SendOriginsHavingData(scopes);
+ }
+}
+
+StorageDBParent::CacheParentBridge* StorageDBParent::NewCache(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) {
+ return new CacheParentBridge(this, aOriginSuffix, aOriginNoSuffix);
+}
+
+void StorageDBParent::ActorDestroy(ActorDestroyReason aWhy) {
+ // Implement me! Bug 1005169
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvDeleteMe() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundStorageParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncPreload(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const bool& aPriority) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ storageThread->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix),
+ aPriority);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncGetUsage(
+ const nsACString& aOriginNoSuffix) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ // The object releases it self in LoadUsage method
+ RefPtr<UsageParentBridge> usage =
+ new UsageParentBridge(this, aOriginNoSuffix);
+
+ storageThread->AsyncGetUsage(usage);
+
+ return IPC_OK();
+}
+
+namespace {
+
+// We need another implementation of LocalStorageCacheBridge 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 LocalStorageCacheBridge {
+ public:
+ SyncLoadCacheHelper(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ uint32_t aAlreadyLoadedCount, nsTArray<nsString>* aKeys,
+ nsTArray<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 override {
+ return LocalStorageManager::CreateOrigin(mSuffix, mOrigin);
+ }
+ virtual const nsCString& OriginNoSuffix() const override { return mOrigin; }
+ virtual const nsCString& OriginSuffix() const override { return mSuffix; }
+ virtual bool Loaded() override { return mLoaded; }
+ virtual uint32_t LoadedCount() override { return mLoadedCount; }
+ virtual bool LoadItem(const nsAString& aKey,
+ const nsAString& aValue) override {
+ // 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) override {
+ // 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() override {
+ // Called on the main thread, exits after LoadDone() call
+ MonitorAutoLock monitor(mMonitor);
+ while (!mLoaded) {
+ monitor.Wait();
+ }
+ }
+
+ private:
+ Monitor mMonitor MOZ_UNANNOTATED;
+ nsCString mSuffix, mOrigin;
+ nsTArray<nsString>* mKeys;
+ nsTArray<nsString>* mValues;
+ nsresult* mRv;
+ bool mLoaded;
+ uint32_t mLoadedCount;
+};
+
+} // namespace
+
+mozilla::ipc::IPCResult StorageDBParent::RecvPreload(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const uint32_t& aAlreadyLoadedCount, nsTArray<nsString>* aKeys,
+ nsTArray<nsString>* aValues, nsresult* aRv) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ RefPtr<SyncLoadCacheHelper> cache(
+ new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix,
+ aAlreadyLoadedCount, aKeys, aValues, aRv));
+
+ storageThread->SyncPreload(cache, true);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncAddItem(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsAString& aKey, const nsAString& aValue) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsresult rv = storageThread->AsyncAddItem(
+ NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncUpdateItem(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsAString& aKey, const nsAString& aValue) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsresult rv = storageThread->AsyncUpdateItem(
+ NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncRemoveItem(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix,
+ const nsAString& aKey) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsresult rv = storageThread->AsyncRemoveItem(
+ NewCache(aOriginSuffix, aOriginNoSuffix), aKey);
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncClear(
+ const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsresult rv =
+ storageThread->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix));
+ if (NS_FAILED(rv) && mIPCOpen) {
+ mozilla::Unused << SendError(rv);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvAsyncFlush() {
+ StorageDBThread* storageThread = StorageDBThread::Get(mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ storageThread->AsyncFlush();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvStartup() {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvClearAll() {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ storageThread->AsyncClearAll();
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvClearMatchingOrigin(
+ const nsACString& aOriginNoSuffix) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ storageThread->AsyncClearMatchingOrigin(aOriginNoSuffix);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StorageDBParent::RecvClearMatchingOriginAttributes(
+ const OriginAttributesPattern& aPattern) {
+ StorageDBThread* storageThread =
+ StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId);
+ if (!storageThread) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ storageThread->AsyncClearMatchingOriginAttributes(aPattern);
+
+ return IPC_OK();
+}
+
+void StorageDBParent::Observe(const nsACString& aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ if (mIPCOpen) {
+ mozilla::Unused << SendObserve(aTopic, aOriginAttributesPattern,
+ aOriginScope);
+ }
+}
+
+namespace {
+
+// Results must be sent back on the main thread
+class LoadRunnable : public Runnable {
+ public:
+ enum TaskType { loadItem, loadDone };
+
+ LoadRunnable(StorageDBParent* aParent, TaskType aType,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey = u""_ns, const nsAString& aValue = u""_ns)
+ : Runnable("dom::LoadRunnable"),
+ mParent(aParent),
+ mType(aType),
+ mSuffix(aOriginSuffix),
+ mOrigin(aOriginNoSuffix),
+ mKey(aKey),
+ mValue(aValue),
+ mRv(NS_ERROR_NOT_INITIALIZED) {}
+
+ LoadRunnable(StorageDBParent* aParent, TaskType aType,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix, nsresult aRv)
+ : Runnable("dom::LoadRunnable"),
+ mParent(aParent),
+ mType(aType),
+ mSuffix(aOriginSuffix),
+ mOrigin(aOriginNoSuffix),
+ mRv(aRv) {}
+
+ private:
+ RefPtr<StorageDBParent> 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;
+ }
+
+ mParent = nullptr;
+
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+// StorageDBParent::CacheParentBridge
+
+const nsCString StorageDBParent::CacheParentBridge::Origin() const {
+ return LocalStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix);
+}
+
+bool StorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey,
+ const nsAString& aValue) {
+ if (mLoaded) {
+ return false;
+ }
+
+ ++mLoadedCount;
+
+ RefPtr<LoadRunnable> r =
+ new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix,
+ mOriginNoSuffix, aKey, aValue);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+
+ return true;
+}
+
+void StorageDBParent::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);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+}
+
+void StorageDBParent::CacheParentBridge::LoadWait() {
+ // Should never be called on this implementation
+ MOZ_ASSERT(false);
+}
+
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::CacheParentBridge, Destroy)
+// But due to different strings used for refcount logging and different return
+// types, this is done manually for now.
+NS_IMETHODIMP_(void)
+StorageDBParent::CacheParentBridge::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "LocalStorageCacheBridge");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(_class); */
+ Destroy();
+ }
+}
+
+void StorageDBParent::CacheParentBridge::Destroy() {
+ if (mOwningEventTarget->IsOnCurrentThread()) {
+ delete this;
+ return;
+ }
+
+ RefPtr<Runnable> destroyRunnable = NewNonOwningRunnableMethod(
+ "CacheParentBridge::Destroy", this, &CacheParentBridge::Destroy);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningEventTarget->Dispatch(destroyRunnable, NS_DISPATCH_NORMAL));
+}
+
+// StorageDBParent::UsageParentBridge
+
+namespace {
+
+class UsageRunnable : public Runnable {
+ public:
+ UsageRunnable(StorageDBParent* aParent, const nsACString& aOriginScope,
+ const int64_t& aUsage)
+ : Runnable("dom::UsageRunnable"),
+ mParent(aParent),
+ mOriginScope(aOriginScope),
+ mUsage(aUsage) {}
+
+ private:
+ NS_IMETHOD Run() override {
+ if (!mParent->IPCOpen()) {
+ return NS_OK;
+ }
+
+ mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage);
+
+ mParent = nullptr;
+
+ return NS_OK;
+ }
+
+ RefPtr<StorageDBParent> mParent;
+ nsCString mOriginScope;
+ int64_t mUsage;
+};
+
+} // namespace
+
+void StorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage) {
+ RefPtr<UsageRunnable> r = new UsageRunnable(mParent, mOriginScope, aUsage);
+
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(r, NS_DISPATCH_NORMAL));
+}
+
+// XXX Fix me!
+// This should be just:
+// NS_IMPL_RELEASE_WITH_DESTROY(StorageDBParent::UsageParentBridge, Destroy)
+// But due to different strings used for refcount logging, this is done manually
+// for now.
+NS_IMETHODIMP_(MozExternalRefCountType)
+StorageDBParent::UsageParentBridge::Release(void) {
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "StorageUsageBridge");
+ if (count == 0) {
+ Destroy();
+ return 0;
+ }
+ return count;
+}
+
+void StorageDBParent::UsageParentBridge::Destroy() {
+ if (mOwningEventTarget->IsOnCurrentThread()) {
+ delete this;
+ return;
+ }
+
+ RefPtr<Runnable> destroyRunnable = NewNonOwningRunnableMethod(
+ "UsageParentBridge::Destroy", this, &UsageParentBridge::Destroy);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningEventTarget->Dispatch(destroyRunnable, NS_DISPATCH_NORMAL));
+}
+
+void StorageDBParent::ObserverSink::Start() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod("StorageDBParent::ObserverSink::AddSink", this,
+ &StorageDBParent::ObserverSink::AddSink);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+}
+
+void StorageDBParent::ObserverSink::Stop() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ mActor = nullptr;
+
+ RefPtr<Runnable> runnable =
+ NewRunnableMethod("StorageDBParent::ObserverSink::RemoveSink", this,
+ &StorageDBParent::ObserverSink::RemoveSink);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+}
+
+void StorageDBParent::ObserverSink::AddSink() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->AddSink(this);
+ }
+}
+
+void StorageDBParent::ObserverSink::RemoveSink() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+}
+
+void StorageDBParent::ObserverSink::Notify(
+ const nsACString& aTopic, const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mActor) {
+ mActor->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
+ }
+}
+
+nsresult StorageDBParent::ObserverSink::Observe(
+ const char* aTopic, const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<Runnable> runnable = NewRunnableMethod<nsCString, nsString, nsCString>(
+ "StorageDBParent::ObserverSink::Observe2", this,
+ &StorageDBParent::ObserverSink::Notify, aTopic, aOriginAttributesPattern,
+ aOriginScope);
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mOwningEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL));
+
+ return NS_OK;
+}
+
+SessionStorageObserverParent::SessionStorageObserverParent()
+ : mActorDestroyed(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->AddSink(this);
+ }
+}
+
+SessionStorageObserverParent::~SessionStorageObserverParent() {
+ MOZ_ASSERT(mActorDestroyed);
+
+ StorageObserver* observer = StorageObserver::Self();
+ if (observer) {
+ observer->RemoveSink(this);
+ }
+}
+
+void SessionStorageObserverParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+}
+
+mozilla::ipc::IPCResult SessionStorageObserverParent::RecvDeleteMe() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mActorDestroyed);
+
+ IProtocol* mgr = Manager();
+ if (!PSessionStorageObserverParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+nsresult SessionStorageObserverParent::Observe(
+ const char* aTopic, const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mActorDestroyed) {
+ mozilla::Unused << SendObserve(nsDependentCString(aTopic),
+ aOriginAttributesPattern, aOriginScope);
+ }
+ return NS_OK;
+}
+
+SessionStorageCacheParent::SessionStorageCacheParent(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, SessionStorageManagerParent* aActor)
+ : mPrincipalInfo(aPrincipalInfo),
+ mOriginKey(aOriginKey),
+ mManagerActor(aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mManagerActor);
+}
+
+SessionStorageCacheParent::~SessionStorageCacheParent() = default;
+
+void SessionStorageCacheParent::ActorDestroy(ActorDestroyReason aWhy) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ mManagerActor = nullptr;
+}
+
+mozilla::ipc::IPCResult SessionStorageCacheParent::RecvLoad(
+ nsTArray<SSSetItemInfo>* aData) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mManagerActor);
+
+ mLoadReceived.Flip();
+
+ RefPtr<BackgroundSessionStorageManager> manager = mManagerActor->GetManager();
+ MOZ_ASSERT(manager);
+
+ OriginAttributes attrs;
+ MOZ_ALWAYS_TRUE(
+ StoragePrincipalHelper::GetOriginAttributes(mPrincipalInfo, attrs));
+
+ nsAutoCString originAttrs;
+ attrs.CreateSuffix(originAttrs);
+
+ manager->CopyDataToContentProcess(originAttrs, mOriginKey, *aData);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SessionStorageCacheParent::RecvCheckpoint(
+ nsTArray<SSWriteInfo>&& aWriteInfos) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mManagerActor);
+
+ RefPtr<BackgroundSessionStorageManager> manager = mManagerActor->GetManager();
+ MOZ_ASSERT(manager);
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(mPrincipalInfo, attrs);
+
+ nsAutoCString originAttrs;
+ attrs.CreateSuffix(originAttrs);
+
+ manager->UpdateData(originAttrs, mOriginKey, aWriteInfos);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SessionStorageCacheParent::RecvDeleteMe() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mManagerActor);
+
+ mManagerActor = nullptr;
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundSessionStorageCacheParent::Send__delete__(this)) {
+ return IPC_FAIL(
+ mgr, "Failed to delete PBackgroundSessionStorageCacheParent actor");
+ }
+ return IPC_OK();
+}
+
+SessionStorageManagerParent::SessionStorageManagerParent(uint64_t aTopContextId)
+ : mBackgroundManager(
+ BackgroundSessionStorageManager::GetOrCreate(aTopContextId)) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mBackgroundManager);
+ mBackgroundManager->AddParticipatingActor(this);
+}
+
+SessionStorageManagerParent::~SessionStorageManagerParent() = default;
+
+void SessionStorageManagerParent::ActorDestroy(ActorDestroyReason aWhy) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mBackgroundManager) {
+ mBackgroundManager->RemoveParticipatingActor(this);
+ }
+
+ mBackgroundManager = nullptr;
+}
+
+already_AddRefed<PBackgroundSessionStorageCacheParent>
+SessionStorageManagerParent::AllocPBackgroundSessionStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey) {
+ return MakeAndAddRef<SessionStorageCacheParent>(aPrincipalInfo, aOriginKey,
+ this);
+}
+
+BackgroundSessionStorageManager* SessionStorageManagerParent::GetManager()
+ const {
+ return mBackgroundManager;
+}
+
+mozilla::ipc::IPCResult SessionStorageManagerParent::RecvClearStorages(
+ const OriginAttributesPattern& aPattern, const nsACString& aOriginScope) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ mBackgroundManager->ClearStorages(aPattern, aOriginScope);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SessionStorageManagerParent::RecvDeleteMe() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mBackgroundManager);
+
+ mBackgroundManager->RemoveParticipatingActor(this);
+
+ mBackgroundManager = nullptr;
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundSessionStorageManagerParent::Send__delete__(this)) {
+ return IPC_FAIL(
+ mgr, "Failed to delete PBackgroundSessionStorageManagerParent actor");
+ }
+ return IPC_OK();
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+
+PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, const uint32_t& aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ RefPtr<LocalStorageCacheParent> actor = new LocalStorageCacheParent(
+ aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor(
+ mozilla::ipc::PBackgroundParent* aBackgroundActor,
+ PBackgroundLocalStorageCacheParent* aActor,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, const uint32_t& aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ auto* actor = static_cast<LocalStorageCacheParent*>(aActor);
+
+ if (!gLocalStorageCacheParents) {
+ gLocalStorageCacheParents = new LocalStorageCacheParentHashtable();
+ }
+
+ gLocalStorageCacheParents->GetOrInsertNew(aOriginKey)->AppendElement(actor);
+
+ // We are currently trusting the content process not to lie to us. It is
+ // future work to consult the ClientManager to determine whether this is a
+ // legitimate origin for the content process.
+
+ return IPC_OK();
+}
+
+bool DeallocPBackgroundLocalStorageCacheParent(
+ PBackgroundLocalStorageCacheParent* aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<LocalStorageCacheParent> actor =
+ dont_AddRef(static_cast<LocalStorageCacheParent*>(aActor));
+
+ return true;
+}
+
+PBackgroundStorageParent* AllocPBackgroundStorageParent(
+ const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(NextGenLocalStorageEnabled()) ||
+ NS_WARN_IF(aPrivateBrowsingId >= kPrivateBrowsingIdCount)) {
+ return nullptr;
+ }
+
+ return new StorageDBParent(aProfilePath, aPrivateBrowsingId);
+}
+
+mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ auto* actor = static_cast<StorageDBParent*>(aActor);
+ actor->Init();
+ return IPC_OK();
+}
+
+bool DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ StorageDBParent* actor = static_cast<StorageDBParent*>(aActor);
+ actor->ReleaseIPDLReference();
+ return true;
+}
+
+PSessionStorageObserverParent* AllocPSessionStorageObserverParent() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<SessionStorageObserverParent> actor =
+ new SessionStorageObserverParent();
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+bool RecvPSessionStorageObserverConstructor(
+ PSessionStorageObserverParent* aActor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aActor);
+
+ return true;
+}
+
+bool DeallocPSessionStorageObserverParent(
+ PSessionStorageObserverParent* aActor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<SessionStorageObserverParent> actor =
+ dont_AddRef(static_cast<SessionStorageObserverParent*>(aActor));
+
+ return true;
+}
+
+already_AddRefed<PBackgroundSessionStorageManagerParent>
+AllocPBackgroundSessionStorageManagerParent(const uint64_t& aTopContextId) {
+ return MakeAndAddRef<SessionStorageManagerParent>(aTopContextId);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageIPC.h b/dom/storage/StorageIPC.h
new file mode 100644
index 0000000000..41d9d88319
--- /dev/null
+++ b/dom/storage/StorageIPC.h
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageIPC_h
+#define mozilla_dom_StorageIPC_h
+
+#include "LocalStorageCache.h"
+#include "StorageDBThread.h"
+#include "StorageObserver.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PBackgroundLocalStorageCacheChild.h"
+#include "mozilla/dom/PBackgroundLocalStorageCacheParent.h"
+#include "mozilla/dom/PBackgroundSessionStorageCacheChild.h"
+#include "mozilla/dom/PBackgroundSessionStorageCacheParent.h"
+#include "mozilla/dom/PBackgroundSessionStorageManagerChild.h"
+#include "mozilla/dom/PBackgroundSessionStorageManagerParent.h"
+#include "mozilla/dom/PBackgroundStorageChild.h"
+#include "mozilla/dom/PBackgroundStorageParent.h"
+#include "mozilla/dom/PSessionStorageObserverChild.h"
+#include "mozilla/dom/PSessionStorageObserverParent.h"
+#include "nsTHashSet.h"
+
+namespace mozilla {
+
+class OriginAttributesPattern;
+
+namespace ipc {
+
+class BackgroundChildImpl;
+class PrincipalInfo;
+
+} // namespace ipc
+
+namespace dom {
+
+class LocalStorageManager;
+class PBackgroundStorageParent;
+class PSessionStorageObserverParent;
+class SessionStorageCache;
+class SessionStorageCacheParent;
+class SessionStorageManager;
+class SessionStorageManagerParent;
+class BackgroundSessionStorageManager;
+class SessionStorageObserver;
+
+class LocalStorageCacheChild final : public PBackgroundLocalStorageCacheChild {
+ friend class mozilla::ipc::BackgroundChildImpl;
+ friend class LocalStorageCache;
+ friend class LocalStorageManager;
+
+ // LocalStorageCache effectively owns this instance, although IPC handles its
+ // allocation/deallocation. When the LocalStorageCache destructor runs, it
+ // will invoke SendDeleteMeInternal() which will trigger both instances to
+ // drop their mutual references and cause IPC to destroy the actor after the
+ // DeleteMe round-trip.
+ LocalStorageCache* MOZ_NON_OWNING_REF mCache;
+
+ NS_DECL_OWNINGTHREAD
+
+ public:
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(LocalStorageCacheChild);
+ }
+
+ private:
+ // Only created by LocalStorageManager.
+ explicit LocalStorageCacheChild(LocalStorageCache* aCache);
+
+ // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+ ~LocalStorageCacheChild();
+
+ // Only called by LocalStorageCache.
+ void SendDeleteMeInternal();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvObserve(const PrincipalInfo& aPrincipalInfo,
+ const PrincipalInfo& aCachePrincipalInfo,
+ const uint32_t& aPrivateBrowsingId,
+ const nsAString& aDocumentURI,
+ const nsAString& aKey,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) override;
+};
+
+// 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 StorageDBChild final : public PBackgroundStorageChild {
+ class ShutdownObserver;
+
+ virtual ~StorageDBChild();
+
+ public:
+ StorageDBChild(LocalStorageManager* aManager, uint32_t aPrivateBrowsingId);
+
+ static StorageDBChild* Get(uint32_t aPrivateBrowsingId);
+
+ static StorageDBChild* GetOrCreate(uint32_t aPrivateBrowsingId);
+
+ NS_INLINE_DECL_REFCOUNTING(StorageDBChild);
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ virtual nsresult Init();
+ virtual nsresult Shutdown();
+
+ virtual void AsyncPreload(LocalStorageCacheBridge* aCache,
+ bool aPriority = false);
+ virtual void AsyncGetUsage(StorageUsageBridge* aUsage);
+
+ virtual void SyncPreload(LocalStorageCacheBridge* aCache,
+ bool aForceSync = false);
+
+ virtual nsresult AsyncAddItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey, const nsAString& aValue);
+ virtual nsresult AsyncUpdateItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey,
+ const nsAString& aValue);
+ virtual nsresult AsyncRemoveItem(LocalStorageCacheBridge* aCache,
+ const nsAString& aKey);
+ virtual nsresult AsyncClear(LocalStorageCacheBridge* aCache);
+
+ virtual void AsyncClearAll() {
+ if (mOriginsHavingData) {
+ mOriginsHavingData->Clear(); /* NO-OP on the child process otherwise */
+ }
+ }
+
+ virtual void AsyncClearMatchingOrigin(const nsACString& aOriginNoSuffix) {
+ MOZ_CRASH("Shouldn't be called!");
+ }
+
+ virtual void AsyncClearMatchingOriginAttributes(
+ const OriginAttributesPattern& aPattern) {
+ MOZ_CRASH("Shouldn't be called!");
+ }
+
+ virtual void AsyncFlush() { MOZ_CRASH("Shouldn't be called!"); }
+
+ virtual bool ShouldPreloadOrigin(const nsACString& aOriginNoSuffix);
+
+ private:
+ mozilla::ipc::IPCResult RecvObserve(const nsACString& aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) override;
+ mozilla::ipc::IPCResult RecvLoadItem(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey,
+ const nsAString& aValue) override;
+ mozilla::ipc::IPCResult RecvLoadDone(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsresult& aRv) override;
+ mozilla::ipc::IPCResult RecvOriginsHavingData(
+ nsTArray<nsCString>&& aOrigins) override;
+ mozilla::ipc::IPCResult RecvLoadUsage(const nsACString& aOriginNoSuffix,
+ const int64_t& aUsage) override;
+ mozilla::ipc::IPCResult RecvError(const nsresult& aRv) override;
+
+ nsTHashSet<nsCString>& OriginsHavingData();
+
+ // Held to get caches to forward answers to.
+ RefPtr<LocalStorageManager> mManager;
+
+ // Origins having data hash, for optimization purposes only
+ UniquePtr<nsTHashSet<nsCString>> mOriginsHavingData;
+
+ // List of caches waiting for preload. This ensures the contract that
+ // AsyncPreload call references the cache for time of the preload.
+ nsTHashSet<RefPtr<LocalStorageCacheBridge>> mLoadingCaches;
+
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+
+ // Status of the remote database
+ nsresult mStatus;
+
+ bool mIPCOpen;
+};
+
+class SessionStorageObserverChild final : public PSessionStorageObserverChild {
+ friend class SessionStorageManager;
+ friend class SessionStorageObserver;
+
+ // SessionStorageObserver effectively owns this instance, although IPC handles
+ // its allocation/deallocation. When the SessionStorageObserver destructor
+ // runs, it will invoke SendDeleteMeInternal() which will trigger both
+ // instances to drop their mutual references and cause IPC to destroy the
+ // actor after the DeleteMe round-trip.
+ SessionStorageObserver* MOZ_NON_OWNING_REF mObserver;
+
+ NS_DECL_OWNINGTHREAD
+
+ public:
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(LocalStorageCacheChild);
+ }
+
+ private:
+ // Only created by SessionStorageManager.
+ explicit SessionStorageObserverChild(SessionStorageObserver* aObserver);
+
+ ~SessionStorageObserverChild();
+
+ // Only called by SessionStorageObserver.
+ void SendDeleteMeInternal();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvObserve(const nsACString& aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) override;
+};
+
+class SessionStorageCacheChild final
+ : public PBackgroundSessionStorageCacheChild {
+ friend class PBackgroundSessionStorageCacheChild;
+ friend class SessionStorageCache;
+ friend class SessionStorageManager;
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+ // SessionStorageManagerChild effectively owns this instance, although IPC
+ // handles its allocation/deallocation. When the SessionStorageManager
+ // destructor runs, it will invoke SendDeleteMeInternal() which will trigger
+ // both instances to drop their mutual references and cause IPC to destroy the
+ // actor after the DeleteMe round-trip.
+ SessionStorageCache* MOZ_NON_OWNING_REF mCache;
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageCacheChild, override)
+
+ public:
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(SesionStoragManagerChild);
+ }
+
+ private:
+ // Only created by SessionStorageManager.
+ explicit SessionStorageCacheChild(SessionStorageCache* aCache);
+
+ // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+ ~SessionStorageCacheChild();
+
+ // Only called by SessionStorageCache.
+ void SendDeleteMeInternal();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class SessionStorageManagerChild final
+ : public PBackgroundSessionStorageManagerChild {
+ friend class PBackgroundSessionStorageManagerChild;
+ friend class SessionStorage;
+ friend class SessionStorageManager;
+ friend class mozilla::ipc::BackgroundChildImpl;
+
+ // SessionStorageManager effectively owns this instance, although IPC handles
+ // its allocation/deallocation. When the SessionStorageManager destructor
+ // runs, it will invoke SendDeleteMeInternal() which will trigger both
+ // instances to drop their mutual references and cause IPC to destroy the
+ // actor after the DeleteMe round-trip.
+ SessionStorageManager* MOZ_NON_OWNING_REF mSSManager;
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageManagerChild, override)
+
+ public:
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(SesionStoragManagerChild);
+ }
+
+ private:
+ // Only created by SessionStorage.
+ explicit SessionStorageManagerChild(SessionStorageManager* aSSManager);
+
+ // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+ ~SessionStorageManagerChild();
+
+ // Only called by SessionStorageManager.
+ void SendDeleteMeInternal();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvClearStoragesForOrigin(
+ const nsACString& aOriginAttrs, const nsACString& aOriginKey) override;
+};
+
+class LocalStorageCacheParent final
+ : public PBackgroundLocalStorageCacheParent {
+ const PrincipalInfo mPrincipalInfo;
+ const nsCString mOriginKey;
+ uint32_t mPrivateBrowsingId;
+ bool mActorDestroyed;
+
+ public:
+ // Created in AllocPBackgroundLocalStorageCacheParent.
+ LocalStorageCacheParent(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey,
+ uint32_t aPrivateBrowsingId);
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::LocalStorageCacheParent)
+
+ const PrincipalInfo& PrincipalInfo() const { return mPrincipalInfo; }
+
+ private:
+ // Reference counted.
+ ~LocalStorageCacheParent();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvDeleteMe() override;
+
+ mozilla::ipc::IPCResult RecvNotify(const nsAString& aDocumentURI,
+ const nsAString& aKey,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) override;
+};
+
+// Receives async requests from child processes and is responsible
+// to send back responses from the DB thread. Exposes as a fake
+// LocalStorageCache consumer.
+// Also responsible for forwardning all chrome operation notifications
+// such as cookie cleaning etc to the child process.
+class StorageDBParent final : public PBackgroundStorageParent {
+ class ObserverSink;
+
+ virtual ~StorageDBParent();
+
+ public:
+ StorageDBParent(const nsAString& aProfilePath, uint32_t aPrivateBrowsingId);
+
+ void Init();
+
+ 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 LocalStorageCacheBridge {
+ public:
+ CacheParentBridge(StorageDBParent* aParentDB,
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix)
+ : mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mParent(aParentDB),
+ mOriginSuffix(aOriginSuffix),
+ mOriginNoSuffix(aOriginNoSuffix),
+ mLoaded(false),
+ mLoadedCount(0) {}
+ virtual ~CacheParentBridge() = default;
+
+ // LocalStorageCacheBridge
+ virtual const nsCString Origin() const override;
+ virtual const nsCString& OriginNoSuffix() const override {
+ return mOriginNoSuffix;
+ }
+ virtual const nsCString& OriginSuffix() const override {
+ return mOriginSuffix;
+ }
+ virtual bool Loaded() override { return mLoaded; }
+ virtual uint32_t LoadedCount() override { return mLoadedCount; }
+
+ virtual bool LoadItem(const nsAString& aKey,
+ const nsAString& aValue) override;
+ virtual void LoadDone(nsresult aRv) override;
+ virtual void LoadWait() override;
+
+ NS_IMETHOD_(void)
+ Release(void) override;
+
+ private:
+ void Destroy();
+
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+ RefPtr<StorageDBParent> mParent;
+ nsCString mOriginSuffix, mOriginNoSuffix;
+ bool mLoaded;
+ uint32_t mLoadedCount;
+ };
+
+ // Fake usage class receiving async callbacks from DB thread
+ class UsageParentBridge : public StorageUsageBridge {
+ public:
+ UsageParentBridge(StorageDBParent* aParentDB,
+ const nsACString& aOriginScope)
+ : mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mParent(aParentDB),
+ mOriginScope(aOriginScope) {}
+ virtual ~UsageParentBridge() = default;
+
+ // StorageUsageBridge
+ virtual const nsCString& OriginScope() override { return mOriginScope; }
+ virtual void LoadUsage(const int64_t usage) override;
+
+ NS_IMETHOD_(MozExternalRefCountType)
+ Release(void) override;
+
+ private:
+ void Destroy();
+
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+ RefPtr<StorageDBParent> mParent;
+ nsCString mOriginScope;
+ };
+
+ private:
+ // IPC
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ mozilla::ipc::IPCResult RecvDeleteMe() override;
+
+ mozilla::ipc::IPCResult RecvAsyncPreload(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const bool& aPriority) override;
+ mozilla::ipc::IPCResult RecvPreload(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const uint32_t& aAlreadyLoadedCount,
+ nsTArray<nsString>* aKeys,
+ nsTArray<nsString>* aValues,
+ nsresult* aRv) override;
+ mozilla::ipc::IPCResult RecvAsyncGetUsage(
+ const nsACString& aOriginNoSuffix) override;
+ mozilla::ipc::IPCResult RecvAsyncAddItem(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey,
+ const nsAString& aValue) override;
+ mozilla::ipc::IPCResult RecvAsyncUpdateItem(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey,
+ const nsAString& aValue) override;
+ mozilla::ipc::IPCResult RecvAsyncRemoveItem(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix,
+ const nsAString& aKey) override;
+ mozilla::ipc::IPCResult RecvAsyncClear(
+ const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix) override;
+ mozilla::ipc::IPCResult RecvAsyncFlush() override;
+
+ mozilla::ipc::IPCResult RecvStartup() override;
+ mozilla::ipc::IPCResult RecvClearAll() override;
+ mozilla::ipc::IPCResult RecvClearMatchingOrigin(
+ const nsACString& aOriginNoSuffix) override;
+ mozilla::ipc::IPCResult RecvClearMatchingOriginAttributes(
+ const OriginAttributesPattern& aPattern) override;
+
+ void Observe(const nsACString& aTopic, const nsAString& aOriginAttrPattern,
+ const nsACString& aOriginScope);
+
+ private:
+ CacheParentBridge* NewCache(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix);
+
+ RefPtr<ObserverSink> mObserverSink;
+
+ // A hack to deal with deadlock between the parent process main thread and
+ // background thread when invoking StorageDBThread::GetOrCreate because it
+ // cannot safely perform a synchronous dispatch back to the main thread
+ // (because we are already synchronously doing things on the stack).
+ // Populated for the same process actors, empty for other process actors.
+ nsString mProfilePath;
+
+ // Expected to be only 0 or 1.
+ const uint32_t mPrivateBrowsingId;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ // True when IPC channel is open and Send*() methods are OK to use.
+ bool mIPCOpen;
+};
+
+class SessionStorageObserverParent final : public PSessionStorageObserverParent,
+ public StorageObserverSink {
+ bool mActorDestroyed;
+
+ public:
+ // Created in AllocPSessionStorageObserverParent.
+ SessionStorageObserverParent();
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageObserverParent)
+
+ private:
+ // Reference counted.
+ ~SessionStorageObserverParent();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvDeleteMe() override;
+
+ // StorageObserverSink
+ nsresult Observe(const char* aTopic, const nsAString& aOriginAttrPattern,
+ const nsACString& aOriginScope) override;
+};
+
+class SessionStorageCacheParent final
+ : public PBackgroundSessionStorageCacheParent {
+ friend class PBackgroundSessionStorageCacheParent;
+ const PrincipalInfo mPrincipalInfo;
+ const nsCString mOriginKey;
+
+ RefPtr<SessionStorageManagerParent> mManagerActor;
+ FlippedOnce<false> mLoadReceived;
+
+ public:
+ SessionStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey,
+ SessionStorageManagerParent* aActor);
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageCacheParent, override)
+
+ const PrincipalInfo& PrincipalInfo() const { return mPrincipalInfo; }
+ const nsACString& OriginKey() const { return mOriginKey; }
+
+ private:
+ ~SessionStorageCacheParent();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvLoad(nsTArray<SSSetItemInfo>* aData) override;
+
+ mozilla::ipc::IPCResult RecvCheckpoint(
+ nsTArray<SSWriteInfo>&& aWriteInfos) override;
+
+ mozilla::ipc::IPCResult RecvDeleteMe() override;
+};
+
+class SessionStorageManagerParent final
+ : public PBackgroundSessionStorageManagerParent {
+ friend class PBackgroundSessionStorageManagerParent;
+
+ RefPtr<BackgroundSessionStorageManager> mBackgroundManager;
+
+ public:
+ explicit SessionStorageManagerParent(uint64_t aTopContextId);
+
+ NS_INLINE_DECL_REFCOUNTING(mozilla::dom::SessionStorageManagerParent,
+ override)
+
+ already_AddRefed<PBackgroundSessionStorageCacheParent>
+ AllocPBackgroundSessionStorageCacheParent(
+ const PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey) override;
+
+ BackgroundSessionStorageManager* GetManager() const;
+
+ mozilla::ipc::IPCResult RecvClearStorages(
+ const OriginAttributesPattern& aPattern,
+ const nsACString& aOriginScope) override;
+
+ private:
+ ~SessionStorageManagerParent();
+
+ // IPDL methods are only called by IPDL.
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvDeleteMe() override;
+};
+
+PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, const uint32_t& aPrivateBrowsingId);
+
+mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor(
+ mozilla::ipc::PBackgroundParent* aBackgroundActor,
+ PBackgroundLocalStorageCacheParent* aActor,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey, const uint32_t& aPrivateBrowsingId);
+
+bool DeallocPBackgroundLocalStorageCacheParent(
+ PBackgroundLocalStorageCacheParent* aActor);
+
+PBackgroundStorageParent* AllocPBackgroundStorageParent(
+ const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId);
+
+mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor(
+ PBackgroundStorageParent* aActor, const nsAString& aProfilePath,
+ const uint32_t& aPrivateBrowsingId);
+
+bool DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor);
+
+PSessionStorageObserverParent* AllocPSessionStorageObserverParent();
+
+bool RecvPSessionStorageObserverConstructor(
+ PSessionStorageObserverParent* aActor);
+
+bool DeallocPSessionStorageObserverParent(
+ PSessionStorageObserverParent* aActor);
+
+already_AddRefed<PBackgroundSessionStorageCacheParent>
+AllocPBackgroundSessionStorageCacheParent(
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+ const nsACString& aOriginKey);
+
+already_AddRefed<PBackgroundSessionStorageManagerParent>
+AllocPBackgroundSessionStorageManagerParent(const uint64_t& aTopContextId);
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_StorageIPC_h
diff --git a/dom/storage/StorageNotifierService.cpp b/dom/storage/StorageNotifierService.cpp
new file mode 100644
index 0000000000..57b1ef6bc3
--- /dev/null
+++ b/dom/storage/StorageNotifierService.cpp
@@ -0,0 +1,129 @@
+/* -*- 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 "StorageNotifierService.h"
+#include "StorageUtils.h"
+#include "mozilla/dom/StorageEvent.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+// This boolean is used to avoid the creation of the service after been
+// distroyed on shutdown.
+bool gStorageShuttingDown = false;
+
+StaticRefPtr<StorageNotifierService> gStorageNotifierService;
+
+} // namespace
+
+/* static */
+StorageNotifierService* StorageNotifierService::GetOrCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gStorageNotifierService && !gStorageShuttingDown) {
+ gStorageNotifierService = new StorageNotifierService();
+ ClearOnShutdown(&gStorageNotifierService);
+ }
+
+ return gStorageNotifierService;
+}
+
+StorageNotifierService::StorageNotifierService() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gStorageNotifierService);
+}
+
+StorageNotifierService::~StorageNotifierService() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!gStorageNotifierService);
+ gStorageShuttingDown = true;
+}
+
+/* static */
+void StorageNotifierService::Broadcast(StorageEvent* aEvent,
+ const char16_t* aStorageType,
+ bool aPrivateBrowsing,
+ bool aImmediateDispatch) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<StorageNotifierService> service = gStorageNotifierService;
+ if (!service) {
+ return;
+ }
+
+ RefPtr<StorageEvent> event = aEvent;
+
+ for (const auto& observer : service->mObservers.ForwardRange()) {
+ // Enforce that the source storage area's private browsing state matches
+ // this window's state. These flag checks and their maintenance independent
+ // from the principal's OriginAttributes matter because chrome docshells
+ // that are part of private browsing windows can be private browsing without
+ // having their OriginAttributes set (because they have the system
+ // principal).
+ if (aPrivateBrowsing != observer->IsPrivateBrowsing()) {
+ continue;
+ }
+
+ // No reasons to continue if the principal of the event doesn't match with
+ // the window's one.
+ if (!StorageUtils::PrincipalsEqual(
+ aEvent->GetPrincipal(), observer->GetEffectiveStoragePrincipal())) {
+ continue;
+ }
+
+ const auto pinnedObserver = observer;
+
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ "StorageNotifierService::Broadcast",
+ [pinnedObserver, event, aStorageType, aPrivateBrowsing,
+ aImmediateDispatch]() {
+ // Check principals again. EffectiveStoragePrincipal may be changed
+ // when relaxed.
+ if (!aImmediateDispatch &&
+ !StorageUtils::PrincipalsEqual(
+ event->GetPrincipal(),
+ pinnedObserver->GetEffectiveStoragePrincipal())) {
+ return;
+ }
+
+ pinnedObserver->ObserveStorageNotification(event, aStorageType,
+ aPrivateBrowsing);
+ });
+
+ if (aImmediateDispatch) {
+ r->Run();
+ } else {
+ nsCOMPtr<nsIEventTarget> et = pinnedObserver->GetEventTarget();
+ if (et) {
+ et->Dispatch(r.forget());
+ }
+ }
+ }
+}
+
+void StorageNotifierService::Register(StorageNotificationObserver* aObserver) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aObserver);
+ MOZ_ASSERT(!mObservers.Contains(aObserver));
+
+ mObservers.AppendElement(aObserver);
+}
+
+void StorageNotifierService::Unregister(
+ StorageNotificationObserver* aObserver) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aObserver);
+
+ // No assertion about mObservers containing aObserver because window calls
+ // this method multiple times.
+
+ mObservers.RemoveElement(aObserver);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageNotifierService.h b/dom/storage/StorageNotifierService.h
new file mode 100644
index 0000000000..9568f4b259
--- /dev/null
+++ b/dom/storage/StorageNotifierService.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageNotifierService_h
+#define mozilla_dom_StorageNotifierService_h
+
+#include "nsISupportsImpl.h"
+#include "nsTObserverArray.h"
+
+class nsIEventTarget;
+class nsIPrincipal;
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+class StorageEvent;
+
+/**
+ * Enables the StorageNotifierService to check whether an observer is interested
+ * in receiving events for the given principal before calling the method, an
+ * optimization refactoring.
+ *
+ * Expected to only be implemented by nsGlobalWindowObserver or its succesor.
+ */
+class StorageNotificationObserver {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void ObserveStorageNotification(StorageEvent* aEvent,
+ const char16_t* aStorageType,
+ bool aPrivateBrowsing) = 0;
+
+ virtual bool IsPrivateBrowsing() const = 0;
+
+ virtual nsIPrincipal* GetEffectiveCookiePrincipal() const = 0;
+
+ virtual nsIPrincipal* GetEffectiveStoragePrincipal() const = 0;
+
+ virtual nsIEventTarget* GetEventTarget() const = 0;
+};
+
+/**
+ * A specialized version of the observer service that uses the custom
+ * StorageNotificationObserver so that principal checks can happen in this class
+ * rather than in the nsIObserver::observe method where they used to happen.
+ *
+ * The only expected consumers are nsGlobalWindowInner instances via their
+ * nsGlobalWindowObserver helper that avoids being able to use the window as an
+ * nsIObserver.
+ */
+class StorageNotifierService final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(StorageNotifierService)
+
+ static StorageNotifierService* GetOrCreate();
+
+ static void Broadcast(StorageEvent* aEvent, const char16_t* aStorageType,
+ bool aPrivateBrowsing, bool aImmediateDispatch);
+
+ void Register(StorageNotificationObserver* aObserver);
+
+ void Unregister(StorageNotificationObserver* aObserver);
+
+ private:
+ StorageNotifierService();
+ ~StorageNotifierService();
+
+ nsTObserverArray<RefPtr<StorageNotificationObserver>> mObservers;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_StorageNotifierService_h
diff --git a/dom/storage/StorageObserver.cpp b/dom/storage/StorageObserver.cpp
new file mode 100644
index 0000000000..bdad1a5ea2
--- /dev/null
+++ b/dom/storage/StorageObserver.cpp
@@ -0,0 +1,532 @@
+/* -*- 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 "StorageObserver.h"
+
+#include "LocalStorageCache.h"
+#include "StorageCommon.h"
+#include "StorageDBThread.h"
+#include "StorageIPC.h"
+#include "StorageUtils.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsIPermission.h"
+#include "nsIIDNService.h"
+#include "nsICookiePermission.h"
+
+#include "nsPrintfCString.h"
+#include "nsXULAppAPI.h"
+#include "nsEscape.h"
+#include "nsNetCID.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::dom {
+
+using namespace StorageUtils;
+
+static const char kStartupTopic[] = "sessionstore-windows-restored";
+static const uint32_t kStartupDelay = 0;
+
+const char kTestingPref[] = "dom.storage.testing";
+
+constexpr auto kPrivateBrowsingPattern = u"{ \"privateBrowsingId\": 1 }"_ns;
+
+NS_IMPL_ISUPPORTS(StorageObserver, nsIObserver, nsINamed,
+ nsISupportsWeakReference)
+
+StorageObserver* StorageObserver::sSelf = nullptr;
+
+// static
+nsresult StorageObserver::Init() {
+ static_assert(kPrivateBrowsingIdCount * sizeof(mBackgroundThread[0]) ==
+ sizeof(mBackgroundThread));
+ if (sSelf) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ sSelf = new StorageObserver();
+ 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, "last-pb-context-exited", true);
+ obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
+ obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true);
+ obs->AddObserver(sSelf, "extension:purge-localStorage", true);
+ obs->AddObserver(sSelf, "browser:purge-sessionStorage", true);
+
+ // Shutdown
+ obs->AddObserver(sSelf, "profile-after-change", true);
+ if (XRE_IsParentProcess()) {
+ obs->AddObserver(sSelf, "profile-before-change", true);
+ }
+
+ // Testing
+#ifdef DOM_STORAGE_TESTS
+ Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
+#endif
+
+ return NS_OK;
+}
+
+// static
+nsresult StorageObserver::Shutdown() {
+ AssertIsOnMainThread();
+
+ if (!sSelf) {
+ return NS_ERROR_NOT_INITIALIZED; // Is this always an error?
+ }
+
+ sSelf->mSinks.Clear();
+
+ NS_RELEASE(sSelf);
+ return NS_OK;
+}
+
+// static
+void StorageObserver::TestingPrefChanged(const char* aPrefName,
+ void* aClosure) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs || !sSelf) {
+ return;
+ }
+
+ if (Preferences::GetBool(kTestingPref)) {
+ 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);
+ } else {
+ obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
+ if (XRE_IsParentProcess()) {
+ // Only to forward to child process.
+ obs->RemoveObserver(sSelf, "domstorage-test-flushed");
+ }
+ obs->RemoveObserver(sSelf, "domstorage-test-reload");
+ }
+}
+
+void StorageObserver::AddSink(StorageObserverSink* aObs) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(sSelf);
+
+ mSinks.AppendElement(aObs);
+}
+
+void StorageObserver::RemoveSink(StorageObserverSink* aObs) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(sSelf);
+
+ mSinks.RemoveElement(aObs);
+}
+
+void StorageObserver::Notify(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) {
+ AssertIsOnMainThread();
+
+ MOZ_ASSERT(sSelf);
+
+ for (auto sink : mSinks.ForwardRange()) {
+ sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
+ }
+}
+
+void StorageObserver::NoteBackgroundThread(const uint32_t aPrivateBrowsingId,
+ nsIEventTarget* aBackgroundThread) {
+ MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
+
+ mBackgroundThread[aPrivateBrowsingId] = aBackgroundThread;
+}
+
+nsresult StorageObserver::GetOriginScope(const char16_t* aData,
+ nsACString& aOriginScope) {
+ nsresult rv;
+
+ NS_ConvertUTF16toUTF8 domain(aData);
+
+ nsAutoCString convertedDomain;
+ nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (converter) {
+ // Convert the domain name to the ACE format
+ rv = converter->ConvertUTF8toACE(domain, convertedDomain);
+ } else {
+ // In case the IDN service is not available, this is the best we can come
+ // up with!
+ rv = NS_EscapeURL(domain, esc_OnlyNonASCII | esc_AlwaysCopy,
+ convertedDomain, fallible);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString originScope;
+ rv = CreateReversedDomain(convertedDomain, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aOriginScope = originScope;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (NS_WARN_IF(!sSelf)) { // Shutdown took place
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ // Start the thread that opens the database.
+ if (!strcmp(aTopic, kStartupTopic)) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kStartupTopic);
+
+ return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer),
+ this, nsITimer::TYPE_ONE_SHOT,
+ kStartupDelay);
+ }
+
+ // Timer callback used to start the database a short timer after startup
+ if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!NextGenLocalStorageEnabled());
+
+ nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
+ if (!timer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (timer == mDBThreadStartDelayTimer) {
+ mDBThreadStartDelayTimer = nullptr;
+
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendStartup();
+ }
+ }
+
+ return NS_OK;
+ }
+
+ // Clear everything, caches + database
+ if (!strcmp(aTopic, "cookie-changed")) {
+ if (!u"cleared"_ns.Equals(aData)) {
+ return NS_OK;
+ }
+
+ if (!NextGenLocalStorageEnabled()) {
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->AsyncClearAll();
+
+ if (XRE_IsParentProcess()) {
+ storageChild->SendClearAll();
+ }
+ }
+ }
+
+ 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 != "cookie"_ns) {
+ return NS_OK;
+ }
+
+ uint32_t cap = 0;
+ perm->GetCapability(&cap);
+ if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
+ !u"deleted"_ns.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);
+
+ nsAutoCString host;
+ principal->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")) {
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ const char topic[] = "extension:purge-localStorage-caches";
+
+ if (aData) {
+ nsCString originScope;
+
+ rv = GetOriginScope(aData, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (XRE_IsParentProcess()) {
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendClearMatchingOrigin(originScope);
+ }
+ }
+
+ Notify(topic, u""_ns, originScope);
+ } else {
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->AsyncClearAll();
+
+ if (XRE_IsParentProcess()) {
+ storageChild->SendClearAll();
+ }
+ }
+
+ Notify(topic);
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
+ if (aData) {
+ nsCString originScope;
+ rv = GetOriginScope(aData, originScope);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Notify(aTopic, u""_ns, originScope);
+ } else {
+ Notify(aTopic, u""_ns, ""_ns);
+ }
+
+ return NS_OK;
+ }
+
+ // Clear all private-browsing caches
+ if (!strcmp(aTopic, "last-pb-context-exited")) {
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ // We get the notification in both processes (parent and content), but the
+ // clearing of the in-memory database should be triggered from the parent
+ // process only to avoid creation of redundant clearing operations.
+ // Also, if we create a new StorageDBChild instance late during content
+ // process shutdown, then it might be leaked in debug builds because it
+ // could happen that there is no chance to properly destroy it.
+ if (XRE_IsParentProcess()) {
+ // This doesn't use a loop with privateBrowsingId 0 and 1, since we only
+ // need to clear the in-memory database which is represented by
+ // privateBrowsingId 1.
+ static const uint32_t id = 1;
+
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(kPrivateBrowsingPattern)) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendClearMatchingOriginAttributes(pattern);
+ }
+
+ Notify("private-browsing-data-cleared", kPrivateBrowsingPattern);
+
+ return NS_OK;
+ }
+
+ // Clear data of the origins whose prefixes will match the suffix.
+ if (!strcmp(aTopic, "clear-origin-attributes-data") ||
+ !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ OriginAttributesPattern pattern;
+ if (!pattern.Init(nsDependentString(aData))) {
+ NS_ERROR("Cannot parse origin attributes pattern");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NextGenLocalStorageEnabled()) {
+ Notify("session-storage:clear-origin-attributes-data",
+ nsDependentString(aData));
+ return NS_OK;
+ }
+
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendClearMatchingOriginAttributes(pattern);
+ }
+
+ Notify(aTopic, nsDependentString(aData));
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-after-change")) {
+ Notify("profile-change");
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "profile-before-change")) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ for (const uint32_t id : {0, 1}) {
+ if (mBackgroundThread[id]) {
+ bool done = false;
+
+ RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
+ new StorageDBThread::ShutdownRunnable(id, done);
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread[id]->Dispatch(
+ shutdownRunnable, NS_DISPATCH_NORMAL));
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+ "StorageObserver::Observe profile-before-change"_ns,
+ [&]() { return done; }));
+
+ mBackgroundThread[id] = nullptr;
+ }
+ }
+
+ return NS_OK;
+ }
+
+#ifdef DOM_STORAGE_TESTS
+ if (!strcmp(aTopic, "domstorage-test-flush-force")) {
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ for (const uint32_t id : {0, 1}) {
+ StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
+ if (NS_WARN_IF(!storageChild)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ storageChild->SendAsyncFlush();
+ }
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "domstorage-test-flushed")) {
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ // Only used to propagate to IPC children
+ Notify("test-flushed");
+
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "domstorage-test-reload")) {
+ if (NextGenLocalStorageEnabled()) {
+ return NS_OK;
+ }
+
+ Notify("test-reload");
+
+ return NS_OK;
+ }
+#endif
+
+ NS_ERROR("Unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+StorageObserver::GetName(nsACString& aName) {
+ aName.AssignLiteral("StorageObserver");
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/storage/StorageObserver.h b/dom/storage/StorageObserver.h
new file mode 100644
index 0000000000..df2b87b2e4
--- /dev/null
+++ b/dom/storage/StorageObserver.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageObserver_h
+#define mozilla_dom_StorageObserver_h
+
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "nsTObserverArray.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+
+class StorageObserver;
+
+// Main-thread interface implemented by legacy LocalStorageManager and current
+// SessionStorageManager for direct consumption. Also implemented by legacy
+// StorageDBParent and current SessionStorageObserverParent for propagation to
+// content processes.
+class StorageObserverSink
+ : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ public:
+ virtual ~StorageObserverSink() = default;
+
+ private:
+ friend class StorageObserver;
+ virtual nsresult Observe(const char* aTopic,
+ const nsAString& aOriginAttributesPattern,
+ const nsACString& aOriginScope) = 0;
+};
+
+// Statically (through layout statics) initialized observer receiving and
+// processing chrome clearing notifications, such as cookie deletion etc.
+class StorageObserver : public nsIObserver,
+ public nsINamed,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ static nsresult Init();
+ static nsresult Shutdown();
+ static StorageObserver* Self() { return sSelf; }
+
+ void AddSink(StorageObserverSink* aObs);
+ void RemoveSink(StorageObserverSink* aObs);
+ void Notify(const char* aTopic,
+ const nsAString& aOriginAttributesPattern = u""_ns,
+ const nsACString& aOriginScope = ""_ns);
+
+ void NoteBackgroundThread(uint32_t aPrivateBrowsingId,
+ nsIEventTarget* aBackgroundThread);
+
+ private:
+ virtual ~StorageObserver() = default;
+
+ nsresult GetOriginScope(const char16_t* aData, nsACString& aOriginScope);
+
+ static void TestingPrefChanged(const char* aPrefName, void* aClosure);
+
+ static StorageObserver* sSelf;
+
+ nsCOMPtr<nsIEventTarget> mBackgroundThread[2];
+
+ // Weak references
+ nsTObserverArray<CheckedUnsafePtr<StorageObserverSink>> mSinks;
+ nsCOMPtr<nsITimer> mDBThreadStartDelayTimer;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_StorageObserver_h
diff --git a/dom/storage/StorageUtils.cpp b/dom/storage/StorageUtils.cpp
new file mode 100644
index 0000000000..39a69c3f08
--- /dev/null
+++ b/dom/storage/StorageUtils.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "nsIURL.h"
+#include "StorageUtils.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "nsDebug.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::dom::StorageUtils {
+
+bool PrincipalsEqual(nsIPrincipal* aObjectPrincipal,
+ nsIPrincipal* aSubjectPrincipal) {
+ if (!aSubjectPrincipal) {
+ return true;
+ }
+
+ if (!aObjectPrincipal) {
+ return false;
+ }
+
+ return aSubjectPrincipal->Equals(aObjectPrincipal);
+}
+
+void ReverseString(const nsACString& aSource, nsACString& aResult) {
+ nsACString::const_iterator sourceBegin, sourceEnd;
+ aSource.BeginReading(sourceBegin);
+ aSource.EndReading(sourceEnd);
+
+ aResult.SetLength(aSource.Length());
+ auto destEnd = aResult.EndWriting();
+
+ 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;
+}
+
+// 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(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix) {
+ nsCString result;
+
+ OriginAttributes oa;
+ if (!aOriginSuffix.IsEmpty()) {
+ DebugOnly<bool> success = oa.PopulateFromSuffix(aOriginSuffix);
+ MOZ_ASSERT(success);
+ }
+
+ if (oa.mInIsolatedMozBrowser) {
+ result.AppendInt(0); // This is the appId to be removed.
+ 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.mInIsolatedMozBrowser = false;
+ oa.CreateSuffix(remaining);
+ if (!remaining.IsEmpty()) {
+ MOZ_ASSERT(!aOriginSuffix.IsEmpty());
+
+ if (result.IsEmpty()) {
+ // Must contain the old prefix, otherwise we won't search for the whole
+ // origin attributes suffix.
+ result.AppendLiteral("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(aOriginSuffix);
+ result.Append(':');
+ }
+
+ result.Append(aOriginNoSuffix);
+
+ return result;
+}
+
+} // namespace mozilla::dom::StorageUtils
diff --git a/dom/storage/StorageUtils.h b/dom/storage/StorageUtils.h
new file mode 100644
index 0000000000..3a8d7c03a6
--- /dev/null
+++ b/dom/storage/StorageUtils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_StorageUtils_h
+#define mozilla_dom_StorageUtils_h
+
+#include "nsStringFwd.h"
+
+class nsIPrincipal;
+
+namespace mozilla::dom::StorageUtils {
+
+bool PrincipalsEqual(nsIPrincipal* aObjectPrincipal,
+ nsIPrincipal* aSubjectPrincipal);
+
+void ReverseString(const nsACString& aSource, nsACString& aResult);
+
+nsresult CreateReversedDomain(const nsACString& aAsciiDomain, nsACString& aKey);
+
+nsCString Scheme0Scope(const nsACString& aOriginSuffix,
+ const nsACString& aOriginNoSuffix);
+
+} // namespace mozilla::dom::StorageUtils
+
+#endif // mozilla_dom_StorageUtils_h
diff --git a/dom/storage/components.conf b/dom/storage/components.conf
new file mode 100644
index 0000000000..15b95f6b0f
--- /dev/null
+++ b/dom/storage/components.conf
@@ -0,0 +1,16 @@
+# -*- 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/.
+
+Classes = [
+ {
+ 'js_name': 'sessionStorage',
+ 'cid': '{7ec4bc82-5022-4d32-8e98-ffbceadb692c}',
+ 'contract_ids': ['@mozilla.org/dom/session-storage-service;1'],
+ 'interfaces': ['nsISessionStorageService'],
+ 'singleton': True,
+ 'type': 'nsISessionStorageService',
+ },
+]
diff --git a/dom/storage/moz.build b/dom/storage/moz.build
new file mode 100644
index 0000000000..963067ced4
--- /dev/null
+++ b/dom/storage/moz.build
@@ -0,0 +1,74 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Storage: localStorage & sessionStorage")
+
+XPIDL_SOURCES += [
+ "nsISessionStorageService.idl",
+]
+
+XPIDL_MODULE = "dom_storage"
+
+EXPORTS.mozilla.dom += [
+ "BackgroundSessionStorageServiceParent.h",
+ "LocalStorage.h",
+ "LocalStorageManager.h",
+ "PartitionedLocalStorage.h",
+ "SessionStorageManager.h",
+ "SessionStorageService.h",
+ "Storage.h",
+ "StorageActivityService.h",
+ "StorageDBUpdater.h",
+ "StorageIPC.h",
+ "StorageNotifierService.h",
+ "StorageObserver.h",
+ "StorageUtils.h",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+UNIFIED_SOURCES += [
+ "BackgroundSessionStorageServiceParent.cpp",
+ "LocalStorage.cpp",
+ "LocalStorageCache.cpp",
+ "LocalStorageManager.cpp",
+ "PartitionedLocalStorage.cpp",
+ "SessionStorage.cpp",
+ "SessionStorageCache.cpp",
+ "SessionStorageManager.cpp",
+ "SessionStorageObserver.cpp",
+ "SessionStorageService.cpp",
+ "Storage.cpp",
+ "StorageActivityService.cpp",
+ "StorageDBThread.cpp",
+ "StorageDBUpdater.cpp",
+ "StorageIPC.cpp",
+ "StorageNotifierService.cpp",
+ "StorageObserver.cpp",
+ "StorageUtils.cpp",
+]
+
+IPDL_SOURCES += [
+ "PBackgroundLocalStorageCache.ipdl",
+ "PBackgroundSessionStorageCache.ipdl",
+ "PBackgroundSessionStorageManager.ipdl",
+ "PBackgroundSessionStorageService.ipdl",
+ "PBackgroundStorage.ipdl",
+ "PSessionStorageObserver.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/dom/base",
+]
+
+if CONFIG["ENABLE_TESTS"]:
+ DEFINES["DOM_STORAGE_TESTS"] = True
diff --git a/dom/storage/nsISessionStorageService.idl b/dom/storage/nsISessionStorageService.idl
new file mode 100644
index 0000000000..743c1ebfaa
--- /dev/null
+++ b/dom/storage/nsISessionStorageService.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "nsISupports.idl"
+
+interface nsIPrincipal;
+
+[scriptable, builtinclass, uuid(3b95dd6c-0293-4e06-baeb-2affaefc9e74)]
+interface nsISessionStorageService : nsISupports
+{
+ /**
+ * Removes all storages stored for the given principal.
+ *
+ * @param aPrincipal
+ * A principal for the origin whose storages are to be cleared.
+ */
+ void clearStoragesForOrigin(in nsIPrincipal aPrincipal);
+};