diff options
Diffstat (limited to 'dom/storage/SessionStorageManager.cpp')
-rw-r--r-- | dom/storage/SessionStorageManager.cpp | 987 |
1 files changed, 987 insertions, 0 deletions
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 |