From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/storage/DOMStorageIPC.cpp | 770 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 770 insertions(+) create mode 100644 dom/storage/DOMStorageIPC.cpp (limited to 'dom/storage/DOMStorageIPC.cpp') diff --git a/dom/storage/DOMStorageIPC.cpp b/dom/storage/DOMStorageIPC.cpp new file mode 100644 index 000000000..a8cd745f1 --- /dev/null +++ b/dom/storage/DOMStorageIPC.cpp @@ -0,0 +1,770 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DOMStorageIPC.h" + +#include "DOMStorageManager.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/Unused.h" +#include "nsIDiskSpaceWatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace dom { + +// ---------------------------------------------------------------------------- +// Child +// ---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DOMStorageDBChild) + +NS_IMETHODIMP_(MozExternalRefCountType) DOMStorageDBChild::Release(void) +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "DOMStorageDBChild"); + if (count == 1 && mIPCOpen) { + Send__delete__(this); + return 0; + } + if (count == 0) { + mRefCnt = 1; + delete this; + return 0; + } + return count; +} + +void +DOMStorageDBChild::AddIPDLReference() +{ + MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references"); + mIPCOpen = true; + AddRef(); +} + +void +DOMStorageDBChild::ReleaseIPDLReference() +{ + MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference"); + mIPCOpen = false; + Release(); +} + +DOMStorageDBChild::DOMStorageDBChild(DOMLocalStorageManager* aManager) + : mManager(aManager) + , mStatus(NS_OK) + , mIPCOpen(false) +{ +} + +DOMStorageDBChild::~DOMStorageDBChild() +{ +} + +nsTHashtable& +DOMStorageDBChild::OriginsHavingData() +{ + if (!mOriginsHavingData) { + mOriginsHavingData = new nsTHashtable; + } + + return *mOriginsHavingData; +} + +nsresult +DOMStorageDBChild::Init() +{ + ContentChild* child = ContentChild::GetSingleton(); + AddIPDLReference(); + child->SendPStorageConstructor(this); + return NS_OK; +} + +nsresult +DOMStorageDBChild::Shutdown() +{ + // There is nothing to do here, IPC will release automatically and + // the actual thread running on the parent process will also stop + // automatically in profile-before-change topic observer. + return NS_OK; +} + +void +DOMStorageDBChild::AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority) +{ + if (mIPCOpen) { + // Adding ref to cache for the time of preload. This ensures a reference to + // to the cache and that all keys will load into this cache object. + mLoadingCaches.PutEntry(aCache); + SendAsyncPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aPriority); + } else { + // No IPC, no love. But the LoadDone call is expected. + aCache->LoadDone(NS_ERROR_UNEXPECTED); + } +} + +void +DOMStorageDBChild::AsyncGetUsage(DOMStorageUsageBridge* aUsage) +{ + if (mIPCOpen) { + SendAsyncGetUsage(aUsage->OriginScope()); + } +} + +void +DOMStorageDBChild::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync) +{ + if (NS_FAILED(mStatus)) { + aCache->LoadDone(mStatus); + return; + } + + if (!mIPCOpen) { + aCache->LoadDone(NS_ERROR_UNEXPECTED); + return; + } + + // There is no way to put the child process to a wait state to receive all + // incoming async responses from the parent, hence we have to do a sync preload + // instead. We are smart though, we only demand keys that are left to load in + // case the async preload has already loaded some keys. + InfallibleTArray keys, values; + nsresult rv; + SendPreload(aCache->OriginSuffix(), aCache->OriginNoSuffix(), aCache->LoadedCount(), + &keys, &values, &rv); + + for (uint32_t i = 0; i < keys.Length(); ++i) { + aCache->LoadItem(keys[i], values[i]); + } + + aCache->LoadDone(rv); +} + +nsresult +DOMStorageDBChild::AsyncAddItem(DOMStorageCacheBridge* aCache, + const nsAString& aKey, + const nsAString& aValue) +{ + if (NS_FAILED(mStatus) || !mIPCOpen) { + return mStatus; + } + + SendAsyncAddItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(), + nsString(aKey), nsString(aValue)); + OriginsHavingData().PutEntry(aCache->Origin()); + return NS_OK; +} + +nsresult +DOMStorageDBChild::AsyncUpdateItem(DOMStorageCacheBridge* aCache, + const nsAString& aKey, + const nsAString& aValue) +{ + if (NS_FAILED(mStatus) || !mIPCOpen) { + return mStatus; + } + + SendAsyncUpdateItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(), + nsString(aKey), nsString(aValue)); + OriginsHavingData().PutEntry(aCache->Origin()); + return NS_OK; +} + +nsresult +DOMStorageDBChild::AsyncRemoveItem(DOMStorageCacheBridge* aCache, + const nsAString& aKey) +{ + if (NS_FAILED(mStatus) || !mIPCOpen) { + return mStatus; + } + + SendAsyncRemoveItem(aCache->OriginSuffix(), aCache->OriginNoSuffix(), + nsString(aKey)); + return NS_OK; +} + +nsresult +DOMStorageDBChild::AsyncClear(DOMStorageCacheBridge* aCache) +{ + if (NS_FAILED(mStatus) || !mIPCOpen) { + return mStatus; + } + + SendAsyncClear(aCache->OriginSuffix(), aCache->OriginNoSuffix()); + OriginsHavingData().RemoveEntry(aCache->Origin()); + return NS_OK; +} + +bool +DOMStorageDBChild::ShouldPreloadOrigin(const nsACString& aOrigin) +{ + // Return true if we didn't receive the origins list yet. + // I tend to rather preserve a bit of early-after-start performance + // than a bit of memory here. + return !mOriginsHavingData || mOriginsHavingData->Contains(aOrigin); +} + +bool +DOMStorageDBChild::RecvObserve(const nsCString& aTopic, + const nsString& aOriginAttributesPattern, + const nsCString& aOriginScope) +{ + DOMStorageObserver::Self()->Notify( + aTopic.get(), aOriginAttributesPattern, aOriginScope); + return true; +} + +bool +DOMStorageDBChild::RecvOriginsHavingData(nsTArray&& aOrigins) +{ + for (uint32_t i = 0; i < aOrigins.Length(); ++i) { + OriginsHavingData().PutEntry(aOrigins[i]); + } + + return true; +} + +bool +DOMStorageDBChild::RecvLoadItem(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const nsString& aKey, + const nsString& aValue) +{ + DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix); + if (aCache) { + aCache->LoadItem(aKey, aValue); + } + + return true; +} + +bool +DOMStorageDBChild::RecvLoadDone(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const nsresult& aRv) +{ + DOMStorageCache* aCache = mManager->GetCache(aOriginSuffix, aOriginNoSuffix); + if (aCache) { + aCache->LoadDone(aRv); + + // Just drop reference to this cache now since the load is done. + mLoadingCaches.RemoveEntry(static_cast(aCache)); + } + + return true; +} + +bool +DOMStorageDBChild::RecvLoadUsage(const nsCString& aOriginNoSuffix, const int64_t& aUsage) +{ + RefPtr scopeUsage = mManager->GetOriginUsage(aOriginNoSuffix); + scopeUsage->LoadUsage(aUsage); + return true; +} + +bool +DOMStorageDBChild::RecvError(const nsresult& aRv) +{ + mStatus = aRv; + return true; +} + +// ---------------------------------------------------------------------------- +// Parent +// ---------------------------------------------------------------------------- + +NS_IMPL_ADDREF(DOMStorageDBParent) +NS_IMPL_RELEASE(DOMStorageDBParent) + +void +DOMStorageDBParent::AddIPDLReference() +{ + MOZ_ASSERT(!mIPCOpen, "Attempting to retain multiple IPDL references"); + mIPCOpen = true; + AddRef(); +} + +void +DOMStorageDBParent::ReleaseIPDLReference() +{ + MOZ_ASSERT(mIPCOpen, "Attempting to release non-existent IPDL reference"); + mIPCOpen = false; + Release(); +} + +namespace { + +class SendInitialChildDataRunnable : public Runnable +{ +public: + explicit SendInitialChildDataRunnable(DOMStorageDBParent* aParent) + : mParent(aParent) + {} + +private: + NS_IMETHOD Run() override + { + if (!mParent->IPCOpen()) { + return NS_OK; + } + + DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); + if (db) { + InfallibleTArray scopes; + db->GetOriginsHavingData(&scopes); + mozilla::Unused << mParent->SendOriginsHavingData(scopes); + } + + // We need to check if the device is in a low disk space situation, so + // we can forbid in that case any write in localStorage. + nsCOMPtr diskSpaceWatcher = + do_GetService("@mozilla.org/toolkit/disk-space-watcher;1"); + if (!diskSpaceWatcher) { + return NS_OK; + } + + bool lowDiskSpace = false; + diskSpaceWatcher->GetIsDiskFull(&lowDiskSpace); + + if (lowDiskSpace) { + mozilla::Unused << mParent->SendObserve( + nsDependentCString("low-disk-space"), EmptyString(), EmptyCString()); + } + + return NS_OK; + } + + RefPtr mParent; +}; + +} // namespace + +DOMStorageDBParent::DOMStorageDBParent() +: mIPCOpen(false) +{ + DOMStorageObserver* observer = DOMStorageObserver::Self(); + if (observer) { + observer->AddSink(this); + } + + // We are always open by IPC only + AddIPDLReference(); + + // Cannot send directly from here since the channel + // is not completely built at this moment. + RefPtr r = + new SendInitialChildDataRunnable(this); + NS_DispatchToCurrentThread(r); +} + +DOMStorageDBParent::~DOMStorageDBParent() +{ + DOMStorageObserver* observer = DOMStorageObserver::Self(); + if (observer) { + observer->RemoveSink(this); + } +} + +DOMStorageDBParent::CacheParentBridge* +DOMStorageDBParent::NewCache(const nsACString& aOriginSuffix, const nsACString& aOriginNoSuffix) +{ + return new CacheParentBridge(this, aOriginSuffix, aOriginNoSuffix); +} + +void +DOMStorageDBParent::ActorDestroy(ActorDestroyReason aWhy) +{ + // Implement me! Bug 1005169 +} + +bool +DOMStorageDBParent::RecvAsyncPreload(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const bool& aPriority) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + db->AsyncPreload(NewCache(aOriginSuffix, aOriginNoSuffix), aPriority); + return true; +} + +bool +DOMStorageDBParent::RecvAsyncGetUsage(const nsCString& aOriginNoSuffix) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + // The object releases it self in LoadUsage method + RefPtr usage = new UsageParentBridge(this, aOriginNoSuffix); + db->AsyncGetUsage(usage); + return true; +} + +namespace { + +// We need another implementation of DOMStorageCacheBridge to do +// synchronous IPC preload. This class just receives Load* notifications +// and fills the returning arguments of RecvPreload with the database +// values for us. +class SyncLoadCacheHelper : public DOMStorageCacheBridge +{ +public: + SyncLoadCacheHelper(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + uint32_t aAlreadyLoadedCount, + InfallibleTArray* aKeys, + InfallibleTArray* aValues, + nsresult* rv) + : mMonitor("DOM Storage SyncLoad IPC") + , mSuffix(aOriginSuffix) + , mOrigin(aOriginNoSuffix) + , mKeys(aKeys) + , mValues(aValues) + , mRv(rv) + , mLoaded(false) + , mLoadedCount(aAlreadyLoadedCount) + { + // Precaution + *mRv = NS_ERROR_UNEXPECTED; + } + + virtual const nsCString Origin() const + { + return DOMStorageManager::CreateOrigin(mSuffix, mOrigin); + } + virtual const nsCString& OriginNoSuffix() const { return mOrigin; } + virtual const nsCString& OriginSuffix() const { return mSuffix; } + virtual bool Loaded() { return mLoaded; } + virtual uint32_t LoadedCount() { return mLoadedCount; } + virtual bool LoadItem(const nsAString& aKey, const nsString& aValue) + { + // Called on the aCache background thread + MOZ_ASSERT(!mLoaded); + if (mLoaded) { + return false; + } + + ++mLoadedCount; + mKeys->AppendElement(aKey); + mValues->AppendElement(aValue); + return true; + } + + virtual void LoadDone(nsresult aRv) + { + // Called on the aCache background thread + MonitorAutoLock monitor(mMonitor); + MOZ_ASSERT(!mLoaded && mRv); + mLoaded = true; + if (mRv) { + *mRv = aRv; + mRv = nullptr; + } + monitor.Notify(); + } + + virtual void LoadWait() + { + // Called on the main thread, exits after LoadDone() call + MonitorAutoLock monitor(mMonitor); + while (!mLoaded) { + monitor.Wait(); + } + } + +private: + Monitor mMonitor; + nsCString mSuffix, mOrigin; + InfallibleTArray* mKeys; + InfallibleTArray* mValues; + nsresult* mRv; + bool mLoaded; + uint32_t mLoadedCount; +}; + +} // namespace + +bool +DOMStorageDBParent::RecvPreload(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const uint32_t& aAlreadyLoadedCount, + InfallibleTArray* aKeys, + InfallibleTArray* aValues, + nsresult* aRv) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + RefPtr cache( + new SyncLoadCacheHelper(aOriginSuffix, aOriginNoSuffix, aAlreadyLoadedCount, aKeys, aValues, aRv)); + + db->SyncPreload(cache, true); + return true; +} + +bool +DOMStorageDBParent::RecvAsyncAddItem(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const nsString& aKey, + const nsString& aValue) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + nsresult rv = db->AsyncAddItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue); + if (NS_FAILED(rv) && mIPCOpen) { + mozilla::Unused << SendError(rv); + } + + return true; +} + +bool +DOMStorageDBParent::RecvAsyncUpdateItem(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const nsString& aKey, + const nsString& aValue) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + nsresult rv = db->AsyncUpdateItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey, aValue); + if (NS_FAILED(rv) && mIPCOpen) { + mozilla::Unused << SendError(rv); + } + + return true; +} + +bool +DOMStorageDBParent::RecvAsyncRemoveItem(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix, + const nsString& aKey) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + nsresult rv = db->AsyncRemoveItem(NewCache(aOriginSuffix, aOriginNoSuffix), aKey); + if (NS_FAILED(rv) && mIPCOpen) { + mozilla::Unused << SendError(rv); + } + + return true; +} + +bool +DOMStorageDBParent::RecvAsyncClear(const nsCString& aOriginSuffix, + const nsCString& aOriginNoSuffix) +{ + DOMStorageDBBridge* db = DOMStorageCache::StartDatabase(); + if (!db) { + return false; + } + + nsresult rv = db->AsyncClear(NewCache(aOriginSuffix, aOriginNoSuffix)); + if (NS_FAILED(rv) && mIPCOpen) { + mozilla::Unused << SendError(rv); + } + + return true; +} + +bool +DOMStorageDBParent::RecvAsyncFlush() +{ + DOMStorageDBBridge* db = DOMStorageCache::GetDatabase(); + if (!db) { + return false; + } + + db->AsyncFlush(); + return true; +} + +// DOMStorageObserverSink + +nsresult +DOMStorageDBParent::Observe(const char* aTopic, + const nsAString& aOriginAttributesPattern, + const nsACString& aOriginScope) +{ + if (mIPCOpen) { + mozilla::Unused << SendObserve(nsDependentCString(aTopic), + nsString(aOriginAttributesPattern), + nsCString(aOriginScope)); + } + + return NS_OK; +} + +namespace { + +// Results must be sent back on the main thread +class LoadRunnable : public Runnable +{ +public: + enum TaskType { + loadItem, + loadDone + }; + + LoadRunnable(DOMStorageDBParent* aParent, + TaskType aType, + const nsACString& aOriginSuffix, + const nsACString& aOriginNoSuffix, + const nsAString& aKey = EmptyString(), + const nsAString& aValue = EmptyString()) + : mParent(aParent) + , mType(aType) + , mSuffix(aOriginSuffix) + , mOrigin(aOriginNoSuffix) + , mKey(aKey) + , mValue(aValue) + { } + + LoadRunnable(DOMStorageDBParent* aParent, + TaskType aType, + const nsACString& aOriginSuffix, + const nsACString& aOriginNoSuffix, + nsresult aRv) + : mParent(aParent) + , mType(aType) + , mSuffix(aOriginSuffix) + , mOrigin(aOriginNoSuffix) + , mRv(aRv) + { } + +private: + RefPtr mParent; + TaskType mType; + nsCString mSuffix, mOrigin; + nsString mKey; + nsString mValue; + nsresult mRv; + + NS_IMETHOD Run() override + { + if (!mParent->IPCOpen()) { + return NS_OK; + } + + switch (mType) + { + case loadItem: + mozilla::Unused << mParent->SendLoadItem(mSuffix, mOrigin, mKey, mValue); + break; + case loadDone: + mozilla::Unused << mParent->SendLoadDone(mSuffix, mOrigin, mRv); + break; + } + + return NS_OK; + } +}; + +} // namespace + +// DOMStorageDBParent::CacheParentBridge + +const nsCString +DOMStorageDBParent::CacheParentBridge::Origin() const +{ + return DOMStorageManager::CreateOrigin(mOriginSuffix, mOriginNoSuffix); +} + +bool +DOMStorageDBParent::CacheParentBridge::LoadItem(const nsAString& aKey, const nsString& aValue) +{ + if (mLoaded) { + return false; + } + + ++mLoadedCount; + + RefPtr r = + new LoadRunnable(mParent, LoadRunnable::loadItem, mOriginSuffix, mOriginNoSuffix, aKey, aValue); + NS_DispatchToMainThread(r); + return true; +} + +void +DOMStorageDBParent::CacheParentBridge::LoadDone(nsresult aRv) +{ + // Prevent send of duplicate LoadDone. + if (mLoaded) { + return; + } + + mLoaded = true; + + RefPtr r = + new LoadRunnable(mParent, LoadRunnable::loadDone, mOriginSuffix, mOriginNoSuffix, aRv); + NS_DispatchToMainThread(r); +} + +void +DOMStorageDBParent::CacheParentBridge::LoadWait() +{ + // Should never be called on this implementation + MOZ_ASSERT(false); +} + +// DOMStorageDBParent::UsageParentBridge + +namespace { + +class UsageRunnable : public Runnable +{ +public: + UsageRunnable(DOMStorageDBParent* aParent, const nsACString& aOriginScope, const int64_t& aUsage) + : mParent(aParent) + , mOriginScope(aOriginScope) + , mUsage(aUsage) + {} + +private: + NS_IMETHOD Run() override + { + if (!mParent->IPCOpen()) { + return NS_OK; + } + + mozilla::Unused << mParent->SendLoadUsage(mOriginScope, mUsage); + return NS_OK; + } + + RefPtr mParent; + nsCString mOriginScope; + int64_t mUsage; +}; + +} // namespace + +void +DOMStorageDBParent::UsageParentBridge::LoadUsage(const int64_t aUsage) +{ + RefPtr r = new UsageRunnable(mParent, mOriginScope, aUsage); + NS_DispatchToMainThread(r); +} + +} // namespace dom +} // namespace mozilla -- cgit v1.2.3