diff options
Diffstat (limited to 'dom/quota')
30 files changed, 12606 insertions, 0 deletions
diff --git a/dom/quota/ActorsChild.cpp b/dom/quota/ActorsChild.cpp new file mode 100644 index 000000000..03ed10981 --- /dev/null +++ b/dom/quota/ActorsChild.cpp @@ -0,0 +1,318 @@ +/* -*- 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 "ActorsChild.h" + +#include "nsVariant.h" +#include "QuotaManagerService.h" +#include "QuotaRequests.h" +#include "QuotaResults.h" + +namespace mozilla { +namespace dom { +namespace quota { + +/******************************************************************************* + * QuotaChild + ******************************************************************************/ + +QuotaChild::QuotaChild(QuotaManagerService* aService) + : mService(aService) +#ifdef DEBUG + , mOwningThread(NS_GetCurrentThread()) +#endif +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aService); + + MOZ_COUNT_CTOR(quota::QuotaChild); +} + +QuotaChild::~QuotaChild() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(quota::QuotaChild); +} + +#ifdef DEBUG + +void +QuotaChild::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mOwningThread); + + bool current; + MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(¤t))); + MOZ_ASSERT(current); +} + +#endif // DEBUG + +void +QuotaChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + if (mService) { + mService->ClearBackgroundActor(); +#ifdef DEBUG + mService = nullptr; +#endif + } +} + +PQuotaUsageRequestChild* +QuotaChild::AllocPQuotaUsageRequestChild(const UsageRequestParams& aParams) +{ + AssertIsOnOwningThread(); + + MOZ_CRASH("PQuotaUsageRequestChild actors should be manually constructed!"); +} + +bool +QuotaChild::DeallocPQuotaUsageRequestChild(PQuotaUsageRequestChild* aActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + delete static_cast<QuotaUsageRequestChild*>(aActor); + return true; +} + +PQuotaRequestChild* +QuotaChild::AllocPQuotaRequestChild(const RequestParams& aParams) +{ + AssertIsOnOwningThread(); + + MOZ_CRASH("PQuotaRequestChild actors should be manually constructed!"); +} + +bool +QuotaChild::DeallocPQuotaRequestChild(PQuotaRequestChild* aActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + delete static_cast<QuotaRequestChild*>(aActor); + return true; +} + +/******************************************************************************* + * QuotaUsageRequestChild + ******************************************************************************/ + +QuotaUsageRequestChild::QuotaUsageRequestChild(UsageRequest* aRequest) + : mRequest(aRequest) +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(quota::QuotaUsageRequestChild); +} + +QuotaUsageRequestChild::~QuotaUsageRequestChild() +{ + // Can't assert owning thread here because the request is cleared. + + MOZ_COUNT_DTOR(quota::QuotaUsageRequestChild); +} + +#ifdef DEBUG + +void +QuotaUsageRequestChild::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mRequest); + mRequest->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void +QuotaUsageRequestChild::HandleResponse(nsresult aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(mRequest); + + mRequest->SetError(aResponse); +} + +void +QuotaUsageRequestChild::HandleResponse(const nsTArray<OriginUsage>& aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + + RefPtr<nsVariant> variant = new nsVariant(); + + if (aResponse.IsEmpty()) { + variant->SetAsEmptyArray(); + } else { + nsTArray<RefPtr<UsageResult>> usageResults; + + const uint32_t count = aResponse.Length(); + + usageResults.SetCapacity(count); + + for (uint32_t index = 0; index < count; index++) { + auto& originUsage = aResponse[index]; + + RefPtr<UsageResult> usageResult = new UsageResult(originUsage.origin(), + originUsage.persisted(), + originUsage.usage()); + + usageResults.AppendElement(usageResult.forget()); + } + + variant->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS, + &NS_GET_IID(nsIQuotaUsageResult), + usageResults.Length(), + static_cast<void*>(usageResults.Elements())); + } + + mRequest->SetResult(variant); +} + +void +QuotaUsageRequestChild::HandleResponse(const OriginUsageResponse& aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + + RefPtr<OriginUsageResult> result = + new OriginUsageResult(aResponse.usage(), + aResponse.fileUsage(), + aResponse.limit()); + + RefPtr<nsVariant> variant = new nsVariant(); + variant->SetAsInterface(NS_GET_IID(nsIQuotaOriginUsageResult), result); + + mRequest->SetResult(variant); +} + +void +QuotaUsageRequestChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + if (mRequest) { + mRequest->ClearBackgroundActor(); +#ifdef DEBUG + mRequest = nullptr; +#endif + } +} + +bool +QuotaUsageRequestChild::Recv__delete__(const UsageRequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + + switch (aResponse.type()) { + case UsageRequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case UsageRequestResponse::TAllUsageResponse: + HandleResponse(aResponse.get_AllUsageResponse().originUsages()); + break; + + case UsageRequestResponse::TOriginUsageResponse: + HandleResponse(aResponse.get_OriginUsageResponse()); + break; + + default: + MOZ_CRASH("Unknown response type!"); + } + + return true; +} + +/******************************************************************************* + * QuotaRequestChild + ******************************************************************************/ + +QuotaRequestChild::QuotaRequestChild(Request* aRequest) + : mRequest(aRequest) +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(quota::QuotaRequestChild); +} + +QuotaRequestChild::~QuotaRequestChild() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(quota::QuotaRequestChild); +} + +#ifdef DEBUG + +void +QuotaRequestChild::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mRequest); + mRequest->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +void +QuotaRequestChild::HandleResponse(nsresult aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(mRequest); + + mRequest->SetError(aResponse); +} + +void +QuotaRequestChild::HandleResponse() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + + mRequest->SetResult(); +} + +void +QuotaRequestChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); +} + +bool +QuotaRequestChild::Recv__delete__(const RequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mRequest); + + switch (aResponse.type()) { + case RequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case RequestResponse::TClearOriginResponse: + case RequestResponse::TClearOriginsResponse: + case RequestResponse::TClearAllResponse: + case RequestResponse::TResetAllResponse: + HandleResponse(); + break; + + default: + MOZ_CRASH("Unknown response type!"); + } + + return true; +} + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/ActorsChild.h b/dom/quota/ActorsChild.h new file mode 100644 index 000000000..7aa4616f5 --- /dev/null +++ b/dom/quota/ActorsChild.h @@ -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/. */ + +#ifndef mozilla_dom_quota_ActorsChild_h +#define mozilla_dom_quota_ActorsChild_h + +#include "mozilla/dom/quota/PQuotaChild.h" +#include "mozilla/dom/quota/PQuotaRequestChild.h" +#include "mozilla/dom/quota/PQuotaUsageRequestChild.h" + +namespace mozilla { +namespace ipc { + +class BackgroundChildImpl; + +} // namespace ipc + +namespace dom { +namespace quota { + +class QuotaManagerService; +class Request; +class UsageRequest; + +class QuotaChild final + : public PQuotaChild +{ + friend class mozilla::ipc::BackgroundChildImpl; + friend class QuotaManagerService; + + QuotaManagerService* mService; + +#ifdef DEBUG + nsCOMPtr<nsIEventTarget> mOwningThread; +#endif + +public: + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + +private: + // Only created by QuotaManagerService. + explicit QuotaChild(QuotaManagerService* aService); + + // Only destroyed by mozilla::ipc::BackgroundChildImpl. + ~QuotaChild(); + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PQuotaUsageRequestChild* + AllocPQuotaUsageRequestChild(const UsageRequestParams& aParams) override; + + virtual bool + DeallocPQuotaUsageRequestChild(PQuotaUsageRequestChild* aActor) override; + + virtual PQuotaRequestChild* + AllocPQuotaRequestChild(const RequestParams& aParams) override; + + virtual bool + DeallocPQuotaRequestChild(PQuotaRequestChild* aActor) override; +}; + +class QuotaUsageRequestChild final + : public PQuotaUsageRequestChild +{ + friend class QuotaChild; + friend class QuotaManagerService; + + RefPtr<UsageRequest> mRequest; + +public: + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + +private: + // Only created by QuotaManagerService. + explicit QuotaUsageRequestChild(UsageRequest* aRequest); + + // Only destroyed by QuotaChild. + ~QuotaUsageRequestChild(); + + void + HandleResponse(nsresult aResponse); + + void + HandleResponse(const nsTArray<OriginUsage>& aResponse); + + void + HandleResponse(const OriginUsageResponse& aResponse); + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__(const UsageRequestResponse& aResponse) override; +}; + +class QuotaRequestChild final + : public PQuotaRequestChild +{ + friend class QuotaChild; + friend class QuotaManagerService; + + RefPtr<Request> mRequest; + +public: + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + +private: + // Only created by QuotaManagerService. + explicit QuotaRequestChild(Request* aRequest); + + // Only destroyed by QuotaChild. + ~QuotaRequestChild(); + + void + HandleResponse(nsresult aResponse); + + void + HandleResponse(); + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__(const RequestResponse& aResponse) override; +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_quota_ActorsChild_h diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp new file mode 100644 index 000000000..afdd0e6df --- /dev/null +++ b/dom/quota/ActorsParent.cpp @@ -0,0 +1,7898 @@ +/* -*- 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 "ActorsParent.h" + +#include "mozIStorageConnection.h" +#include "mozIStorageService.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsIObserverService.h" +#include "nsIPermissionManager.h" +#include "nsIPrincipal.h" +#include "nsIRunnable.h" +#include "nsISimpleEnumerator.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsITimer.h" +#include "nsIURI.h" +#include "nsPIDOMWindow.h" + +#include <algorithm> +#include "GeckoProfiler.h" +#include "mozilla/Atomics.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/CondVar.h" +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/asmjscache/AsmJSCache.h" +#include "mozilla/dom/cache/QuotaClient.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/quota/PQuotaParent.h" +#include "mozilla/dom/quota/PQuotaRequestParent.h" +#include "mozilla/dom/quota/PQuotaUsageRequestParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/Mutex.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Unused.h" +#include "mozStorageCID.h" +#include "mozStorageHelper.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsComponentManagerUtils.h" +#include "nsAboutProtocolUtils.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsCRTGlue.h" +#include "nsDirectoryServiceUtils.h" +#include "nsEscape.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsScriptSecurityManager.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prio.h" +#include "xpcpublic.h" + +#include "OriginScope.h" +#include "QuotaManager.h" +#include "QuotaManagerService.h" +#include "QuotaObject.h" +#include "UsageInfo.h" + +#define DISABLE_ASSERTS_FOR_FUZZING 0 + +#if DISABLE_ASSERTS_FOR_FUZZING +#define ASSERT_UNLESS_FUZZING(...) do { } while (0) +#else +#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) +#endif + +// The amount of time, in milliseconds, that our IO thread will stay alive +// after the last event it processes. +#define DEFAULT_THREAD_TIMEOUT_MS 30000 + +// The amount of time, in milliseconds, that we will wait for active storage +// transactions on shutdown before aborting them. +#define DEFAULT_SHUTDOWN_TIMER_MS 30000 + +// Preference that users can set to override temporary storage smart limit +// calculation. +#define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit" +#define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize" + +// Preference that is used to enable testing features +#define PREF_TESTING_FEATURES "dom.quotaManager.testing" + +// profile-before-change, when we need to shut down quota manager +#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm" + +#define KB * 1024ULL +#define MB * 1024ULL KB +#define GB * 1024ULL MB + +namespace mozilla { +namespace dom { +namespace quota { + +using namespace mozilla::ipc; + +// We want profiles to be platform-independent so we always need to replace +// the same characters on every platform. Windows has the most extensive set +// of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and +// FILE_PATH_SEPARATOR. +const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\"; + +namespace { + +/******************************************************************************* + * Constants + ******************************************************************************/ + +const uint32_t kSQLitePageSizeOverride = 512; + +// Major storage version. Bump for backwards-incompatible changes. +const uint32_t kMajorStorageVersion = 1; + +// Minor storage version. Bump for backwards-compatible changes. +const uint32_t kMinorStorageVersion = 0; + +// The storage version we store in the SQLite database is a (signed) 32-bit +// integer. The major version is left-shifted 16 bits so the max value is +// 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF. +static_assert(kMajorStorageVersion <= 0xFFFF, + "Major version needs to fit in 16 bits."); +static_assert(kMinorStorageVersion <= 0xFFFF, + "Minor version needs to fit in 16 bits."); + +const int32_t kStorageVersion = + int32_t((kMajorStorageVersion << 16) + kMinorStorageVersion); + +static_assert( + static_cast<uint32_t>(StorageType::Persistent) == + static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT), + "Enum values should match."); + +static_assert( + static_cast<uint32_t>(StorageType::Temporary) == + static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY), + "Enum values should match."); + +static_assert( + static_cast<uint32_t>(StorageType::Default) == + static_cast<uint32_t>(PERSISTENCE_TYPE_DEFAULT), + "Enum values should match."); + +const char kChromeOrigin[] = "chrome"; +const char kAboutHomeOriginPrefix[] = "moz-safe-about:home"; +const char kIndexedDBOriginPrefix[] = "indexeddb://"; +const char kResourceOriginPrefix[] = "resource://"; + +#define INDEXEDDB_DIRECTORY_NAME "indexedDB" +#define STORAGE_DIRECTORY_NAME "storage" +#define PERSISTENT_DIRECTORY_NAME "persistent" +#define PERMANENT_DIRECTORY_NAME "permanent" +#define TEMPORARY_DIRECTORY_NAME "temporary" +#define DEFAULT_DIRECTORY_NAME "default" + +enum AppId { + kNoAppId = nsIScriptSecurityManager::NO_APP_ID, + kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID +}; + +#define STORAGE_FILE_NAME "storage.sqlite" + +// The name of the file that we use to load/save the last access time of an +// origin. +#define METADATA_FILE_NAME ".metadata" +#define METADATA_V2_FILE_NAME ".metadata-v2" + +/****************************************************************************** + * SQLite functions + ******************************************************************************/ + +#if 0 +int32_t +MakeStorageVersion(uint32_t aMajorStorageVersion, + uint32_t aMinorStorageVersion) +{ + return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion); +} +#endif + +uint32_t +GetMajorStorageVersion(int32_t aStorageVersion) +{ + return uint32_t(aStorageVersion >> 16); +} + +/****************************************************************************** + * Quota manager class declarations + ******************************************************************************/ + +} // namespace + +class DirectoryLockImpl final + : public DirectoryLock +{ + RefPtr<QuotaManager> mQuotaManager; + + const Nullable<PersistenceType> mPersistenceType; + const nsCString mGroup; + const OriginScope mOriginScope; + const Nullable<bool> mIsApp; + const Nullable<Client::Type> mClientType; + RefPtr<OpenDirectoryListener> mOpenListener; + + nsTArray<DirectoryLockImpl*> mBlocking; + nsTArray<DirectoryLockImpl*> mBlockedOn; + + const bool mExclusive; + + // Internal quota manager operations use this flag to prevent directory lock + // registraction/unregistration from updating origin access time, etc. + const bool mInternal; + + bool mInvalidated; + +public: + DirectoryLockImpl(QuotaManager* aQuotaManager, + Nullable<PersistenceType> aPersistenceType, + const nsACString& aGroup, + const OriginScope& aOriginScope, + Nullable<bool> aIsApp, + Nullable<Client::Type> aClientType, + bool aExclusive, + bool aInternal, + OpenDirectoryListener* aOpenListener); + + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + + const Nullable<PersistenceType>& + GetPersistenceType() const + { + return mPersistenceType; + } + + const nsACString& + GetGroup() const + { + return mGroup; + } + + const OriginScope& + GetOriginScope() const + { + return mOriginScope; + } + + const Nullable<bool>& + GetIsApp() const + { + return mIsApp; + } + + const Nullable<Client::Type>& + GetClientType() const + { + return mClientType; + } + + bool + IsInternal() const + { + return mInternal; + } + + bool + ShouldUpdateLockTable() + { + return !mInternal && + mPersistenceType.Value() != PERSISTENCE_TYPE_PERSISTENT; + } + + // Test whether this DirectoryLock needs to wait for the given lock. + bool + MustWaitFor(const DirectoryLockImpl& aLock); + + void + AddBlockingLock(DirectoryLockImpl* aLock) + { + AssertIsOnOwningThread(); + + mBlocking.AppendElement(aLock); + } + + const nsTArray<DirectoryLockImpl*>& + GetBlockedOnLocks() + { + return mBlockedOn; + } + + void + AddBlockedOnLock(DirectoryLockImpl* aLock) + { + AssertIsOnOwningThread(); + + mBlockedOn.AppendElement(aLock); + } + + void + MaybeUnblock(DirectoryLockImpl* aLock) + { + AssertIsOnOwningThread(); + + mBlockedOn.RemoveElement(aLock); + if (mBlockedOn.IsEmpty()) { + NotifyOpenListener(); + } + } + + void + NotifyOpenListener(); + + void + Invalidate() + { + AssertIsOnOwningThread(); + + mInvalidated = true; + } + + NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl) + +private: + ~DirectoryLockImpl(); +}; + +class QuotaManager::CreateRunnable final + : public BackgroundThreadObject + , public Runnable +{ + nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks; + nsString mBaseDirPath; + RefPtr<QuotaManager> mManager; + nsresult mResultCode; + + enum class State + { + Initial, + CreatingManager, + RegisteringObserver, + CallingCallbacks, + Completed + }; + + State mState; + +public: + CreateRunnable() + : mResultCode(NS_OK) + , mState(State::Initial) + { + AssertIsOnBackgroundThread(); + } + + void + AddCallback(nsIRunnable* aCallback) + { + AssertIsOnOwningThread(); + MOZ_ASSERT(aCallback); + + mCallbacks.AppendElement(aCallback); + } + +private: + ~CreateRunnable() + { } + + nsresult + Init(); + + nsresult + CreateManager(); + + nsresult + RegisterObserver(); + + void + CallCallbacks(); + + State + GetNextState(nsCOMPtr<nsIEventTarget>& aThread); + + NS_DECL_NSIRUNNABLE +}; + +class QuotaManager::ShutdownRunnable final + : public Runnable +{ + // Only touched on the main thread. + bool& mDone; + +public: + explicit ShutdownRunnable(bool& aDone) + : mDone(aDone) + { + MOZ_ASSERT(NS_IsMainThread()); + } + +private: + ~ShutdownRunnable() + { } + + NS_DECL_NSIRUNNABLE +}; + +class QuotaManager::ShutdownObserver final + : public nsIObserver +{ + nsCOMPtr<nsIEventTarget> mBackgroundThread; + +public: + explicit ShutdownObserver(nsIEventTarget* aBackgroundThread) + : mBackgroundThread(aBackgroundThread) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~ShutdownObserver() + { + MOZ_ASSERT(NS_IsMainThread()); + } +}; + +namespace { + +/******************************************************************************* + * Local class declarations + ******************************************************************************/ + +} // namespace + +class OriginInfo final +{ + friend class GroupInfo; + friend class QuotaManager; + friend class QuotaObject; + +public: + OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin, bool aIsApp, + uint64_t aUsage, int64_t aAccessTime) + : mGroupInfo(aGroupInfo), mOrigin(aOrigin), mUsage(aUsage), + mAccessTime(aAccessTime), mIsApp(aIsApp) + { + MOZ_COUNT_CTOR(OriginInfo); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OriginInfo) + + int64_t + AccessTime() const + { + return mAccessTime; + } + +private: + // Private destructor, to discourage deletion outside of Release(): + ~OriginInfo() + { + MOZ_COUNT_DTOR(OriginInfo); + + MOZ_ASSERT(!mQuotaObjects.Count()); + } + + void + LockedDecreaseUsage(int64_t aSize); + + void + LockedUpdateAccessTime(int64_t aAccessTime) + { + AssertCurrentThreadOwnsQuotaMutex(); + + mAccessTime = aAccessTime; + } + + nsDataHashtable<nsStringHashKey, QuotaObject*> mQuotaObjects; + + GroupInfo* mGroupInfo; + const nsCString mOrigin; + uint64_t mUsage; + int64_t mAccessTime; + const bool mIsApp; +}; + +class OriginInfoLRUComparator +{ +public: + bool + Equals(const OriginInfo* a, const OriginInfo* b) const + { + return + a && b ? a->AccessTime() == b->AccessTime() : !a && !b ? true : false; + } + + bool + LessThan(const OriginInfo* a, const OriginInfo* b) const + { + return a && b ? a->AccessTime() < b->AccessTime() : b ? true : false; + } +}; + +class GroupInfo final +{ + friend class GroupInfoPair; + friend class OriginInfo; + friend class QuotaManager; + friend class QuotaObject; + +public: + GroupInfo(GroupInfoPair* aGroupInfoPair, PersistenceType aPersistenceType, + const nsACString& aGroup) + : mGroupInfoPair(aGroupInfoPair), mPersistenceType(aPersistenceType), + mGroup(aGroup), mUsage(0) + { + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + MOZ_COUNT_CTOR(GroupInfo); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GroupInfo) + +private: + // Private destructor, to discourage deletion outside of Release(): + ~GroupInfo() + { + MOZ_COUNT_DTOR(GroupInfo); + } + + already_AddRefed<OriginInfo> + LockedGetOriginInfo(const nsACString& aOrigin); + + void + LockedAddOriginInfo(OriginInfo* aOriginInfo); + + void + LockedRemoveOriginInfo(const nsACString& aOrigin); + + void + LockedRemoveOriginInfos(); + + bool + LockedHasOriginInfos() + { + AssertCurrentThreadOwnsQuotaMutex(); + + return !mOriginInfos.IsEmpty(); + } + + nsTArray<RefPtr<OriginInfo> > mOriginInfos; + + GroupInfoPair* mGroupInfoPair; + PersistenceType mPersistenceType; + nsCString mGroup; + uint64_t mUsage; +}; + +class GroupInfoPair +{ + friend class QuotaManager; + friend class QuotaObject; + +public: + GroupInfoPair() + { + MOZ_COUNT_CTOR(GroupInfoPair); + } + + ~GroupInfoPair() + { + MOZ_COUNT_DTOR(GroupInfoPair); + } + +private: + already_AddRefed<GroupInfo> + LockedGetGroupInfo(PersistenceType aPersistenceType) + { + AssertCurrentThreadOwnsQuotaMutex(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + RefPtr<GroupInfo> groupInfo = + GetGroupInfoForPersistenceType(aPersistenceType); + return groupInfo.forget(); + } + + void + LockedSetGroupInfo(PersistenceType aPersistenceType, GroupInfo* aGroupInfo) + { + AssertCurrentThreadOwnsQuotaMutex(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + RefPtr<GroupInfo>& groupInfo = + GetGroupInfoForPersistenceType(aPersistenceType); + groupInfo = aGroupInfo; + } + + void + LockedClearGroupInfo(PersistenceType aPersistenceType) + { + AssertCurrentThreadOwnsQuotaMutex(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + RefPtr<GroupInfo>& groupInfo = + GetGroupInfoForPersistenceType(aPersistenceType); + groupInfo = nullptr; + } + + bool + LockedHasGroupInfos() + { + AssertCurrentThreadOwnsQuotaMutex(); + + return mTemporaryStorageGroupInfo || mDefaultStorageGroupInfo; + } + + RefPtr<GroupInfo>& + GetGroupInfoForPersistenceType(PersistenceType aPersistenceType); + + RefPtr<GroupInfo> mTemporaryStorageGroupInfo; + RefPtr<GroupInfo> mDefaultStorageGroupInfo; +}; + +namespace { + +class CollectOriginsHelper final + : public Runnable +{ + uint64_t mMinSizeToBeFreed; + + Mutex& mMutex; + CondVar mCondVar; + + // The members below are protected by mMutex. + nsTArray<RefPtr<DirectoryLockImpl>> mLocks; + uint64_t mSizeToBeFreed; + bool mWaiting; + +public: + CollectOriginsHelper(mozilla::Mutex& aMutex, + uint64_t aMinSizeToBeFreed); + + // Blocks the current thread until origins are collected on the main thread. + // The returned value contains an aggregate size of those origins. + int64_t + BlockAndReturnOriginsForEviction( + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks); + +private: + ~CollectOriginsHelper() + { } + + NS_IMETHOD + Run() override; +}; + +class OriginOperationBase + : public BackgroundThreadObject + , public Runnable +{ +protected: + nsresult mResultCode; + + enum State { + // Not yet run. + State_Initial, + + // Running initialization on the main thread. + State_Initializing, + + // Running initialization on the owning thread. + State_FinishingInit, + + // Running quota manager initialization on the owning thread. + State_CreatingQuotaManager, + + // Running on the owning thread in the listener for OpenDirectory. + State_DirectoryOpenPending, + + // Running on the IO thread. + State_DirectoryWorkOpen, + + // Running on the owning thread after all work is done. + State_UnblockingOpen, + + // All done. + State_Complete + }; + +private: + State mState; + bool mActorDestroyed; + +protected: + bool mNeedsMainThreadInit; + bool mNeedsQuotaManagerInit; + +public: + void + NoteActorDestroyed() + { + AssertIsOnOwningThread(); + + mActorDestroyed = true; + } + + bool + IsActorDestroyed() const + { + AssertIsOnOwningThread(); + + return mActorDestroyed; + } + +protected: + explicit OriginOperationBase( + nsIEventTarget* aOwningThread = NS_GetCurrentThread()) + : BackgroundThreadObject(aOwningThread) + , mResultCode(NS_OK) + , mState(State_Initial) + , mActorDestroyed(false) + , mNeedsMainThreadInit(false) + , mNeedsQuotaManagerInit(false) + { } + + // Reference counted. + virtual ~OriginOperationBase() + { + MOZ_ASSERT(mState == State_Complete); + MOZ_ASSERT(mActorDestroyed); + } + + State + GetState() const + { + return mState; + } + + void + SetState(State aState) + { + MOZ_ASSERT(mState == State_Initial); + mState = aState; + } + + void + AdvanceState() + { + switch (mState) { + case State_Initial: + mState = State_Initializing; + return; + case State_Initializing: + mState = State_FinishingInit; + return; + case State_FinishingInit: + mState = State_CreatingQuotaManager; + return; + case State_CreatingQuotaManager: + mState = State_DirectoryOpenPending; + return; + case State_DirectoryOpenPending: + mState = State_DirectoryWorkOpen; + return; + case State_DirectoryWorkOpen: + mState = State_UnblockingOpen; + return; + case State_UnblockingOpen: + mState = State_Complete; + return; + default: + MOZ_CRASH("Bad state!"); + } + } + + NS_IMETHOD + Run() override; + + virtual nsresult + DoInitOnMainThread() + { + return NS_OK; + } + + virtual void + Open() = 0; + + nsresult + DirectoryOpen(); + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) = 0; + + void + Finish(nsresult aResult); + + virtual void + UnblockOpen() = 0; + +private: + nsresult + Init(); + + nsresult + InitOnMainThread(); + + nsresult + FinishInit(); + + nsresult + QuotaManagerOpen(); + + nsresult + DirectoryWork(); +}; + +class FinalizeOriginEvictionOp + : public OriginOperationBase +{ + nsTArray<RefPtr<DirectoryLockImpl>> mLocks; + +public: + FinalizeOriginEvictionOp(nsIEventTarget* aBackgroundThread, + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) + : OriginOperationBase(aBackgroundThread) + { + MOZ_ASSERT(!NS_IsMainThread()); + + mLocks.SwapElements(aLocks); + } + + void + Dispatch(); + + void + RunOnIOThreadImmediately(); + +private: + ~FinalizeOriginEvictionOp() + { } + + virtual void + Open() override; + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + virtual void + UnblockOpen() override; +}; + +class NormalOriginOperationBase + : public OriginOperationBase + , public OpenDirectoryListener +{ + RefPtr<DirectoryLock> mDirectoryLock; + +protected: + Nullable<PersistenceType> mPersistenceType; + OriginScope mOriginScope; + mozilla::Atomic<bool> mCanceled; + const bool mExclusive; + +public: + void + RunImmediately() + { + MOZ_ASSERT(GetState() == State_Initial); + + MOZ_ALWAYS_SUCCEEDS(this->Run()); + } + +protected: + NormalOriginOperationBase(Nullable<PersistenceType> aPersistenceType, + const OriginScope& aOriginScope, + bool aExclusive) + : mPersistenceType(aPersistenceType) + , mOriginScope(aOriginScope) + , mExclusive(aExclusive) + { + AssertIsOnOwningThread(); + } + + ~NormalOriginOperationBase() + { } + +private: + NS_DECL_ISUPPORTS_INHERITED + + virtual void + Open() override; + + virtual void + UnblockOpen() override; + + // OpenDirectoryListener overrides. + virtual void + DirectoryLockAcquired(DirectoryLock* aLock) override; + + virtual void + DirectoryLockFailed() override; + + // Used to send results before unblocking open. + virtual void + SendResults() = 0; +}; + +class SaveOriginAccessTimeOp + : public NormalOriginOperationBase +{ + int64_t mTimestamp; + +public: + SaveOriginAccessTimeOp(PersistenceType aPersistenceType, + const nsACString& aOrigin, + int64_t aTimestamp) + : NormalOriginOperationBase(Nullable<PersistenceType>(aPersistenceType), + OriginScope::FromOrigin(aOrigin), + /* aExclusive */ false) + , mTimestamp(aTimestamp) + { + AssertIsOnOwningThread(); + } + +private: + ~SaveOriginAccessTimeOp() + { } + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + virtual void + SendResults() override; +}; + +/******************************************************************************* + * Actor class declarations + ******************************************************************************/ + +class Quota final + : public PQuotaParent +{ +#ifdef DEBUG + bool mActorDestroyed; +#endif + +public: + Quota(); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota) + +private: + ~Quota(); + + void + StartIdleMaintenance(); + + // IPDL methods. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PQuotaUsageRequestParent* + AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams) override; + + virtual bool + RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor, + const UsageRequestParams& aParams) override; + + virtual bool + DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) override; + + virtual PQuotaRequestParent* + AllocPQuotaRequestParent(const RequestParams& aParams) override; + + virtual bool + RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor, + const RequestParams& aParams) override; + + virtual bool + DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override; + + virtual bool + RecvStartIdleMaintenance() override; + + virtual bool + RecvStopIdleMaintenance() override; +}; + +class QuotaUsageRequestBase + : public NormalOriginOperationBase + , public PQuotaUsageRequestParent +{ +public: + // May be overridden by subclasses if they need to perform work on the + // background thread before being run. + virtual bool + Init(Quota* aQuota); + +protected: + QuotaUsageRequestBase() + : NormalOriginOperationBase(Nullable<PersistenceType>(), + OriginScope::FromNull(), + /* aExclusive */ false) + { } + + nsresult + GetUsageForOrigin(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + UsageInfo* aUsageInfo); + + // Subclasses use this override to set the IPDL response value. + virtual void + GetResponse(UsageRequestResponse& aResponse) = 0; + +private: + void + SendResults() override; + + // IPDL methods. + void + ActorDestroy(ActorDestroyReason aWhy) override; + + bool + RecvCancel() override; +}; + +class GetUsageOp final + : public QuotaUsageRequestBase +{ + nsTArray<OriginUsage> mOriginUsages; + nsDataHashtable<nsCStringHashKey, uint32_t> mOriginUsagesIndex; + + bool mGetAll; + +public: + explicit GetUsageOp(const UsageRequestParams& aParams); + +private: + ~GetUsageOp() + { } + + nsresult + TraverseRepository(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType); + + nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + void + GetResponse(UsageRequestResponse& aResponse) override; +}; + +class GetOriginUsageOp final + : public QuotaUsageRequestBase +{ + // If mGetGroupUsage is false, we use mUsageInfo to record the origin usage + // and the file usage. Otherwise, we use it to record the group usage and the + // limit. + UsageInfo mUsageInfo; + + const OriginUsageParams mParams; + nsCString mSuffix; + nsCString mGroup; + bool mIsApp; + bool mGetGroupUsage; + +public: + explicit GetOriginUsageOp(const UsageRequestParams& aParams); + + MOZ_IS_CLASS_INIT bool + Init(Quota* aQuota) override; + +private: + ~GetOriginUsageOp() + { } + + MOZ_IS_CLASS_INIT virtual nsresult + DoInitOnMainThread() override; + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + void + GetResponse(UsageRequestResponse& aResponse) override; +}; + +class QuotaRequestBase + : public NormalOriginOperationBase + , public PQuotaRequestParent +{ +public: + // May be overridden by subclasses if they need to perform work on the + // background thread before being run. + virtual bool + Init(Quota* aQuota); + +protected: + explicit QuotaRequestBase(bool aExclusive) + : NormalOriginOperationBase(Nullable<PersistenceType>(), + OriginScope::FromNull(), + aExclusive) + { } + + // Subclasses use this override to set the IPDL response value. + virtual void + GetResponse(RequestResponse& aResponse) = 0; + +private: + virtual void + SendResults() override; + + // IPDL methods. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; +}; + +class ResetOrClearOp final + : public QuotaRequestBase +{ + const bool mClear; + +public: + explicit ResetOrClearOp(bool aClear) + : QuotaRequestBase(/* aExclusive */ true) + , mClear(aClear) + { + AssertIsOnOwningThread(); + } + +private: + ~ResetOrClearOp() + { } + + void + DeleteFiles(QuotaManager* aQuotaManager); + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + virtual void + GetResponse(RequestResponse& aResponse) override; +}; + +class OriginClearOp final + : public QuotaRequestBase +{ + const RequestParams mParams; + const bool mMultiple; + +public: + explicit OriginClearOp(const RequestParams& aParams); + + virtual bool + Init(Quota* aQuota) override; + +private: + ~OriginClearOp() + { } + + virtual nsresult + DoInitOnMainThread() override; + + void + DeleteFiles(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType); + + virtual nsresult + DoDirectoryWork(QuotaManager* aQuotaManager) override; + + virtual void + GetResponse(RequestResponse& aResponse) override; +}; + +/******************************************************************************* + * Helper Functions + ******************************************************************************/ + +template <typename T, bool = mozilla::IsUnsigned<T>::value> +struct IntChecker +{ + static void + Assert(T aInt) + { + static_assert(mozilla::IsIntegral<T>::value, "Not an integer!"); + MOZ_ASSERT(aInt >= 0); + } +}; + +template <typename T> +struct IntChecker<T, true> +{ + static void + Assert(T aInt) + { + static_assert(mozilla::IsIntegral<T>::value, "Not an integer!"); + } +}; + +template <typename T> +void +AssertNoOverflow(uint64_t aDest, T aArg) +{ + IntChecker<T>::Assert(aDest); + IntChecker<T>::Assert(aArg); + MOZ_ASSERT(UINT64_MAX - aDest >= uint64_t(aArg)); +} + +template <typename T, typename U> +void +AssertNoUnderflow(T aDest, U aArg) +{ + IntChecker<T>::Assert(aDest); + IntChecker<T>::Assert(aArg); + MOZ_ASSERT(uint64_t(aDest) >= uint64_t(aArg)); +} + +} // namespace + +BackgroundThreadObject::BackgroundThreadObject() + : mOwningThread(NS_GetCurrentThread()) +{ + AssertIsOnOwningThread(); +} + +BackgroundThreadObject::BackgroundThreadObject(nsIEventTarget* aOwningThread) + : mOwningThread(aOwningThread) +{ +} + +#ifdef DEBUG + +void +BackgroundThreadObject::AssertIsOnOwningThread() const +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mOwningThread); + bool current; + MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(¤t))); + MOZ_ASSERT(current); +} + +#endif // DEBUG + +nsIEventTarget* +BackgroundThreadObject::OwningThread() const +{ + MOZ_ASSERT(mOwningThread); + return mOwningThread; +} + +bool +IsOnIOThread() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Must have a manager here!"); + + bool currentThread; + return NS_SUCCEEDED(quotaManager->IOThread()-> + IsOnCurrentThread(¤tThread)) && currentThread; +} + +void +AssertIsOnIOThread() +{ + NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!"); +} + +void +AssertCurrentThreadOwnsQuotaMutex() +{ +#ifdef DEBUG + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Must have a manager here!"); + + quotaManager->AssertCurrentThreadOwnsQuotaMutex(); +#endif +} + +void +ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr) +{ + // Get leaf of file path + for (const char* p = aFile; *p; ++p) { + if (*p == '/' && *(p + 1)) { + aFile = p + 1; + } + } + + nsContentUtils::LogSimpleConsoleError( + NS_ConvertUTF8toUTF16(nsPrintfCString( + "Quota %s: %s:%lu", aStr, aFile, aLine)), + "quota"); +} + +namespace { + +StaticRefPtr<QuotaManager> gInstance; +bool gCreateFailed = false; +StaticRefPtr<QuotaManager::CreateRunnable> gCreateRunnable; +mozilla::Atomic<bool> gShutdown(false); + +// Constants for temporary storage limit computing. +static const int32_t kDefaultFixedLimitKB = -1; +static const uint32_t kDefaultChunkSizeKB = 10 * 1024; +int32_t gFixedLimitKB = kDefaultFixedLimitKB; +uint32_t gChunkSizeKB = kDefaultChunkSizeKB; + +bool gTestingEnabled = false; + +class StorageDirectoryHelper + : public Runnable +{ + mozilla::Mutex mMutex; + mozilla::CondVar mCondVar; + nsresult mMainThreadResultCode; + bool mWaiting; + +protected: + struct OriginProps; + + nsTArray<OriginProps> mOriginProps; + + nsCOMPtr<nsIFile> mDirectory; + +public: + StorageDirectoryHelper(nsIFile* aDirectory) + : mMutex("StorageDirectoryHelper::mMutex") + , mCondVar(mMutex, "StorageDirectoryHelper::mCondVar") + , mMainThreadResultCode(NS_OK) + , mWaiting(true) + , mDirectory(aDirectory) + { + AssertIsOnIOThread(); + } + +protected: + ~StorageDirectoryHelper() + { } + + nsresult + AddOriginDirectory(nsIFile* aDirectory, + OriginProps** aOriginProps); + + nsresult + ProcessOriginDirectories(); + + virtual nsresult + DoProcessOriginDirectories() = 0; + +private: + nsresult + RunOnMainThread(); + + NS_IMETHOD + Run() override; +}; + +struct StorageDirectoryHelper::OriginProps +{ + enum Type + { + eChrome, + eContent + }; + + nsCOMPtr<nsIFile> mDirectory; + nsCString mSpec; + PrincipalOriginAttributes mAttrs; + int64_t mTimestamp; + nsCString mSuffix; + nsCString mGroup; + nsCString mOrigin; + + Type mType; + bool mIsApp; + bool mNeedsRestore; + bool mIgnore; + +public: + explicit OriginProps() + : mTimestamp(0) + , mType(eContent) + , mIsApp(false) + , mNeedsRestore(false) + , mIgnore(false) + { } +}; + +class MOZ_STACK_CLASS OriginParser final +{ + static bool + IgnoreWhitespace(char16_t /* aChar */) + { + return false; + } + + typedef nsCCharSeparatedTokenizerTemplate<IgnoreWhitespace> Tokenizer; + + enum SchemaType { + eNone, + eFile, + eAbout + }; + + enum State { + eExpectingAppIdOrSchema, + eExpectingInMozBrowser, + eExpectingSchema, + eExpectingEmptyToken1, + eExpectingEmptyToken2, + eExpectingEmptyToken3, + eExpectingHost, + eExpectingPort, + eExpectingEmptyTokenOrDriveLetterOrPathnameComponent, + eExpectingEmptyTokenOrPathnameComponent, + eComplete, + eHandledTrailingSeparator + }; + + const nsCString mOrigin; + const PrincipalOriginAttributes mOriginAttributes; + Tokenizer mTokenizer; + + uint32_t mAppId; + nsCString mSchema; + nsCString mHost; + Nullable<uint32_t> mPort; + nsTArray<nsCString> mPathnameComponents; + nsCString mHandledTokens; + + SchemaType mSchemaType; + State mState; + bool mInIsolatedMozBrowser; + bool mMaybeDriveLetter; + bool mError; + +public: + OriginParser(const nsACString& aOrigin, + const PrincipalOriginAttributes& aOriginAttributes) + : mOrigin(aOrigin) + , mOriginAttributes(aOriginAttributes) + , mTokenizer(aOrigin, '+') + , mAppId(kNoAppId) + , mPort() + , mSchemaType(eNone) + , mState(eExpectingAppIdOrSchema) + , mInIsolatedMozBrowser(false) + , mMaybeDriveLetter(false) + , mError(false) + { } + + static bool + ParseOrigin(const nsACString& aOrigin, + nsCString& aSpec, + PrincipalOriginAttributes* aAttrs); + + bool + Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs); + +private: + void + HandleSchema(const nsDependentCSubstring& aSchema); + + void + HandlePathnameComponent(const nsDependentCSubstring& aSchema); + + void + HandleToken(const nsDependentCSubstring& aToken); + + void + HandleTrailingSeparator(); +}; + +class CreateOrUpgradeDirectoryMetadataHelper final + : public StorageDirectoryHelper +{ + const bool mPersistent; + +public: + CreateOrUpgradeDirectoryMetadataHelper(nsIFile* aDirectory, + bool aPersistent) + : StorageDirectoryHelper(aDirectory) + , mPersistent(aPersistent) + { } + + nsresult + CreateOrUpgradeMetadataFiles(); + +private: + nsresult + MaybeUpgradeOriginDirectory(nsIFile* aDirectory); + + nsresult + GetDirectoryMetadata(nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aGroup, + nsACString& aOrigin, + bool* aHasIsApp); + + virtual nsresult + DoProcessOriginDirectories(); +}; + +class UpgradeDirectoryMetadataFrom1To2Helper final + : public StorageDirectoryHelper +{ + const bool mPersistent; + +public: + UpgradeDirectoryMetadataFrom1To2Helper(nsIFile* aDirectory, + bool aPersistent) + : StorageDirectoryHelper(aDirectory) + , mPersistent(aPersistent) + { } + + nsresult + UpgradeMetadataFiles(); + +private: + nsresult + GetDirectoryMetadata(nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp); + + virtual nsresult + DoProcessOriginDirectories() override; +}; + +class RestoreDirectoryMetadata2Helper final + : public StorageDirectoryHelper +{ + const bool mPersistent; + +public: + RestoreDirectoryMetadata2Helper(nsIFile* aDirectory, + bool aPersistent) + : StorageDirectoryHelper(aDirectory) + , mPersistent(aPersistent) + { } + + nsresult + RestoreMetadata2File(); + +private: + virtual nsresult + DoProcessOriginDirectories(); +}; + +class OriginKey : public nsAutoCString +{ +public: + OriginKey(PersistenceType aPersistenceType, + const nsACString& aOrigin) + { + PersistenceTypeToText(aPersistenceType, *this); + Append(':'); + Append(aOrigin); + } +}; + +void +SanitizeOriginString(nsCString& aOrigin) +{ + +#ifdef XP_WIN + NS_ASSERTION(!strcmp(QuotaManager::kReplaceChars, + FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR), + "Illegal file characters have changed!"); +#endif + + aOrigin.ReplaceChar(QuotaManager::kReplaceChars, '+'); +} + +bool +IsTreatedAsPersistent(PersistenceType aPersistenceType, + bool aIsApp) +{ + if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT || + (aPersistenceType == PERSISTENCE_TYPE_DEFAULT && aIsApp)) { + return true; + } + + return false; +} + +bool +IsTreatedAsTemporary(PersistenceType aPersistenceType, + bool aIsApp) +{ + return !IsTreatedAsPersistent(aPersistenceType, aIsApp); +} + +nsresult +CloneStoragePath(nsIFile* aBaseDir, + const nsAString& aStorageName, + nsAString& aStoragePath) +{ + nsresult rv; + + nsCOMPtr<nsIFile> storageDir; + rv = aBaseDir->Clone(getter_AddRefs(storageDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = storageDir->Append(aStorageName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = storageDir->GetPath(aStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aFile); + MOZ_ASSERT(aTimestamp); + + class MOZ_STACK_CLASS Helper final + { + public: + static nsresult + GetLastModifiedTime(nsIFile* aFile, int64_t* aTimestamp) + { + AssertIsOnIOThread(); + MOZ_ASSERT(aFile); + MOZ_ASSERT(aTimestamp); + + bool isDirectory; + nsresult rv = aFile->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + nsString leafName; + rv = aFile->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (leafName.EqualsLiteral(METADATA_FILE_NAME) || + leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || + leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + return NS_OK; + } + + int64_t timestamp; + rv = aFile->GetLastModifiedTime(×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Need to convert from milliseconds to microseconds. + MOZ_ASSERT((INT64_MAX / PR_USEC_PER_MSEC) > timestamp); + timestamp *= int64_t(PR_USEC_PER_MSEC); + + if (timestamp > *aTimestamp) { + *aTimestamp = timestamp; + } + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aFile->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + MOZ_ASSERT(file); + + rv = GetLastModifiedTime(file, aTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + }; + + int64_t timestamp = INT64_MIN; + nsresult rv = Helper::GetLastModifiedTime(aFile, ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aTimestamp = timestamp; + return NS_OK; +} + +nsresult +EnsureDirectory(nsIFile* aDirectory, bool* aCreated) +{ + AssertIsOnIOThread(); + + nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { + bool isDirectory; + rv = aDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); + + *aCreated = false; + } + else { + NS_ENSURE_SUCCESS(rv, rv); + + *aCreated = true; + } + + return NS_OK; +} + +enum FileFlag { + kTruncateFileFlag, + kUpdateFileFlag, + kAppendFileFlag +}; + +nsresult +GetOutputStream(nsIFile* aDirectory, + const nsAString& aFilename, + FileFlag aFileFlag, + nsIOutputStream** aStream) +{ + AssertIsOnIOThread(); + + nsCOMPtr<nsIFile> file; + nsresult rv = aDirectory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->Append(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIOutputStream> outputStream; + switch (aFileFlag) { + case kTruncateFileFlag: { + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), + file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + break; + } + + case kUpdateFileFlag: { + bool exists; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + *aStream = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIFileStream> stream; + rv = NS_NewLocalFileStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + outputStream = do_QueryInterface(stream); + if (NS_WARN_IF(!outputStream)) { + return NS_ERROR_FAILURE; + } + + break; + } + + case kAppendFileFlag: { + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), + file, + PR_WRONLY | PR_CREATE_FILE | PR_APPEND); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + break; + } + + default: + MOZ_CRASH("Should never get here!"); + } + + outputStream.forget(aStream); + return NS_OK; +} + +nsresult +GetBinaryOutputStream(nsIFile* aDirectory, + const nsAString& aFilename, + FileFlag aFileFlag, + nsIBinaryOutputStream** aStream) +{ + nsCOMPtr<nsIOutputStream> outputStream; + nsresult rv = GetOutputStream(aDirectory, + aFilename, + aFileFlag, + getter_AddRefs(outputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + + nsCOMPtr<nsIBinaryOutputStream> binaryStream = + do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + if (NS_WARN_IF(!binaryStream)) { + return NS_ERROR_FAILURE; + } + + rv = binaryStream->SetOutputStream(outputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + binaryStream.forget(aStream); + return NS_OK; +} + +void +GetJarPrefix(uint32_t aAppId, + bool aInIsolatedMozBrowser, + nsACString& aJarPrefix) +{ + MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); + + if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { + aAppId = nsIScriptSecurityManager::NO_APP_ID; + } + + aJarPrefix.Truncate(); + + // Fallback. + if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInIsolatedMozBrowser) { + return; + } + + // aJarPrefix = appId + "+" + { 't', 'f' } + "+"; + aJarPrefix.AppendInt(aAppId); + aJarPrefix.Append('+'); + aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f'); + aJarPrefix.Append('+'); +} + +nsresult +CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp, + const nsACString& aSuffix, const nsACString& aGroup, + const nsACString& aOrigin, bool aIsApp) +{ + AssertIsOnIOThread(); + + PrincipalOriginAttributes groupAttributes; + + nsCString groupNoSuffix; + bool ok = groupAttributes.PopulateFromOrigin(aGroup, groupNoSuffix); + if (!ok) { + return NS_ERROR_FAILURE; + } + + nsCString groupPrefix; + GetJarPrefix(groupAttributes.mAppId, + groupAttributes.mInIsolatedMozBrowser, + groupPrefix); + + nsCString group = groupPrefix + groupNoSuffix; + + PrincipalOriginAttributes originAttributes; + + nsCString originNoSuffix; + ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix); + if (!ok) { + return NS_ERROR_FAILURE; + } + + nsCString originPrefix; + GetJarPrefix(originAttributes.mAppId, + originAttributes.mInIsolatedMozBrowser, + originPrefix); + + nsCString origin = originPrefix + originNoSuffix; + + MOZ_ASSERT(groupPrefix == originPrefix); + + nsCOMPtr<nsIBinaryOutputStream> stream; + nsresult rv = GetBinaryOutputStream(aDirectory, + NS_LITERAL_STRING(METADATA_FILE_NAME), + kTruncateFileFlag, + getter_AddRefs(stream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(stream); + + rv = stream->Write64(aTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteStringZ(group.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteStringZ(origin.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteBoolean(aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +CreateDirectoryMetadata2(nsIFile* aDirectory, int64_t aTimestamp, + const nsACString& aSuffix, const nsACString& aGroup, + const nsACString& aOrigin, bool aIsApp) +{ + AssertIsOnIOThread(); + + nsCOMPtr<nsIBinaryOutputStream> stream; + nsresult rv = GetBinaryOutputStream(aDirectory, + NS_LITERAL_STRING(METADATA_V2_FILE_NAME), + kTruncateFileFlag, + getter_AddRefs(stream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(stream); + + rv = stream->Write64(aTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Reserved for navigator.persist() + rv = stream->WriteBoolean(false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Reserved data 1 + rv = stream->Write32(0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Reserved data 2 + rv = stream->Write32(0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteStringZ(PromiseFlatCString(aSuffix).get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stream->WriteBoolean(aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +GetBinaryInputStream(nsIFile* aDirectory, + const nsAString& aFilename, + nsIBinaryInputStream** aStream) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(aStream); + + nsCOMPtr<nsIFile> file; + nsresult rv = aDirectory->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = file->Append(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> bufferedStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIBinaryInputStream> binaryStream = + do_CreateInstance("@mozilla.org/binaryinputstream;1"); + if (NS_WARN_IF(!binaryStream)) { + return NS_ERROR_FAILURE; + } + + rv = binaryStream->SetInputStream(bufferedStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + binaryStream.forget(aStream); + return NS_OK; +} + +// This method computes and returns our best guess for the temporary storage +// limit (in bytes), based on the amount of space users have free on their hard +// drive and on given temporary storage usage (also in bytes). +nsresult +GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage, + uint64_t* aLimit) +{ + // Check for free space on device where temporary storage directory lives. + int64_t bytesAvailable; + nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!"); + + uint64_t availableKB = + static_cast<uint64_t>((bytesAvailable + aCurrentUsage) / 1024); + + // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case + // we don't shrink temporary storage and evict origin data every time we + // initialize. + availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB; + + // Allow temporary storage to consume up to half the available space. + uint64_t resultKB = availableKB * .50; + + *aLimit = resultKB * 1024; + return NS_OK; +} + +} // namespace + +/******************************************************************************* + * Exported functions + ******************************************************************************/ + +PQuotaParent* +AllocPQuotaParent() +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(QuotaManager::IsShuttingDown())) { + return nullptr; + } + + RefPtr<Quota> actor = new Quota(); + + return actor.forget().take(); +} + +bool +DeallocPQuotaParent(PQuotaParent* aActor) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor)); + return true; +} + +/******************************************************************************* + * Directory lock + ******************************************************************************/ + +DirectoryLockImpl::DirectoryLockImpl(QuotaManager* aQuotaManager, + Nullable<PersistenceType> aPersistenceType, + const nsACString& aGroup, + const OriginScope& aOriginScope, + Nullable<bool> aIsApp, + Nullable<Client::Type> aClientType, + bool aExclusive, + bool aInternal, + OpenDirectoryListener* aOpenListener) + : mQuotaManager(aQuotaManager) + , mPersistenceType(aPersistenceType) + , mGroup(aGroup) + , mOriginScope(aOriginScope) + , mIsApp(aIsApp) + , mClientType(aClientType) + , mOpenListener(aOpenListener) + , mExclusive(aExclusive) + , mInternal(aInternal) + , mInvalidated(false) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuotaManager); + MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty()); + MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull()); + MOZ_ASSERT_IF(!aInternal, + aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID); + MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty()); + MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin()); + MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull()); + MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull()); + MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX); + MOZ_ASSERT_IF(!aInternal, aOpenListener); +} + +DirectoryLockImpl::~DirectoryLockImpl() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mQuotaManager); + + for (DirectoryLockImpl* blockingLock : mBlocking) { + blockingLock->MaybeUnblock(this); + } + + mBlocking.Clear(); + + mQuotaManager->UnregisterDirectoryLock(this); +} + +#ifdef DEBUG + +void +DirectoryLockImpl::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mQuotaManager); + mQuotaManager->AssertIsOnOwningThread(); +} + +#endif // DEBUG + +bool +DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aExistingLock) +{ + AssertIsOnOwningThread(); + + // Waiting is never required if the ops in comparison represent shared locks. + if (!aExistingLock.mExclusive && !mExclusive) { + return false; + } + + // If the persistence types don't overlap, the op can proceed. + if (!aExistingLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() && + aExistingLock.mPersistenceType.Value() != mPersistenceType.Value()) { + return false; + } + + // If the origin scopes don't overlap, the op can proceed. + bool match = aExistingLock.mOriginScope.Matches(mOriginScope); + if (!match) { + return false; + } + + // If the client types don't overlap, the op can proceed. + if (!aExistingLock.mClientType.IsNull() && !mClientType.IsNull() && + aExistingLock.mClientType.Value() != mClientType.Value()) { + return false; + } + + // Otherwise, when all attributes overlap (persistence type, origin scope and + // client type) the op must wait. + return true; +} + +void +DirectoryLockImpl::NotifyOpenListener() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mQuotaManager); + MOZ_ASSERT(mOpenListener); + + if (mInvalidated) { + mOpenListener->DirectoryLockFailed(); + } else { + mOpenListener->DirectoryLockAcquired(this); + } + + mOpenListener = nullptr; + + mQuotaManager->RemovePendingDirectoryLock(this); +} + +nsresult +QuotaManager:: +CreateRunnable::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mState == State::Initial); + + nsresult rv; + + nsCOMPtr<nsIFile> baseDir; + rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR, + getter_AddRefs(baseDir)); + if (NS_FAILED(rv)) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(baseDir)); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = baseDir->GetPath(mBaseDirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +QuotaManager:: +CreateRunnable::CreateManager() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::CreatingManager); + + mManager = new QuotaManager(); + + nsresult rv = mManager->Init(mBaseDirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +QuotaManager:: +CreateRunnable::RegisterObserver() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mState == State::RegisteringObserver); + + if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT, + kDefaultFixedLimitKB)) || + NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB, + PREF_CHUNK_SIZE, + kDefaultChunkSizeKB))) { + NS_WARNING("Unable to respond to temp storage pref changes!"); + } + + if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled, + PREF_TESTING_FEATURES, false))) { + NS_WARNING("Unable to respond to testing pref changes!"); + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (NS_WARN_IF(!observerService)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIObserver> observer = new ShutdownObserver(mOwningThread); + + nsresult rv = + observerService->AddObserver(observer, + PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, + false); + 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; + } + + QuotaManagerService* qms = QuotaManagerService::GetOrCreate(); + if (NS_WARN_IF(!qms)) { + return rv; + } + + qms->NoteLiveManager(mManager); + + for (RefPtr<Client>& client : mManager->mClients) { + client->DidInitialize(mManager); + } + + return NS_OK; +} + +void +QuotaManager:: +CreateRunnable::CallCallbacks() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State::CallingCallbacks); + + gCreateRunnable = nullptr; + + if (NS_FAILED(mResultCode)) { + gCreateFailed = true; + } else { + gInstance = mManager; + } + + mManager = nullptr; + + nsTArray<nsCOMPtr<nsIRunnable>> callbacks; + mCallbacks.SwapElements(callbacks); + + for (nsCOMPtr<nsIRunnable>& callback : callbacks) { + Unused << callback->Run(); + } +} + +auto +QuotaManager:: +CreateRunnable::GetNextState(nsCOMPtr<nsIEventTarget>& aThread) -> State +{ + switch (mState) { + case State::Initial: + aThread = mOwningThread; + return State::CreatingManager; + case State::CreatingManager: + aThread = do_GetMainThread(); + return State::RegisteringObserver; + case State::RegisteringObserver: + aThread = mOwningThread; + return State::CallingCallbacks; + case State::CallingCallbacks: + aThread = nullptr; + return State::Completed; + default: + MOZ_CRASH("Bad state!"); + } +} + +NS_IMETHODIMP +QuotaManager:: +CreateRunnable::Run() +{ + nsresult rv; + + switch (mState) { + case State::Initial: + rv = Init(); + break; + + case State::CreatingManager: + rv = CreateManager(); + break; + + case State::RegisteringObserver: + rv = RegisterObserver(); + break; + + case State::CallingCallbacks: + CallCallbacks(); + rv = NS_OK; + break; + + case State::Completed: + default: + MOZ_CRASH("Bad state!"); + } + + nsCOMPtr<nsIEventTarget> thread; + if (NS_WARN_IF(NS_FAILED(rv))) { + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = rv; + } + + mState = State::CallingCallbacks; + thread = mOwningThread; + } else { + mState = GetNextState(thread); + } + + if (thread) { + MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(this, NS_DISPATCH_NORMAL)); + } + + return NS_OK; +} + +NS_IMETHODIMP +QuotaManager:: +ShutdownRunnable::Run() +{ + if (NS_IsMainThread()) { + mDone = true; + + return NS_OK; + } + + AssertIsOnBackgroundThread(); + + RefPtr<QuotaManager> quotaManager = gInstance.get(); + if (quotaManager) { + quotaManager->Shutdown(); + + gInstance = nullptr; + } + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(QuotaManager::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +QuotaManager:: +ShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)); + MOZ_ASSERT(gInstance); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (NS_WARN_IF(!observerService)) { + return NS_ERROR_FAILURE; + } + + // Unregister ourselves from the observer service first to make sure the + // nested event loop below will not cause re-entrancy issues. + Unused << + observerService->RemoveObserver(this, + PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID); + + QuotaManagerService* qms = QuotaManagerService::Get(); + MOZ_ASSERT(qms); + + qms->NoteShuttingDownManager(); + + for (RefPtr<Client>& client : gInstance->mClients) { + client->WillShutdown(); + } + + bool done = false; + + RefPtr<ShutdownRunnable> shutdownRunnable = new ShutdownRunnable(done); + MOZ_ALWAYS_SUCCEEDS( + mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL)); + + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + while (!done) { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); + } + + return NS_OK; +} + +/******************************************************************************* + * Quota object + ******************************************************************************/ + +void +QuotaObject::AddRef() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + ++mRefCnt; + + return; + } + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + ++mRefCnt; +} + +void +QuotaObject::Release() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); + + nsrefcnt count = --mRefCnt; + if (count == 0) { + mRefCnt = 1; + delete this; + } + + return; + } + + { + MutexAutoLock lock(quotaManager->mQuotaMutex); + + --mRefCnt; + + if (mRefCnt > 0) { + return; + } + + if (mOriginInfo) { + mOriginInfo->mQuotaObjects.Remove(mPath); + } + } + + delete this; +} + +bool +QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + if (mQuotaCheckDisabled) { + return true; + } + + if (mSize == aSize) { + return true; + } + + if (!mOriginInfo) { + mSize = aSize; + return true; + } + + GroupInfo* groupInfo = mOriginInfo->mGroupInfo; + MOZ_ASSERT(groupInfo); + + if (mSize > aSize) { + if (aTruncate) { + const int64_t delta = mSize - aSize; + + AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, delta); + quotaManager->mTemporaryStorageUsage -= delta; + + AssertNoUnderflow(groupInfo->mUsage, delta); + groupInfo->mUsage -= delta; + + AssertNoUnderflow(mOriginInfo->mUsage, delta); + mOriginInfo->mUsage -= delta; + + mSize = aSize; + } + return true; + } + + MOZ_ASSERT(mSize < aSize); + + RefPtr<GroupInfo> complementaryGroupInfo = + groupInfo->mGroupInfoPair->LockedGetGroupInfo( + ComplementaryPersistenceType(groupInfo->mPersistenceType)); + + uint64_t delta = aSize - mSize; + + AssertNoOverflow(mOriginInfo->mUsage, delta); + uint64_t newUsage = mOriginInfo->mUsage + delta; + + // Temporary storage has no limit for origin usage (there's a group and the + // global limit though). + + AssertNoOverflow(groupInfo->mUsage, delta); + uint64_t newGroupUsage = groupInfo->mUsage + delta; + + uint64_t groupUsage = groupInfo->mUsage; + if (complementaryGroupInfo) { + AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage); + groupUsage += complementaryGroupInfo->mUsage; + } + + // Temporary storage has a hard limit for group usage (20 % of the global + // limit). + AssertNoOverflow(groupUsage, delta); + if (groupUsage + delta > quotaManager->GetGroupLimit()) { + return false; + } + + AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta); + uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + + delta; + + if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) { + // This will block the thread without holding the lock while waitting. + + AutoTArray<RefPtr<DirectoryLockImpl>, 10> locks; + + uint64_t sizeToBeFreed = + quotaManager->LockedCollectOriginsForEviction(delta, locks); + + if (!sizeToBeFreed) { + return false; + } + + NS_ASSERTION(sizeToBeFreed >= delta, "Huh?"); + + { + MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); + + for (RefPtr<DirectoryLockImpl>& lock : locks) { + MOZ_ASSERT(!lock->GetPersistenceType().IsNull()); + MOZ_ASSERT(lock->GetOriginScope().IsOrigin()); + MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty()); + + quotaManager->DeleteFilesForOrigin(lock->GetPersistenceType().Value(), + lock->GetOriginScope().GetOrigin()); + } + } + + // Relocked. + + NS_ASSERTION(mOriginInfo, "How come?!"); + + for (DirectoryLockImpl* lock : locks) { + MOZ_ASSERT(!lock->GetPersistenceType().IsNull()); + MOZ_ASSERT(!lock->GetGroup().IsEmpty()); + MOZ_ASSERT(lock->GetOriginScope().IsOrigin()); + MOZ_ASSERT(!lock->GetOriginScope().GetOrigin().IsEmpty()); + MOZ_ASSERT(lock->GetOriginScope().GetOrigin() != mOriginInfo->mOrigin, + "Deleted itself!"); + + quotaManager->LockedRemoveQuotaForOrigin( + lock->GetPersistenceType().Value(), + lock->GetGroup(), + lock->GetOriginScope().GetOrigin()); + } + + // We unlocked and relocked several times so we need to recompute all the + // essential variables and recheck the group limit. + + AssertNoUnderflow(aSize, mSize); + delta = aSize - mSize; + + AssertNoOverflow(mOriginInfo->mUsage, delta); + newUsage = mOriginInfo->mUsage + delta; + + AssertNoOverflow(groupInfo->mUsage, delta); + newGroupUsage = groupInfo->mUsage + delta; + + groupUsage = groupInfo->mUsage; + if (complementaryGroupInfo) { + AssertNoOverflow(groupUsage, complementaryGroupInfo->mUsage); + groupUsage += complementaryGroupInfo->mUsage; + } + + AssertNoOverflow(groupUsage, delta); + if (groupUsage + delta > quotaManager->GetGroupLimit()) { + // Unfortunately some other thread increased the group usage in the + // meantime and we are not below the group limit anymore. + + // However, the origin eviction must be finalized in this case too. + MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); + + quotaManager->FinalizeOriginEviction(locks); + + return false; + } + + AssertNoOverflow(quotaManager->mTemporaryStorageUsage, delta); + newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta; + + NS_ASSERTION(newTemporaryStorageUsage <= + quotaManager->mTemporaryStorageLimit, "How come?!"); + + // Ok, we successfully freed enough space and the operation can continue + // without throwing the quota error. + mOriginInfo->mUsage = newUsage; + groupInfo->mUsage = newGroupUsage; + quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;; + + // Some other thread could increase the size in the meantime, but no more + // than this one. + MOZ_ASSERT(mSize < aSize); + mSize = aSize; + + // Finally, release IO thread only objects and allow next synchronized + // ops for the evicted origins. + MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); + + quotaManager->FinalizeOriginEviction(locks); + + return true; + } + + mOriginInfo->mUsage = newUsage; + groupInfo->mUsage = newGroupUsage; + quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage; + + mSize = aSize; + + return true; +} + +void +QuotaObject::DisableQuotaCheck() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + mQuotaCheckDisabled = true; +} + +void +QuotaObject::EnableQuotaCheck() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + mQuotaCheckDisabled = false; +} + +/******************************************************************************* + * Quota manager + ******************************************************************************/ + +QuotaManager::QuotaManager() +: mQuotaMutex("QuotaManager.mQuotaMutex"), + mTemporaryStorageLimit(0), + mTemporaryStorageUsage(0), + mTemporaryStorageInitialized(false), + mStorageInitialized(false) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!gInstance); +} + +QuotaManager::~QuotaManager() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!gInstance || gInstance == this); +} + +void +QuotaManager::GetOrCreate(nsIRunnable* aCallback) +{ + AssertIsOnBackgroundThread(); + + if (IsShuttingDown()) { + MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!"); + return; + } + + if (gInstance || gCreateFailed) { + MOZ_ASSERT(!gCreateRunnable); + MOZ_ASSERT_IF(gCreateFailed, !gInstance); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback)); + } else { + if (!gCreateRunnable) { + gCreateRunnable = new CreateRunnable(); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable)); + } + + gCreateRunnable->AddCallback(aCallback); + } +} + +// static +QuotaManager* +QuotaManager::Get() +{ + // Does not return an owning reference. + return gInstance; +} + +// static +bool +QuotaManager::IsShuttingDown() +{ + return gShutdown; +} + +auto +QuotaManager::CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType, + const nsACString& aGroup, + const OriginScope& aOriginScope, + Nullable<bool> aIsApp, + Nullable<Client::Type> aClientType, + bool aExclusive, + bool aInternal, + OpenDirectoryListener* aOpenListener) + -> already_AddRefed<DirectoryLockImpl> +{ + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty()); + MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull()); + MOZ_ASSERT_IF(!aInternal, + aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID); + MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty()); + MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin()); + MOZ_ASSERT_IF(!aInternal, !aIsApp.IsNull()); + MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull()); + MOZ_ASSERT_IF(!aInternal, aClientType.Value() != Client::TYPE_MAX); + MOZ_ASSERT_IF(!aInternal, aOpenListener); + + RefPtr<DirectoryLockImpl> lock = new DirectoryLockImpl(this, + aPersistenceType, + aGroup, + aOriginScope, + aIsApp, + aClientType, + aExclusive, + aInternal, + aOpenListener); + + mPendingDirectoryLocks.AppendElement(lock); + + // See if this lock needs to wait. + bool blocked = false; + for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) { + DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1]; + if (lock->MustWaitFor(*existingLock)) { + existingLock->AddBlockingLock(lock); + lock->AddBlockedOnLock(existingLock); + blocked = true; + } + } + + RegisterDirectoryLock(lock); + + // Otherwise, notify the open listener immediately. + if (!blocked) { + lock->NotifyOpenListener(); + } + + return lock.forget(); +} + +auto +QuotaManager::CreateDirectoryLockForEviction(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp) + -> already_AddRefed<DirectoryLockImpl> +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + RefPtr<DirectoryLockImpl> lock = + new DirectoryLockImpl(this, + Nullable<PersistenceType>(aPersistenceType), + aGroup, + OriginScope::FromOrigin(aOrigin), + Nullable<bool>(aIsApp), + Nullable<Client::Type>(), + /* aExclusive */ true, + /* aInternal */ true, + nullptr); + +#ifdef DEBUG + for (uint32_t index = mDirectoryLocks.Length(); index > 0; index--) { + DirectoryLockImpl* existingLock = mDirectoryLocks[index - 1]; + MOZ_ASSERT(!lock->MustWaitFor(*existingLock)); + } +#endif + + RegisterDirectoryLock(lock); + + return lock.forget(); +} + +void +QuotaManager::RegisterDirectoryLock(DirectoryLockImpl* aLock) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aLock); + + mDirectoryLocks.AppendElement(aLock); + + if (aLock->ShouldUpdateLockTable()) { + const Nullable<PersistenceType>& persistenceType = + aLock->GetPersistenceType(); + const OriginScope& originScope = aLock->GetOriginScope(); + + MOZ_ASSERT(!persistenceType.IsNull()); + MOZ_ASSERT(!aLock->GetGroup().IsEmpty()); + MOZ_ASSERT(originScope.IsOrigin()); + MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); + + DirectoryLockTable& directoryLockTable = + GetDirectoryLockTable(persistenceType.Value()); + + nsTArray<DirectoryLockImpl*>* array; + if (!directoryLockTable.Get(originScope.GetOrigin(), &array)) { + array = new nsTArray<DirectoryLockImpl*>(); + directoryLockTable.Put(originScope.GetOrigin(), array); + + if (!IsShuttingDown()) { + UpdateOriginAccessTime(persistenceType.Value(), + aLock->GetGroup(), + originScope.GetOrigin()); + } + } + array->AppendElement(aLock); + } +} + +void +QuotaManager::UnregisterDirectoryLock(DirectoryLockImpl* aLock) +{ + AssertIsOnOwningThread(); + + MOZ_ALWAYS_TRUE(mDirectoryLocks.RemoveElement(aLock)); + + if (aLock->ShouldUpdateLockTable()) { + const Nullable<PersistenceType>& persistenceType = + aLock->GetPersistenceType(); + const OriginScope& originScope = aLock->GetOriginScope(); + + MOZ_ASSERT(!persistenceType.IsNull()); + MOZ_ASSERT(!aLock->GetGroup().IsEmpty()); + MOZ_ASSERT(originScope.IsOrigin()); + MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); + + DirectoryLockTable& directoryLockTable = + GetDirectoryLockTable(persistenceType.Value()); + + nsTArray<DirectoryLockImpl*>* array; + MOZ_ALWAYS_TRUE(directoryLockTable.Get(originScope.GetOrigin(), &array)); + + MOZ_ALWAYS_TRUE(array->RemoveElement(aLock)); + if (array->IsEmpty()) { + directoryLockTable.Remove(originScope.GetOrigin()); + + if (!IsShuttingDown()) { + UpdateOriginAccessTime(persistenceType.Value(), + aLock->GetGroup(), + originScope.GetOrigin()); + } + } + } +} + +void +QuotaManager::RemovePendingDirectoryLock(DirectoryLockImpl* aLock) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aLock); + + MOZ_ALWAYS_TRUE(mPendingDirectoryLocks.RemoveElement(aLock)); +} + +uint64_t +QuotaManager::CollectOriginsForEviction( + uint64_t aMinSizeToBeFreed, + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aLocks.IsEmpty()); + + struct MOZ_STACK_CLASS Helper final + { + static void + GetInactiveOriginInfos(nsTArray<RefPtr<OriginInfo>>& aOriginInfos, + nsTArray<DirectoryLockImpl*>& aLocks, + nsTArray<OriginInfo*>& aInactiveOriginInfos) + { + for (OriginInfo* originInfo : aOriginInfos) { + MOZ_ASSERT(IsTreatedAsTemporary(originInfo->mGroupInfo->mPersistenceType, + originInfo->mIsApp)); + + OriginScope originScope = OriginScope::FromOrigin(originInfo->mOrigin); + + bool match = false; + for (uint32_t j = aLocks.Length(); j > 0; j--) { + DirectoryLockImpl* lock = aLocks[j - 1]; + if (originScope.Matches(lock->GetOriginScope())) { + match = true; + break; + } + } + + if (!match) { + MOZ_ASSERT(!originInfo->mQuotaObjects.Count(), + "Inactive origin shouldn't have open files!"); + aInactiveOriginInfos.InsertElementSorted(originInfo, + OriginInfoLRUComparator()); + } + } + } + }; + + // Split locks into separate arrays and filter out locks for persistent + // storage, they can't block us. + nsTArray<DirectoryLockImpl*> temporaryStorageLocks; + nsTArray<DirectoryLockImpl*> defaultStorageLocks; + for (DirectoryLockImpl* lock : mDirectoryLocks) { + const Nullable<PersistenceType>& persistenceType = + lock->GetPersistenceType(); + + if (persistenceType.IsNull()) { + temporaryStorageLocks.AppendElement(lock); + defaultStorageLocks.AppendElement(lock); + } else if (persistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) { + temporaryStorageLocks.AppendElement(lock); + } else if (persistenceType.Value() == PERSISTENCE_TYPE_DEFAULT) { + defaultStorageLocks.AppendElement(lock); + } else { + MOZ_ASSERT(persistenceType.Value() == PERSISTENCE_TYPE_PERSISTENT); + + // Do nothing here, persistent origins don't need to be collected ever. + } + } + + nsTArray<OriginInfo*> inactiveOrigins; + + // Enumerate and process inactive origins. This must be protected by the + // mutex. + MutexAutoLock lock(mQuotaMutex); + + for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { + GroupInfoPair* pair = iter.UserData(); + + MOZ_ASSERT(!iter.Key().IsEmpty()); + MOZ_ASSERT(pair); + + RefPtr<GroupInfo> groupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (groupInfo) { + Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos, + temporaryStorageLocks, + inactiveOrigins); + } + + groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (groupInfo) { + Helper::GetInactiveOriginInfos(groupInfo->mOriginInfos, + defaultStorageLocks, + inactiveOrigins); + } + } + +#ifdef DEBUG + // Make sure the array is sorted correctly. + for (uint32_t index = inactiveOrigins.Length(); index > 1; index--) { + MOZ_ASSERT(inactiveOrigins[index - 1]->mAccessTime >= + inactiveOrigins[index - 2]->mAccessTime); + } +#endif + + // Create a list of inactive and the least recently used origins + // whose aggregate size is greater or equals the minimal size to be freed. + uint64_t sizeToBeFreed = 0; + for(uint32_t count = inactiveOrigins.Length(), index = 0; + index < count; + index++) { + if (sizeToBeFreed >= aMinSizeToBeFreed) { + inactiveOrigins.TruncateLength(index); + break; + } + + sizeToBeFreed += inactiveOrigins[index]->mUsage; + } + + if (sizeToBeFreed >= aMinSizeToBeFreed) { + // Success, add directory locks for these origins, so any other + // operations for them will be delayed (until origin eviction is finalized). + + for (OriginInfo* originInfo : inactiveOrigins) { + RefPtr<DirectoryLockImpl> lock = + CreateDirectoryLockForEviction(originInfo->mGroupInfo->mPersistenceType, + originInfo->mGroupInfo->mGroup, + originInfo->mOrigin, + originInfo->mIsApp); + aLocks.AppendElement(lock.forget()); + } + + return sizeToBeFreed; + } + + return 0; +} + +nsresult +QuotaManager::Init(const nsAString& aBasePath) +{ + nsresult rv; + + mBasePath = aBasePath; + + nsCOMPtr<nsIFile> baseDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = baseDir->InitWithPath(aBasePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CloneStoragePath(baseDir, + NS_LITERAL_STRING(INDEXEDDB_DIRECTORY_NAME), + mIndexedDBPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = baseDir->Append(NS_LITERAL_STRING(STORAGE_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = baseDir->GetPath(mStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CloneStoragePath(baseDir, + NS_LITERAL_STRING(PERMANENT_DIRECTORY_NAME), + mPermanentStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CloneStoragePath(baseDir, + NS_LITERAL_STRING(TEMPORARY_DIRECTORY_NAME), + mTemporaryStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CloneStoragePath(baseDir, + NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME), + mDefaultStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Make a lazy thread for any IO we need (like clearing or enumerating the + // contents of storage directories). + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, + NS_LITERAL_CSTRING("Storage I/O"), + LazyIdleThread::ManualShutdown); + + // Make a timer here to avoid potential failures later. We don't actually + // initialize the timer until shutdown. + mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (NS_WARN_IF(!mShutdownTimer)) { + return NS_ERROR_FAILURE; + } + + static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::DOMCACHE == 2 && + Client::TYPE_MAX == 3, "Fix the registration!"); + + MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX, + "Should be using an auto array with correct capacity!"); + + // Register clients. + mClients.AppendElement(indexedDB::CreateQuotaClient()); + mClients.AppendElement(asmjscache::CreateClient()); + mClients.AppendElement(cache::CreateQuotaClient()); + + return NS_OK; +} + +void +QuotaManager::Shutdown() +{ + AssertIsOnOwningThread(); + + // Setting this flag prevents the service from being recreated and prevents + // further storagess from being created. + if (gShutdown.exchange(true)) { + NS_ERROR("Shutdown more than once?!"); + } + + StopIdleMaintenance(); + + // Kick off the shutdown timer. + MOZ_ALWAYS_SUCCEEDS( + mShutdownTimer->InitWithFuncCallback(&ShutdownTimerCallback, + this, + DEFAULT_SHUTDOWN_TIMER_MS, + nsITimer::TYPE_ONE_SHOT)); + + // Each client will spin the event loop while we wait on all the threads + // to close. Our timer may fire during that loop. + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + mClients[index]->ShutdownWorkThreads(); + } + + // Cancel the timer regardless of whether it actually fired. + if (NS_FAILED(mShutdownTimer->Cancel())) { + NS_WARNING("Failed to cancel shutdown timer!"); + } + + // NB: It's very important that runnable is destroyed on this thread + // (i.e. after we join the IO thread) because we can't release the + // QuotaManager on the IO thread. This should probably use + // NewNonOwningRunnableMethod ... + RefPtr<Runnable> runnable = + NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects); + MOZ_ASSERT(runnable); + + // Give clients a chance to cleanup IO thread only objects. + if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) { + NS_WARNING("Failed to dispatch runnable!"); + } + + // Make sure to join with our IO thread. + if (NS_FAILED(mIOThread->Shutdown())) { + NS_WARNING("Failed to shutdown IO thread!"); + } + + for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) { + lock->Invalidate(); + } +} + +void +QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + uint64_t aUsageBytes, + int64_t aAccessTime) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(IsTreatedAsTemporary(aPersistenceType, aIsApp)); + + MutexAutoLock lock(mQuotaMutex); + + GroupInfoPair* pair; + if (!mGroupInfoPairs.Get(aGroup, &pair)) { + pair = new GroupInfoPair(); + mGroupInfoPairs.Put(aGroup, pair); + // The hashtable is now responsible to delete the GroupInfoPair. + } + + RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (!groupInfo) { + groupInfo = new GroupInfo(pair, aPersistenceType, aGroup); + pair->LockedSetGroupInfo(aPersistenceType, groupInfo); + } + + RefPtr<OriginInfo> originInfo = + new OriginInfo(groupInfo, aOrigin, aIsApp, aUsageBytes, aAccessTime); + groupInfo->LockedAddOriginInfo(originInfo); +} + +void +QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + int64_t aSize) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + MutexAutoLock lock(mQuotaMutex); + + GroupInfoPair* pair; + if (!mGroupInfoPairs.Get(aGroup, &pair)) { + return; + } + + RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (!groupInfo) { + return; + } + + RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin); + if (originInfo) { + originInfo->LockedDecreaseUsage(aSize); + } +} + +void +QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + MutexAutoLock lock(mQuotaMutex); + + GroupInfoPair* pair; + if (!mGroupInfoPairs.Get(aGroup, &pair)) { + return; + } + + RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (!groupInfo) { + return; + } + + RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin); + if (originInfo) { + int64_t timestamp = PR_Now(); + originInfo->LockedUpdateAccessTime(timestamp); + + MutexAutoUnlock autoUnlock(mQuotaMutex); + + RefPtr<SaveOriginAccessTimeOp> op = + new SaveOriginAccessTimeOp(aPersistenceType, aOrigin, timestamp); + + op->RunImmediately(); + } +} + +void +QuotaManager::RemoveQuota() +{ + MutexAutoLock lock(mQuotaMutex); + + for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<GroupInfoPair>& pair = iter.Data(); + + MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); + MOZ_ASSERT(pair, "Null pointer!"); + + RefPtr<GroupInfo> groupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (groupInfo) { + groupInfo->LockedRemoveOriginInfos(); + } + + groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (groupInfo) { + groupInfo->LockedRemoveOriginInfos(); + } + + iter.Remove(); + } + + NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!"); +} + +already_AddRefed<QuotaObject> +QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + nsIFile* aFile) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { + return nullptr; + } + + nsString path; + nsresult rv = aFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, nullptr); + + int64_t fileSize; + + bool exists; + rv = aFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, nullptr); + + if (exists) { + rv = aFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, nullptr); + } + else { + fileSize = 0; + } + + // Re-escape our parameters above to make sure we get the right quota group. + nsAutoCString group; + rv = NS_EscapeURL(aGroup, esc_Query, group, fallible); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsAutoCString origin; + rv = NS_EscapeURL(aOrigin, esc_Query, origin, fallible); + NS_ENSURE_SUCCESS(rv, nullptr); + + RefPtr<QuotaObject> result; + { + MutexAutoLock lock(mQuotaMutex); + + GroupInfoPair* pair; + if (!mGroupInfoPairs.Get(group, &pair)) { + return nullptr; + } + + RefPtr<GroupInfo> groupInfo = + pair->LockedGetGroupInfo(aPersistenceType); + + if (!groupInfo) { + return nullptr; + } + + RefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(origin); + + if (!originInfo) { + return nullptr; + } + + // We need this extra raw pointer because we can't assign to the smart + // pointer directly since QuotaObject::AddRef would try to acquire the same + // mutex. + QuotaObject* quotaObject; + if (!originInfo->mQuotaObjects.Get(path, "aObject)) { + // Create a new QuotaObject. + quotaObject = new QuotaObject(originInfo, path, fileSize); + + // Put it to the hashtable. The hashtable is not responsible to delete + // the QuotaObject. + originInfo->mQuotaObjects.Put(path, quotaObject); + } + + // Addref the QuotaObject and move the ownership to the result. This must + // happen before we unlock! + result = quotaObject->LockedAddRef(); + } + + // The caller becomes the owner of the QuotaObject, that is, the caller is + // is responsible to delete it when the last reference is removed. + return result.forget(); +} + +already_AddRefed<QuotaObject> +QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const nsAString& aPath) +{ + nsresult rv; + nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = file->InitWithPath(aPath); + NS_ENSURE_SUCCESS(rv, nullptr); + + return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file); +} + +void +QuotaManager::AbortOperationsForProcess(ContentParentId aContentParentId) +{ + AssertIsOnOwningThread(); + + for (RefPtr<Client>& client : mClients) { + client->AbortOperationsForProcess(aContentParentId); + } +} + +nsresult +QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType, + const nsACString& aASCIIOrigin, + nsIFile** aDirectory) const +{ + nsresult rv; + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->InitWithPath(GetStoragePath(aPersistenceType)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString originSanitized(aASCIIOrigin); + SanitizeOriginString(originSanitized); + + rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized)); + NS_ENSURE_SUCCESS(rv, rv); + + directory.forget(aDirectory); + return NS_OK; +} + +nsresult +QuotaManager::RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(mStorageInitialized); + + RefPtr<RestoreDirectoryMetadata2Helper> helper = + new RestoreDirectoryMetadata2Helper(aDirectory, aPersistent); + + nsresult rv = helper->RestoreMetadata2File(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aSuffix, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(aIsApp); + MOZ_ASSERT(mStorageInitialized); + + nsCOMPtr<nsIBinaryInputStream> binaryStream; + nsresult rv = GetBinaryInputStream(aDirectory, + NS_LITERAL_STRING(METADATA_V2_FILE_NAME), + getter_AddRefs(binaryStream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t timestamp; + rv = binaryStream->Read64(×tamp); + NS_ENSURE_SUCCESS(rv, rv); + + bool persisted; + rv = binaryStream->ReadBoolean(&persisted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t reservedData1; + rv = binaryStream->Read32(&reservedData1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint32_t reservedData2; + rv = binaryStream->Read32(&reservedData2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString suffix; + rv = binaryStream->ReadCString(suffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString group; + rv = binaryStream->ReadCString(group); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString origin; + rv = binaryStream->ReadCString(origin); + NS_ENSURE_SUCCESS(rv, rv); + + bool isApp; + rv = binaryStream->ReadBoolean(&isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aTimestamp = timestamp; + aSuffix = suffix; + aGroup = group; + aOrigin = origin; + *aIsApp = isApp; + return NS_OK; +} + +nsresult +QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, + bool aPersistent, + int64_t* aTimestamp, + nsACString& aSuffix, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp) +{ + nsresult rv = GetDirectoryMetadata2(aDirectory, + aTimestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = GetDirectoryMetadata2(aDirectory, + aTimestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +nsresult +QuotaManager::GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(mStorageInitialized); + + nsCOMPtr<nsIBinaryInputStream> binaryStream; + nsresult rv = GetBinaryInputStream(aDirectory, + NS_LITERAL_STRING(METADATA_V2_FILE_NAME), + getter_AddRefs(binaryStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint64_t timestamp; + rv = binaryStream->Read64(×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aTimestamp = timestamp; + return NS_OK; +} + +nsresult +QuotaManager::GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, + bool aPersistent, + int64_t* aTimestamp) +{ + nsresult rv = GetDirectoryMetadata2(aDirectory, aTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + rv = RestoreDirectoryMetadata2(aDirectory, aPersistent); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = GetDirectoryMetadata2(aDirectory, aTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +nsresult +QuotaManager::InitializeRepository(PersistenceType aPersistenceType) +{ + MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY || + aPersistenceType == PERSISTENCE_TYPE_DEFAULT); + + nsresult rv; + + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = directory->InitWithPath(GetStoragePath(aPersistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool created; + rv = EnsureDirectory(directory, &created); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> childDirectory = do_QueryInterface(entry); + MOZ_ASSERT(childDirectory); + + bool isDirectory; + rv = childDirectory->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + nsString leafName; + rv = childDirectory->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + continue; + } + + QM_WARNING("Something (%s) in the repository that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()); + return NS_ERROR_UNEXPECTED; + } + + int64_t timestamp; + nsCString suffix; + nsCString group; + nsCString origin; + bool isApp; + rv = GetDirectoryMetadata2WithRestore(childDirectory, + /* aPersistent */ false, + ×tamp, + suffix, + group, + origin, + &isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (IsTreatedAsPersistent(aPersistenceType, isApp)) { + continue; + } + + rv = InitializeOrigin(aPersistenceType, group, origin, isApp, timestamp, + childDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +namespace { + +// The Cache API was creating top level morgue directories by accident for +// a short time in nightly. This unfortunately prevents all storage from +// working. So recover these profiles by removing these corrupt directories. +// This should be removed at some point in the future. +bool +MaybeRemoveCorruptDirectory(const nsAString& aLeafName, nsIFile* aDir) +{ +#ifdef NIGHTLY_BUILD + MOZ_ASSERT(aDir); + + if (aLeafName != NS_LITERAL_STRING("morgue")) { + return false; + } + + NS_WARNING("QuotaManager removing corrupt morgue directory!"); + + nsresult rv = aDir->Remove(true /* recursive */); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +#else + return false; +#endif // NIGHTLY_BUILD +} + +} // namespace + +nsresult +QuotaManager::InitializeOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + int64_t aAccessTime, + nsIFile* aDirectory) +{ + AssertIsOnIOThread(); + + nsresult rv; + + bool trackQuota = IsQuotaEnforced(aPersistenceType, aOrigin, aIsApp); + + // We need to initialize directories of all clients if they exists and also + // get the total usage to initialize the quota. + nsAutoPtr<UsageInfo> usageInfo; + if (trackQuota) { + usageInfo = new UsageInfo(); + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (leafName.EqualsLiteral(METADATA_FILE_NAME) || + leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || + leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + continue; + } + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isDirectory) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + + if (MaybeRemoveCorruptDirectory(leafName, file)) { + continue; + } + + Client::Type clientType; + rv = Client::TypeFromText(leafName, clientType); + if (NS_FAILED(rv)) { + NS_WARNING("Unknown directory found!"); + return NS_ERROR_UNEXPECTED; + } + + Atomic<bool> dummy(false); + rv = mClients[clientType]->InitOrigin(aPersistenceType, + aGroup, + aOrigin, + /* aCanceled */ dummy, + usageInfo); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (trackQuota) { + InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, + usageInfo->TotalUsage(), aAccessTime); + } + + return NS_OK; +} + +nsresult +QuotaManager::MaybeUpgradeIndexedDBDirectory() +{ + AssertIsOnIOThread(); + + nsresult rv; + + nsCOMPtr<nsIFile> indexedDBDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = indexedDBDir->InitWithPath(mIndexedDBPath); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = indexedDBDir->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) { + // Nothing to upgrade. + return NS_OK; + } + + bool isDirectory; + rv = indexedDBDir->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isDirectory) { + NS_WARNING("indexedDB entry is not a directory!"); + return NS_OK; + } + + nsCOMPtr<nsIFile> persistentStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = persistentStorageDir->InitWithPath(mStoragePath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = persistentStorageDir->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + NS_WARNING("indexedDB directory shouldn't exist after the upgrade!"); + return NS_OK; + } + + nsCOMPtr<nsIFile> storageDir; + rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // MoveTo() is atomic if the move happens on the same volume which should + // be our case, so even if we crash in the middle of the operation nothing + // breaks next time we try to initialize. + // However there's a theoretical possibility that the indexedDB directory + // is on different volume, but it should be rare enough that we don't have + // to worry about it. + rv = indexedDBDir->MoveTo(storageDir, NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +QuotaManager::MaybeUpgradePersistentStorageDirectory() +{ + AssertIsOnIOThread(); + + nsresult rv; + + nsCOMPtr<nsIFile> persistentStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = persistentStorageDir->InitWithPath(mStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = persistentStorageDir->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + // Nothing to upgrade. + return NS_OK; + } + + bool isDirectory; + rv = persistentStorageDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + NS_WARNING("persistent entry is not a directory!"); + return NS_OK; + } + + nsCOMPtr<nsIFile> defaultStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = defaultStorageDir->InitWithPath(mDefaultStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = defaultStorageDir->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + NS_WARNING("storage/persistent shouldn't exist after the upgrade!"); + return NS_OK; + } + + // Create real metadata files for origin directories in persistent storage. + RefPtr<CreateOrUpgradeDirectoryMetadataHelper> helper = + new CreateOrUpgradeDirectoryMetadataHelper(persistentStorageDir, + /* aPersistent */ true); + + rv = helper->CreateOrUpgradeMetadataFiles(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Upgrade metadata files for origin directories in temporary storage. + nsCOMPtr<nsIFile> temporaryStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = temporaryStorageDir->InitWithPath(mTemporaryStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = temporaryStorageDir->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + rv = temporaryStorageDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + NS_WARNING("temporary entry is not a directory!"); + return NS_OK; + } + + helper = + new CreateOrUpgradeDirectoryMetadataHelper(temporaryStorageDir, + /* aPersistent */ false); + + rv = helper->CreateOrUpgradeMetadataFiles(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // And finally rename persistent to default. + rv = persistentStorageDir->RenameTo(nullptr, NS_LITERAL_STRING(DEFAULT_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +QuotaManager::MaybeRemoveOldDirectories() +{ + AssertIsOnIOThread(); + + nsresult rv; + + nsCOMPtr<nsIFile> indexedDBDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = indexedDBDir->InitWithPath(mIndexedDBPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = indexedDBDir->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + QM_WARNING("Deleting old <profile>/indexedDB directory!"); + + rv = indexedDBDir->Remove(/* aRecursive */ true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsCOMPtr<nsIFile> persistentStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = persistentStorageDir->InitWithPath(mStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = persistentStorageDir->Append(NS_LITERAL_STRING(PERSISTENT_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = persistentStorageDir->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + QM_WARNING("Deleting old <profile>/storage/persistent directory!"); + + rv = persistentStorageDir->Remove(/* aRecursive */ true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +nsresult +QuotaManager::UpgradeStorageFrom0ToCurrent(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + nsresult rv; + + for (const PersistenceType persistenceType : kAllPersistenceTypes) { + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = directory->InitWithPath(GetStoragePath(persistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT; + RefPtr<UpgradeDirectoryMetadataFrom1To2Helper> helper = + new UpgradeDirectoryMetadataFrom1To2Helper(directory, persistent); + + rv = helper->UpgradeMetadataFiles(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef DEBUG + { + int32_t storageVersion; + rv = aConnection->GetSchemaVersion(&storageVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(storageVersion == 0); + } +#endif + + rv = aConnection->SetSchemaVersion(kStorageVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +#if 0 +nsresult +QuotaManager::UpgradeStorageFrom1To2(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + nsresult rv = aConnection->SetSchemaVersion(MakeStorageVersion(2, 0)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} +#endif + +nsresult +QuotaManager::EnsureStorageIsInitialized() +{ + AssertIsOnIOThread(); + + if (mStorageInitialized) { + return NS_OK; + } + + nsresult rv; + + nsCOMPtr<nsIFile> storageFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = storageFile->InitWithPath(mBasePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<mozIStorageService> ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<mozIStorageConnection> connection; + rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection)); + if (rv == NS_ERROR_FILE_CORRUPTED) { + // Nuke the database file. + rv = storageFile->Remove(false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = ss->OpenUnsharedDatabase(storageFile, getter_AddRefs(connection)); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We want extra durability for this important file. + rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA synchronous = EXTRA;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Check to make sure that the storage version is correct. + int32_t storageVersion; + rv = connection->GetSchemaVersion(&storageVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (GetMajorStorageVersion(storageVersion) > kMajorStorageVersion) { + NS_WARNING("Unable to initialize storage, version is too high!"); + return NS_ERROR_FAILURE; + } + + if (storageVersion < kStorageVersion) { + const bool newDatabase = !storageVersion; + + if (newDatabase) { + // Set the page size first. + if (kSQLitePageSizeOverride) { + rv = connection->ExecuteSimpleSQL( + nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride) + ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + mozStorageTransaction transaction(connection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + if (newDatabase) { + rv = MaybeUpgradeIndexedDBDirectory(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = MaybeUpgradePersistentStorageDirectory(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = MaybeRemoveOldDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = UpgradeStorageFrom0ToCurrent(connection); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&storageVersion))); + MOZ_ASSERT(storageVersion == kStorageVersion); + } else { + // This logic needs to change next time we change the storage! + static_assert(kStorageVersion == int32_t((1 << 16) + 0), + "Upgrade function needed due to storage version increase."); + + while (storageVersion != kStorageVersion) { + /* if (storageVersion == MakeStorageVersion(1, 0)) { + rv = UpgradeStorageFrom1To2(connection); + } else */ { + NS_WARNING("Unable to initialize storage, no upgrade path is " + "available!"); + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = connection->GetSchemaVersion(&storageVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + MOZ_ASSERT(storageVersion == kStorageVersion); + } + + rv = transaction.Commit(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mStorageInitialized = true; + + return NS_OK; +} + +void +QuotaManager::OpenDirectory(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + Client::Type aClientType, + bool aExclusive, + OpenDirectoryListener* aOpenListener) +{ + AssertIsOnOwningThread(); + + RefPtr<DirectoryLockImpl> lock = + CreateDirectoryLock(Nullable<PersistenceType>(aPersistenceType), + aGroup, + OriginScope::FromOrigin(aOrigin), + Nullable<bool>(aIsApp), + Nullable<Client::Type>(aClientType), + aExclusive, + false, + aOpenListener); + MOZ_ASSERT(lock); +} + +void +QuotaManager::OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType, + const OriginScope& aOriginScope, + Nullable<Client::Type> aClientType, + bool aExclusive, + OpenDirectoryListener* aOpenListener) +{ + AssertIsOnOwningThread(); + + RefPtr<DirectoryLockImpl> lock = + CreateDirectoryLock(aPersistenceType, + EmptyCString(), + aOriginScope, + Nullable<bool>(), + Nullable<Client::Type>(aClientType), + aExclusive, + true, + aOpenListener); + MOZ_ASSERT(lock); + + if (!aExclusive) { + return; + } + + // All the locks that block this new exclusive lock need to be invalidated. + // We also need to notify clients to abort operations for them. + AutoTArray<nsAutoPtr<nsTHashtable<nsCStringHashKey>>, + Client::TYPE_MAX> origins; + origins.SetLength(Client::TYPE_MAX); + + const nsTArray<DirectoryLockImpl*>& blockedOnLocks = + lock->GetBlockedOnLocks(); + + for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) { + blockedOnLock->Invalidate(); + + if (!blockedOnLock->IsInternal()) { + MOZ_ASSERT(!blockedOnLock->GetClientType().IsNull()); + Client::Type clientType = blockedOnLock->GetClientType().Value(); + MOZ_ASSERT(clientType < Client::TYPE_MAX); + + const OriginScope& originScope = blockedOnLock->GetOriginScope(); + MOZ_ASSERT(originScope.IsOrigin()); + MOZ_ASSERT(!originScope.GetOrigin().IsEmpty()); + + nsAutoPtr<nsTHashtable<nsCStringHashKey>>& origin = origins[clientType]; + if (!origin) { + origin = new nsTHashtable<nsCStringHashKey>(); + } + origin->PutEntry(originScope.GetOrigin()); + } + } + + for (uint32_t index : MakeRange(uint32_t(Client::TYPE_MAX))) { + if (origins[index]) { + for (auto iter = origins[index]->Iter(); !iter.Done(); iter.Next()) { + MOZ_ASSERT(mClients[index]); + + mClients[index]->AbortOperations(iter.Get()->GetKey()); + } + } + } +} + +nsresult +QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType, + const nsACString& aSuffix, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + nsIFile** aDirectory) +{ + AssertIsOnIOThread(); + + nsresult rv = EnsureStorageIsInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + // Get directory for this origin and persistence type. + nsCOMPtr<nsIFile> directory; + rv = GetDirectoryForOrigin(aPersistenceType, aOrigin, + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { + if (mInitializedOrigins.Contains(OriginKey(aPersistenceType, aOrigin))) { + directory.forget(aDirectory); + return NS_OK; + } + } else if (!mTemporaryStorageInitialized) { + rv = InitializeRepository(aPersistenceType); + if (NS_WARN_IF(NS_FAILED(rv))) { + // We have to cleanup partially initialized quota. + RemoveQuota(); + + return rv; + } + + rv = InitializeRepository(ComplementaryPersistenceType(aPersistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + // We have to cleanup partially initialized quota. + RemoveQuota(); + + return rv; + } + + if (gFixedLimitKB >= 0) { + mTemporaryStorageLimit = static_cast<uint64_t>(gFixedLimitKB) * 1024; + } + else { + nsCOMPtr<nsIFile> storageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = storageDir->InitWithPath(GetStoragePath()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = GetTemporaryStorageLimit(storageDir, mTemporaryStorageUsage, + &mTemporaryStorageLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + mTemporaryStorageInitialized = true; + + CheckTemporaryStorageLimits(); + } + + int64_t timestamp; + + bool created; + rv = EnsureDirectory(directory, &created); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { + if (created) { + timestamp = PR_Now(); + + rv = CreateDirectoryMetadata(directory, + timestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CreateDirectoryMetadata2(directory, + timestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + NS_ENSURE_SUCCESS(rv, rv); + } else { + bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; + rv = GetDirectoryMetadata2WithRestore(directory, + persistent, + ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(timestamp <= PR_Now()); + } + + rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp, + directory); + NS_ENSURE_SUCCESS(rv, rv); + + mInitializedOrigins.AppendElement(OriginKey(aPersistenceType, aOrigin)); + } else if (created) { + timestamp = PR_Now(); + + rv = CreateDirectoryMetadata(directory, + timestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CreateDirectoryMetadata2(directory, + timestamp, + aSuffix, + aGroup, + aOrigin, + aIsApp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aIsApp, timestamp, + directory); + NS_ENSURE_SUCCESS(rv, rv); + } + + directory.forget(aDirectory); + return NS_OK; +} + +void +QuotaManager::OriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp) +{ + AssertIsOnIOThread(); + + if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { + mInitializedOrigins.RemoveElement(OriginKey(aPersistenceType, aOrigin)); + } + + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + mClients[index]->OnOriginClearCompleted(aPersistenceType, aOrigin); + } +} + +void +QuotaManager::ResetOrClearCompleted() +{ + AssertIsOnIOThread(); + + mInitializedOrigins.Clear(); + mTemporaryStorageInitialized = false; + mStorageInitialized = false; + + ReleaseIOThreadObjects(); +} + +Client* +QuotaManager::GetClient(Client::Type aClientType) +{ + MOZ_ASSERT(aClientType >= Client::IDB); + MOZ_ASSERT(aClientType < Client::TYPE_MAX); + + return mClients.ElementAt(aClientType); +} + +uint64_t +QuotaManager::GetGroupLimit() const +{ + MOZ_ASSERT(mTemporaryStorageInitialized); + + // To avoid one group evicting all the rest, limit the amount any one group + // can use to 20%. To prevent individual sites from using exorbitant amounts + // of storage where there is a lot of free space, cap the group limit to 2GB. + uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit * .20, 2 GB); + + // In low-storage situations, make an exception (while not exceeding the total + // storage limit). + return std::min<uint64_t>(mTemporaryStorageLimit, + std::max<uint64_t>(x, 10 MB)); +} + +void +QuotaManager::GetGroupUsageAndLimit(const nsACString& aGroup, + UsageInfo* aUsageInfo) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aUsageInfo); + + { + MutexAutoLock lock(mQuotaMutex); + + aUsageInfo->SetLimit(GetGroupLimit()); + aUsageInfo->ResetUsage(); + + GroupInfoPair* pair; + if (!mGroupInfoPairs.Get(aGroup, &pair)) { + return; + } + + // Calculate temporary group usage + RefPtr<GroupInfo> temporaryGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (temporaryGroupInfo) { + aUsageInfo->AppendToDatabaseUsage(temporaryGroupInfo->mUsage); + } + + // Calculate default group usage + RefPtr<GroupInfo> defaultGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (defaultGroupInfo) { + aUsageInfo->AppendToDatabaseUsage(defaultGroupInfo->mUsage); + } + } +} + +// static +void +QuotaManager::GetStorageId(PersistenceType aPersistenceType, + const nsACString& aOrigin, + Client::Type aClientType, + nsACString& aDatabaseId) +{ + nsAutoCString str; + str.AppendInt(aPersistenceType); + str.Append('*'); + str.Append(aOrigin); + str.Append('*'); + str.AppendInt(aClientType); + + aDatabaseId = str; +} + +// static +nsresult +QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal, + nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + GetInfoForChrome(aSuffix, aGroup, aOrigin, aIsApp); + return NS_OK; + } + + + if (aPrincipal->GetIsNullPrincipal()) { + NS_WARNING("IndexedDB not supported from this principal!"); + return NS_ERROR_FAILURE; + } + + nsCString origin; + nsresult rv = aPrincipal->GetOrigin(origin); + NS_ENSURE_SUCCESS(rv, rv); + + if (origin.EqualsLiteral(kChromeOrigin)) { + NS_WARNING("Non-chrome principal can't use chrome origin!"); + return NS_ERROR_FAILURE; + } + + nsCString suffix; + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix); + + if (aSuffix) + { + aSuffix->Assign(suffix); + } + + if (aGroup) { + nsCString baseDomain; + rv = aPrincipal->GetBaseDomain(baseDomain); + if (NS_FAILED(rv)) { + // A hack for JetPack. + + nsCOMPtr<nsIURI> uri; + rv = aPrincipal->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isIndexedDBURI = false; + rv = uri->SchemeIs("indexedDB", &isIndexedDBURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (isIndexedDBURI) { + rv = NS_OK; + } + } + NS_ENSURE_SUCCESS(rv, rv); + + if (baseDomain.IsEmpty()) { + aGroup->Assign(origin); + } else { + aGroup->Assign(baseDomain + suffix); + } + } + + if (aOrigin) { + aOrigin->Assign(origin); + } + + if (aIsApp) { + *aIsApp = aPrincipal->GetAppStatus() != + nsIPrincipal::APP_STATUS_NOT_INSTALLED; + } + + return NS_OK; +} + +// static +nsresult +QuotaManager::GetInfoFromWindow(nsPIDOMWindowOuter* aWindow, + nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aWindow); + + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow); + NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); + + nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); + NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE); + + nsresult rv = + GetInfoFromPrincipal(principal, aSuffix, aGroup, aOrigin, aIsApp); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// static +void +QuotaManager::GetInfoForChrome(nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode()); + + if (aSuffix) { + aSuffix->Assign(EmptyCString()); + } + if (aGroup) { + ChromeOrigin(*aGroup); + } + if (aOrigin) { + ChromeOrigin(*aOrigin); + } + if (aIsApp) { + *aIsApp = false; + } +} + +// static +bool +QuotaManager::IsOriginInternal(const nsACString& aOrigin) +{ + // The first prompt is not required for these origins. + if (aOrigin.EqualsLiteral(kChromeOrigin) || + StringBeginsWith(aOrigin, nsDependentCString(kAboutHomeOriginPrefix)) || + StringBeginsWith(aOrigin, nsDependentCString(kIndexedDBOriginPrefix)) || + StringBeginsWith(aOrigin, nsDependentCString(kResourceOriginPrefix))) { + return true; + } + + return false; +} + +// static +bool +QuotaManager::IsFirstPromptRequired(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp) +{ + if (IsTreatedAsTemporary(aPersistenceType, aIsApp)) { + return false; + } + + return !IsOriginInternal(aOrigin); +} + +// static +bool +QuotaManager::IsQuotaEnforced(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp) +{ + return IsTreatedAsTemporary(aPersistenceType, aIsApp); +} + +// static +void +QuotaManager::ChromeOrigin(nsACString& aOrigin) +{ + aOrigin.AssignLiteral(kChromeOrigin); +} + +uint64_t +QuotaManager::LockedCollectOriginsForEviction( + uint64_t aMinSizeToBeFreed, + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) +{ + mQuotaMutex.AssertCurrentThreadOwns(); + + RefPtr<CollectOriginsHelper> helper = + new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed); + + // Unlock while calling out to XPCOM (code behind the dispatch method needs + // to acquire its own lock which can potentially lead to a deadlock and it + // also calls an observer that can do various stuff like IO, so it's better + // to not hold our mutex while that happens). + { + MutexAutoUnlock autoUnlock(mQuotaMutex); + + MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(helper, NS_DISPATCH_NORMAL)); + } + + return helper->BlockAndReturnOriginsForEviction(aLocks); +} + +void +QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin) +{ + mQuotaMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_PERSISTENT); + + GroupInfoPair* pair; + mGroupInfoPairs.Get(aGroup, &pair); + + if (!pair) { + return; + } + + RefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType); + if (groupInfo) { + groupInfo->LockedRemoveOriginInfo(aOrigin); + + if (!groupInfo->LockedHasOriginInfos()) { + pair->LockedClearGroupInfo(aPersistenceType); + + if (!pair->LockedHasGroupInfos()) { + mGroupInfoPairs.Remove(aGroup); + } + } + } +} + +void +QuotaManager::CheckTemporaryStorageLimits() +{ + AssertIsOnIOThread(); + + nsTArray<OriginInfo*> doomedOriginInfos; + { + MutexAutoLock lock(mQuotaMutex); + + for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { + GroupInfoPair* pair = iter.UserData(); + + MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); + MOZ_ASSERT(pair, "Null pointer!"); + + uint64_t groupUsage = 0; + + RefPtr<GroupInfo> temporaryGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (temporaryGroupInfo) { + groupUsage += temporaryGroupInfo->mUsage; + } + + RefPtr<GroupInfo> defaultGroupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (defaultGroupInfo) { + groupUsage += defaultGroupInfo->mUsage; + } + + if (groupUsage > 0) { + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager, "Shouldn't be null!"); + + if (groupUsage > quotaManager->GetGroupLimit()) { + nsTArray<OriginInfo*> originInfos; + if (temporaryGroupInfo) { + originInfos.AppendElements(temporaryGroupInfo->mOriginInfos); + } + if (defaultGroupInfo) { + originInfos.AppendElements(defaultGroupInfo->mOriginInfos); + } + originInfos.Sort(OriginInfoLRUComparator()); + + for (uint32_t i = 0; i < originInfos.Length(); i++) { + OriginInfo* originInfo = originInfos[i]; + + doomedOriginInfos.AppendElement(originInfo); + groupUsage -= originInfo->mUsage; + + if (groupUsage <= quotaManager->GetGroupLimit()) { + break; + } + } + } + } + } + + uint64_t usage = 0; + for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { + usage += doomedOriginInfos[index]->mUsage; + } + + if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) { + nsTArray<OriginInfo*> originInfos; + + for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) { + GroupInfoPair* pair = iter.UserData(); + + MOZ_ASSERT(!iter.Key().IsEmpty(), "Empty key!"); + MOZ_ASSERT(pair, "Null pointer!"); + + RefPtr<GroupInfo> groupInfo = + pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY); + if (groupInfo) { + originInfos.AppendElements(groupInfo->mOriginInfos); + } + + groupInfo = pair->LockedGetGroupInfo(PERSISTENCE_TYPE_DEFAULT); + if (groupInfo) { + originInfos.AppendElements(groupInfo->mOriginInfos); + } + } + + for (uint32_t index = originInfos.Length(); index > 0; index--) { + if (doomedOriginInfos.Contains(originInfos[index - 1])) { + originInfos.RemoveElementAt(index - 1); + } + } + + originInfos.Sort(OriginInfoLRUComparator()); + + for (uint32_t i = 0; i < originInfos.Length(); i++) { + if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) { + originInfos.TruncateLength(i); + break; + } + + usage += originInfos[i]->mUsage; + } + + doomedOriginInfos.AppendElements(originInfos); + } + } + + for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { + OriginInfo* doomedOriginInfo = doomedOriginInfos[index]; + + DeleteFilesForOrigin(doomedOriginInfo->mGroupInfo->mPersistenceType, + doomedOriginInfo->mOrigin); + } + + nsTArray<OriginParams> doomedOrigins; + { + MutexAutoLock lock(mQuotaMutex); + + for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) { + OriginInfo* doomedOriginInfo = doomedOriginInfos[index]; + + PersistenceType persistenceType = + doomedOriginInfo->mGroupInfo->mPersistenceType; + nsCString group = doomedOriginInfo->mGroupInfo->mGroup; + nsCString origin = doomedOriginInfo->mOrigin; + bool isApp = doomedOriginInfo->mIsApp; + LockedRemoveQuotaForOrigin(persistenceType, group, origin); + +#ifdef DEBUG + doomedOriginInfos[index] = nullptr; +#endif + + doomedOrigins.AppendElement(OriginParams(persistenceType, origin, isApp)); + } + } + + for (const OriginParams& doomedOrigin : doomedOrigins) { + OriginClearCompleted(doomedOrigin.mPersistenceType, + doomedOrigin.mOrigin, + doomedOrigin.mIsApp); + } +} + +void +QuotaManager::DeleteFilesForOrigin(PersistenceType aPersistenceType, + const nsACString& aOrigin) +{ + nsCOMPtr<nsIFile> directory; + nsresult rv = GetDirectoryForOrigin(aPersistenceType, aOrigin, + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = directory->Remove(true); + if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && + rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { + // This should never fail if we've closed all storage connections + // correctly... + NS_ERROR("Failed to remove directory!"); + } +} + +void +QuotaManager::FinalizeOriginEviction( + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + RefPtr<FinalizeOriginEvictionOp> op = + new FinalizeOriginEvictionOp(mOwningThread, aLocks); + + if (IsOnIOThread()) { + op->RunOnIOThreadImmediately(); + } else { + op->Dispatch(); + } +} + +void +QuotaManager::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) +{ + AssertIsOnBackgroundThread(); + + auto quotaManager = static_cast<QuotaManager*>(aClosure); + MOZ_ASSERT(quotaManager); + + NS_WARNING("Some storage operations are taking longer than expected " + "during shutdown and will be aborted!"); + + // Abort all operations. + for (RefPtr<Client>& client : quotaManager->mClients) { + client->AbortOperations(NullCString()); + } +} + +auto +QuotaManager::GetDirectoryLockTable(PersistenceType aPersistenceType) + -> DirectoryLockTable& +{ + switch (aPersistenceType) { + case PERSISTENCE_TYPE_TEMPORARY: + return mTemporaryDirectoryLockTable; + case PERSISTENCE_TYPE_DEFAULT: + return mDefaultDirectoryLockTable; + + case PERSISTENCE_TYPE_PERSISTENT: + case PERSISTENCE_TYPE_INVALID: + default: + MOZ_CRASH("Bad persistence type value!"); + } +} + +/******************************************************************************* + * Local class implementations + ******************************************************************************/ + +void +OriginInfo::LockedDecreaseUsage(int64_t aSize) +{ + AssertCurrentThreadOwnsQuotaMutex(); + + AssertNoUnderflow(mUsage, aSize); + mUsage -= aSize; + + AssertNoUnderflow(mGroupInfo->mUsage, aSize); + mGroupInfo->mUsage -= aSize; + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, aSize); + quotaManager->mTemporaryStorageUsage -= aSize; +} + +already_AddRefed<OriginInfo> +GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin) +{ + AssertCurrentThreadOwnsQuotaMutex(); + + for (RefPtr<OriginInfo>& originInfo : mOriginInfos) { + if (originInfo->mOrigin == aOrigin) { + RefPtr<OriginInfo> result = originInfo; + return result.forget(); + } + } + + return nullptr; +} + +void +GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo) +{ + AssertCurrentThreadOwnsQuotaMutex(); + + NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo), + "Replacing an existing entry!"); + mOriginInfos.AppendElement(aOriginInfo); + + AssertNoOverflow(mUsage, aOriginInfo->mUsage); + mUsage += aOriginInfo->mUsage; + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + AssertNoOverflow(quotaManager->mTemporaryStorageUsage, aOriginInfo->mUsage); + quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage; +} + +void +GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) +{ + AssertCurrentThreadOwnsQuotaMutex(); + + for (uint32_t index = 0; index < mOriginInfos.Length(); index++) { + if (mOriginInfos[index]->mOrigin == aOrigin) { + AssertNoUnderflow(mUsage, mOriginInfos[index]->mUsage); + mUsage -= mOriginInfos[index]->mUsage; + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, + mOriginInfos[index]->mUsage); + quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage; + + mOriginInfos.RemoveElementAt(index); + + return; + } + } +} + +void +GroupInfo::LockedRemoveOriginInfos() +{ + AssertCurrentThreadOwnsQuotaMutex(); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + for (uint32_t index = mOriginInfos.Length(); index > 0; index--) { + OriginInfo* originInfo = mOriginInfos[index - 1]; + + AssertNoUnderflow(mUsage, originInfo->mUsage); + mUsage -= originInfo->mUsage; + + AssertNoUnderflow(quotaManager->mTemporaryStorageUsage, originInfo->mUsage); + quotaManager->mTemporaryStorageUsage -= originInfo->mUsage; + + mOriginInfos.RemoveElementAt(index - 1); + } +} + +RefPtr<GroupInfo>& +GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType) +{ + switch (aPersistenceType) { + case PERSISTENCE_TYPE_TEMPORARY: + return mTemporaryStorageGroupInfo; + case PERSISTENCE_TYPE_DEFAULT: + return mDefaultStorageGroupInfo; + + case PERSISTENCE_TYPE_PERSISTENT: + case PERSISTENCE_TYPE_INVALID: + default: + MOZ_CRASH("Bad persistence type value!"); + } +} + +CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex, + uint64_t aMinSizeToBeFreed) +: mMinSizeToBeFreed(aMinSizeToBeFreed), + mMutex(aMutex), + mCondVar(aMutex, "CollectOriginsHelper::mCondVar"), + mSizeToBeFreed(0), + mWaiting(true) +{ + MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); + mMutex.AssertCurrentThreadOwns(); +} + +int64_t +CollectOriginsHelper::BlockAndReturnOriginsForEviction( + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks) +{ + MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!"); + mMutex.AssertCurrentThreadOwns(); + + while (mWaiting) { + mCondVar.Wait(); + } + + mLocks.SwapElements(aLocks); + return mSizeToBeFreed; +} + +NS_IMETHODIMP +CollectOriginsHelper::Run() +{ + AssertIsOnBackgroundThread(); + + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + // We use extra stack vars here to avoid race detector warnings (the same + // memory accessed with and without the lock held). + nsTArray<RefPtr<DirectoryLockImpl>> locks; + uint64_t sizeToBeFreed = + quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, locks); + + MutexAutoLock lock(mMutex); + + NS_ASSERTION(mWaiting, "Huh?!"); + + mLocks.SwapElements(locks); + mSizeToBeFreed = sizeToBeFreed; + mWaiting = false; + mCondVar.Notify(); + + return NS_OK; +} + +/******************************************************************************* + * OriginOperationBase + ******************************************************************************/ + +NS_IMETHODIMP +OriginOperationBase::Run() +{ + nsresult rv; + + switch (mState) { + case State_Initial: { + rv = Init(); + break; + } + + case State_Initializing: { + rv = InitOnMainThread(); + break; + } + + case State_FinishingInit: { + rv = FinishInit(); + break; + } + + case State_CreatingQuotaManager: { + rv = QuotaManagerOpen(); + break; + } + + case State_DirectoryOpenPending: { + rv = DirectoryOpen(); + break; + } + + case State_DirectoryWorkOpen: { + rv = DirectoryWork(); + break; + } + + case State_UnblockingOpen: { + UnblockOpen(); + return NS_OK; + } + + default: + MOZ_CRASH("Bad state!"); + } + + if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) { + Finish(rv); + } + + return NS_OK; +} + +nsresult +OriginOperationBase::DirectoryOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State_DirectoryOpenPending); + + QuotaManager* quotaManager = QuotaManager::Get(); + if (NS_WARN_IF(!quotaManager)) { + return NS_ERROR_FAILURE; + } + + // Must set this before dispatching otherwise we will race with the IO thread. + AdvanceState(); + + nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +OriginOperationBase::Finish(nsresult aResult) +{ + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = aResult; + } + + // Must set mState before dispatching otherwise we will race with the main + // thread. + mState = State_UnblockingOpen; + + MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +nsresult +OriginOperationBase::Init() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State_Initial); + + AdvanceState(); + + if (mNeedsMainThreadInit) { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + } else { + AdvanceState(); + MOZ_ALWAYS_SUCCEEDS(Run()); + } + + return NS_OK; +} + +nsresult +OriginOperationBase::InitOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mState == State_Initializing); + + nsresult rv = DoInitOnMainThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + AdvanceState(); + + MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); + + return NS_OK; +} + +nsresult +OriginOperationBase::FinishInit() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State_FinishingInit); + + if (QuotaManager::IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + AdvanceState(); + + if (mNeedsQuotaManagerInit && !QuotaManager::Get()) { + QuotaManager::GetOrCreate(this); + } else { + Open(); + } + + return NS_OK; +} + +nsresult +OriginOperationBase::QuotaManagerOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mState == State_CreatingQuotaManager); + + if (NS_WARN_IF(!QuotaManager::Get())) { + return NS_ERROR_FAILURE; + } + + Open(); + + return NS_OK; +} + +nsresult +OriginOperationBase::DirectoryWork() +{ + AssertIsOnIOThread(); + MOZ_ASSERT(mState == State_DirectoryWorkOpen); + + QuotaManager* quotaManager = QuotaManager::Get(); + if (NS_WARN_IF(!quotaManager)) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + + if (mNeedsQuotaManagerInit) { + rv = quotaManager->EnsureStorageIsInitialized(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = DoDirectoryWork(quotaManager); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Must set mState before dispatching otherwise we will race with the owning + // thread. + AdvanceState(); + + MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); + + return NS_OK; +} + +void +FinalizeOriginEvictionOp::Dispatch() +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(GetState() == State_Initial); + + SetState(State_DirectoryOpenPending); + + MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)); +} + +void +FinalizeOriginEvictionOp::RunOnIOThreadImmediately() +{ + AssertIsOnIOThread(); + MOZ_ASSERT(GetState() == State_Initial); + + SetState(State_DirectoryWorkOpen); + + MOZ_ALWAYS_SUCCEEDS(this->Run()); +} + +void +FinalizeOriginEvictionOp::Open() +{ + MOZ_CRASH("Shouldn't get here!"); +} + +nsresult +FinalizeOriginEvictionOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + + PROFILER_LABEL("Quota", "FinalizeOriginEvictionOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + for (RefPtr<DirectoryLockImpl>& lock : mLocks) { + aQuotaManager->OriginClearCompleted(lock->GetPersistenceType().Value(), + lock->GetOriginScope().GetOrigin(), + lock->GetIsApp().Value()); + } + + return NS_OK; +} + +void +FinalizeOriginEvictionOp::UnblockOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(GetState() == State_UnblockingOpen); + +#ifdef DEBUG + NoteActorDestroyed(); +#endif + + mLocks.Clear(); + + AdvanceState(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(NormalOriginOperationBase, Runnable) + +void +NormalOriginOperationBase::Open() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(GetState() == State_CreatingQuotaManager); + MOZ_ASSERT(QuotaManager::Get()); + + AdvanceState(); + + QuotaManager::Get()->OpenDirectoryInternal(mPersistenceType, + mOriginScope, + Nullable<Client::Type>(), + mExclusive, + this); +} + +void +NormalOriginOperationBase::UnblockOpen() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(GetState() == State_UnblockingOpen); + + SendResults(); + + mDirectoryLock = nullptr; + + AdvanceState(); +} + +void +NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aLock); + MOZ_ASSERT(GetState() == State_DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + mDirectoryLock = aLock; + + nsresult rv = DirectoryOpen(); + if (NS_WARN_IF(NS_FAILED(rv))) { + Finish(rv); + return; + } +} + +void +NormalOriginOperationBase::DirectoryLockFailed() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(GetState() == State_DirectoryOpenPending); + MOZ_ASSERT(!mDirectoryLock); + + Finish(NS_ERROR_FAILURE); +} + +nsresult +SaveOriginAccessTimeOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(!mPersistenceType.IsNull()); + MOZ_ASSERT(mOriginScope.IsOrigin()); + + PROFILER_LABEL("Quota", "SaveOriginAccessTimeOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + nsCOMPtr<nsIFile> directory; + nsresult rv = + aQuotaManager->GetDirectoryForOrigin(mPersistenceType.Value(), + mOriginScope.GetOrigin(), + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIBinaryOutputStream> stream; + rv = GetBinaryOutputStream(directory, + NS_LITERAL_STRING(METADATA_V2_FILE_NAME), + kUpdateFileFlag, + getter_AddRefs(stream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // The origin directory may not exist anymore. + if (stream) { + rv = stream->Write64(mTimestamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +void +SaveOriginAccessTimeOp::SendResults() +{ +#ifdef DEBUG + NoteActorDestroyed(); +#endif +} + +/******************************************************************************* + * Quota + ******************************************************************************/ + +Quota::Quota() +#ifdef DEBUG + : mActorDestroyed(false) +#endif +{ +} + +Quota::~Quota() +{ + MOZ_ASSERT(mActorDestroyed); +} + +void +Quota::StartIdleMaintenance() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!QuotaManager::IsShuttingDown()); + + QuotaManager* quotaManager = QuotaManager::Get(); + if (NS_WARN_IF(!quotaManager)) { + return; + } + + quotaManager->StartIdleMaintenance(); +} + +void +Quota::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnBackgroundThread(); +#ifdef DEBUG + MOZ_ASSERT(!mActorDestroyed); + mActorDestroyed = true; +#endif +} + +PQuotaUsageRequestParent* +Quota::AllocPQuotaUsageRequestParent(const UsageRequestParams& aParams) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None); + + RefPtr<QuotaUsageRequestBase> actor; + + switch (aParams.type()) { + case UsageRequestParams::TAllUsageParams: + actor = new GetUsageOp(aParams); + break; + + case UsageRequestParams::TOriginUsageParams: + actor = new GetOriginUsageOp(aParams); + break; + + default: + MOZ_CRASH("Should never get here!"); + } + + MOZ_ASSERT(actor); + + // Transfer ownership to IPDL. + return actor.forget().take(); +} + +bool +Quota::RecvPQuotaUsageRequestConstructor(PQuotaUsageRequestParent* aActor, + const UsageRequestParams& aParams) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None); + + auto* op = static_cast<QuotaUsageRequestBase*>(aActor); + + if (NS_WARN_IF(!op->Init(this))) { + return false; + } + + op->RunImmediately(); + return true; +} + +bool +Quota::DeallocPQuotaUsageRequestParent(PQuotaUsageRequestParent* aActor) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + // Transfer ownership back from IPDL. + RefPtr<QuotaUsageRequestBase> actor = + dont_AddRef(static_cast<QuotaUsageRequestBase*>(aActor)); + return true; +} + +PQuotaRequestParent* +Quota::AllocPQuotaRequestParent(const RequestParams& aParams) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aParams.type() != RequestParams::T__None); + + if (aParams.type() == RequestParams::TClearOriginsParams) { + PBackgroundParent* actor = Manager(); + MOZ_ASSERT(actor); + + if (BackgroundParent::IsOtherProcessActor(actor)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + } + + RefPtr<QuotaRequestBase> actor; + + switch (aParams.type()) { + case RequestParams::TClearOriginParams: + case RequestParams::TClearOriginsParams: + actor = new OriginClearOp(aParams); + break; + + case RequestParams::TClearAllParams: + actor = new ResetOrClearOp(/* aClear */ true); + break; + + case RequestParams::TResetAllParams: + actor = new ResetOrClearOp(/* aClear */ false); + break; + + default: + MOZ_CRASH("Should never get here!"); + } + + MOZ_ASSERT(actor); + + // Transfer ownership to IPDL. + return actor.forget().take(); +} + +bool +Quota::RecvPQuotaRequestConstructor(PQuotaRequestParent* aActor, + const RequestParams& aParams) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(aParams.type() != RequestParams::T__None); + + auto* op = static_cast<QuotaRequestBase*>(aActor); + + if (NS_WARN_IF(!op->Init(this))) { + return false; + } + + op->RunImmediately(); + return true; +} + +bool +Quota::DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + // Transfer ownership back from IPDL. + RefPtr<QuotaRequestBase> actor = + dont_AddRef(static_cast<QuotaRequestBase*>(aActor)); + return true; +} + +bool +Quota::RecvStartIdleMaintenance() +{ + AssertIsOnBackgroundThread(); + + PBackgroundParent* actor = Manager(); + MOZ_ASSERT(actor); + + if (BackgroundParent::IsOtherProcessActor(actor)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (QuotaManager::IsShuttingDown()) { + return true; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + nsCOMPtr<nsIRunnable> callback = + NewRunnableMethod(this, &Quota::StartIdleMaintenance); + + QuotaManager::GetOrCreate(callback); + return true; + } + + quotaManager->StartIdleMaintenance(); + + return true; +} + +bool +Quota::RecvStopIdleMaintenance() +{ + AssertIsOnBackgroundThread(); + + PBackgroundParent* actor = Manager(); + MOZ_ASSERT(actor); + + if (BackgroundParent::IsOtherProcessActor(actor)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (QuotaManager::IsShuttingDown()) { + return true; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + return true; + } + + quotaManager->StopIdleMaintenance(); + + return true; +} + +bool +QuotaUsageRequestBase::Init(Quota* aQuota) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuota); + + mNeedsQuotaManagerInit = true; + + return true; +} + +nsresult +QuotaUsageRequestBase::GetUsageForOrigin(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + UsageInfo* aUsageInfo) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aQuotaManager); + MOZ_ASSERT(aUsageInfo); + MOZ_ASSERT(aUsageInfo->TotalUsage() == 0); + + nsCOMPtr<nsIFile> directory; + nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, + aOrigin, + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + // If the directory exists then enumerate all the files inside, adding up + // the sizes to get the final usage statistic. + if (exists && !mCanceled) { + bool initialized; + + if (IsTreatedAsPersistent(aPersistenceType, aIsApp)) { + nsCString originKey = OriginKey(aPersistenceType, aOrigin); + initialized = aQuotaManager->IsOriginInitialized(originKey); + } else { + initialized = aQuotaManager->IsTemporaryStorageInitialized(); + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !mCanceled) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + if (leafName.EqualsLiteral(METADATA_FILE_NAME) || + leafName.EqualsLiteral(METADATA_V2_FILE_NAME) || + leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + continue; + } + + if (!initialized) { + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isDirectory) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + } + + if (MaybeRemoveCorruptDirectory(leafName, file)) { + continue; + } + + Client::Type clientType; + rv = Client::TypeFromText(leafName, clientType); + if (NS_FAILED(rv)) { + NS_WARNING("Unknown directory found!"); + if (!initialized) { + return NS_ERROR_UNEXPECTED; + } + continue; + } + + Client* client = aQuotaManager->GetClient(clientType); + MOZ_ASSERT(client); + + if (initialized) { + rv = client->GetUsageForOrigin(aPersistenceType, + aGroup, + aOrigin, + mCanceled, + aUsageInfo); + } + else { + rv = client->InitOrigin(aPersistenceType, + aGroup, + aOrigin, + mCanceled, + aUsageInfo); + } + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +void +QuotaUsageRequestBase::SendResults() +{ + AssertIsOnOwningThread(); + + if (IsActorDestroyed()) { + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = NS_ERROR_FAILURE; + } + } else { + if (mCanceled) { + mResultCode = NS_ERROR_FAILURE; + } + + UsageRequestResponse response; + + if (NS_SUCCEEDED(mResultCode)) { + GetResponse(response); + } else { + response = mResultCode; + } + + Unused << PQuotaUsageRequestParent::Send__delete__(this, response); + } +} + +void +QuotaUsageRequestBase::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + NoteActorDestroyed(); +} + +bool +QuotaUsageRequestBase::RecvCancel() +{ + AssertIsOnOwningThread(); + + if (mCanceled.exchange(true)) { + NS_WARNING("Canceled more than once?!"); + return false; + } + + return true; +} + +GetUsageOp::GetUsageOp(const UsageRequestParams& aParams) + : mGetAll(aParams.get_AllUsageParams().getAll()) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aParams.type() == UsageRequestParams::TAllUsageParams); +} + +nsresult +GetUsageOp::TraverseRepository(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aQuotaManager); + + nsresult rv; + + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = directory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !mCanceled) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry); + MOZ_ASSERT(originDir); + + bool isDirectory; + rv = originDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + nsString leafName; + rv = originDir->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + QM_WARNING("Something (%s) in the repository that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()); + } + continue; + } + + int64_t timestamp; + nsCString suffix; + nsCString group; + nsCString origin; + bool isApp; + rv = aQuotaManager->GetDirectoryMetadata2WithRestore(originDir, + persistent, + ×tamp, + suffix, + group, + origin, + &isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mGetAll && aQuotaManager->IsOriginInternal(origin)) { + continue; + } + + OriginUsage* originUsage; + + // We can't store pointers to OriginUsage objects in the hashtable + // since AppendElement() reallocates its internal array buffer as number + // of elements grows. + uint32_t index; + if (mOriginUsagesIndex.Get(origin, &index)) { + originUsage = &mOriginUsages[index]; + } else { + index = mOriginUsages.Length(); + + originUsage = mOriginUsages.AppendElement(); + + originUsage->origin() = origin; + originUsage->persisted() = false; + originUsage->usage() = 0; + + mOriginUsagesIndex.Put(origin, index); + } + + UsageInfo usageInfo; + rv = GetUsageForOrigin(aQuotaManager, + aPersistenceType, + group, + origin, + isApp, + &usageInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + originUsage->usage() = originUsage->usage() + usageInfo.TotalUsage(); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +GetUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + + PROFILER_LABEL("Quota", "GetUsageOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + nsresult rv; + + for (const PersistenceType type : kAllPersistenceTypes) { + rv = TraverseRepository(aQuotaManager, type); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +void +GetUsageOp::GetResponse(UsageRequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + aResponse = AllUsageResponse(); + + if (!mOriginUsages.IsEmpty()) { + nsTArray<OriginUsage>& originUsages = + aResponse.get_AllUsageResponse().originUsages(); + + mOriginUsages.SwapElements(originUsages); + } +} + +GetOriginUsageOp::GetOriginUsageOp(const UsageRequestParams& aParams) + : mParams(aParams.get_OriginUsageParams()) + , mGetGroupUsage(aParams.get_OriginUsageParams().getGroupUsage()) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aParams.type() == UsageRequestParams::TOriginUsageParams); +} + +bool +GetOriginUsageOp::Init(Quota* aQuota) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuota); + + if (NS_WARN_IF(!QuotaUsageRequestBase::Init(aQuota))) { + return false; + } + + mNeedsMainThreadInit = true; + + return true; +} + +nsresult +GetOriginUsageOp::DoInitOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(GetState() == State_Initializing); + MOZ_ASSERT(mNeedsMainThreadInit); + + const PrincipalInfo& principalInfo = mParams.principalInfo(); + + nsresult rv; + nsCOMPtr<nsIPrincipal> principal = + PrincipalInfoToPrincipal(principalInfo, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Figure out which origin we're dealing with. + nsCString origin; + rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, + &origin, &mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mOriginScope.SetFromOrigin(origin); + + return NS_OK; +} + +nsresult +GetOriginUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(mUsageInfo.TotalUsage() == 0); + + PROFILER_LABEL("Quota", "GetOriginUsageOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + nsresult rv; + + if (mGetGroupUsage) { + nsCOMPtr<nsIFile> directory; + + // Ensure origin is initialized first. It will initialize all origins for + // temporary storage including origins belonging to our group. + rv = aQuotaManager->EnsureOriginIsInitialized(PERSISTENCE_TYPE_TEMPORARY, + mSuffix, mGroup, + mOriginScope.GetOrigin(), + mIsApp, + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Get cached usage and limit (the method doesn't have to stat any files). + aQuotaManager->GetGroupUsageAndLimit(mGroup, &mUsageInfo); + + return NS_OK; + } + + // Add all the persistent/temporary/default storage files we care about. + for (const PersistenceType type : kAllPersistenceTypes) { + UsageInfo usageInfo; + rv = GetUsageForOrigin(aQuotaManager, + type, + mGroup, + mOriginScope.GetOrigin(), + mIsApp, + &usageInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mUsageInfo.Append(usageInfo); + } + + return NS_OK; +} + +void +GetOriginUsageOp::GetResponse(UsageRequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + OriginUsageResponse usageResponse; + + // We'll get the group usage when mGetGroupUsage is true and get the + // origin usage when mGetGroupUsage is false. + usageResponse.usage() = mUsageInfo.TotalUsage(); + + if (mGetGroupUsage) { + usageResponse.limit() = mUsageInfo.Limit(); + } else { + usageResponse.fileUsage() = mUsageInfo.FileUsage(); + } + + aResponse = usageResponse; +} + +bool +QuotaRequestBase::Init(Quota* aQuota) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuota); + + mNeedsQuotaManagerInit = true; + + return true; +} + +void +QuotaRequestBase::SendResults() +{ + AssertIsOnOwningThread(); + + if (IsActorDestroyed()) { + if (NS_SUCCEEDED(mResultCode)) { + mResultCode = NS_ERROR_FAILURE; + } + } else { + RequestResponse response; + + if (NS_SUCCEEDED(mResultCode)) { + GetResponse(response); + } else { + response = mResultCode; + } + + Unused << PQuotaRequestParent::Send__delete__(this, response); + } +} + +void +QuotaRequestBase::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + NoteActorDestroyed(); +} + +void +ResetOrClearOp::DeleteFiles(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aQuotaManager); + + nsresult rv; + + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = directory->InitWithPath(aQuotaManager->GetStoragePath()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = directory->Remove(true); + if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && + rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { + // This should never fail if we've closed all storage connections + // correctly... + MOZ_ASSERT(false, "Failed to remove storage directory!"); + } + + nsCOMPtr<nsIFile> storageFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = storageFile->InitWithPath(aQuotaManager->GetBasePath()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = storageFile->Append(NS_LITERAL_STRING(STORAGE_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = storageFile->Remove(true); + if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && + rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) { + // This should never fail if we've closed the storage connection + // correctly... + MOZ_ASSERT(false, "Failed to remove storage file!"); + } +} + +nsresult +ResetOrClearOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + + PROFILER_LABEL("Quota", "ResetOrClearOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + if (mClear) { + DeleteFiles(aQuotaManager); + } + + aQuotaManager->RemoveQuota(); + + aQuotaManager->ResetOrClearCompleted(); + + return NS_OK; +} + +void +ResetOrClearOp::GetResponse(RequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + if (mClear) { + aResponse = ClearAllResponse(); + } else { + aResponse = ResetAllResponse(); + } +} + +OriginClearOp::OriginClearOp(const RequestParams& aParams) + : QuotaRequestBase(/* aExclusive */ true) + , mParams(aParams) + , mMultiple(aParams.type() == RequestParams::TClearOriginsParams) +{ + MOZ_ASSERT(aParams.type() == RequestParams::TClearOriginParams || + aParams.type() == RequestParams::TClearOriginsParams); +} + +bool +OriginClearOp::Init(Quota* aQuota) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aQuota); + + if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) { + return false; + } + + if (!mMultiple) { + const ClearOriginParams& params = mParams.get_ClearOriginParams(); + + if (params.persistenceTypeIsExplicit()) { + MOZ_ASSERT(params.persistenceType() != PERSISTENCE_TYPE_INVALID); + + mPersistenceType.SetValue(params.persistenceType()); + } + } + + mNeedsMainThreadInit = true; + + return true; +} + +nsresult +OriginClearOp::DoInitOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(GetState() == State_Initializing); + MOZ_ASSERT(mNeedsMainThreadInit); + + if (mMultiple) { + const ClearOriginsParams& params = mParams.get_ClearOriginsParams(); + + mOriginScope.SetFromJSONPattern(params.pattern()); + } else { + const ClearOriginParams& params = mParams.get_ClearOriginParams(); + + const PrincipalInfo& principalInfo = params.principalInfo(); + + nsresult rv; + nsCOMPtr<nsIPrincipal> principal = + PrincipalInfoToPrincipal(principalInfo, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Figure out which origin we're dealing with. + nsCString origin; + rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin, + nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (params.clearAll()) { + mOriginScope.SetFromPrefix(origin); + } else { + mOriginScope.SetFromOrigin(origin); + } + } + + return NS_OK; +} + +void +OriginClearOp::DeleteFiles(QuotaManager* aQuotaManager, + PersistenceType aPersistenceType) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aQuotaManager); + + nsresult rv; + + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + if (NS_WARN_IF(NS_FAILED( + directory->GetDirectoryEntries(getter_AddRefs(entries)))) || !entries) { + return; + } + + OriginScope originScope = mOriginScope.Clone(); + if (originScope.IsOrigin()) { + nsCString originSanitized(originScope.GetOrigin()); + SanitizeOriginString(originSanitized); + originScope.SetOrigin(originSanitized); + } else if (originScope.IsPrefix()) { + nsCString prefixSanitized(originScope.GetPrefix()); + SanitizeOriginString(prefixSanitized); + originScope.SetPrefix(prefixSanitized); + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + MOZ_ASSERT(file); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsString leafName; + rv = file->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (!isDirectory) { + if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + QM_WARNING("Something (%s) in the repository that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()); + } + continue; + } + + // Skip the origin directory if it doesn't match the pattern. + if (!originScope.MatchesOrigin(OriginScope::FromOrigin( + NS_ConvertUTF16toUTF8(leafName)))) { + continue; + } + + bool persistent = aPersistenceType == PERSISTENCE_TYPE_PERSISTENT; + + int64_t timestamp; + nsCString suffix; + nsCString group; + nsCString origin; + bool isApp; + rv = aQuotaManager->GetDirectoryMetadata2WithRestore(file, + persistent, + ×tamp, + suffix, + group, + origin, + &isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + for (uint32_t index = 0; index < 10; index++) { + // We can't guarantee that this will always succeed on Windows... + if (NS_SUCCEEDED((rv = file->Remove(true)))) { + break; + } + + NS_WARNING("Failed to remove directory, retrying after a short delay."); + + PR_Sleep(PR_MillisecondsToInterval(200)); + } + + if (NS_FAILED(rv)) { + NS_WARNING("Failed to remove directory, giving up!"); + } + + if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) { + aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin); + } + + aQuotaManager->OriginClearCompleted(aPersistenceType, origin, isApp); + } + +} + +nsresult +OriginClearOp::DoDirectoryWork(QuotaManager* aQuotaManager) +{ + AssertIsOnIOThread(); + + PROFILER_LABEL("Quota", "OriginClearOp::DoDirectoryWork", + js::ProfileEntry::Category::OTHER); + + if (mPersistenceType.IsNull()) { + for (const PersistenceType type : kAllPersistenceTypes) { + DeleteFiles(aQuotaManager, type); + } + } else { + DeleteFiles(aQuotaManager, mPersistenceType.Value()); + } + + return NS_OK; +} + +void +OriginClearOp::GetResponse(RequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + if (mMultiple) { + aResponse = ClearOriginsResponse(); + } else { + aResponse = ClearOriginResponse(); + } +} + +nsresult +StorageDirectoryHelper::AddOriginDirectory(nsIFile* aDirectory, + OriginProps** aOriginProps) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + + OriginProps* originProps; + + nsString leafName; + nsresult rv = aDirectory->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (leafName.EqualsLiteral(kChromeOrigin)) { + originProps = mOriginProps.AppendElement(); + originProps->mDirectory = aDirectory; + originProps->mSpec = kChromeOrigin; + originProps->mType = OriginProps::eChrome; + } else { + nsCString spec; + PrincipalOriginAttributes attrs; + bool result = OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName), + spec, &attrs); + if (NS_WARN_IF(!result)) { + return NS_ERROR_FAILURE; + } + + originProps = mOriginProps.AppendElement(); + originProps->mDirectory = aDirectory; + originProps->mSpec = spec; + originProps->mAttrs = attrs; + originProps->mType = OriginProps::eContent; + } + + if (aOriginProps) { + *aOriginProps = originProps; + } + + return NS_OK; +} + +nsresult +StorageDirectoryHelper::ProcessOriginDirectories() +{ + AssertIsOnIOThread(); + MOZ_ASSERT(!mOriginProps.IsEmpty()); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + + { + mozilla::MutexAutoLock autolock(mMutex); + while (mWaiting) { + mCondVar.Wait(); + } + } + + if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) { + return mMainThreadResultCode; + } + + // Verify that the bounce to the main thread didn't start the shutdown + // sequence. + if (NS_WARN_IF(QuotaManager::IsShuttingDown())) { + return NS_ERROR_FAILURE; + } + + nsresult rv = DoProcessOriginDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +StorageDirectoryHelper::RunOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mOriginProps.IsEmpty()); + + nsresult rv; + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (uint32_t count = mOriginProps.Length(), index = 0; + index < count; + index++) { + OriginProps& originProps = mOriginProps[index]; + + switch (originProps.mType) { + case OriginProps::eChrome: { + QuotaManager::GetInfoForChrome(&originProps.mSuffix, + &originProps.mGroup, + &originProps.mOrigin, + &originProps.mIsApp); + break; + } + + case OriginProps::eContent: { + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), originProps.mSpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateCodebasePrincipal(uri, originProps.mAttrs); + if (NS_WARN_IF(!principal)) { + return NS_ERROR_FAILURE; + } + + rv = QuotaManager::GetInfoFromPrincipal(principal, + &originProps.mSuffix, + &originProps.mGroup, + &originProps.mOrigin, + &originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + break; + } + + default: + MOZ_CRASH("Bad type!"); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +StorageDirectoryHelper::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv = RunOnMainThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mMainThreadResultCode = rv; + } + + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mWaiting); + + mWaiting = false; + mCondVar.Notify(); + + return NS_OK; +} + +// static +bool +OriginParser::ParseOrigin(const nsACString& aOrigin, + nsCString& aSpec, + PrincipalOriginAttributes* aAttrs) +{ + MOZ_ASSERT(!aOrigin.IsEmpty()); + MOZ_ASSERT(aAttrs); + + PrincipalOriginAttributes originAttributes; + + nsCString originNoSuffix; + bool ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix); + if (!ok) { + return false; + } + + OriginParser parser(originNoSuffix, originAttributes); + return parser.Parse(aSpec, aAttrs); +} + +bool +OriginParser::Parse(nsACString& aSpec, PrincipalOriginAttributes* aAttrs) +{ + MOZ_ASSERT(aAttrs); + + while (mTokenizer.hasMoreTokens()) { + const nsDependentCSubstring& token = mTokenizer.nextToken(); + + HandleToken(token); + + if (mError) { + break; + } + + if (!mHandledTokens.IsEmpty()) { + mHandledTokens.Append(NS_LITERAL_CSTRING(", ")); + } + mHandledTokens.Append('\''); + mHandledTokens.Append(token); + mHandledTokens.Append('\''); + } + + if (!mError && mTokenizer.separatorAfterCurrentToken()) { + HandleTrailingSeparator(); + } + + if (mError) { + QM_WARNING("Origin '%s' failed to parse, handled tokens: %s", mOrigin.get(), + mHandledTokens.get()); + + return false; + } + + MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator); + + if (mAppId == kNoAppId) { + *aAttrs = mOriginAttributes; + } else { + MOZ_ASSERT(mOriginAttributes.mAppId == kNoAppId); + + *aAttrs = PrincipalOriginAttributes(mAppId, mInIsolatedMozBrowser); + } + + nsAutoCString spec(mSchema); + + if (mSchemaType == eFile) { + spec.AppendLiteral("://"); + + for (uint32_t count = mPathnameComponents.Length(), index = 0; + index < count; + index++) { + spec.Append('/'); + spec.Append(mPathnameComponents[index]); + } + + aSpec = spec; + + return true; + } + + if (mSchemaType == eAbout) { + spec.Append(':'); + } else { + spec.AppendLiteral("://"); + } + + spec.Append(mHost); + + if (!mPort.IsNull()) { + spec.Append(':'); + spec.AppendInt(mPort.Value()); + } + + aSpec = spec; + + return true; +} + +void +OriginParser::HandleSchema(const nsDependentCSubstring& aToken) +{ + MOZ_ASSERT(!aToken.IsEmpty()); + MOZ_ASSERT(mState == eExpectingAppIdOrSchema || mState == eExpectingSchema); + + bool isAbout = false; + bool isFile = false; + if (aToken.EqualsLiteral("http") || + aToken.EqualsLiteral("https") || + (isAbout = aToken.EqualsLiteral("about") || + aToken.EqualsLiteral("moz-safe-about")) || + aToken.EqualsLiteral("indexeddb") || + (isFile = aToken.EqualsLiteral("file")) || + aToken.EqualsLiteral("app") || + aToken.EqualsLiteral("resource")) { + mSchema = aToken; + + if (isAbout) { + mSchemaType = eAbout; + mState = eExpectingHost; + } else { + if (isFile) { + mSchemaType = eFile; + } + mState = eExpectingEmptyToken1; + } + + return; + } + + QM_WARNING("'%s' is not a valid schema!", nsCString(aToken).get()); + + mError = true; +} + +void +OriginParser::HandlePathnameComponent(const nsDependentCSubstring& aToken) +{ + MOZ_ASSERT(!aToken.IsEmpty()); + MOZ_ASSERT(mState == eExpectingEmptyTokenOrDriveLetterOrPathnameComponent || + mState == eExpectingEmptyTokenOrPathnameComponent); + MOZ_ASSERT(mSchemaType == eFile); + + mPathnameComponents.AppendElement(aToken); + + mState = mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent + : eComplete; +} + +void +OriginParser::HandleToken(const nsDependentCSubstring& aToken) +{ + switch (mState) { + case eExpectingAppIdOrSchema: { + if (aToken.IsEmpty()) { + QM_WARNING("Expected an app id or schema (not an empty string)!"); + + mError = true; + return; + } + + if (NS_IsAsciiDigit(aToken.First())) { + // nsDependentCSubstring doesn't provice ToInteger() + nsCString token(aToken); + + nsresult rv; + uint32_t appId = token.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) { + mAppId = appId; + mState = eExpectingInMozBrowser; + return; + } + } + + HandleSchema(aToken); + + return; + } + + case eExpectingInMozBrowser: { + if (aToken.Length() != 1) { + QM_WARNING("'%d' is not a valid length for the inMozBrowser flag!", + aToken.Length()); + + mError = true; + return; + } + + if (aToken.First() == 't') { + mInIsolatedMozBrowser = true; + } else if (aToken.First() == 'f') { + mInIsolatedMozBrowser = false; + } else { + QM_WARNING("'%s' is not a valid value for the inMozBrowser flag!", + nsCString(aToken).get()); + + mError = true; + return; + } + + mState = eExpectingSchema; + + return; + } + + case eExpectingSchema: { + if (aToken.IsEmpty()) { + QM_WARNING("Expected a schema (not an empty string)!"); + + mError = true; + return; + } + + HandleSchema(aToken); + + return; + } + + case eExpectingEmptyToken1: { + if (!aToken.IsEmpty()) { + QM_WARNING("Expected the first empty token!"); + + mError = true; + return; + } + + mState = eExpectingEmptyToken2; + + return; + } + + case eExpectingEmptyToken2: { + if (!aToken.IsEmpty()) { + QM_WARNING("Expected the second empty token!"); + + mError = true; + return; + } + + if (mSchemaType == eFile) { + mState = eExpectingEmptyToken3; + } else { + mState = eExpectingHost; + } + + return; + } + + case eExpectingEmptyToken3: { + MOZ_ASSERT(mSchemaType == eFile); + + if (!aToken.IsEmpty()) { + QM_WARNING("Expected the third empty token!"); + + mError = true; + return; + } + + mState = mTokenizer.hasMoreTokens() + ? eExpectingEmptyTokenOrDriveLetterOrPathnameComponent + : eComplete; + + return; + } + + case eExpectingHost: { + if (aToken.IsEmpty()) { + QM_WARNING("Expected a host (not an empty string)!"); + + mError = true; + return; + } + + mHost = aToken; + + mState = mTokenizer.hasMoreTokens() ? eExpectingPort : eComplete; + + return; + } + + case eExpectingPort: { + MOZ_ASSERT(mSchemaType == eNone); + + if (aToken.IsEmpty()) { + QM_WARNING("Expected a port (not an empty string)!"); + + mError = true; + return; + } + + // nsDependentCSubstring doesn't provice ToInteger() + nsCString token(aToken); + + nsresult rv; + uint32_t port = token.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) { + mPort.SetValue() = port; + } else { + QM_WARNING("'%s' is not a valid port number!", token.get()); + + mError = true; + return; + } + + mState = eComplete; + + return; + } + + case eExpectingEmptyTokenOrDriveLetterOrPathnameComponent: { + MOZ_ASSERT(mSchemaType == eFile); + + if (aToken.IsEmpty()) { + mPathnameComponents.AppendElement(EmptyCString()); + + mState = + mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent + : eComplete; + + return; + } + + if (aToken.Length() == 1 && NS_IsAsciiAlpha(aToken.First())) { + mMaybeDriveLetter = true; + + mPathnameComponents.AppendElement(aToken); + + mState = + mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent + : eComplete; + + return; + } + + HandlePathnameComponent(aToken); + + return; + } + + case eExpectingEmptyTokenOrPathnameComponent: { + MOZ_ASSERT(mSchemaType == eFile); + + if (aToken.IsEmpty()) { + if (mMaybeDriveLetter) { + MOZ_ASSERT(mPathnameComponents.Length() == 1); + + nsCString& pathnameComponent = mPathnameComponents[0]; + pathnameComponent.Append(':'); + + mMaybeDriveLetter = false; + } else { + mPathnameComponents.AppendElement(EmptyCString()); + } + + mState = + mTokenizer.hasMoreTokens() ? eExpectingEmptyTokenOrPathnameComponent + : eComplete; + + return; + } + + HandlePathnameComponent(aToken); + + return; + } + + default: + MOZ_CRASH("Should never get here!"); + } +} + +void +OriginParser::HandleTrailingSeparator() +{ + MOZ_ASSERT(mState == eComplete); + MOZ_ASSERT(mSchemaType == eFile); + + mPathnameComponents.AppendElement(EmptyCString()); + + mState = eHandledTrailingSeparator; +} + +nsresult +CreateOrUpgradeDirectoryMetadataHelper::CreateOrUpgradeMetadataFiles() +{ + AssertIsOnIOThread(); + + bool exists; + nsresult rv = mDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry); + MOZ_ASSERT(originDir); + + nsString leafName; + rv = originDir->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool isDirectory; + rv = originDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (isDirectory) { + if (leafName.EqualsLiteral("moz-safe-about+++home")) { + // This directory was accidentally created by a buggy nightly and can + // be safely removed. + + QM_WARNING("Deleting accidental moz-safe-about+++home directory!"); + + rv = originDir->Remove(/* aRecursive */ true); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + continue; + } + } else { + if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + QM_WARNING("Something (%s) in the storage directory that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()); + + } + continue; + } + + if (mPersistent) { + rv = MaybeUpgradeOriginDirectory(originDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + OriginProps* originProps; + rv = AddOriginDirectory(originDir, &originProps); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mPersistent) { + int64_t timestamp; + nsCString group; + nsCString origin; + bool hasIsApp; + rv = GetDirectoryMetadata(originDir, + ×tamp, + group, + origin, + &hasIsApp); + if (NS_FAILED(rv)) { + timestamp = INT64_MIN; + rv = GetLastModifiedTime(originDir, ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + originProps->mTimestamp = timestamp; + originProps->mNeedsRestore = true; + } else if (hasIsApp) { + originProps->mIgnore = true; + } + } + else if (!QuotaManager::IsOriginInternal(originProps->mSpec)) { + int64_t timestamp = INT64_MIN; + rv = GetLastModifiedTime(originDir, ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + originProps->mTimestamp = timestamp; + } + } + + if (mOriginProps.IsEmpty()) { + return NS_OK; + } + + rv = ProcessOriginDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +CreateOrUpgradeDirectoryMetadataHelper::MaybeUpgradeOriginDirectory( + nsIFile* aDirectory) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + + nsCOMPtr<nsIFile> metadataFile; + nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = metadataFile->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + // Directory structure upgrade needed. + // Move all files to IDB specific directory. + + nsString idbDirectoryName; + rv = Client::TypeToText(Client::IDB, idbDirectoryName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> idbDirectory; + rv = aDirectory->Clone(getter_AddRefs(idbDirectory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = idbDirectory->Append(idbDirectoryName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { + NS_WARNING("IDB directory already exists!"); + + bool isDirectory; + rv = idbDirectory->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!isDirectory)) { + return NS_ERROR_UNEXPECTED; + } + } + else { + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + if (NS_WARN_IF(!file)) { + return rv; + } + + nsString leafName; + rv = file->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!leafName.Equals(idbDirectoryName)) { + rv = file->MoveTo(idbDirectory, EmptyString()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +nsresult +CreateOrUpgradeDirectoryMetadataHelper::GetDirectoryMetadata( + nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aGroup, + nsACString& aOrigin, + bool* aHasIsApp) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(aHasIsApp); + MOZ_ASSERT(!mPersistent); + + nsCOMPtr<nsIBinaryInputStream> binaryStream; + nsresult rv = GetBinaryInputStream(aDirectory, + NS_LITERAL_STRING(METADATA_FILE_NAME), + getter_AddRefs(binaryStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint64_t timestamp; + rv = binaryStream->Read64(×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString group; + rv = binaryStream->ReadCString(group); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString origin; + rv = binaryStream->ReadCString(origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool dummyIsApp; + bool hasIsApp = NS_SUCCEEDED(binaryStream->ReadBoolean(&dummyIsApp)); + + *aTimestamp = timestamp; + aGroup = group; + aOrigin = origin; + *aHasIsApp = hasIsApp; + return NS_OK; +} + +nsresult +CreateOrUpgradeDirectoryMetadataHelper::DoProcessOriginDirectories() +{ + AssertIsOnIOThread(); + + nsresult rv; + + nsCOMPtr<nsIFile> permanentStorageDir; + + for (uint32_t count = mOriginProps.Length(), index = 0; + index < count; + index++) { + OriginProps& originProps = mOriginProps[index]; + + if (mPersistent) { + rv = CreateDirectoryMetadata(originProps.mDirectory, + originProps.mTimestamp, + originProps.mSuffix, + originProps.mGroup, + originProps.mOrigin, + originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Move internal origins to new persistent storage. + if (QuotaManager::IsOriginInternal(originProps.mSpec)) { + if (!permanentStorageDir) { + permanentStorageDir = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + const nsString& permanentStoragePath = + quotaManager->GetStoragePath(PERSISTENCE_TYPE_PERSISTENT); + + rv = permanentStorageDir->InitWithPath(permanentStoragePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsString leafName; + rv = originProps.mDirectory->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> newDirectory; + rv = permanentStorageDir->Clone(getter_AddRefs(newDirectory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = newDirectory->Append(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = newDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (exists) { + QM_WARNING("Found %s in storage/persistent and storage/permanent !", + NS_ConvertUTF16toUTF8(leafName).get()); + + rv = originProps.mDirectory->Remove(/* recursive */ true); + } else { + rv = originProps.mDirectory->MoveTo(permanentStorageDir, EmptyString()); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } else if (originProps.mNeedsRestore) { + rv = CreateDirectoryMetadata(originProps.mDirectory, + originProps.mTimestamp, + originProps.mSuffix, + originProps.mGroup, + originProps.mOrigin, + originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else if (!originProps.mIgnore) { + nsCOMPtr<nsIBinaryOutputStream> stream; + rv = GetBinaryOutputStream(originProps.mDirectory, + NS_LITERAL_STRING(METADATA_FILE_NAME), + kAppendFileFlag, + getter_AddRefs(stream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(stream); + + rv = stream->WriteBoolean(originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + return NS_OK; +} + +nsresult +UpgradeDirectoryMetadataFrom1To2Helper::UpgradeMetadataFiles() +{ + AssertIsOnIOThread(); + + bool exists; + nsresult rv = mDirectory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsISimpleEnumerator> entries; + rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry); + MOZ_ASSERT(originDir); + + bool isDirectory; + rv = originDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + nsString leafName; + rv = originDir->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { + QM_WARNING("Something (%s) in the storage directory that doesn't belong!", + NS_ConvertUTF16toUTF8(leafName).get()); + + } + continue; + } + + OriginProps* originProps; + rv = AddOriginDirectory(originDir, &originProps); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t timestamp; + nsCString group; + nsCString origin; + bool isApp; + nsresult rv = GetDirectoryMetadata(originDir, + ×tamp, + group, + origin, + &isApp); + if (NS_FAILED(rv)) { + if (!mPersistent) { + rv = GetLastModifiedTime(originDir, ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + originProps->mTimestamp = timestamp; + } + originProps->mNeedsRestore = true; + } else { + originProps->mTimestamp = timestamp; + } + } + + if (mOriginProps.IsEmpty()) { + return NS_OK; + } + + rv = ProcessOriginDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +UpgradeDirectoryMetadataFrom1To2Helper::GetDirectoryMetadata( + nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aDirectory); + MOZ_ASSERT(aTimestamp); + MOZ_ASSERT(aIsApp); + + nsCOMPtr<nsIBinaryInputStream> binaryStream; + nsresult rv = GetBinaryInputStream(aDirectory, + NS_LITERAL_STRING(METADATA_FILE_NAME), + getter_AddRefs(binaryStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + uint64_t timestamp; + rv = binaryStream->Read64(×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString group; + rv = binaryStream->ReadCString(group); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString origin; + rv = binaryStream->ReadCString(origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool isApp; + rv = binaryStream->ReadBoolean(&isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aTimestamp = timestamp; + aGroup = group; + aOrigin = origin; + *aIsApp = isApp; + return NS_OK; +} + +nsresult +UpgradeDirectoryMetadataFrom1To2Helper::DoProcessOriginDirectories() +{ + AssertIsOnIOThread(); + + for (uint32_t count = mOriginProps.Length(), index = 0; + index < count; + index++) { + OriginProps& originProps = mOriginProps[index]; + + nsresult rv; + + if (originProps.mNeedsRestore) { + rv = CreateDirectoryMetadata(originProps.mDirectory, + originProps.mTimestamp, + originProps.mSuffix, + originProps.mGroup, + originProps.mOrigin, + originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = CreateDirectoryMetadata2(originProps.mDirectory, + originProps.mTimestamp, + originProps.mSuffix, + originProps.mGroup, + originProps.mOrigin, + originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString oldName; + rv = originProps.mDirectory->GetLeafName(oldName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString originSanitized(originProps.mOrigin); + SanitizeOriginString(originSanitized); + + NS_ConvertASCIItoUTF16 newName(originSanitized); + + if (!oldName.Equals(newName)) { + rv = originProps.mDirectory->RenameTo(nullptr, newName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } + + return NS_OK; +} + +nsresult +RestoreDirectoryMetadata2Helper::RestoreMetadata2File() +{ + AssertIsOnIOThread(); + + nsresult rv; + + OriginProps* originProps; + rv = AddOriginDirectory(mDirectory, &originProps); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!mPersistent) { + int64_t timestamp = INT64_MIN; + rv = GetLastModifiedTime(mDirectory, ×tamp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + originProps->mTimestamp = timestamp; + } + + rv = ProcessOriginDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +RestoreDirectoryMetadata2Helper::DoProcessOriginDirectories() +{ + AssertIsOnIOThread(); + MOZ_ASSERT(mOriginProps.Length() == 1); + + OriginProps& originProps = mOriginProps[0]; + + nsresult rv = CreateDirectoryMetadata2(originProps.mDirectory, + originProps.mTimestamp, + originProps.mSuffix, + originProps.mGroup, + originProps.mOrigin, + originProps.mIsApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/ActorsParent.h b/dom/quota/ActorsParent.h new file mode 100644 index 000000000..06ed9d342 --- /dev/null +++ b/dom/quota/ActorsParent.h @@ -0,0 +1,26 @@ +/* -*- 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_quota_ActorsParent_h +#define mozilla_dom_quota_ActorsParent_h + +namespace mozilla { +namespace dom { +namespace quota { + +class PQuotaParent; + +PQuotaParent* +AllocPQuotaParent(); + +bool +DeallocPQuotaParent(PQuotaParent* aActor); + +} // namespace quota +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_quota_ActorsParent_h diff --git a/dom/quota/Client.h b/dom/quota/Client.h new file mode 100644 index 000000000..ecbddebdf --- /dev/null +++ b/dom/quota/Client.h @@ -0,0 +1,151 @@ +/* -*- 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_quota_client_h__ +#define mozilla_dom_quota_client_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "mozilla/dom/ipc/IdType.h" + +#include "PersistenceType.h" + +class nsIRunnable; + +#define IDB_DIRECTORY_NAME "idb" +#define ASMJSCACHE_DIRECTORY_NAME "asmjs" +#define DOMCACHE_DIRECTORY_NAME "cache" + +BEGIN_QUOTA_NAMESPACE + +class QuotaManager; +class UsageInfo; + +// An abstract interface for quota manager clients. +// Each storage API must provide an implementation of this interface in order +// to participate in centralized quota and storage handling. +class Client +{ +public: + typedef mozilla::Atomic<bool> AtomicBool; + + NS_IMETHOD_(MozExternalRefCountType) + AddRef() = 0; + + NS_IMETHOD_(MozExternalRefCountType) + Release() = 0; + + enum Type { + IDB = 0, + //LS, + //APPCACHE, + ASMJS, + DOMCACHE, + TYPE_MAX + }; + + virtual Type + GetType() = 0; + + static nsresult + TypeToText(Type aType, nsAString& aText) + { + switch (aType) { + case IDB: + aText.AssignLiteral(IDB_DIRECTORY_NAME); + break; + + case ASMJS: + aText.AssignLiteral(ASMJSCACHE_DIRECTORY_NAME); + break; + + case DOMCACHE: + aText.AssignLiteral(DOMCACHE_DIRECTORY_NAME); + break; + + case TYPE_MAX: + default: + NS_NOTREACHED("Bad id value!"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; + } + + static nsresult + TypeFromText(const nsAString& aText, Type& aType) + { + if (aText.EqualsLiteral(IDB_DIRECTORY_NAME)) { + aType = IDB; + } + else if (aText.EqualsLiteral(ASMJSCACHE_DIRECTORY_NAME)) { + aType = ASMJS; + } + else if (aText.EqualsLiteral(DOMCACHE_DIRECTORY_NAME)) { + aType = DOMCACHE; + } + else { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + // Methods which are called on the IO thred. + virtual nsresult + InitOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) = 0; + + virtual nsresult + GetUsageForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) = 0; + + virtual void + OnOriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin) = 0; + + virtual void + ReleaseIOThreadObjects() = 0; + + // Methods which are called on the background thred. + virtual void + AbortOperations(const nsACString& aOrigin) = 0; + + virtual void + AbortOperationsForProcess(ContentParentId aContentParentId) = 0; + + virtual void + StartIdleMaintenance() = 0; + + virtual void + StopIdleMaintenance() = 0; + + virtual void + ShutdownWorkThreads() = 0; + + // Methods which are called on the main thread. + virtual void + DidInitialize(QuotaManager* aQuotaManager) + { } + + virtual void + WillShutdown() + { } + +protected: + virtual ~Client() + { } +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_client_h__ diff --git a/dom/quota/FileStreams.cpp b/dom/quota/FileStreams.cpp new file mode 100644 index 000000000..785a7db2e --- /dev/null +++ b/dom/quota/FileStreams.cpp @@ -0,0 +1,135 @@ +/* -*- 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 "FileStreams.h" + +#include "QuotaManager.h" +#include "prio.h" + +USING_QUOTA_NAMESPACE + +template <class FileStreamBase> +NS_IMETHODIMP +FileQuotaStream<FileStreamBase>::SetEOF() +{ + nsresult rv = FileStreamBase::SetEOF(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mQuotaObject) { + int64_t offset; + nsresult rv = FileStreamBase::Tell(&offset); + NS_ENSURE_SUCCESS(rv, rv); + + mQuotaObject->MaybeUpdateSize(offset, /* aTruncate */ true); + } + + return NS_OK; +} + +template <class FileStreamBase> +NS_IMETHODIMP +FileQuotaStream<FileStreamBase>::Close() +{ + nsresult rv = FileStreamBase::Close(); + NS_ENSURE_SUCCESS(rv, rv); + + mQuotaObject = nullptr; + + return NS_OK; +} + +template <class FileStreamBase> +nsresult +FileQuotaStream<FileStreamBase>::DoOpen() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + NS_ASSERTION(quotaManager, "Shouldn't be null!"); + + NS_ASSERTION(!mQuotaObject, "Creating quota object more than once?"); + mQuotaObject = quotaManager->GetQuotaObject(mPersistenceType, mGroup, mOrigin, + FileStreamBase::mOpenParams.localFile); + + nsresult rv = FileStreamBase::DoOpen(); + NS_ENSURE_SUCCESS(rv, rv); + + if (mQuotaObject && (FileStreamBase::mOpenParams.ioFlags & PR_TRUNCATE)) { + mQuotaObject->MaybeUpdateSize(0, /* aTruncate */ true); + } + + return NS_OK; +} + +template <class FileStreamBase> +NS_IMETHODIMP +FileQuotaStreamWithWrite<FileStreamBase>::Write(const char* aBuf, + uint32_t aCount, + uint32_t* _retval) +{ + nsresult rv; + + if (FileQuotaStreamWithWrite::mQuotaObject) { + int64_t offset; + rv = FileStreamBase::Tell(&offset); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ASSERT(INT64_MAX - offset >= int64_t(aCount)); + + if (!FileQuotaStreamWithWrite:: + mQuotaObject->MaybeUpdateSize(offset + int64_t(aCount), + /* aTruncate */ false)) { + return NS_ERROR_FILE_NO_DEVICE_SPACE; + } + } + + rv = FileStreamBase::Write(aBuf, aCount, _retval); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileInputStream, nsFileInputStream) + +already_AddRefed<FileInputStream> +FileInputStream::Create(PersistenceType aPersistenceType, + const nsACString& aGroup, const nsACString& aOrigin, + nsIFile* aFile, int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) +{ + RefPtr<FileInputStream> stream = + new FileInputStream(aPersistenceType, aGroup, aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileOutputStream, nsFileOutputStream) + +already_AddRefed<FileOutputStream> +FileOutputStream::Create(PersistenceType aPersistenceType, + const nsACString& aGroup, const nsACString& aOrigin, + nsIFile* aFile, int32_t aIOFlags, int32_t aPerm, + int32_t aBehaviorFlags) +{ + RefPtr<FileOutputStream> stream = + new FileOutputStream(aPersistenceType, aGroup, aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED0(FileStream, nsFileStream) + +already_AddRefed<FileStream> +FileStream::Create(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags, + int32_t aPerm, int32_t aBehaviorFlags) +{ + RefPtr<FileStream> stream = + new FileStream(aPersistenceType, aGroup, aOrigin); + nsresult rv = stream->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + NS_ENSURE_SUCCESS(rv, nullptr); + return stream.forget(); +} diff --git a/dom/quota/FileStreams.h b/dom/quota/FileStreams.h new file mode 100644 index 000000000..67b09fa7d --- /dev/null +++ b/dom/quota/FileStreams.h @@ -0,0 +1,127 @@ +/* -*- 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_quota_filestreams_h__ +#define mozilla_dom_quota_filestreams_h__ + +#include "QuotaCommon.h" + +#include "nsFileStreams.h" + +#include "PersistenceType.h" +#include "QuotaObject.h" + +BEGIN_QUOTA_NAMESPACE + +template <class FileStreamBase> +class FileQuotaStream : public FileStreamBase +{ +public: + // nsFileStreamBase override + NS_IMETHOD + SetEOF() override; + + NS_IMETHOD + Close() override; + +protected: + FileQuotaStream(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin) + : mPersistenceType(aPersistenceType), mGroup(aGroup), mOrigin(aOrigin) + { } + + // nsFileStreamBase override + virtual nsresult + DoOpen() override; + + PersistenceType mPersistenceType; + nsCString mGroup; + nsCString mOrigin; + RefPtr<QuotaObject> mQuotaObject; +}; + +template <class FileStreamBase> +class FileQuotaStreamWithWrite : public FileQuotaStream<FileStreamBase> +{ +public: + // nsFileStreamBase override + NS_IMETHOD + Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) override; + +protected: + FileQuotaStreamWithWrite(PersistenceType aPersistenceType, + const nsACString& aGroup, const nsACString& aOrigin) + : FileQuotaStream<FileStreamBase>(aPersistenceType, aGroup, aOrigin) + { } +}; + +class FileInputStream : public FileQuotaStream<nsFileInputStream> +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed<FileInputStream> + Create(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileInputStream(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin) + : FileQuotaStream<nsFileInputStream>(aPersistenceType, aGroup, aOrigin) + { } + + virtual ~FileInputStream() { + Close(); + } +}; + +class FileOutputStream : public FileQuotaStreamWithWrite<nsFileOutputStream> +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed<FileOutputStream> + Create(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileOutputStream(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin) + : FileQuotaStreamWithWrite<nsFileOutputStream>(aPersistenceType, aGroup, + aOrigin) + { } + + virtual ~FileOutputStream() { + Close(); + } +}; + +class FileStream : public FileQuotaStreamWithWrite<nsFileStream> +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + static already_AddRefed<FileStream> + Create(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin, nsIFile* aFile, int32_t aIOFlags = -1, + int32_t aPerm = -1, int32_t aBehaviorFlags = 0); + +private: + FileStream(PersistenceType aPersistenceType, const nsACString& aGroup, + const nsACString& aOrigin) + : FileQuotaStreamWithWrite<nsFileStream>(aPersistenceType, aGroup, aOrigin) + { } + + virtual ~FileStream() { + Close(); + } +}; + +END_QUOTA_NAMESPACE + +#endif /* mozilla_dom_quota_filestreams_h__ */ diff --git a/dom/quota/OriginScope.h b/dom/quota/OriginScope.h new file mode 100644 index 000000000..e57f2dd76 --- /dev/null +++ b/dom/quota/OriginScope.h @@ -0,0 +1,428 @@ +/* -*- 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_quota_originorpatternstring_h__ +#define mozilla_dom_quota_originorpatternstring_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "mozilla/BasePrincipal.h" + +BEGIN_QUOTA_NAMESPACE + +class OriginScope +{ +public: + enum Type + { + eOrigin, + ePattern, + ePrefix, + eNull + }; + +private: + struct OriginAndAttributes + { + nsCString mOrigin; + PrincipalOriginAttributes mAttributes; + + OriginAndAttributes(const OriginAndAttributes& aOther) + : mOrigin(aOther.mOrigin) + , mAttributes(aOther.mAttributes) + { + MOZ_COUNT_CTOR(OriginAndAttributes); + } + + explicit OriginAndAttributes(const nsACString& aOrigin) + : mOrigin(aOrigin) + { + nsCString originNoSuffix; + MOZ_ALWAYS_TRUE(mAttributes.PopulateFromOrigin(aOrigin, originNoSuffix)); + + MOZ_COUNT_CTOR(OriginAndAttributes); + } + + ~OriginAndAttributes() + { + MOZ_COUNT_DTOR(OriginAndAttributes); + } + }; + + union { + // eOrigin + OriginAndAttributes* mOriginAndAttributes; + + // ePattern + mozilla::OriginAttributesPattern* mPattern; + + // ePrefix + nsCString* mPrefix; + + // eNull + void* mDummy; + }; + + Type mType; + +public: + static OriginScope + FromOrigin(const nsACString& aOrigin) + { + return OriginScope(aOrigin, true); + } + + static OriginScope + FromPattern(const mozilla::OriginAttributesPattern& aPattern) + { + return OriginScope(aPattern); + } + + static OriginScope + FromJSONPattern(const nsAString& aJSONPattern) + { + return OriginScope(aJSONPattern); + } + + static OriginScope + FromPrefix(const nsACString& aPrefix) + { + return OriginScope(aPrefix, false); + } + + static OriginScope + FromNull() + { + return OriginScope(); + } + + OriginScope(const OriginScope& aOther) + { + if (aOther.IsOrigin()) { + mOriginAndAttributes = + new OriginAndAttributes(*aOther.mOriginAndAttributes); + } else if (aOther.IsPattern()) { + mPattern = new mozilla::OriginAttributesPattern(*aOther.mPattern); + } else if (aOther.IsPrefix()) { + mPrefix = new nsCString(*aOther.mPrefix); + } else { + mDummy = aOther.mDummy; + } + + mType = aOther.mType; + } + + ~OriginScope() + { + Destroy(); + } + + bool + IsOrigin() const + { + return mType == eOrigin; + } + + bool + IsPattern() const + { + return mType == ePattern; + } + + bool + IsPrefix() const + { + return mType == ePrefix; + } + + bool + IsNull() const + { + return mType == eNull; + } + + Type + GetType() const + { + return mType; + } + + void + SetFromOrigin(const nsACString& aOrigin) + { + Destroy(); + + mOriginAndAttributes = new OriginAndAttributes(aOrigin); + + mType = eOrigin; + } + + void + SetFromPattern(const mozilla::OriginAttributesPattern& aPattern) + { + Destroy(); + + mPattern = new mozilla::OriginAttributesPattern(aPattern); + + mType = ePattern; + } + + void + SetFromJSONPattern(const nsAString& aJSONPattern) + { + Destroy(); + + mPattern = new mozilla::OriginAttributesPattern(); + MOZ_ALWAYS_TRUE(mPattern->Init(aJSONPattern)); + + mType = ePattern; + } + + void + SetFromPrefix(const nsACString& aPrefix) + { + Destroy(); + + mPrefix = new nsCString(aPrefix); + + mType = ePrefix; + } + + void + SetFromNull() + { + Destroy(); + + mDummy = nullptr; + + mType = eNull; + } + + const nsACString& + GetOrigin() const + { + MOZ_ASSERT(IsOrigin()); + MOZ_ASSERT(mOriginAndAttributes); + + return mOriginAndAttributes->mOrigin; + } + + void + SetOrigin(const nsACString& aOrigin) + { + MOZ_ASSERT(IsOrigin()); + MOZ_ASSERT(mOriginAndAttributes); + mOriginAndAttributes->mOrigin = aOrigin; + } + + const mozilla::OriginAttributes& + GetOriginAttributes() const + { + MOZ_ASSERT(IsOrigin()); + MOZ_ASSERT(mOriginAndAttributes); + return mOriginAndAttributes->mAttributes; + } + + const mozilla::OriginAttributesPattern& + GetPattern() const + { + MOZ_ASSERT(IsPattern()); + MOZ_ASSERT(mPattern); + return *mPattern; + } + + const nsACString& + GetPrefix() const + { + MOZ_ASSERT(IsPrefix()); + MOZ_ASSERT(mPrefix); + + return *mPrefix; + } + + void + SetPrefix(const nsACString& aPrefix) + { + MOZ_ASSERT(IsPrefix()); + MOZ_ASSERT(mPrefix); + + *mPrefix = aPrefix; + } + + bool MatchesOrigin(const OriginScope& aOther) const + { + MOZ_ASSERT(aOther.IsOrigin()); + MOZ_ASSERT(aOther.mOriginAndAttributes); + + bool match; + + if (IsOrigin()) { + MOZ_ASSERT(mOriginAndAttributes); + match = mOriginAndAttributes->mOrigin.Equals( + aOther.mOriginAndAttributes->mOrigin); + } else if (IsPattern()) { + MOZ_ASSERT(mPattern); + match = mPattern->Matches(aOther.mOriginAndAttributes->mAttributes); + } else if (IsPrefix()) { + MOZ_ASSERT(mPrefix); + match = StringBeginsWith(aOther.mOriginAndAttributes->mOrigin, *mPrefix); + } else { + match = true; + } + + return match; + } + + bool MatchesPattern(const OriginScope& aOther) const + { + MOZ_ASSERT(aOther.IsPattern()); + MOZ_ASSERT(aOther.mPattern); + + bool match; + + if (IsOrigin()) { + MOZ_ASSERT(mOriginAndAttributes); + match = aOther.mPattern->Matches(mOriginAndAttributes->mAttributes); + } else if (IsPattern()) { + MOZ_ASSERT(mPattern); + match = mPattern->Overlaps(*aOther.mPattern); + } else if (IsPrefix()) { + MOZ_ASSERT(mPrefix); + // The match will be always true here because any origin attributes + // pattern overlaps any origin prefix (an origin prefix targets all + // origin attributes). + match = true; + } else { + match = true; + } + + return match; + } + + bool MatchesPrefix(const OriginScope& aOther) const + { + MOZ_ASSERT(aOther.IsPrefix()); + MOZ_ASSERT(aOther.mPrefix); + + bool match; + + if (IsOrigin()) { + MOZ_ASSERT(mOriginAndAttributes); + match = StringBeginsWith(mOriginAndAttributes->mOrigin, *aOther.mPrefix); + } else if (IsPattern()) { + MOZ_ASSERT(mPattern); + // The match will be always true here because any origin attributes + // pattern overlaps any origin prefix (an origin prefix targets all + // origin attributes). + match = true; + } else if (IsPrefix()) { + MOZ_ASSERT(mPrefix); + match = mPrefix->Equals(*aOther.mPrefix); + } else { + match = true; + } + + return match; + } + + bool Matches(const OriginScope& aOther) const + { + bool match; + + if (aOther.IsOrigin()) { + match = MatchesOrigin(aOther); + } else if (aOther.IsPattern()) { + match = MatchesPattern(aOther); + } else if (aOther.IsPrefix()) { + match = MatchesPrefix(aOther); + } else { + match = true; + } + + return match; + } + + OriginScope + Clone() + { + if (IsOrigin()) { + MOZ_ASSERT(mOriginAndAttributes); + return OriginScope(*mOriginAndAttributes); + } + + if (IsPattern()) { + MOZ_ASSERT(mPattern); + return OriginScope(*mPattern); + } + + if (IsPrefix()) { + MOZ_ASSERT(mPrefix); + return OriginScope(*mPrefix, false); + } + + MOZ_ASSERT(IsNull()); + return OriginScope(); + } + +private: + explicit OriginScope(const OriginAndAttributes& aOriginAndAttributes) + : mOriginAndAttributes(new OriginAndAttributes(aOriginAndAttributes)) + , mType(eOrigin) + { } + + explicit OriginScope(const nsACString& aOriginOrPrefix, bool aOrigin) + { + if (aOrigin) { + mOriginAndAttributes = new OriginAndAttributes(aOriginOrPrefix); + mType = eOrigin; + } else { + mPrefix = new nsCString(aOriginOrPrefix); + mType = ePrefix; + } + } + + explicit OriginScope(const mozilla::OriginAttributesPattern& aPattern) + : mPattern(new mozilla::OriginAttributesPattern(aPattern)) + , mType(ePattern) + { } + + explicit OriginScope(const nsAString& aJSONPattern) + : mPattern(new mozilla::OriginAttributesPattern()) + , mType(ePattern) + { + MOZ_ALWAYS_TRUE(mPattern->Init(aJSONPattern)); + } + + OriginScope() + : mDummy(nullptr) + , mType(eNull) + { } + + void + Destroy() + { + if (IsOrigin()) { + MOZ_ASSERT(mOriginAndAttributes); + delete mOriginAndAttributes; + mOriginAndAttributes = nullptr; + } else if (IsPattern()) { + MOZ_ASSERT(mPattern); + delete mPattern; + mPattern = nullptr; + } else if (IsPrefix()) { + MOZ_ASSERT(mPrefix); + delete mPrefix; + mPrefix = nullptr; + } + } + + bool + operator==(const OriginScope& aOther) = delete; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_originorpatternstring_h__ diff --git a/dom/quota/PQuota.ipdl b/dom/quota/PQuota.ipdl new file mode 100644 index 000000000..b9a7a3b84 --- /dev/null +++ b/dom/quota/PQuota.ipdl @@ -0,0 +1,87 @@ +/* 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 PQuotaRequest; +include protocol PQuotaUsageRequest; + +include PBackgroundSharedTypes; + +include "mozilla/dom/quota/SerializationHelpers.h"; + +using mozilla::dom::quota::PersistenceType + from "mozilla/dom/quota/PersistenceType.h"; + +namespace mozilla { +namespace dom { +namespace quota { + +struct AllUsageParams +{ + bool getAll; +}; + +struct OriginUsageParams +{ + PrincipalInfo principalInfo; + bool getGroupUsage; +}; + +union UsageRequestParams +{ + AllUsageParams; + OriginUsageParams; +}; + +struct ClearOriginParams +{ + PrincipalInfo principalInfo; + PersistenceType persistenceType; + bool persistenceTypeIsExplicit; + bool clearAll; +}; + +struct ClearOriginsParams +{ + nsString pattern; +}; + +struct ClearAllParams +{ +}; + +struct ResetAllParams +{ +}; + +union RequestParams +{ + ClearOriginParams; + ClearOriginsParams; + ClearAllParams; + ResetAllParams; +}; + +protocol PQuota +{ + manager PBackground; + + manages PQuotaRequest; + manages PQuotaUsageRequest; + +parent: + async __delete__(); + + async PQuotaUsageRequest(UsageRequestParams params); + + async PQuotaRequest(RequestParams params); + + async StartIdleMaintenance(); + + async StopIdleMaintenance(); +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/PQuotaRequest.ipdl b/dom/quota/PQuotaRequest.ipdl new file mode 100644 index 000000000..212846929 --- /dev/null +++ b/dom/quota/PQuotaRequest.ipdl @@ -0,0 +1,46 @@ +/* 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 PQuota; + +namespace mozilla { +namespace dom { +namespace quota { + +struct ClearOriginResponse +{ +}; + +struct ClearOriginsResponse +{ +}; + +struct ClearAllResponse +{ +}; + +struct ResetAllResponse +{ +}; + +union RequestResponse +{ + nsresult; + ClearOriginResponse; + ClearOriginsResponse; + ClearAllResponse; + ResetAllResponse; +}; + +protocol PQuotaRequest +{ + manager PQuota; + +child: + async __delete__(RequestResponse response); +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/PQuotaUsageRequest.ipdl b/dom/quota/PQuotaUsageRequest.ipdl new file mode 100644 index 000000000..16994e627 --- /dev/null +++ b/dom/quota/PQuotaUsageRequest.ipdl @@ -0,0 +1,50 @@ +/* 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 PQuota; + +namespace mozilla { +namespace dom { +namespace quota { + +struct OriginUsage +{ + nsCString origin; + bool persisted; + uint64_t usage; +}; + +struct AllUsageResponse +{ + OriginUsage[] originUsages; +}; + +struct OriginUsageResponse +{ + uint64_t usage; + uint64_t fileUsage; + uint64_t limit; +}; + +union UsageRequestResponse +{ + nsresult; + AllUsageResponse; + OriginUsageResponse; +}; + +protocol PQuotaUsageRequest +{ + manager PQuota; + +parent: + async Cancel(); + +child: + async __delete__(UsageRequestResponse response); +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/PersistenceType.h b/dom/quota/PersistenceType.h new file mode 100644 index 000000000..3e749e16c --- /dev/null +++ b/dom/quota/PersistenceType.h @@ -0,0 +1,128 @@ +/* -*- 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_quota_persistencetype_h__ +#define mozilla_dom_quota_persistencetype_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "mozilla/dom/StorageTypeBinding.h" + +BEGIN_QUOTA_NAMESPACE + +enum PersistenceType +{ + PERSISTENCE_TYPE_PERSISTENT = 0, + PERSISTENCE_TYPE_TEMPORARY, + PERSISTENCE_TYPE_DEFAULT, + + // Only needed for IPC serialization helper, should never be used in code. + PERSISTENCE_TYPE_INVALID +}; + +static const PersistenceType kAllPersistenceTypes[] = { + PERSISTENCE_TYPE_PERSISTENT, + PERSISTENCE_TYPE_TEMPORARY, + PERSISTENCE_TYPE_DEFAULT +}; + +inline void +PersistenceTypeToText(PersistenceType aPersistenceType, nsACString& aText) +{ + switch (aPersistenceType) { + case PERSISTENCE_TYPE_PERSISTENT: + aText.AssignLiteral("persistent"); + return; + case PERSISTENCE_TYPE_TEMPORARY: + aText.AssignLiteral("temporary"); + return; + case PERSISTENCE_TYPE_DEFAULT: + aText.AssignLiteral("default"); + return; + + case PERSISTENCE_TYPE_INVALID: + default: + MOZ_CRASH("Bad persistence type value!"); + } +} + +inline PersistenceType +PersistenceTypeFromText(const nsACString& aText) +{ + if (aText.EqualsLiteral("persistent")) { + return PERSISTENCE_TYPE_PERSISTENT; + } + + if (aText.EqualsLiteral("temporary")) { + return PERSISTENCE_TYPE_TEMPORARY; + } + + if (aText.EqualsLiteral("default")) { + return PERSISTENCE_TYPE_DEFAULT; + } + + MOZ_CRASH("Should never get here!"); +} + +inline nsresult +NullablePersistenceTypeFromText(const nsACString& aText, + Nullable<PersistenceType>* aPersistenceType) +{ + if (aText.IsVoid()) { + *aPersistenceType = Nullable<PersistenceType>(); + return NS_OK; + } + + if (aText.EqualsLiteral("persistent")) { + *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT); + return NS_OK; + } + + if (aText.EqualsLiteral("temporary")) { + *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY); + return NS_OK; + } + + if (aText.EqualsLiteral("default")) { + *aPersistenceType = Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +inline mozilla::dom::StorageType +PersistenceTypeToStorage(PersistenceType aPersistenceType) +{ + return mozilla::dom::StorageType(static_cast<int>(aPersistenceType)); +} + +inline PersistenceType +PersistenceTypeFromStorage(const Optional<mozilla::dom::StorageType>& aStorage) +{ + if (aStorage.WasPassed()) { + return PersistenceType(static_cast<int>(aStorage.Value())); + } + + return PERSISTENCE_TYPE_DEFAULT; +} + +inline PersistenceType +ComplementaryPersistenceType(PersistenceType aPersistenceType) +{ + MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT || + aPersistenceType == PERSISTENCE_TYPE_TEMPORARY); + + if (aPersistenceType == PERSISTENCE_TYPE_DEFAULT) { + return PERSISTENCE_TYPE_TEMPORARY; + } + + return PERSISTENCE_TYPE_DEFAULT; +} + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_persistencetype_h__ diff --git a/dom/quota/QuotaCommon.h b/dom/quota/QuotaCommon.h new file mode 100644 index 000000000..d07d64293 --- /dev/null +++ b/dom/quota/QuotaCommon.h @@ -0,0 +1,73 @@ +/* -*- 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_quota_quotacommon_h__ +#define mozilla_dom_quota_quotacommon_h__ + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsTArray.h" + +#define BEGIN_QUOTA_NAMESPACE \ + namespace mozilla { namespace dom { namespace quota { +#define END_QUOTA_NAMESPACE \ + } /* namespace quota */ } /* namespace dom */ } /* namespace mozilla */ +#define USING_QUOTA_NAMESPACE \ + using namespace mozilla::dom::quota; + +#define DSSTORE_FILE_NAME ".DS_Store" + +#define QM_WARNING(...) \ + do { \ + nsPrintfCString str(__VA_ARGS__); \ + mozilla::dom::quota::ReportInternalError(__FILE__, __LINE__, str.get()); \ + NS_WARNING(str.get()); \ + } while (0) + +class nsIEventTarget; + +BEGIN_QUOTA_NAMESPACE + +class BackgroundThreadObject +{ +protected: + nsCOMPtr<nsIEventTarget> mOwningThread; + +public: + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + + nsIEventTarget* + OwningThread() const; + +protected: + BackgroundThreadObject(); + + explicit BackgroundThreadObject(nsIEventTarget* aOwningThread); +}; + +void +AssertIsOnIOThread(); + +void +AssertCurrentThreadOwnsQuotaMutex(); + +bool +IsOnIOThread(); + +void +ReportInternalError(const char* aFile, uint32_t aLine, const char* aStr); + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_quotacommon_h__ diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h new file mode 100644 index 000000000..206c3c665 --- /dev/null +++ b/dom/quota/QuotaManager.h @@ -0,0 +1,546 @@ +/* -*- 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_quota_quotamanager_h__ +#define mozilla_dom_quota_quotamanager_h__ + +#include "QuotaCommon.h" + +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/Mutex.h" + +#include "nsClassHashtable.h" +#include "nsRefPtrHashtable.h" + +#include "Client.h" +#include "PersistenceType.h" + +#include "prenv.h" + +#define QUOTA_MANAGER_CONTRACTID "@mozilla.org/dom/quota/manager;1" + +class mozIStorageConnection; +class nsIEventTarget; +class nsIPrincipal; +class nsIThread; +class nsITimer; +class nsIURI; +class nsPIDOMWindowOuter; +class nsIRunnable; + +BEGIN_QUOTA_NAMESPACE + +class DirectoryLockImpl; +class GroupInfo; +class GroupInfoPair; +class OriginInfo; +class OriginScope; +class QuotaObject; + +class NS_NO_VTABLE RefCountedObject +{ +public: + NS_IMETHOD_(MozExternalRefCountType) + AddRef() = 0; + + NS_IMETHOD_(MozExternalRefCountType) + Release() = 0; +}; + +class DirectoryLock + : public RefCountedObject +{ + friend class DirectoryLockImpl; + +private: + DirectoryLock() + { } + + ~DirectoryLock() + { } +}; + +class NS_NO_VTABLE OpenDirectoryListener + : public RefCountedObject +{ +public: + virtual void + DirectoryLockAcquired(DirectoryLock* aLock) = 0; + + virtual void + DirectoryLockFailed() = 0; + +protected: + virtual ~OpenDirectoryListener() + { } +}; + +struct OriginParams +{ + OriginParams(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp) + : mOrigin(aOrigin) + , mPersistenceType(aPersistenceType) + , mIsApp(aIsApp) + { } + + nsCString mOrigin; + PersistenceType mPersistenceType; + bool mIsApp; +}; + +class QuotaManager final + : public BackgroundThreadObject +{ + friend class DirectoryLockImpl; + friend class GroupInfo; + friend class OriginInfo; + friend class QuotaObject; + + typedef nsClassHashtable<nsCStringHashKey, + nsTArray<DirectoryLockImpl*>> DirectoryLockTable; + +public: + class CreateRunnable; + +private: + class ShutdownRunnable; + class ShutdownObserver; + +public: + NS_INLINE_DECL_REFCOUNTING(QuotaManager) + + static bool IsRunningXPCShellTests() + { + static bool kRunningXPCShellTests = !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR"); + return kRunningXPCShellTests; + } + + static const char kReplaceChars[]; + + static void + GetOrCreate(nsIRunnable* aCallback); + + // Returns a non-owning reference. + static QuotaManager* + Get(); + + // Returns true if we've begun the shutdown process. + static bool IsShuttingDown(); + + bool + IsOriginInitialized(const nsACString& aOrigin) const + { + AssertIsOnIOThread(); + + return mInitializedOrigins.Contains(aOrigin); + } + + bool + IsTemporaryStorageInitialized() const + { + AssertIsOnIOThread(); + + return mTemporaryStorageInitialized; + } + + void + InitQuotaForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + uint64_t aUsageBytes, + int64_t aAccessTime); + + void + DecreaseUsageForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + int64_t aSize); + + void + UpdateOriginAccessTime(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin); + + void + RemoveQuota(); + + void + RemoveQuotaForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin) + { + MutexAutoLock lock(mQuotaMutex); + LockedRemoveQuotaForOrigin(aPersistenceType, aGroup, aOrigin); + } + + already_AddRefed<QuotaObject> + GetQuotaObject(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + nsIFile* aFile); + + already_AddRefed<QuotaObject> + GetQuotaObject(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const nsAString& aPath); + + // Called when a process is being shot down. Aborts any running operations + // for the given process. + void + AbortOperationsForProcess(ContentParentId aContentParentId); + + nsresult + GetDirectoryForOrigin(PersistenceType aPersistenceType, + const nsACString& aASCIIOrigin, + nsIFile** aDirectory) const; + + nsresult + RestoreDirectoryMetadata2(nsIFile* aDirectory, bool aPersistent); + + nsresult + GetDirectoryMetadata2(nsIFile* aDirectory, + int64_t* aTimestamp, + nsACString& aSuffix, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp); + + nsresult + GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, + bool aPersistent, + int64_t* aTimestamp, + nsACString& aSuffix, + nsACString& aGroup, + nsACString& aOrigin, + bool* aIsApp); + + nsresult + GetDirectoryMetadata2(nsIFile* aDirectory, int64_t* aTimestamp); + + nsresult + GetDirectoryMetadata2WithRestore(nsIFile* aDirectory, + bool aPersistent, + int64_t* aTimestamp); + + // This is the main entry point into the QuotaManager API. + // Any storage API implementation (quota client) that participates in + // centralized quota and storage handling should call this method to get + // a directory lock which will protect client's files from being deleted + // while they are still in use. + // After a lock is acquired, client is notified via the open listener's + // method DirectoryLockAcquired. If the lock couldn't be acquired, client + // gets DirectoryLockFailed notification. + // A lock is a reference counted object and at the time DirectoryLockAcquired + // is called, quota manager holds just one strong reference to it which is + // then immediatelly cleared by quota manager. So it's up to client to add + // a new reference in order to keep the lock alive. + // Unlocking is simply done by dropping all references to the lock object. + // In other words, protection which the lock represents dies with the lock + // object itself. + void + OpenDirectory(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + Client::Type aClientType, + bool aExclusive, + OpenDirectoryListener* aOpenListener); + + // XXX RemoveMe once bug 1170279 gets fixed. + void + OpenDirectoryInternal(Nullable<PersistenceType> aPersistenceType, + const OriginScope& aOriginScope, + Nullable<Client::Type> aClientType, + bool aExclusive, + OpenDirectoryListener* aOpenListener); + + // Collect inactive and the least recently used origins. + uint64_t + CollectOriginsForEviction(uint64_t aMinSizeToBeFreed, + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks); + + nsresult + EnsureStorageIsInitialized(); + + nsresult + EnsureOriginIsInitialized(PersistenceType aPersistenceType, + const nsACString& aSuffix, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + nsIFile** aDirectory); + + void + OriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp); + + void + ResetOrClearCompleted(); + + void + StartIdleMaintenance() + { + AssertIsOnOwningThread(); + + for (auto& client : mClients) { + client->StartIdleMaintenance(); + } + } + + void + StopIdleMaintenance() + { + AssertIsOnOwningThread(); + + for (auto& client : mClients) { + client->StopIdleMaintenance(); + } + } + + void + AssertCurrentThreadOwnsQuotaMutex() + { + mQuotaMutex.AssertCurrentThreadOwns(); + } + + nsIThread* + IOThread() + { + NS_ASSERTION(mIOThread, "This should never be null!"); + return mIOThread; + } + + Client* + GetClient(Client::Type aClientType); + + const nsString& + GetBasePath() const + { + return mBasePath; + } + + const nsString& + GetStoragePath() const + { + return mStoragePath; + } + + const nsString& + GetStoragePath(PersistenceType aPersistenceType) const + { + if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { + return mPermanentStoragePath; + } + + if (aPersistenceType == PERSISTENCE_TYPE_TEMPORARY) { + return mTemporaryStoragePath; + } + + MOZ_ASSERT(aPersistenceType == PERSISTENCE_TYPE_DEFAULT); + + return mDefaultStoragePath; + } + + uint64_t + GetGroupLimit() const; + + void + GetGroupUsageAndLimit(const nsACString& aGroup, + UsageInfo* aUsageInfo); + + static void + GetStorageId(PersistenceType aPersistenceType, + const nsACString& aOrigin, + Client::Type aClientType, + nsACString& aDatabaseId); + + static nsresult + GetInfoFromPrincipal(nsIPrincipal* aPrincipal, + nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp); + + static nsresult + GetInfoFromWindow(nsPIDOMWindowOuter* aWindow, + nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp); + + static void + GetInfoForChrome(nsACString* aSuffix, + nsACString* aGroup, + nsACString* aOrigin, + bool* aIsApp); + + static bool + IsOriginInternal(const nsACString& aOrigin); + + static bool + IsFirstPromptRequired(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp); + + static bool + IsQuotaEnforced(PersistenceType aPersistenceType, + const nsACString& aOrigin, + bool aIsApp); + + static void + ChromeOrigin(nsACString& aOrigin); + +private: + QuotaManager(); + + virtual ~QuotaManager(); + + nsresult + Init(const nsAString& aBaseDirPath); + + void + Shutdown(); + + already_AddRefed<DirectoryLockImpl> + CreateDirectoryLock(Nullable<PersistenceType> aPersistenceType, + const nsACString& aGroup, + const OriginScope& aOriginScope, + Nullable<bool> aIsApp, + Nullable<Client::Type> aClientType, + bool aExclusive, + bool aInternal, + OpenDirectoryListener* aOpenListener); + + already_AddRefed<DirectoryLockImpl> + CreateDirectoryLockForEviction(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp); + + void + RegisterDirectoryLock(DirectoryLockImpl* aLock); + + void + UnregisterDirectoryLock(DirectoryLockImpl* aLock); + + void + RemovePendingDirectoryLock(DirectoryLockImpl* aLock); + + uint64_t + LockedCollectOriginsForEviction( + uint64_t aMinSizeToBeFreed, + nsTArray<RefPtr<DirectoryLockImpl>>& aLocks); + + void + LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin); + + nsresult + MaybeUpgradeIndexedDBDirectory(); + + nsresult + MaybeUpgradePersistentStorageDirectory(); + + nsresult + MaybeRemoveOldDirectories(); + + nsresult + UpgradeStorageFrom0ToCurrent(mozIStorageConnection* aConnection); + +#if 0 + nsresult + UpgradeStorageFrom1To2(mozIStorageConnection* aConnection); +#endif + + nsresult + InitializeRepository(PersistenceType aPersistenceType); + + nsresult + InitializeOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + bool aIsApp, + int64_t aAccessTime, + nsIFile* aDirectory); + + void + CheckTemporaryStorageLimits(); + + void + DeleteFilesForOrigin(PersistenceType aPersistenceType, + const nsACString& aOrigin); + + void + FinalizeOriginEviction(nsTArray<RefPtr<DirectoryLockImpl>>& aLocks); + + void + ReleaseIOThreadObjects() + { + AssertIsOnIOThread(); + + for (uint32_t index = 0; index < Client::TYPE_MAX; index++) { + mClients[index]->ReleaseIOThreadObjects(); + } + } + + DirectoryLockTable& + GetDirectoryLockTable(PersistenceType aPersistenceType); + + static void + ShutdownTimerCallback(nsITimer* aTimer, void* aClosure); + + mozilla::Mutex mQuotaMutex; + + nsClassHashtable<nsCStringHashKey, GroupInfoPair> mGroupInfoPairs; + + // Maintains a list of directory locks that are queued. + nsTArray<RefPtr<DirectoryLockImpl>> mPendingDirectoryLocks; + + // Maintains a list of directory locks that are acquired or queued. + nsTArray<DirectoryLockImpl*> mDirectoryLocks; + + // Directory lock tables that are used to update origin access time. + DirectoryLockTable mTemporaryDirectoryLockTable; + DirectoryLockTable mDefaultDirectoryLockTable; + + // Thread on which IO is performed. + nsCOMPtr<nsIThread> mIOThread; + + // A timer that gets activated at shutdown to ensure we close all storages. + nsCOMPtr<nsITimer> mShutdownTimer; + + // A list of all successfully initialized origins. This list isn't protected + // by any mutex but it is only ever touched on the IO thread. + nsTArray<nsCString> mInitializedOrigins; + + // This array is populated at initialization time and then never modified, so + // it can be iterated on any thread. + AutoTArray<RefPtr<Client>, Client::TYPE_MAX> mClients; + + nsString mBasePath; + nsString mIndexedDBPath; + nsString mStoragePath; + nsString mPermanentStoragePath; + nsString mTemporaryStoragePath; + nsString mDefaultStoragePath; + + uint64_t mTemporaryStorageLimit; + uint64_t mTemporaryStorageUsage; + bool mTemporaryStorageInitialized; + + bool mStorageInitialized; +}; + +END_QUOTA_NAMESPACE + +#endif /* mozilla_dom_quota_quotamanager_h__ */ diff --git a/dom/quota/QuotaManagerService.cpp b/dom/quota/QuotaManagerService.cpp new file mode 100644 index 000000000..fb5f0f3a1 --- /dev/null +++ b/dom/quota/QuotaManagerService.cpp @@ -0,0 +1,845 @@ +/* -*- 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 "QuotaManagerService.h" + +#include "ActorsChild.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Hal.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "nsIIdleService.h" +#include "nsIIPCBackgroundChildCreateCallback.h" +#include "nsIObserverService.h" +#include "nsIScriptSecurityManager.h" +#include "nsXULAppAPI.h" +#include "QuotaManager.h" +#include "QuotaRequests.h" + +#define PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID "profile-before-change-qm" + +namespace mozilla { +namespace dom { +namespace quota { + +using namespace mozilla::ipc; + +namespace { + +// Preference that is used to enable testing features. +const char kTestingPref[] = "dom.quotaManager.testing"; + +const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1"; + +// The number of seconds we will wait after receiving the idle-daily +// notification before beginning maintenance. +const uint32_t kIdleObserverTimeSec = 1; + +mozilla::StaticRefPtr<QuotaManagerService> gQuotaManagerService; + +mozilla::Atomic<bool> gInitialized(false); +mozilla::Atomic<bool> gClosed(false); +mozilla::Atomic<bool> gTestingMode(false); + +void +TestingPrefChangedCallback(const char* aPrefName, + void* aClosure) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aPrefName, kTestingPref)); + MOZ_ASSERT(!aClosure); + + gTestingMode = Preferences::GetBool(aPrefName); +} + +class AbortOperationsRunnable final + : public Runnable +{ + ContentParentId mContentParentId; + +public: + explicit AbortOperationsRunnable(ContentParentId aContentParentId) + : mContentParentId(aContentParentId) + { } + +private: + NS_DECL_NSIRUNNABLE +}; + +} // namespace + +class QuotaManagerService::BackgroundCreateCallback final + : public nsIIPCBackgroundChildCreateCallback +{ + RefPtr<QuotaManagerService> mService; + +public: + explicit + BackgroundCreateCallback(QuotaManagerService* aService) + : mService(aService) + { + MOZ_ASSERT(aService); + } + + NS_DECL_ISUPPORTS + +private: + ~BackgroundCreateCallback() + { } + + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK +}; + +class QuotaManagerService::PendingRequestInfo +{ +protected: + RefPtr<RequestBase> mRequest; + +public: + explicit PendingRequestInfo(RequestBase* aRequest) + : mRequest(aRequest) + { } + + virtual ~PendingRequestInfo() + { } + + RequestBase* + GetRequest() const + { + return mRequest; + } + + virtual nsresult + InitiateRequest(QuotaChild* aActor) = 0; +}; + +class QuotaManagerService::UsageRequestInfo + : public PendingRequestInfo +{ + UsageRequestParams mParams; + +public: + UsageRequestInfo(UsageRequest* aRequest, + const UsageRequestParams& aParams) + : PendingRequestInfo(aRequest) + , mParams(aParams) + { + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None); + } + + virtual nsresult + InitiateRequest(QuotaChild* aActor) override; +}; + +class QuotaManagerService::RequestInfo + : public PendingRequestInfo +{ + RequestParams mParams; + +public: + RequestInfo(Request* aRequest, + const RequestParams& aParams) + : PendingRequestInfo(aRequest) + , mParams(aParams) + { + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aParams.type() != RequestParams::T__None); + } + + virtual nsresult + InitiateRequest(QuotaChild* aActor) override; +}; + +class QuotaManagerService::IdleMaintenanceInfo + : public PendingRequestInfo +{ + const bool mStart; + +public: + explicit IdleMaintenanceInfo(bool aStart) + : PendingRequestInfo(nullptr) + , mStart(aStart) + { } + + virtual nsresult + InitiateRequest(QuotaChild* aActor) override; +}; + +QuotaManagerService::QuotaManagerService() + : mBackgroundActor(nullptr) + , mBackgroundActorFailed(false) + , mIdleObserverRegistered(false) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +QuotaManagerService::~QuotaManagerService() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIdleObserverRegistered); +} + +// static +QuotaManagerService* +QuotaManagerService::GetOrCreate() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (gClosed) { + MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!"); + return nullptr; + } + + if (!gQuotaManagerService) { + RefPtr<QuotaManagerService> instance(new QuotaManagerService()); + + nsresult rv = instance->Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + if (gInitialized.exchange(true)) { + MOZ_ASSERT(false, "Initialized more than once?!"); + } + + gQuotaManagerService = instance; + + ClearOnShutdown(&gQuotaManagerService); + } + + return gQuotaManagerService; +} + +// static +QuotaManagerService* +QuotaManagerService::Get() +{ + // Does not return an owning reference. + return gQuotaManagerService; +} + +// static +QuotaManagerService* +QuotaManagerService::FactoryCreate() +{ + // Returns a raw pointer that carries an owning reference! Lame, but the + // singleton factory macros force this. + QuotaManagerService* quotaManagerService = GetOrCreate(); + NS_IF_ADDREF(quotaManagerService); + return quotaManagerService; +} + +void +QuotaManagerService::ClearBackgroundActor() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mBackgroundActor = nullptr; +} + +void +QuotaManagerService::NoteLiveManager(QuotaManager* aManager) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aManager); + + mBackgroundThread = aManager->OwningThread(); +} + +void +QuotaManagerService::NoteShuttingDownManager() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + mBackgroundThread = nullptr; +} + +void +QuotaManagerService::AbortOperationsForProcess(ContentParentId aContentParentId) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mBackgroundThread) { + return; + } + + RefPtr<AbortOperationsRunnable> runnable = + new AbortOperationsRunnable(aContentParentId); + + MOZ_ALWAYS_SUCCEEDS( + mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)); +} + +nsresult +QuotaManagerService::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (NS_WARN_IF(!observerService)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = + observerService->AddObserver(this, + PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, + false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + Preferences::RegisterCallbackAndCall(TestingPrefChangedCallback, + kTestingPref); + + return NS_OK; +} + +void +QuotaManagerService::Destroy() +{ + // Setting the closed flag prevents the service from being recreated. + // Don't set it though if there's no real instance created. + if (gInitialized && gClosed.exchange(true)) { + MOZ_ASSERT(false, "Shutdown more than once?!"); + } + + Preferences::UnregisterCallback(TestingPrefChangedCallback, kTestingPref); + + delete this; +} + +nsresult +QuotaManagerService::InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo) +{ + // Nothing can be done here if we have previously failed to create a + // background actor. + if (mBackgroundActorFailed) { + return NS_ERROR_FAILURE; + } + + if (!mBackgroundActor && mPendingRequests.IsEmpty()) { + if (PBackgroundChild* actor = BackgroundChild::GetForCurrentThread()) { + BackgroundActorCreated(actor); + } else { + // We need to start the sequence to create a background actor for this + // thread. + RefPtr<BackgroundCreateCallback> cb = new BackgroundCreateCallback(this); + if (NS_WARN_IF(!BackgroundChild::GetOrCreateForCurrentThread(cb))) { + return NS_ERROR_FAILURE; + } + } + } + + // If we already have a background actor then we can start this request now. + if (mBackgroundActor) { + nsresult rv = aInfo->InitiateRequest(mBackgroundActor); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + mPendingRequests.AppendElement(aInfo.forget()); + } + + return NS_OK; +} + +nsresult +QuotaManagerService::BackgroundActorCreated(PBackgroundChild* aBackgroundActor) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(!mBackgroundActor); + MOZ_ASSERT(!mBackgroundActorFailed); + + { + QuotaChild* actor = new QuotaChild(this); + + mBackgroundActor = + static_cast<QuotaChild*>(aBackgroundActor->SendPQuotaConstructor(actor)); + } + + if (NS_WARN_IF(!mBackgroundActor)) { + BackgroundActorFailed(); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + + for (uint32_t index = 0, count = mPendingRequests.Length(); + index < count; + index++) { + nsAutoPtr<PendingRequestInfo> info(mPendingRequests[index].forget()); + + nsresult rv2 = info->InitiateRequest(mBackgroundActor); + + // Warn for every failure, but just return the first failure if there are + // multiple failures. + if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) { + rv = rv2; + } + } + + mPendingRequests.Clear(); + + return rv; +} + +void +QuotaManagerService::BackgroundActorFailed() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mPendingRequests.IsEmpty()); + MOZ_ASSERT(!mBackgroundActor); + MOZ_ASSERT(!mBackgroundActorFailed); + + mBackgroundActorFailed = true; + + for (uint32_t index = 0, count = mPendingRequests.Length(); + index < count; + index++) { + nsAutoPtr<PendingRequestInfo> info(mPendingRequests[index].forget()); + + RequestBase* request = info->GetRequest(); + if (request) { + request->SetError(NS_ERROR_FAILURE); + } + } + + mPendingRequests.Clear(); +} + +void +QuotaManagerService::PerformIdleMaintenance() +{ + using namespace mozilla::hal; + + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + // If we're running on battery power then skip all idle maintenance since we + // would otherwise be doing lots of disk I/O. + BatteryInformation batteryInfo; + +#ifdef MOZ_WIDGET_ANDROID + // Android XPCShell doesn't load the AndroidBridge that is needed to make + // GetCurrentBatteryInformation work... + if (!QuotaManager::IsRunningXPCShellTests()) +#endif + { + GetCurrentBatteryInformation(&batteryInfo); + } + + // If we're running XPCShell because we always want to be able to test this + // code so pretend that we're always charging. + if (QuotaManager::IsRunningXPCShellTests()) { + batteryInfo.level() = 100; + batteryInfo.charging() = true; + } + + if (NS_WARN_IF(!batteryInfo.charging())) { + return; + } + + if (QuotaManager::IsRunningXPCShellTests()) { + // We don't want user activity to impact this code if we're running tests. + Unused << Observe(nullptr, OBSERVER_TOPIC_IDLE, nullptr); + } else if (!mIdleObserverRegistered) { + nsCOMPtr<nsIIdleService> idleService = + do_GetService(kIdleServiceContractId); + MOZ_ASSERT(idleService); + + MOZ_ALWAYS_SUCCEEDS( + idleService->AddIdleObserver(this, kIdleObserverTimeSec)); + + mIdleObserverRegistered = true; + } +} + +void +QuotaManagerService::RemoveIdleObserver() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (mIdleObserverRegistered) { + nsCOMPtr<nsIIdleService> idleService = + do_GetService(kIdleServiceContractId); + MOZ_ASSERT(idleService); + + MOZ_ALWAYS_SUCCEEDS( + idleService->RemoveIdleObserver(this, kIdleObserverTimeSec)); + + mIdleObserverRegistered = false; + } +} + +NS_IMPL_ADDREF(QuotaManagerService) +NS_IMPL_RELEASE_WITH_DESTROY(QuotaManagerService, Destroy()) +NS_IMPL_QUERY_INTERFACE(QuotaManagerService, + nsIQuotaManagerService, + nsIObserver) + +NS_IMETHODIMP +QuotaManagerService::GetUsage(nsIQuotaUsageCallback* aCallback, + bool aGetAll, + nsIQuotaUsageRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCallback); + + RefPtr<UsageRequest> request = new UsageRequest(aCallback); + + AllUsageParams params; + + params.getAll() = aGetAll; + + nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::GetUsageForPrincipal(nsIPrincipal* aPrincipal, + nsIQuotaUsageCallback* aCallback, + bool aGetGroupUsage, + nsIQuotaUsageRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); + + RefPtr<UsageRequest> request = new UsageRequest(aPrincipal, aCallback); + + OriginUsageParams params; + + PrincipalInfo& principalInfo = params.principalInfo(); + nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo && + principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { + return NS_ERROR_UNEXPECTED; + } + + params.getGroupUsage() = aGetGroupUsage; + + nsAutoPtr<PendingRequestInfo> info(new UsageRequestInfo(request, params)); + + rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::Clear(nsIQuotaRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + if (NS_WARN_IF(!gTestingMode)) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<Request> request = new Request(); + + ClearAllParams params; + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::ClearStoragesForPrincipal(nsIPrincipal* aPrincipal, + const nsACString& aPersistenceType, + bool aClearAll, + nsIQuotaRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + nsCString suffix; + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef().CreateSuffix(suffix); + + if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) { + // The originAttributes should be default originAttributes when the + // aClearAll flag is set. + return NS_ERROR_INVALID_ARG; + } + + RefPtr<Request> request = new Request(aPrincipal); + + ClearOriginParams params; + + PrincipalInfo& principalInfo = params.principalInfo(); + + nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo && + principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) { + return NS_ERROR_UNEXPECTED; + } + + Nullable<PersistenceType> persistenceType; + rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_INVALID_ARG; + } + + if (persistenceType.IsNull()) { + params.persistenceTypeIsExplicit() = false; + } else { + params.persistenceType() = persistenceType.Value(); + params.persistenceTypeIsExplicit() = true; + } + + params.clearAll() = aClearAll; + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params)); + + rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::Reset(nsIQuotaRequest** _retval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + if (NS_WARN_IF(!gTestingMode)) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<Request> request = new Request(); + + ResetAllParams params; + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + request.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +QuotaManagerService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) { + RemoveIdleObserver(); + return NS_OK; + } + + if (!strcmp(aTopic, "clear-origin-attributes-data")) { + RefPtr<Request> request = new Request(); + + ClearOriginsParams requestParams; + requestParams.pattern() = nsDependentString(aData); + + nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, requestParams)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) { + PerformIdleMaintenance(); + return NS_OK; + } + + if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE)) { + nsAutoPtr<PendingRequestInfo> info( + new IdleMaintenanceInfo(/* aStart */ true)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + if (!strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) { + RemoveIdleObserver(); + + nsAutoPtr<PendingRequestInfo> info( + new IdleMaintenanceInfo(/* aStart */ false)); + + nsresult rv = InitiateRequest(info); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("Should never get here!"); + return NS_OK; +} + +NS_IMETHODIMP +AbortOperationsRunnable::Run() +{ + AssertIsOnBackgroundThread(); + + if (QuotaManager::IsShuttingDown()) { + return NS_OK; + } + + QuotaManager* quotaManager = QuotaManager::Get(); + if (!quotaManager) { + return NS_OK; + } + + quotaManager->AbortOperationsForProcess(mContentParentId); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(QuotaManagerService::BackgroundCreateCallback, + nsIIPCBackgroundChildCreateCallback) + +void +QuotaManagerService:: +BackgroundCreateCallback::ActorCreated(PBackgroundChild* aActor) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + MOZ_ASSERT(mService); + + RefPtr<QuotaManagerService> service; + mService.swap(service); + + service->BackgroundActorCreated(aActor); +} + +void +QuotaManagerService:: +BackgroundCreateCallback::ActorFailed() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mService); + + RefPtr<QuotaManagerService> service; + mService.swap(service); + + service->BackgroundActorFailed(); +} + +nsresult +QuotaManagerService:: +UsageRequestInfo::InitiateRequest(QuotaChild* aActor) +{ + MOZ_ASSERT(aActor); + + auto request = static_cast<UsageRequest*>(mRequest.get()); + + auto actor = new QuotaUsageRequestChild(request); + + if (!aActor->SendPQuotaUsageRequestConstructor(actor, mParams)) { + request->SetError(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + request->SetBackgroundActor(actor); + + return NS_OK; +} + +nsresult +QuotaManagerService:: +RequestInfo::InitiateRequest(QuotaChild* aActor) +{ + MOZ_ASSERT(aActor); + + auto request = static_cast<Request*>(mRequest.get()); + + auto actor = new QuotaRequestChild(request); + + if (!aActor->SendPQuotaRequestConstructor(actor, mParams)) { + request->SetError(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +QuotaManagerService:: +IdleMaintenanceInfo::InitiateRequest(QuotaChild* aActor) +{ + MOZ_ASSERT(aActor); + + bool result; + + if (mStart) { + result = aActor->SendStartIdleMaintenance(); + } else { + result = aActor->SendStopIdleMaintenance(); + } + + if (!result) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/QuotaManagerService.h b/dom/quota/QuotaManagerService.h new file mode 100644 index 000000000..bbf249e69 --- /dev/null +++ b/dom/quota/QuotaManagerService.h @@ -0,0 +1,113 @@ +/* -*- 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_quota_QuotaManagerService_h +#define mozilla_dom_quota_QuotaManagerService_h + +#include "mozilla/dom/ipc/IdType.h" +#include "nsAutoPtr.h" +#include "nsIObserver.h" +#include "nsIQuotaManagerService.h" + +#define QUOTAMANAGER_SERVICE_CONTRACTID \ + "@mozilla.org/dom/quota-manager-service;1" + +namespace mozilla { +namespace ipc { + +class PBackgroundChild; + +} // namespace ipc + +namespace dom { +namespace quota { + +class QuotaChild; +class QuotaManager; + +class QuotaManagerService final + : public nsIQuotaManagerService + , public nsIObserver +{ + typedef mozilla::ipc::PBackgroundChild PBackgroundChild; + + class BackgroundCreateCallback; + class PendingRequestInfo; + class UsageRequestInfo; + class RequestInfo; + class IdleMaintenanceInfo; + + nsCOMPtr<nsIEventTarget> mBackgroundThread; + + nsTArray<nsAutoPtr<PendingRequestInfo>> mPendingRequests; + + QuotaChild* mBackgroundActor; + + bool mBackgroundActorFailed; + bool mIdleObserverRegistered; + +public: + // Returns a non-owning reference. + static QuotaManagerService* + GetOrCreate(); + + // Returns a non-owning reference. + static QuotaManagerService* + Get(); + + // Returns an owning reference! No one should call this but the factory. + static QuotaManagerService* + FactoryCreate(); + + void + ClearBackgroundActor(); + + void + NoteLiveManager(QuotaManager* aManager); + + void + NoteShuttingDownManager(); + + // Called when a process is being shot down. Aborts any running operations + // for the given process. + void + AbortOperationsForProcess(ContentParentId aContentParentId); + +private: + QuotaManagerService(); + ~QuotaManagerService(); + + nsresult + Init(); + + void + Destroy(); + + nsresult + InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo); + + nsresult + BackgroundActorCreated(PBackgroundChild* aBackgroundActor); + + void + BackgroundActorFailed(); + + void + PerformIdleMaintenance(); + + void + RemoveIdleObserver(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIQUOTAMANAGERSERVICE + NS_DECL_NSIOBSERVER +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_quota_QuotaManagerService_h */ diff --git a/dom/quota/QuotaObject.h b/dom/quota/QuotaObject.h new file mode 100644 index 000000000..370d04da1 --- /dev/null +++ b/dom/quota/QuotaObject.h @@ -0,0 +1,85 @@ +/* -*- 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_quota_quotaobject_h__ +#define mozilla_dom_quota_quotaobject_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "nsDataHashtable.h" + +#include "PersistenceType.h" + +BEGIN_QUOTA_NAMESPACE + +class OriginInfo; +class QuotaManager; + +class QuotaObject +{ + friend class OriginInfo; + friend class QuotaManager; + +public: + void + AddRef(); + + void + Release(); + + const nsAString& + Path() const + { + return mPath; + } + + bool + MaybeUpdateSize(int64_t aSize, bool aTruncate); + + void + DisableQuotaCheck(); + + void + EnableQuotaCheck(); + +private: + QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize) + : mOriginInfo(aOriginInfo) + , mPath(aPath) + , mSize(aSize) + , mQuotaCheckDisabled(false) + { + MOZ_COUNT_CTOR(QuotaObject); + } + + ~QuotaObject() + { + MOZ_COUNT_DTOR(QuotaObject); + } + + already_AddRefed<QuotaObject> + LockedAddRef() + { + AssertCurrentThreadOwnsQuotaMutex(); + + ++mRefCnt; + + RefPtr<QuotaObject> result = dont_AddRef(this); + return result.forget(); + } + + mozilla::ThreadSafeAutoRefCnt mRefCnt; + + OriginInfo* mOriginInfo; + nsString mPath; + int64_t mSize; + + bool mQuotaCheckDisabled; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_quotaobject_h__ diff --git a/dom/quota/QuotaRequests.cpp b/dom/quota/QuotaRequests.cpp new file mode 100644 index 000000000..369bc790c --- /dev/null +++ b/dom/quota/QuotaRequests.cpp @@ -0,0 +1,291 @@ +/* -*- 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 "QuotaRequests.h" + +#include "ActorsChild.h" +#include "nsIQuotaCallbacks.h" + +namespace mozilla { +namespace dom { +namespace quota { + +RequestBase::RequestBase() + : mResultCode(NS_OK) + , mHaveResultOrErrorCode(false) +{ +#ifdef DEBUG + mOwningThread = PR_GetCurrentThread(); +#endif + AssertIsOnOwningThread(); +} + +RequestBase::RequestBase(nsIPrincipal* aPrincipal) + : mPrincipal(aPrincipal) + , mResultCode(NS_OK) + , mHaveResultOrErrorCode(false) +{ +#ifdef DEBUG + mOwningThread = PR_GetCurrentThread(); +#endif + AssertIsOnOwningThread(); +} + +#ifdef DEBUG + +void +RequestBase::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mOwningThread); + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); +} + +#endif // DEBUG + +void +RequestBase::SetError(nsresult aRv) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mResultCode == NS_OK); + MOZ_ASSERT(!mHaveResultOrErrorCode); + + mResultCode = aRv; + mHaveResultOrErrorCode = true; + + FireCallback(); +} + +NS_IMPL_CYCLE_COLLECTION_0(RequestBase) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RequestBase) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RequestBase) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RequestBase) + +NS_IMETHODIMP +RequestBase::GetPrincipal(nsIPrincipal** aPrincipal) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aPrincipal); + + NS_IF_ADDREF(*aPrincipal = mPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +RequestBase::GetResultCode(nsresult* aResultCode) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aResultCode); + + if (!mHaveResultOrErrorCode) { + return NS_ERROR_FAILURE; + } + + *aResultCode = mResultCode; + return NS_OK; +} + +UsageRequest::UsageRequest(nsIQuotaUsageCallback* aCallback) + : mCallback(aCallback) + , mBackgroundActor(nullptr) + , mCanceled(false) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aCallback); +} + +UsageRequest::UsageRequest(nsIPrincipal* aPrincipal, + nsIQuotaUsageCallback* aCallback) + : RequestBase(aPrincipal) + , mCallback(aCallback) + , mBackgroundActor(nullptr) + , mCanceled(false) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); +} + +UsageRequest::~UsageRequest() +{ + AssertIsOnOwningThread(); +} + +void +UsageRequest::SetBackgroundActor(QuotaUsageRequestChild* aBackgroundActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(!mBackgroundActor); + + mBackgroundActor = aBackgroundActor; + + if (mCanceled) { + mBackgroundActor->SendCancel(); + } +} + +void +UsageRequest::SetResult(nsIVariant* aResult) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aResult); + MOZ_ASSERT(!mHaveResultOrErrorCode); + + mResult = aResult; + + mHaveResultOrErrorCode = true; + + FireCallback(); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(UsageRequest, RequestBase, mCallback) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(UsageRequest) + NS_INTERFACE_MAP_ENTRY(nsIQuotaUsageRequest) +NS_INTERFACE_MAP_END_INHERITING(RequestBase) + +NS_IMPL_ADDREF_INHERITED(UsageRequest, RequestBase) +NS_IMPL_RELEASE_INHERITED(UsageRequest, RequestBase) + +NS_IMETHODIMP +UsageRequest::GetResult(nsIVariant** aResult) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aResult); + + if (!mHaveResultOrErrorCode) { + return NS_ERROR_FAILURE; + } + + NS_IF_ADDREF(*aResult = mResult); + return NS_OK; +} + +NS_IMETHODIMP +UsageRequest::GetCallback(nsIQuotaUsageCallback** aCallback) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aCallback); + + NS_IF_ADDREF(*aCallback = mCallback); + return NS_OK; +} + +NS_IMETHODIMP +UsageRequest::SetCallback(nsIQuotaUsageCallback* aCallback) +{ + AssertIsOnOwningThread(); + + mCallback = aCallback; + return NS_OK; +} + +NS_IMETHODIMP +UsageRequest::Cancel() +{ + AssertIsOnOwningThread(); + + if (mCanceled) { + NS_WARNING("Canceled more than once?!"); + return NS_ERROR_UNEXPECTED; + } + + if (mBackgroundActor) { + mBackgroundActor->SendCancel(); + } + + mCanceled = true; + + return NS_OK; +} + +void +UsageRequest::FireCallback() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mCallback); + + mCallback->OnUsageResult(this); + + // Clean up. + mCallback = nullptr; +} + +Request::Request() +{ + AssertIsOnOwningThread(); +} + +Request::Request(nsIPrincipal* aPrincipal) + : RequestBase(aPrincipal) +{ + AssertIsOnOwningThread(); +} + +Request::~Request() +{ + AssertIsOnOwningThread(); +} + +void +Request::SetResult() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mHaveResultOrErrorCode); + + mHaveResultOrErrorCode = true; + + FireCallback(); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(Request, RequestBase, mCallback) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Request) + NS_INTERFACE_MAP_ENTRY(nsIQuotaRequest) +NS_INTERFACE_MAP_END_INHERITING(RequestBase) + +NS_IMPL_ADDREF_INHERITED(mozilla::dom::quota::Request, RequestBase) +NS_IMPL_RELEASE_INHERITED(mozilla::dom::quota::Request, RequestBase) + +NS_IMETHODIMP +Request::GetCallback(nsIQuotaCallback** aCallback) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aCallback); + + NS_IF_ADDREF(*aCallback = mCallback); + return NS_OK; +} + +NS_IMETHODIMP +Request::SetCallback(nsIQuotaCallback* aCallback) +{ + AssertIsOnOwningThread(); + + mCallback = aCallback; + return NS_OK; +} + +void +Request::FireCallback() +{ + AssertIsOnOwningThread(); + + if (mCallback) { + mCallback->OnComplete(this); + + // Clean up. + mCallback = nullptr; + } +} + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/QuotaRequests.h b/dom/quota/QuotaRequests.h new file mode 100644 index 000000000..cb483753b --- /dev/null +++ b/dom/quota/QuotaRequests.h @@ -0,0 +1,142 @@ +/* -*- 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_quota_UsageRequest_h +#define mozilla_dom_quota_UsageRequest_h + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIQuotaRequests.h" + +class nsIPrincipal; +class nsIQuotaCallback; +class nsIQuotaUsageCallback; +struct PRThread; + +namespace mozilla { +namespace dom { +namespace quota { + +class QuotaUsageRequestChild; + +class RequestBase + : public nsIQuotaRequestBase +{ +protected: +#ifdef DEBUG + PRThread* mOwningThread; +#endif + + nsCOMPtr<nsIPrincipal> mPrincipal; + + nsresult mResultCode; + bool mHaveResultOrErrorCode; + +public: + void + AssertIsOnOwningThread() const +#ifdef DEBUG + ; +#else + { } +#endif + + void + SetError(nsresult aRv); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIQUOTAREQUESTBASE + NS_DECL_CYCLE_COLLECTION_CLASS(RequestBase) + +protected: + RequestBase(); + + RequestBase(nsIPrincipal* aPrincipal); + + virtual ~RequestBase() + { + AssertIsOnOwningThread(); + } + + virtual void + FireCallback() = 0; +}; + +class UsageRequest final + : public RequestBase + , public nsIQuotaUsageRequest +{ + nsCOMPtr<nsIQuotaUsageCallback> mCallback; + + nsCOMPtr<nsIVariant> mResult; + + QuotaUsageRequestChild* mBackgroundActor; + + bool mCanceled; + +public: + explicit UsageRequest(nsIQuotaUsageCallback* aCallback); + + UsageRequest(nsIPrincipal* aPrincipal, + nsIQuotaUsageCallback* aCallback); + + void + SetBackgroundActor(QuotaUsageRequestChild* aBackgroundActor); + + void + ClearBackgroundActor() + { + AssertIsOnOwningThread(); + + mBackgroundActor = nullptr; + } + + void + SetResult(nsIVariant* aResult); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIQUOTAREQUESTBASE(RequestBase::) + NS_DECL_NSIQUOTAUSAGEREQUEST + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UsageRequest, RequestBase) + +private: + ~UsageRequest(); + + virtual void + FireCallback() override; +}; + +class Request final + : public RequestBase + , public nsIQuotaRequest +{ + nsCOMPtr<nsIQuotaCallback> mCallback; + +public: + Request(); + + explicit Request(nsIPrincipal* aPrincipal); + + void + SetResult(); + + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIQUOTAREQUESTBASE(RequestBase::) + NS_DECL_NSIQUOTAREQUEST + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Request, RequestBase) + +private: + ~Request(); + + virtual void + FireCallback() override; +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_quota_UsageRequest_h diff --git a/dom/quota/QuotaResults.cpp b/dom/quota/QuotaResults.cpp new file mode 100644 index 000000000..f5dbbd657 --- /dev/null +++ b/dom/quota/QuotaResults.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "QuotaResults.h" + +namespace mozilla { +namespace dom { +namespace quota { + +UsageResult::UsageResult(const nsACString& aOrigin, + bool aPersisted, + uint64_t aUsage) + : mOrigin(aOrigin) + , mUsage(aUsage) + , mPersisted(aPersisted) +{ +} + +NS_IMPL_ISUPPORTS(UsageResult, + nsIQuotaUsageResult) + +NS_IMETHODIMP +UsageResult::GetOrigin(nsACString& aOrigin) +{ + aOrigin = mOrigin; + return NS_OK; +} + +NS_IMETHODIMP +UsageResult::GetPersisted(bool* aPersisted) +{ + MOZ_ASSERT(aPersisted); + + *aPersisted = mPersisted; + return NS_OK; +} + +NS_IMETHODIMP +UsageResult::GetUsage(uint64_t* aUsage) +{ + MOZ_ASSERT(aUsage); + + *aUsage = mUsage; + return NS_OK; +} + +OriginUsageResult::OriginUsageResult(uint64_t aUsage, + uint64_t aFileUsage, + uint64_t aLimit) + : mUsage(aUsage) + , mFileUsage(aFileUsage) + , mLimit(aLimit) +{ +} + +NS_IMPL_ISUPPORTS(OriginUsageResult, + nsIQuotaOriginUsageResult) + +NS_IMETHODIMP +OriginUsageResult::GetUsage(uint64_t* aUsage) +{ + MOZ_ASSERT(aUsage); + + *aUsage = mUsage; + return NS_OK; +} + +NS_IMETHODIMP +OriginUsageResult::GetFileUsage(uint64_t* aFileUsage) +{ + MOZ_ASSERT(aFileUsage); + + *aFileUsage = mFileUsage; + return NS_OK; +} + +NS_IMETHODIMP +OriginUsageResult::GetLimit(uint64_t* aLimit) +{ + MOZ_ASSERT(aLimit); + + *aLimit = mLimit; + return NS_OK; +} + +} // namespace quota +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/QuotaResults.h b/dom/quota/QuotaResults.h new file mode 100644 index 000000000..73fe6b790 --- /dev/null +++ b/dom/quota/QuotaResults.h @@ -0,0 +1,60 @@ +/* -*- 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_quota_QuotaResults_h +#define mozilla_dom_quota_QuotaResults_h + +#include "nsIQuotaResults.h" + +namespace mozilla { +namespace dom { +namespace quota { + +class UsageResult + : public nsIQuotaUsageResult +{ + nsCString mOrigin; + uint64_t mUsage; + bool mPersisted; + +public: + UsageResult(const nsACString& aOrigin, + bool aPersisted, + uint64_t aUsage); + +private: + virtual ~UsageResult() + { } + + NS_DECL_ISUPPORTS + NS_DECL_NSIQUOTAUSAGERESULT +}; + +class OriginUsageResult + : public nsIQuotaOriginUsageResult +{ + uint64_t mUsage; + uint64_t mFileUsage; + uint64_t mLimit; + +public: + OriginUsageResult(uint64_t aUsage, + uint64_t aFileUsage, + uint64_t aLimit); + +private: + virtual ~OriginUsageResult() + { } + + NS_DECL_ISUPPORTS + NS_DECL_NSIQUOTAORIGINUSAGERESULT +}; + +} // namespace quota +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_quota_QuotaResults_h diff --git a/dom/quota/SerializationHelpers.h b/dom/quota/SerializationHelpers.h new file mode 100644 index 000000000..e3a79e4e9 --- /dev/null +++ b/dom/quota/SerializationHelpers.h @@ -0,0 +1,26 @@ +/* -*- 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_quota_SerializationHelpers_h +#define mozilla_dom_quota_SerializationHelpers_h + +#include "ipc/IPCMessageUtils.h" + +#include "mozilla/dom/quota/PersistenceType.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::dom::quota::PersistenceType> : + public ContiguousEnumSerializer< + mozilla::dom::quota::PersistenceType, + mozilla::dom::quota::PERSISTENCE_TYPE_PERSISTENT, + mozilla::dom::quota::PERSISTENCE_TYPE_INVALID> +{ }; + +} // namespace IPC + +#endif // mozilla_dom_quota_SerializationHelpers_h diff --git a/dom/quota/StorageManager.cpp b/dom/quota/StorageManager.cpp new file mode 100644 index 000000000..c8455f0fe --- /dev/null +++ b/dom/quota/StorageManager.cpp @@ -0,0 +1,378 @@ +/* -*- 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 "StorageManager.h" + +#include "mozilla/dom/PromiseWorkerProxy.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "mozilla/dom/StorageManagerBinding.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ErrorResult.h" +#include "nsIQuotaCallbacks.h" +#include "nsIQuotaRequests.h" +#include "nsPIDOMWindow.h" + +using namespace mozilla::dom::workers; + +namespace mozilla { +namespace dom { + +namespace { + +// This class is used to get quota usage callback. +class EstimateResolver final + : public nsIQuotaUsageCallback +{ + class FinishWorkerRunnable; + + // If this resolver was created for a window then mPromise must be non-null. + // Otherwise mProxy must be non-null. + RefPtr<Promise> mPromise; + RefPtr<PromiseWorkerProxy> mProxy; + + nsresult mResultCode; + StorageEstimate mStorageEstimate; + +public: + explicit EstimateResolver(Promise* aPromise) + : mPromise(aPromise) + , mResultCode(NS_OK) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPromise); + } + + explicit EstimateResolver(PromiseWorkerProxy* aProxy) + : mProxy(aProxy) + , mResultCode(NS_OK) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aProxy); + } + + void + ResolveOrReject(Promise* aPromise); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIQUOTAUSAGECALLBACK + +private: + ~EstimateResolver() + { } +}; + +// This class is used to return promise on worker thread. +class EstimateResolver::FinishWorkerRunnable final + : public WorkerRunnable +{ + RefPtr<EstimateResolver> mResolver; + +public: + explicit FinishWorkerRunnable(EstimateResolver* aResolver) + : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate()) + , mResolver(aResolver) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aResolver); + } + + virtual bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; +}; + +class EstimateWorkerMainThreadRunnable + : public WorkerMainThreadRunnable +{ + RefPtr<PromiseWorkerProxy> mProxy; + +public: + EstimateWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aProxy) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("StorageManager :: Estimate")) + , mProxy(aProxy) + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aProxy); + } + + virtual bool + MainThreadRun() override; +}; + +nsresult +GetUsageForPrincipal(nsIPrincipal* aPrincipal, + nsIQuotaUsageCallback* aCallback, + nsIQuotaUsageRequest** aRequest) +{ + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aRequest); + + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + if (NS_WARN_IF(!qms)) { + return NS_ERROR_FAILURE; + } + + nsresult rv = qms->GetUsageForPrincipal(aPrincipal, aCallback, true, aRequest); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +}; + +nsresult +GetStorageEstimate(nsIQuotaUsageRequest* aRequest, + StorageEstimate& aStorageEstimate) +{ + MOZ_ASSERT(aRequest); + + nsCOMPtr<nsIVariant> result; + nsresult rv = aRequest->GetResult(getter_AddRefs(result)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsID* iid; + nsCOMPtr<nsISupports> supports; + rv = result->GetAsInterface(&iid, getter_AddRefs(supports)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + free(iid); + + nsCOMPtr<nsIQuotaOriginUsageResult> originUsageResult = + do_QueryInterface(supports); + MOZ_ASSERT(originUsageResult); + + MOZ_ALWAYS_SUCCEEDS( + originUsageResult->GetUsage(&aStorageEstimate.mUsage.Construct())); + + MOZ_ALWAYS_SUCCEEDS( + originUsageResult->GetLimit(&aStorageEstimate.mQuota.Construct())); + + return NS_OK; +} + +} // namespace + +/******************************************************************************* + * Local class implementations + ******************************************************************************/ + +void +EstimateResolver::ResolveOrReject(Promise* aPromise) +{ + MOZ_ASSERT(aPromise); + + if (NS_SUCCEEDED(mResultCode)) { + aPromise->MaybeResolve(mStorageEstimate); + } else { + aPromise->MaybeReject(mResultCode); + } +} + +NS_IMPL_ISUPPORTS(EstimateResolver, nsIQuotaUsageCallback) + +NS_IMETHODIMP +EstimateResolver::OnUsageResult(nsIQuotaUsageRequest *aRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRequest); + + nsresult rv = aRequest->GetResultCode(&mResultCode); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResultCode = rv; + } else if (NS_SUCCEEDED(mResultCode)) { + rv = GetStorageEstimate(aRequest, mStorageEstimate); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResultCode = rv; + } + } + + // In a main thread request. + if (!mProxy) { + MOZ_ASSERT(mPromise); + + ResolveOrReject(mPromise); + return NS_OK; + } + + // In a worker thread request. + MutexAutoLock lock(mProxy->Lock()); + + if (NS_WARN_IF(mProxy->CleanedUp())) { + return NS_ERROR_FAILURE; + } + + RefPtr<FinishWorkerRunnable> runnable = new FinishWorkerRunnable(this); + if (NS_WARN_IF(!runnable->Dispatch())) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +EstimateResolver:: +FinishWorkerRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) +{ + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr<PromiseWorkerProxy> proxy = mResolver->mProxy; + MOZ_ASSERT(proxy); + + RefPtr<Promise> promise = proxy->WorkerPromise(); + MOZ_ASSERT(promise); + + mResolver->ResolveOrReject(promise); + + proxy->CleanUp(); + + return true; +} + +bool +EstimateWorkerMainThreadRunnable::MainThreadRun() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIPrincipal> principal; + + { + MutexAutoLock lock(mProxy->Lock()); + if (mProxy->CleanedUp()) { + return true; + } + principal = mProxy->GetWorkerPrivate()->GetPrincipal(); + } + + MOZ_ASSERT(principal); + + RefPtr<EstimateResolver> resolver = new EstimateResolver(mProxy); + + RefPtr<nsIQuotaUsageRequest> request; + nsresult rv = + GetUsageForPrincipal(principal, resolver, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +/******************************************************************************* + * StorageManager + ******************************************************************************/ + +StorageManager::StorageManager(nsIGlobalObject* aGlobal) + : mOwner(aGlobal) +{ + MOZ_ASSERT(aGlobal); +} + +StorageManager::~StorageManager() +{ +} + +already_AddRefed<Promise> +StorageManager::Estimate(ErrorResult& aRv) +{ + MOZ_ASSERT(mOwner); + + RefPtr<Promise> promise = Promise::Create(mOwner, aRv); + if (NS_WARN_IF(!promise)) { + return nullptr; + } + + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mOwner); + if (NS_WARN_IF(!window)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); + MOZ_ASSERT(principal); + + RefPtr<EstimateResolver> resolver = new EstimateResolver(promise); + + RefPtr<nsIQuotaUsageRequest> request; + nsresult rv = + GetUsageForPrincipal(principal, resolver, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + return promise.forget(); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + RefPtr<PromiseWorkerProxy> promiseProxy = + PromiseWorkerProxy::Create(workerPrivate, promise); + if (NS_WARN_IF(!promiseProxy)) { + return nullptr; + } + + RefPtr<EstimateWorkerMainThreadRunnable> runnnable = + new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(), + promiseProxy); + + runnnable->Dispatch(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return promise.forget(); +} + +// static +bool +StorageManager::PrefEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.storageManager.enabled"); + } + + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(workerPrivate); + + return workerPrivate->StorageManagerEnabled(); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(StorageManager, mOwner) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(StorageManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(StorageManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StorageManager) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +StorageManager::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return StorageManagerBinding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/quota/StorageManager.h b/dom/quota/StorageManager.h new file mode 100644 index 000000000..162390f48 --- /dev/null +++ b/dom/quota/StorageManager.h @@ -0,0 +1,59 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace dom { + +class Promise; +struct StorageEstimate; + +class StorageManager final + : public nsISupports + , public nsWrapperCache +{ + nsCOMPtr<nsIGlobalObject> mOwner; + +public: + // Return dom.quota.storageManager.enabled on main/worker thread. + static bool + PrefEnabled(JSContext* aCx, JSObject* aObj); + + explicit + StorageManager(nsIGlobalObject* aGlobal); + + nsIGlobalObject* + GetParentObject() const + { + return mOwner; + } + + // WebIDL + already_AddRefed<Promise> + Estimate(ErrorResult& aRv); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(StorageManager) + + // nsWrapperCache + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + +private: + ~StorageManager(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_StorageManager_h diff --git a/dom/quota/UsageInfo.h b/dom/quota/UsageInfo.h new file mode 100644 index 000000000..9d34f1bff --- /dev/null +++ b/dom/quota/UsageInfo.h @@ -0,0 +1,108 @@ +/* -*- 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_quota_usageinfo_h__ +#define mozilla_dom_quota_usageinfo_h__ + +#include "mozilla/dom/quota/QuotaCommon.h" + +#include "mozilla/Atomics.h" +#include "mozilla/CheckedInt.h" + +BEGIN_QUOTA_NAMESPACE + +class UsageInfo +{ +public: + UsageInfo() + : mDatabaseUsage(0) + , mFileUsage(0) + , mLimit(0) + { } + + virtual ~UsageInfo() + { } + + void + Append(const UsageInfo& aUsageInfo) + { + IncrementUsage(&mDatabaseUsage, aUsageInfo.mDatabaseUsage); + IncrementUsage(&mFileUsage, aUsageInfo.mFileUsage); + } + + void + AppendToDatabaseUsage(uint64_t aUsage) + { + IncrementUsage(&mDatabaseUsage, aUsage); + } + + void + AppendToFileUsage(uint64_t aUsage) + { + IncrementUsage(&mFileUsage, aUsage); + } + + void + SetLimit(uint64_t aLimit) + { + mLimit = aLimit; + } + + uint64_t + DatabaseUsage() + { + return mDatabaseUsage; + } + + uint64_t + FileUsage() + { + return mFileUsage; + } + + uint64_t + Limit() + { + return mLimit; + } + + uint64_t + TotalUsage() + { + uint64_t totalUsage = mDatabaseUsage; + IncrementUsage(&totalUsage, mFileUsage); + return totalUsage; + } + + void + ResetUsage() + { + mDatabaseUsage = 0; + mFileUsage = 0; + } + + static void + IncrementUsage(uint64_t* aUsage, uint64_t aDelta) + { + MOZ_ASSERT(aUsage); + CheckedUint64 value = *aUsage; + value += aDelta; + if (value.isValid()) { + *aUsage = value.value(); + } else { + *aUsage = UINT64_MAX; + } + } + +private: + uint64_t mDatabaseUsage; + uint64_t mFileUsage; + uint64_t mLimit; +}; + +END_QUOTA_NAMESPACE + +#endif // mozilla_dom_quota_usageinfo_h__ diff --git a/dom/quota/moz.build b/dom/quota/moz.build new file mode 100644 index 000000000..66c4f4f45 --- /dev/null +++ b/dom/quota/moz.build @@ -0,0 +1,59 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'nsIQuotaCallbacks.idl', + 'nsIQuotaManagerService.idl', + 'nsIQuotaRequests.idl', + 'nsIQuotaResults.idl', +] + +XPIDL_MODULE = 'dom_quota' + +EXPORTS.mozilla.dom += [ + 'StorageManager.h', +] + +EXPORTS.mozilla.dom.quota += [ + 'ActorsParent.h', + 'Client.h', + 'FileStreams.h', + 'OriginScope.h', + 'PersistenceType.h', + 'QuotaCommon.h', + 'QuotaManager.h', + 'QuotaManagerService.h', + 'QuotaObject.h', + 'SerializationHelpers.h', + 'UsageInfo.h', +] + +UNIFIED_SOURCES += [ + 'ActorsChild.cpp', + 'ActorsParent.cpp', + 'FileStreams.cpp', + 'QuotaManagerService.cpp', + 'QuotaRequests.cpp', + 'QuotaResults.cpp', + 'StorageManager.cpp', +] + +IPDL_SOURCES += [ + 'PQuota.ipdl', + 'PQuotaRequest.ipdl', + 'PQuotaUsageRequest.ipdl', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../workers', + '/caps', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/quota/nsIQuotaCallbacks.idl b/dom/quota/nsIQuotaCallbacks.idl new file mode 100644 index 000000000..7c53db20c --- /dev/null +++ b/dom/quota/nsIQuotaCallbacks.idl @@ -0,0 +1,22 @@ +/* -*- 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 nsIQuotaRequest; +interface nsIQuotaUsageRequest; + +[scriptable, function, uuid(c8a21a2a-17b9-4b63-ad95-e0fbcff5de18)] +interface nsIQuotaUsageCallback : nsISupports +{ + void onUsageResult(in nsIQuotaUsageRequest aRequest); +}; + +[scriptable, function, uuid(a08a28e2-5a74-4c84-8070-ed45a07eb013)] +interface nsIQuotaCallback : nsISupports +{ + void onComplete(in nsIQuotaRequest aRequest); +}; diff --git a/dom/quota/nsIQuotaManagerService.idl b/dom/quota/nsIQuotaManagerService.idl new file mode 100644 index 000000000..f24ce2833 --- /dev/null +++ b/dom/quota/nsIQuotaManagerService.idl @@ -0,0 +1,91 @@ +/* -*- 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; +interface nsIQuotaRequest; +interface nsIQuotaUsageCallback; +interface nsIQuotaUsageRequest; + +[scriptable, builtinclass, uuid(1b3d0a38-8151-4cf9-89fa-4f92c2ef0e7e)] +interface nsIQuotaManagerService : nsISupports +{ + /** + * Schedules an asynchronous callback that will inspect all origins and + * return the total amount of disk space being used by storages for each + * origin separately. + * + * @param aCallback + * The callback that will be called when the usage is available. + * @param aGetAll + * An optional boolean to indicate inspection of all origins, + * including internal ones. + */ + [must_use] nsIQuotaUsageRequest + getUsage(in nsIQuotaUsageCallback aCallback, + [optional] in boolean aGetAll); + + /** + * Schedules an asynchronous callback that will return the total amount of + * disk space being used by storages for the given origin. + * + * @param aPrincipal + * A principal for the origin whose usage is being queried. + * @param aCallback + * The callback that will be called when the usage is available. + * @param aGetGroupUsage + * An optional flag to indicate whether getting group usage and limit + * or origin usage and file usage. The default value is false. + * Note: Origin usage here represents total usage of an origin. However, + * group usage here represents only non-persistent usage of a group. + */ + [must_use] nsIQuotaUsageRequest + getUsageForPrincipal(in nsIPrincipal aPrincipal, + in nsIQuotaUsageCallback aCallback, + [optional] in boolean aGetGroupUsage); + + /** + * Removes all storages. The files may not be deleted immediately depending + * on prohibitive concurrent operations. + * Be careful, this removes *all* the data that has ever been stored! + * + * If the dom.quotaManager.testing preference is not true the call will be + * a no-op. + */ + [must_use] nsIQuotaRequest + clear(); + + /** + * Removes all storages stored for the given URI. The files may not be + * deleted immediately depending on prohibitive concurrent operations. + * + * @param aPrincipal + * A principal for the origin whose storages are to be cleared. + * @param aPersistenceType + * An optional string that tells what persistence type of storages + * will be cleared. + * @param aClearAll + * An optional boolean to indicate clearing all storages under the + * given origin. + */ + [must_use] nsIQuotaRequest + clearStoragesForPrincipal(in nsIPrincipal aPrincipal, + [optional] in ACString aPersistenceType, + [optional] in boolean aClearAll); + + /** + * Resets quota and storage management. This can be used to force + * reinitialization of the temp storage, for example when the pref for + * overriding the temp storage limit has changed. + * Be carefull, this invalidates all live storages! + * + * If the dom.quotaManager.testing preference is not true the call will be + * a no-op. + */ + [must_use] nsIQuotaRequest + reset(); +}; diff --git a/dom/quota/nsIQuotaRequests.idl b/dom/quota/nsIQuotaRequests.idl new file mode 100644 index 000000000..155486217 --- /dev/null +++ b/dom/quota/nsIQuotaRequests.idl @@ -0,0 +1,40 @@ +/* -*- 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; +interface nsIQuotaCallback; +interface nsIQuotaUsageCallback; +interface nsIVariant; + +[scriptable, uuid(9af54222-0407-48fd-a4ab-9457c986fc49)] +interface nsIQuotaRequestBase : nsISupports +{ + readonly attribute nsIPrincipal principal; + + [must_use] readonly attribute nsresult resultCode; +}; + +[scriptable, uuid(166e28e6-cf6d-4927-a6d7-b51bca9d3469)] +interface nsIQuotaUsageRequest : nsIQuotaRequestBase +{ + // The result can contain one of these types: + // array of nsIQuotaUsageResult + // nsIQuotaOriginUsageResult + [must_use] readonly attribute nsIVariant result; + + attribute nsIQuotaUsageCallback callback; + + [must_use] void + cancel(); +}; + +[scriptable, uuid(22890e3e-ff25-4372-9684-d901060e2f6c)] +interface nsIQuotaRequest : nsIQuotaRequestBase +{ + attribute nsIQuotaCallback callback; +}; diff --git a/dom/quota/nsIQuotaResults.idl b/dom/quota/nsIQuotaResults.idl new file mode 100644 index 000000000..cd7ffd3a0 --- /dev/null +++ b/dom/quota/nsIQuotaResults.idl @@ -0,0 +1,27 @@ +/* -*- 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" + +[scriptable, function, uuid(d8c9328b-9aa8-4f5d-90e6-482de4a6d5b8)] +interface nsIQuotaUsageResult : nsISupports +{ + readonly attribute ACString origin; + + readonly attribute boolean persisted; + + readonly attribute unsigned long long usage; +}; + +[scriptable, function, uuid(96df03d2-116a-493f-bb0b-118c212a6b32)] +interface nsIQuotaOriginUsageResult : nsISupports +{ + readonly attribute unsigned long long usage; + + readonly attribute unsigned long long fileUsage; + + readonly attribute unsigned long long limit; +}; |