ServiceWorkerQuotaUtils.cpp (9446B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ServiceWorkerQuotaUtils.h" 8 9 #include "MainThreadUtils.h" 10 #include "mozilla/ScopeExit.h" 11 #include "mozilla/Services.h" 12 #include "mozilla/StaticPrefs_dom.h" 13 #include "mozilla/dom/quota/QuotaManagerService.h" 14 #include "nsIClearDataService.h" 15 #include "nsID.h" 16 #include "nsIPrincipal.h" 17 #include "nsIQuotaCallbacks.h" 18 #include "nsIQuotaRequests.h" 19 #include "nsIQuotaResults.h" 20 #include "nsISupports.h" 21 #include "nsIVariant.h" 22 #include "nsServiceManagerUtils.h" 23 24 using mozilla::dom::quota::QuotaManagerService; 25 26 namespace mozilla::dom { 27 28 /* 29 * QuotaUsageChecker implements the quota usage checking algorithm. 30 * 31 * 1. Getting the given origin/group usage through QuotaManagerService. 32 * QuotaUsageCheck::Start() implements this step. 33 * 2. Checking if the group usage headroom is satisfied. 34 * It could be following three situations. 35 * a. Group headroom is satisfied without any usage mitigation. 36 * b. Group headroom is satisfied after origin usage mitigation. 37 * This invokes nsIClearDataService::DeleteDataFromPrincipal(). 38 * c. Group headroom is satisfied after group usage mitigation. 39 * This invokes nsIClearDataService::DeleteDataFromSite(). 40 * QuotaUsageChecker::CheckQuotaHeadroom() implements this step. 41 * 42 * If the algorithm is done or error out, the QuotaUsageCheck::mCallback will 43 * be called with a bool result for external handling. 44 */ 45 class QuotaUsageChecker final : public nsIQuotaCallback, 46 public nsIQuotaUsageCallback, 47 public nsIClearDataCallback { 48 public: 49 NS_DECL_ISUPPORTS 50 // For QuotaManagerService::Estimate() 51 NS_DECL_NSIQUOTACALLBACK 52 53 // For QuotaManagerService::GetUsageForPrincipal() 54 NS_DECL_NSIQUOTAUSAGECALLBACK 55 56 // For nsIClearDataService::DeleteDataFromPrincipal() and 57 // nsIClearDataService::DeleteDataFromSite() 58 NS_DECL_NSICLEARDATACALLBACK 59 60 QuotaUsageChecker(nsIPrincipal* aPrincipal, 61 ServiceWorkerQuotaMitigationCallback&& aCallback); 62 63 void Start(); 64 65 void RunCallback(bool aResult); 66 67 private: 68 ~QuotaUsageChecker() = default; 69 70 // This is a general help method to get nsIQuotaResult/nsIQuotaUsageResult 71 // from nsIQuotaRequest/nsIQuotaUsageRequest 72 template <typename T, typename U> 73 nsresult GetResult(T* aRequest, U&); 74 75 void CheckQuotaHeadroom(); 76 77 nsCOMPtr<nsIPrincipal> mPrincipal; 78 79 // The external callback. Calling RunCallback(bool) instead of calling it 80 // directly, RunCallback(bool) handles the internal status. 81 ServiceWorkerQuotaMitigationCallback mCallback; 82 bool mGettingOriginUsageDone; 83 bool mGettingGroupUsageDone; 84 bool mIsChecking; 85 uint64_t mOriginUsage; 86 uint64_t mGroupUsage; 87 uint64_t mGroupLimit; 88 }; 89 90 NS_IMPL_ISUPPORTS(QuotaUsageChecker, nsIQuotaCallback, nsIQuotaUsageCallback, 91 nsIClearDataCallback) 92 93 QuotaUsageChecker::QuotaUsageChecker( 94 nsIPrincipal* aPrincipal, ServiceWorkerQuotaMitigationCallback&& aCallback) 95 : mPrincipal(aPrincipal), 96 mCallback(std::move(aCallback)), 97 mGettingOriginUsageDone(false), 98 mGettingGroupUsageDone(false), 99 mIsChecking(false), 100 mOriginUsage(0), 101 mGroupUsage(0), 102 mGroupLimit(0) {} 103 104 void QuotaUsageChecker::Start() { 105 MOZ_ASSERT(NS_IsMainThread()); 106 107 if (mIsChecking) { 108 return; 109 } 110 mIsChecking = true; 111 112 RefPtr<QuotaUsageChecker> self = this; 113 auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); 114 115 RefPtr<QuotaManagerService> qms = QuotaManagerService::GetOrCreate(); 116 MOZ_ASSERT(qms); 117 118 // Asynchronious getting quota usage for principal 119 nsCOMPtr<nsIQuotaUsageRequest> usageRequest; 120 if (NS_WARN_IF(NS_FAILED(qms->GetUsageForPrincipal( 121 mPrincipal, this, getter_AddRefs(usageRequest))))) { 122 return; 123 } 124 125 // Asynchronious getting group usage and limit 126 nsCOMPtr<nsIQuotaRequest> request; 127 if (NS_WARN_IF( 128 NS_FAILED(qms->Estimate(mPrincipal, getter_AddRefs(request))))) { 129 return; 130 } 131 request->SetCallback(this); 132 133 scopeExit.release(); 134 } 135 136 void QuotaUsageChecker::RunCallback(bool aResult) { 137 MOZ_ASSERT(mIsChecking && mCallback); 138 if (!mIsChecking) { 139 return; 140 } 141 mIsChecking = false; 142 mGettingOriginUsageDone = false; 143 mGettingGroupUsageDone = false; 144 145 mCallback(aResult); 146 mCallback = nullptr; 147 } 148 149 template <typename T, typename U> 150 nsresult QuotaUsageChecker::GetResult(T* aRequest, U& aResult) { 151 nsCOMPtr<nsIVariant> result; 152 nsresult rv = aRequest->GetResult(getter_AddRefs(result)); 153 if (NS_WARN_IF(NS_FAILED(rv))) { 154 return rv; 155 } 156 157 nsID* iid; 158 nsCOMPtr<nsISupports> supports; 159 rv = result->GetAsInterface(&iid, getter_AddRefs(supports)); 160 if (NS_WARN_IF(NS_FAILED(rv))) { 161 return rv; 162 } 163 164 free(iid); 165 166 aResult = do_QueryInterface(supports); 167 return NS_OK; 168 } 169 170 void QuotaUsageChecker::CheckQuotaHeadroom() { 171 MOZ_ASSERT(NS_IsMainThread()); 172 173 const uint64_t groupHeadroom = 174 static_cast<uint64_t>( 175 StaticPrefs:: 176 dom_serviceWorkers_mitigations_group_usage_headroom_kb()) * 177 1024; 178 const uint64_t groupAvailable = mGroupLimit - mGroupUsage; 179 180 // Group usage head room is satisfied, does not need the usage mitigation. 181 if (groupAvailable > groupHeadroom) { 182 RunCallback(true); 183 return; 184 } 185 186 RefPtr<QuotaUsageChecker> self = this; 187 auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); 188 189 nsCOMPtr<nsIClearDataService> csd = 190 do_GetService("@mozilla.org/clear-data-service;1"); 191 MOZ_ASSERT(csd); 192 193 // Group usage headroom is not satisfied even removing the origin usage, 194 // clear all group usage. 195 if ((groupAvailable + mOriginUsage) < groupHeadroom) { 196 nsAutoCString baseDomain; 197 nsresult rv = mPrincipal->GetBaseDomain(baseDomain); 198 if (NS_WARN_IF(NS_FAILED(rv))) { 199 return; 200 } 201 rv = csd->DeleteDataFromSiteAndOriginAttributesPatternString( 202 baseDomain, u""_ns, false, nsIClearDataService::CLEAR_DOM_QUOTA, this); 203 if (NS_WARN_IF(NS_FAILED(rv))) { 204 return; 205 } 206 207 // clear the origin usage since it makes group usage headroom be satisifed. 208 } else { 209 nsresult rv = csd->DeleteDataFromPrincipal( 210 mPrincipal, false, nsIClearDataService::CLEAR_DOM_QUOTA, this); 211 if (NS_WARN_IF(NS_FAILED(rv))) { 212 return; 213 } 214 } 215 216 scopeExit.release(); 217 } 218 219 // nsIQuotaUsageCallback implementation 220 221 NS_IMETHODIMP QuotaUsageChecker::OnUsageResult( 222 nsIQuotaUsageRequest* aUsageRequest) { 223 MOZ_ASSERT(NS_IsMainThread()); 224 MOZ_ASSERT(aUsageRequest); 225 226 RefPtr<QuotaUsageChecker> self = this; 227 auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); 228 229 nsresult resultCode; 230 nsresult rv = aUsageRequest->GetResultCode(&resultCode); 231 if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(resultCode))) { 232 return rv; 233 } 234 235 nsCOMPtr<nsIQuotaOriginUsageResult> usageResult; 236 rv = GetResult(aUsageRequest, usageResult); 237 if (NS_WARN_IF(NS_FAILED(rv))) { 238 return rv; 239 } 240 MOZ_ASSERT(usageResult); 241 242 rv = usageResult->GetUsage(&mOriginUsage); 243 if (NS_WARN_IF(NS_FAILED(rv))) { 244 return rv; 245 } 246 247 MOZ_ASSERT(!mGettingOriginUsageDone); 248 mGettingOriginUsageDone = true; 249 250 scopeExit.release(); 251 252 // Call CheckQuotaHeadroom() when both 253 // QuotaManagerService::GetUsageForPrincipal() and 254 // QuotaManagerService::Estimate() are done. 255 if (mGettingOriginUsageDone && mGettingGroupUsageDone) { 256 CheckQuotaHeadroom(); 257 } 258 return NS_OK; 259 } 260 261 // nsIQuotaCallback implementation 262 263 NS_IMETHODIMP QuotaUsageChecker::OnComplete(nsIQuotaRequest* aRequest) { 264 MOZ_ASSERT(NS_IsMainThread()); 265 MOZ_ASSERT(aRequest); 266 267 RefPtr<QuotaUsageChecker> self = this; 268 auto scopeExit = MakeScopeExit([self]() { self->RunCallback(false); }); 269 270 nsresult resultCode; 271 nsresult rv = aRequest->GetResultCode(&resultCode); 272 if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(resultCode))) { 273 return rv; 274 } 275 276 nsCOMPtr<nsIQuotaEstimateResult> estimateResult; 277 rv = GetResult(aRequest, estimateResult); 278 if (NS_WARN_IF(NS_FAILED(rv))) { 279 return rv; 280 } 281 MOZ_ASSERT(estimateResult); 282 283 rv = estimateResult->GetUsage(&mGroupUsage); 284 if (NS_WARN_IF(NS_FAILED(rv))) { 285 return rv; 286 } 287 288 rv = estimateResult->GetLimit(&mGroupLimit); 289 if (NS_WARN_IF(NS_FAILED(rv))) { 290 return rv; 291 } 292 293 MOZ_ASSERT(!mGettingGroupUsageDone); 294 mGettingGroupUsageDone = true; 295 296 scopeExit.release(); 297 298 // Call CheckQuotaHeadroom() when both 299 // QuotaManagerService::GetUsageForPrincipal() and 300 // QuotaManagerService::Estimate() are done. 301 if (mGettingOriginUsageDone && mGettingGroupUsageDone) { 302 CheckQuotaHeadroom(); 303 } 304 return NS_OK; 305 } 306 307 // nsIClearDataCallback implementation 308 309 NS_IMETHODIMP QuotaUsageChecker::OnDataDeleted(uint32_t aFailedFlags) { 310 RunCallback(true); 311 return NS_OK; 312 } 313 314 // Helper methods in ServiceWorkerQuotaUtils.h 315 316 void ClearQuotaUsageIfNeeded(nsIPrincipal* aPrincipal, 317 ServiceWorkerQuotaMitigationCallback&& aCallback) { 318 MOZ_ASSERT(NS_IsMainThread()); 319 MOZ_ASSERT(aPrincipal); 320 321 RefPtr<QuotaUsageChecker> checker = 322 MakeRefPtr<QuotaUsageChecker>(aPrincipal, std::move(aCallback)); 323 checker->Start(); 324 } 325 326 } // namespace mozilla::dom