/* -*- 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 "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/Unused.h" #include "nsCOMPtr.h" #include "nsIPrincipal.h" #include "nsThreadUtils.h" namespace mozilla { namespace dom { namespace { typedef nsClassHashtable> LocalStorageCacheParentHashtable; StaticAutoPtr gLocalStorageCacheParents; StorageDBChild* sStorageChild[2] = {nullptr, nullptr}; // False until we shut the storage child down. bool sStorageChildDown[2] = {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 nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& 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 principal = principalOrErr.unwrap(); nsCOMPtr 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()); } 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_ASSERT(aPrivateBrowsingId <= 1); MOZ_ASSERT(!NextGenLocalStorageEnabled()); } StorageDBChild::~StorageDBChild() = default; // static StorageDBChild* StorageDBChild::Get(const uint32_t aPrivateBrowsingId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrivateBrowsingId <= 1); MOZ_ASSERT(!NextGenLocalStorageEnabled()); return sStorageChild[aPrivateBrowsingId]; } // static StorageDBChild* StorageDBChild::GetOrCreate(const uint32_t aPrivateBrowsingId) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrivateBrowsingId <= 1); 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 newStorageChild = new StorageDBChild(LocalStorageManager::Ensure(), aPrivateBrowsingId); nsresult rv = newStorageChild->Init(); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } newStorageChild.forget(&storageChild); return storageChild; } nsTHashtable& StorageDBChild::OriginsHavingData() { if (!mOriginsHavingData) { mOriginsHavingData = MakeUnique>(); } 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 observerService = services::GetObserverService(); MOZ_ASSERT(observerService); nsCOMPtr 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.PutEntry(aCache); SendAsyncPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aPriority); } else { // No IPC, no love. But the LoadDone call is expected. aCache->LoadDone(NS_ERROR_UNEXPECTED); } } void 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 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().PutEntry(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().PutEntry(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().RemoveEntry(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 nsCString& aTopic, const nsString& aOriginAttributesPattern, const nsCString& aOriginScope) { MOZ_ASSERT(!XRE_IsParentProcess()); StorageObserver::Self()->Notify(aTopic.get(), aOriginAttributesPattern, aOriginScope); return IPC_OK(); } mozilla::ipc::IPCResult StorageDBChild::RecvOriginsHavingData( nsTArray&& 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().PutEntry(aOrigins[i]); } return IPC_OK(); } mozilla::ipc::IPCResult StorageDBChild::RecvLoadItem( const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& aValue) { LocalStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix); if (aCache) { aCache->LoadItem(aKey, aValue); } return IPC_OK(); } mozilla::ipc::IPCResult StorageDBChild::RecvLoadDone( const nsCString& aOriginSuffix, const nsCString& 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.RemoveEntry(static_cast(aCache)); } return IPC_OK(); } mozilla::ipc::IPCResult StorageDBChild::RecvLoadUsage( const nsCString& aOriginNoSuffix, const int64_t& aUsage) { RefPtr 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 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 nsCString& aTopic, const nsString& aOriginAttributesPattern, const nsCString& aOriginScope) { AssertIsOnOwningThread(); StorageObserver::Self()->Notify(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; } } 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* 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 nsString& aDocumentURI, const nsString& aKey, const nsString& aOldValue, const nsString& aNewValue) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(gLocalStorageCacheParents); nsTArray* 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 mOwningEventTarget; // Only touched on the PBackground thread. StorageDBParent* MOZ_NON_OWNING_REF mActor; public: explicit ObserverSink(StorageDBParent* aActor) : mOwningEventTarget(GetCurrentEventTarget()), 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 nsCString& aTopic, const nsString& aOriginAttributesPattern, const nsCString& 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 nsString& 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 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 nsCString& aOriginSuffix, const nsCString& 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 nsCString& aOriginNoSuffix) { StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId); if (!storageThread) { return IPC_FAIL_NO_REASON(this); } // The object releases it self in LoadUsage method RefPtr 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 nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, uint32_t aAlreadyLoadedCount, nsTArray* aKeys, nsTArray* 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 nsString& 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; nsCString mSuffix, mOrigin; nsTArray* mKeys; nsTArray* mValues; nsresult* mRv; bool mLoaded; uint32_t mLoadedCount; }; } // namespace mozilla::ipc::IPCResult StorageDBParent::RecvPreload( const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const uint32_t& aAlreadyLoadedCount, nsTArray* aKeys, nsTArray* aValues, nsresult* aRv) { StorageDBThread* storageThread = StorageDBThread::GetOrCreate(mProfilePath, mPrivateBrowsingId); if (!storageThread) { return IPC_FAIL_NO_REASON(this); } RefPtr cache( new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount, aKeys, aValues, aRv)); storageThread->SyncPreload(cache, true); return IPC_OK(); } mozilla::ipc::IPCResult StorageDBParent::RecvAsyncAddItem( const nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& 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 nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& aKey, const nsString& 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 nsCString& aOriginSuffix, const nsCString& aOriginNoSuffix, const nsString& 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 nsCString& aOriginSuffix, const nsCString& 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 nsCString& 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 nsCString& aTopic, const nsString& aOriginAttributesPattern, const nsCString& 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 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 nsString& aValue) { if (mLoaded) { return false; } ++mLoadedCount; RefPtr 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 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 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 mParent; nsCString mOriginScope; int64_t mUsage; }; } // namespace void StorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage) { RefPtr 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 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 = 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 = 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 nsCString& aTopic, const nsString& aOriginAttributesPattern, const nsCString& 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 = NewRunnableMethod( "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(nsCString(aTopic), nsString(aOriginAttributesPattern), nsCString(aOriginScope)); } return NS_OK; } SessionStorageCacheParent::SessionStorageCacheParent( const nsCString& aOriginAttrs, const nsCString& aOriginKey, SessionStorageManagerParent* aActor) : mOriginAttrs(aOriginAttrs), 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* aDefaultData, nsTArray* aSessionData) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(mManagerActor); mLoadReceived.Flip(); RefPtr manager = mManagerActor->GetManager(); MOZ_ASSERT(manager); manager->CopyDataToContentProcess(mOriginAttrs, mOriginKey, *aDefaultData, *aSessionData); return IPC_OK(); } mozilla::ipc::IPCResult SessionStorageCacheParent::RecvCheckpoint( nsTArray&& aDefaultWriteInfos, nsTArray&& aSessionWriteInfos) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(mManagerActor); RefPtr manager = mManagerActor->GetManager(); MOZ_ASSERT(manager); manager->UpdateData(mOriginAttrs, mOriginKey, aDefaultWriteInfos, aSessionWriteInfos); 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); } SessionStorageManagerParent::~SessionStorageManagerParent() = default; void SessionStorageManagerParent::ActorDestroy(ActorDestroyReason aWhy) { ::mozilla::ipc::AssertIsOnBackgroundThread(); mBackgroundManager = nullptr; } already_AddRefed SessionStorageManagerParent::AllocPBackgroundSessionStorageCacheParent( const nsCString& aOriginAttrs, const nsCString& aOriginKey) { return MakeAndAddRef(aOriginAttrs, aOriginKey, this); } BackgroundSessionStorageManager* SessionStorageManagerParent::GetManager() const { return mBackgroundManager; } mozilla::ipc::IPCResult SessionStorageManagerParent::RecvDeleteMe() { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(mBackgroundManager); 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 nsCString& aOriginKey, const uint32_t& aPrivateBrowsingId) { ::mozilla::ipc::AssertIsOnBackgroundThread(); RefPtr 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 nsCString& aOriginKey, const uint32_t& aPrivateBrowsingId) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); auto* actor = static_cast(aActor); if (!gLocalStorageCacheParents) { gLocalStorageCacheParents = new LocalStorageCacheParentHashtable(); } nsTArray* array; if (!gLocalStorageCacheParents->Get(aOriginKey, &array)) { array = new nsTArray(); gLocalStorageCacheParents->Put(aOriginKey, array); } array->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 actor = dont_AddRef(static_cast(aActor)); return true; } PBackgroundStorageParent* AllocPBackgroundStorageParent( const nsString& aProfilePath, const uint32_t& aPrivateBrowsingId) { ::mozilla::ipc::AssertIsOnBackgroundThread(); return new StorageDBParent(aProfilePath, aPrivateBrowsingId); } mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor( PBackgroundStorageParent* aActor, const nsString& aProfilePath, const uint32_t& aPrivateBrowsingId) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); auto* actor = static_cast(aActor); actor->Init(); return IPC_OK(); } bool DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor) { ::mozilla::ipc::AssertIsOnBackgroundThread(); MOZ_ASSERT(aActor); StorageDBParent* actor = static_cast(aActor); actor->ReleaseIPDLReference(); return true; } PSessionStorageObserverParent* AllocPSessionStorageObserverParent() { MOZ_ASSERT(NS_IsMainThread()); RefPtr 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 actor = dont_AddRef(static_cast(aActor)); return true; } already_AddRefed AllocPBackgroundSessionStorageManagerParent(const uint64_t& aTopContextId) { return MakeAndAddRef(aTopContextId); } } // namespace dom } // namespace mozilla