diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/quota/StorageManager.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/quota/StorageManager.cpp')
-rw-r--r-- | dom/quota/StorageManager.cpp | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/dom/quota/StorageManager.cpp b/dom/quota/StorageManager.cpp new file mode 100644 index 0000000000..bf05265bc0 --- /dev/null +++ b/dom/quota/StorageManager.cpp @@ -0,0 +1,808 @@ +/* -*- 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 "fs/FileSystemRequestHandler.h" + +#include <cstdint> +#include <cstdlib> +#include <utility> +#include "ErrorList.h" +#include "fs/FileSystemRequestHandler.h" +#include "MainThreadUtils.h" +#include "js/CallArgs.h" +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryScalarEnums.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseWorkerProxy.h" +#include "mozilla/dom/StorageManagerBinding.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/WorkerStatus.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "nsContentPermissionHelper.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIGlobalObject.h" +#include "nsIPrincipal.h" +#include "nsIQuotaCallbacks.h" +#include "nsIQuotaManagerService.h" +#include "nsIQuotaRequests.h" +#include "nsIQuotaResults.h" +#include "nsIVariant.h" +#include "nsLiteralString.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" +#include "nsStringFlags.h" +#include "nsTLiteralString.h" +#include "nscore.h" + +class JSObject; +struct JSContext; +struct nsID; + +namespace mozilla { +class Runnable; +} + +using namespace mozilla::dom::quota; + +namespace mozilla::dom { + +namespace { + +// This class is used to get quota usage, request persist and check persisted +// status callbacks. +class RequestResolver final : public nsIQuotaCallback { + public: + enum Type { Estimate, Persist, Persisted }; + + private: + 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; + const Type mType; + bool mPersisted; + + public: + RequestResolver(Type aType, Promise* aPromise) + : mPromise(aPromise), + mResultCode(NS_OK), + mType(aType), + mPersisted(false) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPromise); + } + + RequestResolver(Type aType, PromiseWorkerProxy* aProxy) + : mProxy(aProxy), mResultCode(NS_OK), mType(aType), mPersisted(false) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aProxy); + } + + void ResolveOrReject(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIQUOTACALLBACK + + private: + ~RequestResolver() = default; + + nsresult GetStorageEstimate(nsIVariant* aResult); + + nsresult GetPersisted(nsIVariant* aResult); + + nsresult OnCompleteInternal(nsIQuotaRequest* aRequest); + + nsresult Finish(); +}; + +// This class is used to return promise on worker thread. +class RequestResolver::FinishWorkerRunnable final : public WorkerRunnable { + RefPtr<RequestResolver> mResolver; + + public: + explicit FinishWorkerRunnable(RequestResolver* aResolver) + : WorkerRunnable(aResolver->mProxy->GetWorkerPrivate()), + mResolver(aResolver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aResolver); + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; +}; + +class EstimateWorkerMainThreadRunnable final : public WorkerMainThreadRunnable { + RefPtr<PromiseWorkerProxy> mProxy; + + public: + EstimateWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aProxy) + : WorkerMainThreadRunnable(aWorkerPrivate, + "StorageManager :: Estimate"_ns), + mProxy(aProxy) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aProxy); + } + + bool MainThreadRun() override; +}; + +class PersistedWorkerMainThreadRunnable final + : public WorkerMainThreadRunnable { + RefPtr<PromiseWorkerProxy> mProxy; + + public: + PersistedWorkerMainThreadRunnable(WorkerPrivate* aWorkerPrivate, + PromiseWorkerProxy* aProxy) + : WorkerMainThreadRunnable(aWorkerPrivate, + "StorageManager :: Persisted"_ns), + mProxy(aProxy) { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aProxy); + } + + bool MainThreadRun() override; +}; + +/******************************************************************************* + * PersistentStoragePermissionRequest + ******************************************************************************/ + +class PersistentStoragePermissionRequest final + : public ContentPermissionRequestBase { + RefPtr<Promise> mPromise; + + public: + PersistentStoragePermissionRequest(nsIPrincipal* aPrincipal, + nsPIDOMWindowInner* aWindow, + Promise* aPromise) + : ContentPermissionRequestBase(aPrincipal, aWindow, + "dom.storageManager"_ns, + "persistent-storage"_ns), + mPromise(aPromise) { + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aPromise); + } + + nsresult Start(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PersistentStoragePermissionRequest, + ContentPermissionRequestBase) + + // nsIContentPermissionRequest + NS_IMETHOD Cancel(void) override; + NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override; + + private: + ~PersistentStoragePermissionRequest() = default; +}; + +nsresult Estimate(nsIPrincipal* aPrincipal, nsIQuotaCallback* aCallback, + nsIQuotaRequest** aRequest) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aRequest); + + // Firefox and Quota Manager have always used the schemeless origin group + // (https://storage.spec.whatwg.org/#schemeless-origin-group) for quota limit + // purposes. This has been to prevent a site/eTLD+1 from claiming more than + // its fair share of storage through the use of sub-domains. Because the limit + // is at the group level and the usage needs to make sense in the context of + // that limit, we also expose the group usage. Bug 1374970 reflects this + // reality and bug 1305665 tracks our plan to eliminate our use of groups for + // this. + + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + if (NS_WARN_IF(!qms)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIQuotaRequest> request; + nsresult rv = qms->Estimate(aPrincipal, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ALWAYS_SUCCEEDS(request->SetCallback(aCallback)); + + request.forget(aRequest); + return NS_OK; +}; + +nsresult Persisted(nsIPrincipal* aPrincipal, nsIQuotaCallback* aCallback, + nsIQuotaRequest** aRequest) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCallback); + MOZ_ASSERT(aRequest); + + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + if (NS_WARN_IF(!qms)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIQuotaRequest> request; + nsresult rv = qms->Persisted(aPrincipal, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // All the methods in nsIQuotaManagerService shouldn't synchronously fire + // any callbacks when they are being executed. Even when a result is ready, + // a new runnable should be dispatched to current thread to fire the callback + // asynchronously. It's safe to set the callback after we call Persisted(). + MOZ_ALWAYS_SUCCEEDS(request->SetCallback(aCallback)); + + request.forget(aRequest); + + return NS_OK; +}; + +already_AddRefed<Promise> ExecuteOpOnMainOrWorkerThread( + nsIGlobalObject* aGlobal, RequestResolver::Type aType, ErrorResult& aRv) { + MOZ_ASSERT(aGlobal); + MOZ_ASSERT_IF(aType == RequestResolver::Type::Persist, NS_IsMainThread()); + + RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); + if (NS_WARN_IF(!promise)) { + return nullptr; + } + + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal); + if (NS_WARN_IF(!window)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<Document> doc = window->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); + MOZ_ASSERT(principal); + + // Storage Standard 7. API + // If origin is an opaque origin, then reject promise with a TypeError. + if (principal->GetIsNullPrincipal()) { + switch (aType) { + case RequestResolver::Type::Persisted: + promise->MaybeRejectWithTypeError( + "persisted() called for opaque origin"); + break; + case RequestResolver::Type::Persist: + promise->MaybeRejectWithTypeError( + "persist() called for opaque origin"); + break; + case RequestResolver::Type::Estimate: + promise->MaybeRejectWithTypeError( + "estimate() called for opaque origin"); + break; + } + + return promise.forget(); + } + + switch (aType) { + case RequestResolver::Type::Persisted: { + RefPtr<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Persisted, promise); + + RefPtr<nsIQuotaRequest> request; + aRv = Persisted(principal, resolver, getter_AddRefs(request)); + + break; + } + + case RequestResolver::Type::Persist: { + RefPtr<PersistentStoragePermissionRequest> request = + new PersistentStoragePermissionRequest(principal, window, promise); + + // In private browsing mode, no permission prompt. + if (nsContentUtils::IsInPrivateBrowsing(doc)) { + aRv = request->Cancel(); + } else if (!request->CheckPermissionDelegate()) { + aRv = request->Cancel(); + } else { + aRv = request->Start(); + } + + break; + } + + case RequestResolver::Type::Estimate: { + RefPtr<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Estimate, promise); + + RefPtr<nsIQuotaRequest> request; + aRv = Estimate(principal, resolver, getter_AddRefs(request)); + + break; + } + + default: + MOZ_CRASH("Invalid aRequest type!"); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return promise.forget(); + } + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + RefPtr<PromiseWorkerProxy> promiseProxy = + PromiseWorkerProxy::Create(workerPrivate, promise); + if (NS_WARN_IF(!promiseProxy)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + switch (aType) { + case RequestResolver::Type::Estimate: { + RefPtr<EstimateWorkerMainThreadRunnable> runnnable = + new EstimateWorkerMainThreadRunnable(promiseProxy->GetWorkerPrivate(), + promiseProxy); + runnnable->Dispatch(Canceling, aRv); + + break; + } + + case RequestResolver::Type::Persisted: { + RefPtr<PersistedWorkerMainThreadRunnable> runnnable = + new PersistedWorkerMainThreadRunnable( + promiseProxy->GetWorkerPrivate(), promiseProxy); + runnnable->Dispatch(Canceling, aRv); + + break; + } + + default: + MOZ_CRASH("Invalid aRequest type"); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return promise.forget(); +}; + +} // namespace + +/******************************************************************************* + * Local class implementations + ******************************************************************************/ + +void RequestResolver::ResolveOrReject() { + class MOZ_STACK_CLASS AutoCleanup final { + RefPtr<PromiseWorkerProxy> mProxy; + + public: + explicit AutoCleanup(PromiseWorkerProxy* aProxy) : mProxy(aProxy) { + MOZ_ASSERT(aProxy); + } + + ~AutoCleanup() { + MOZ_ASSERT(mProxy); + + mProxy->CleanUp(); + } + }; + + RefPtr<Promise> promise; + Maybe<AutoCleanup> autoCleanup; + + if (mPromise) { + promise = mPromise; + } else { + MOZ_ASSERT(mProxy); + + promise = mProxy->WorkerPromise(); + + // Only clean up for worker case. + autoCleanup.emplace(mProxy); + } + + MOZ_ASSERT(promise); + + if (mType == Type::Estimate) { + if (NS_SUCCEEDED(mResultCode)) { + promise->MaybeResolve(mStorageEstimate); + } else { + promise->MaybeRejectWithTypeError( + "Internal error while estimating storage usage"); + } + + return; + } + + MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted); + + if (NS_SUCCEEDED(mResultCode)) { + promise->MaybeResolve(mPersisted); + } else { + promise->MaybeResolve(false); + } +} + +NS_IMPL_ISUPPORTS(RequestResolver, nsIQuotaCallback) + +nsresult RequestResolver::GetStorageEstimate(nsIVariant* aResult) { + MOZ_ASSERT(aResult); + MOZ_ASSERT(mType == Type::Estimate); + + MOZ_ASSERT(aResult->GetDataType() == nsIDataType::VTYPE_INTERFACE_IS); + + nsID* iid; + nsCOMPtr<nsISupports> supports; + nsresult rv = aResult->GetAsInterface(&iid, getter_AddRefs(supports)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + free(iid); + + nsCOMPtr<nsIQuotaEstimateResult> estimateResult = do_QueryInterface(supports); + MOZ_ASSERT(estimateResult); + + MOZ_ALWAYS_SUCCEEDS( + estimateResult->GetUsage(&mStorageEstimate.mUsage.Construct())); + + MOZ_ALWAYS_SUCCEEDS( + estimateResult->GetLimit(&mStorageEstimate.mQuota.Construct())); + + return NS_OK; +} + +nsresult RequestResolver::GetPersisted(nsIVariant* aResult) { + MOZ_ASSERT(aResult); + MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted); + +#ifdef DEBUG + uint16_t dataType = aResult->GetDataType(); +#endif + + if (mType == Type::Persist) { + MOZ_ASSERT(dataType == nsIDataType::VTYPE_VOID); + + mPersisted = true; + return NS_OK; + } + + MOZ_ASSERT(dataType == nsIDataType::VTYPE_BOOL); + + bool persisted; + nsresult rv = aResult->GetAsBool(&persisted); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mPersisted = persisted; + return NS_OK; +} + +nsresult RequestResolver::OnCompleteInternal(nsIQuotaRequest* aRequest) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRequest); + + nsresult resultCode; + nsresult rv = aRequest->GetResultCode(&resultCode); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_FAILED(resultCode)) { + return resultCode; + } + + nsCOMPtr<nsIVariant> result; + rv = aRequest->GetResult(getter_AddRefs(result)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mType == Type::Estimate) { + rv = GetStorageEstimate(result); + } else { + MOZ_ASSERT(mType == Type::Persist || mType == Type::Persisted); + + rv = GetPersisted(result); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult RequestResolver::Finish() { + // In a main thread request. + if (!mProxy) { + MOZ_ASSERT(mPromise); + + ResolveOrReject(); + 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; +} + +NS_IMETHODIMP +RequestResolver::OnComplete(nsIQuotaRequest* aRequest) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRequest); + + mResultCode = OnCompleteInternal(aRequest); + + nsresult rv = Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +bool RequestResolver::FinishWorkerRunnable::WorkerRun( + JSContext* aCx, WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aCx); + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(mResolver); + mResolver->ResolveOrReject(); + + 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<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Estimate, mProxy); + + RefPtr<nsIQuotaRequest> request; + nsresult rv = Estimate(principal, resolver, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +bool PersistedWorkerMainThreadRunnable::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<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Persisted, mProxy); + + RefPtr<nsIQuotaRequest> request; + nsresult rv = Persisted(principal, resolver, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +nsresult PersistentStoragePermissionRequest::Start() { + MOZ_ASSERT(NS_IsMainThread()); + + PromptResult pr; +#ifdef MOZ_WIDGET_ANDROID + // on Android calling `ShowPrompt` here calls + // `nsContentPermissionUtils::AskPermission` once, and a response of + // `PromptResult::Pending` calls it again. This results in multiple requests + // for storage access, so we check the prompt prefs only to ensure we only + // request it once. + pr = CheckPromptPrefs(); +#else + nsresult rv = ShowPrompt(pr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + if (pr == PromptResult::Granted) { + return Allow(JS::UndefinedHandleValue); + } + if (pr == PromptResult::Denied) { + return Cancel(); + } + + return nsContentPermissionUtils::AskPermission(this, mWindow); +} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0( + PersistentStoragePermissionRequest, ContentPermissionRequestBase) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(PersistentStoragePermissionRequest, + ContentPermissionRequestBase, mPromise) + +NS_IMETHODIMP +PersistentStoragePermissionRequest::Cancel() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPromise); + + RefPtr<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Persisted, mPromise); + + RefPtr<nsIQuotaRequest> request; + + return Persisted(mPrincipal, resolver, getter_AddRefs(request)); +} + +NS_IMETHODIMP +PersistentStoragePermissionRequest::Allow(JS::Handle<JS::Value> aChoices) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<RequestResolver> resolver = + new RequestResolver(RequestResolver::Type::Persist, mPromise); + + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + if (NS_WARN_IF(!qms)) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsIQuotaRequest> request; + + nsresult rv = qms->Persist(mPrincipal, getter_AddRefs(request)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ALWAYS_SUCCEEDS(request->SetCallback(resolver)); + + return NS_OK; +} + +/******************************************************************************* + * StorageManager + ******************************************************************************/ + +StorageManager::StorageManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) { + MOZ_ASSERT(aGlobal); +} + +StorageManager::~StorageManager() { Shutdown(); } + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StorageManager) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(StorageManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(StorageManager) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(StorageManager) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(StorageManager) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(StorageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileSystemManager) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void StorageManager::Shutdown() { + if (mFileSystemManager) { + mFileSystemManager->Shutdown(); + mFileSystemManager = nullptr; + } +} + +already_AddRefed<FileSystemManager> StorageManager::GetFileSystemManager() { + if (!mFileSystemManager) { + MOZ_ASSERT(mOwner); + + mFileSystemManager = MakeRefPtr<FileSystemManager>(mOwner, this); + } + + return do_AddRef(mFileSystemManager); +} + +// WebIDL Boilerplate + +JSObject* StorageManager::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return StorageManager_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +already_AddRefed<Promise> StorageManager::Persisted(ErrorResult& aRv) { + MOZ_ASSERT(mOwner); + + return ExecuteOpOnMainOrWorkerThread(mOwner, RequestResolver::Type::Persisted, + aRv); +} + +already_AddRefed<Promise> StorageManager::Persist(ErrorResult& aRv) { + MOZ_ASSERT(mOwner); + + Telemetry::ScalarAdd(Telemetry::ScalarID::NAVIGATOR_STORAGE_PERSIST_COUNT, 1); + return ExecuteOpOnMainOrWorkerThread(mOwner, RequestResolver::Type::Persist, + aRv); +} + +already_AddRefed<Promise> StorageManager::Estimate(ErrorResult& aRv) { + MOZ_ASSERT(mOwner); + + Telemetry::ScalarAdd(Telemetry::ScalarID::NAVIGATOR_STORAGE_ESTIMATE_COUNT, + 1); + return ExecuteOpOnMainOrWorkerThread(mOwner, RequestResolver::Type::Estimate, + aRv); +} + +already_AddRefed<Promise> StorageManager::GetDirectory(ErrorResult& aRv) { + return RefPtr(GetFileSystemManager())->GetDirectory(aRv); +} + +} // namespace mozilla::dom |