diff options
Diffstat (limited to '')
-rw-r--r-- | dom/serviceworkers/ServiceWorkerQuotaUtils.cpp | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/dom/serviceworkers/ServiceWorkerQuotaUtils.cpp b/dom/serviceworkers/ServiceWorkerQuotaUtils.cpp new file mode 100644 index 0000000000..65e051eb40 --- /dev/null +++ b/dom/serviceworkers/ServiceWorkerQuotaUtils.cpp @@ -0,0 +1,327 @@ +/* -*- 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 "MainThreadUtils.h" +#include "ServiceWorkerQuotaUtils.h" + +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "nsIClearDataService.h" +#include "nsID.h" +#include "nsIPrincipal.h" +#include "nsIQuotaCallbacks.h" +#include "nsIQuotaRequests.h" +#include "nsIQuotaResults.h" +#include "nsISupports.h" +#include "nsIVariant.h" +#include "nsServiceManagerUtils.h" + +using mozilla::dom::quota::QuotaManagerService; + +namespace mozilla::dom { + +/* + * QuotaUsageChecker implements the quota usage checking algorithm. + * + * 1. Getting the given origin/group usage through QuotaManagerService. + * QuotaUsageCheck::Start() implements this step. + * 2. Checking if the group usage headroom is satisfied. + * It could be following three situations. + * a. Group headroom is satisfied without any usage mitigation. + * b. Group headroom is satisfied after origin usage mitigation. + * This invokes nsIClearDataService::DeleteDataFromPrincipal(). + * c. Group headroom is satisfied after group usage mitigation. + * This invokes nsIClearDataService::DeleteDataFromBaseDomain(). + * QuotaUsageChecker::CheckQuotaHeadroom() implements this step. + * + * If the algorithm is done or error out, the QuotaUsageCheck::mCallback will + * be called with a bool result for external handling. + */ +class QuotaUsageChecker final : public nsIQuotaCallback, + public nsIQuotaUsageCallback, + public nsIClearDataCallback { + public: + NS_DECL_ISUPPORTS + // For QuotaManagerService::Estimate() + NS_DECL_NSIQUOTACALLBACK + + // For QuotaManagerService::GetUsageForPrincipal() + NS_DECL_NSIQUOTAUSAGECALLBACK + + // For nsIClearDataService::DeleteDataFromPrincipal() and + // nsIClearDataService::DeleteDataFromBaseDomain() + NS_DECL_NSICLEARDATACALLBACK + + QuotaUsageChecker(nsIPrincipal* aPrincipal, + ServiceWorkerQuotaMitigationCallback&& aCallback); + + void Start(); + + void RunCallback(bool aResult); + + private: + ~QuotaUsageChecker() = default; + + // This is a general help method to get nsIQuotaResult/nsIQuotaUsageResult + // from nsIQuotaRequest/nsIQuotaUsageRequest + template <typename T, typename U> + nsresult GetResult(T* aRequest, U&); + + void CheckQuotaHeadroom(); + + nsCOMPtr<nsIPrincipal> mPrincipal; + + // The external callback. Calling RunCallback(bool) instead of calling it + // directly, RunCallback(bool) handles the internal status. + ServiceWorkerQuotaMitigationCallback mCallback; + bool mGettingOriginUsageDone; + bool mGettingGroupUsageDone; + bool mIsChecking; + uint64_t mOriginUsage; + uint64_t mGroupUsage; + uint64_t mGroupLimit; +}; + +NS_IMPL_ISUPPORTS(QuotaUsageChecker, nsIQuotaCallback, nsIQuotaUsageCallback, + nsIClearDataCallback) + +QuotaUsageChecker::QuotaUsageChecker( + nsIPrincipal* aPrincipal, ServiceWorkerQuotaMitigationCallback&& aCallback) + : mPrincipal(aPrincipal), + mCallback(std::move(aCallback)), + mGettingOriginUsageDone(false), + mGettingGroupUsageDone(false), + mIsChecking(false), + mOriginUsage(0), + mGroupUsage(0), + mGroupLimit(0) {} + +void QuotaUsageChecker::Start() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mIsChecking) { + return; + } + mIsChecking = true; + + RefPtr<QuotaUsageChecker> self = this; + auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); + + RefPtr<QuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + MOZ_ASSERT(qms); + + // Asynchronious getting quota usage for principal + nsCOMPtr<nsIQuotaUsageRequest> usageRequest; + if (NS_WARN_IF(NS_FAILED(qms->GetUsageForPrincipal( + mPrincipal, this, false, getter_AddRefs(usageRequest))))) { + return; + } + + // Asynchronious getting group usage and limit + nsCOMPtr<nsIQuotaRequest> request; + if (NS_WARN_IF( + NS_FAILED(qms->Estimate(mPrincipal, getter_AddRefs(request))))) { + return; + } + request->SetCallback(this); + + scopeExit.release(); +} + +void QuotaUsageChecker::RunCallback(bool aResult) { + MOZ_ASSERT(mIsChecking && mCallback); + if (!mIsChecking) { + return; + } + mIsChecking = false; + mGettingOriginUsageDone = false; + mGettingGroupUsageDone = false; + + mCallback(aResult); + mCallback = nullptr; +} + +template <typename T, typename U> +nsresult QuotaUsageChecker::GetResult(T* aRequest, U& aResult) { + 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); + + aResult = do_QueryInterface(supports); + return NS_OK; +} + +void QuotaUsageChecker::CheckQuotaHeadroom() { + MOZ_ASSERT(NS_IsMainThread()); + + const uint64_t groupHeadroom = + static_cast<uint64_t>( + StaticPrefs:: + dom_serviceWorkers_mitigations_group_usage_headroom_kb()) * + 1024; + const uint64_t groupAvailable = mGroupLimit - mGroupUsage; + + // Group usage head room is satisfied, does not need the usage mitigation. + if (groupAvailable > groupHeadroom) { + RunCallback(true); + return; + } + + RefPtr<QuotaUsageChecker> self = this; + auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); + + nsCOMPtr<nsIClearDataService> csd = + do_GetService("@mozilla.org/clear-data-service;1"); + MOZ_ASSERT(csd); + + // Group usage headroom is not satisfied even removing the origin usage, + // clear all group usage. + if ((groupAvailable + mOriginUsage) < groupHeadroom) { + nsAutoCString host; + nsresult rv = mPrincipal->GetHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = csd->DeleteDataFromBaseDomain( + host, false, nsIClearDataService::CLEAR_DOM_QUOTA, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // clear the origin usage since it makes group usage headroom be satisifed. + } else { + nsresult rv = csd->DeleteDataFromPrincipal( + mPrincipal, false, nsIClearDataService::CLEAR_DOM_QUOTA, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + scopeExit.release(); +} + +// nsIQuotaUsageCallback implementation + +NS_IMETHODIMP QuotaUsageChecker::OnUsageResult( + nsIQuotaUsageRequest* aUsageRequest) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aUsageRequest); + + RefPtr<QuotaUsageChecker> self = this; + auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); + + nsresult resultCode; + nsresult rv = aUsageRequest->GetResultCode(&resultCode); + if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(resultCode))) { + return rv; + } + + nsCOMPtr<nsIQuotaOriginUsageResult> usageResult; + rv = GetResult(aUsageRequest, usageResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(usageResult); + + rv = usageResult->GetUsage(&mOriginUsage); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(!mGettingOriginUsageDone); + mGettingOriginUsageDone = true; + + scopeExit.release(); + + // Call CheckQuotaHeadroom() when both + // QuotaManagerService::GetUsageForPrincipal() and + // QuotaManagerService::Estimate() are done. + if (mGettingOriginUsageDone && mGettingGroupUsageDone) { + CheckQuotaHeadroom(); + } + return NS_OK; +} + +// nsIQuotaCallback implementation + +NS_IMETHODIMP QuotaUsageChecker::OnComplete(nsIQuotaRequest* aRequest) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRequest); + + RefPtr<QuotaUsageChecker> self = this; + auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); + + nsresult resultCode; + nsresult rv = aRequest->GetResultCode(&resultCode); + if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(resultCode))) { + return rv; + } + + nsCOMPtr<nsIQuotaEstimateResult> estimateResult; + rv = GetResult(aRequest, estimateResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(estimateResult); + + rv = estimateResult->GetUsage(&mGroupUsage); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = estimateResult->GetLimit(&mGroupLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(!mGettingGroupUsageDone); + mGettingGroupUsageDone = true; + + scopeExit.release(); + + // Call CheckQuotaHeadroom() when both + // QuotaManagerService::GetUsageForPrincipal() and + // QuotaManagerService::Estimate() are done. + if (mGettingOriginUsageDone && mGettingGroupUsageDone) { + CheckQuotaHeadroom(); + } + return NS_OK; +} + +// nsIClearDataCallback implementation + +NS_IMETHODIMP QuotaUsageChecker::OnDataDeleted(uint32_t aFailedFlags) { + RunCallback(true); + return NS_OK; +} + +// Helper methods in ServiceWorkerQuotaUtils.h + +void ClearQuotaUsageIfNeeded(nsIPrincipal* aPrincipal, + ServiceWorkerQuotaMitigationCallback&& aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + + RefPtr<QuotaUsageChecker> checker = + MakeRefPtr<QuotaUsageChecker>(aPrincipal, std::move(aCallback)); + checker->Start(); +} + +} // namespace mozilla::dom |