ServiceWorkerManager.h (19172B)
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 #ifndef mozilla_dom_workers_serviceworkermanager_h 8 #define mozilla_dom_workers_serviceworkermanager_h 9 10 #include <cstdint> 11 12 #include "ErrorList.h" 13 #include "ServiceWorkerDescriptor.h" 14 #include "ServiceWorkerShutdownState.h" 15 #include "js/ErrorReport.h" 16 #include "mozilla/AlreadyAddRefed.h" 17 #include "mozilla/Assertions.h" 18 #include "mozilla/HashTable.h" 19 #include "mozilla/MozPromise.h" 20 #include "mozilla/RefPtr.h" 21 #include "mozilla/UniquePtr.h" 22 #include "mozilla/dom/ClientHandle.h" 23 #include "mozilla/dom/ClientOpPromise.h" 24 #include "mozilla/dom/ServiceWorkerLifetimeExtension.h" 25 #include "mozilla/dom/ServiceWorkerRegistrationBinding.h" 26 #include "mozilla/dom/ServiceWorkerRegistrationInfo.h" 27 #include "mozilla/dom/ServiceWorkerUtils.h" 28 #include "mozilla/mozalloc.h" 29 #include "nsClassHashtable.h" 30 #include "nsContentUtils.h" 31 #include "nsHashKeys.h" 32 #include "nsIObserver.h" 33 #include "nsIServiceWorkerManager.h" 34 #include "nsISupports.h" 35 #include "nsStringFwd.h" 36 #include "nsTArray.h" 37 38 class nsIConsoleReportCollector; 39 40 namespace mozilla { 41 42 class OriginAttributes; 43 44 namespace ipc { 45 class PrincipalInfo; 46 } // namespace ipc 47 48 namespace net { 49 class CookieStruct; 50 } 51 52 namespace dom { 53 54 class ContentParent; 55 class ServiceWorkerInfo; 56 class ServiceWorkerJobQueue; 57 class ServiceWorkerManagerChild; 58 class ServiceWorkerPrivate; 59 class ServiceWorkerRegistrar; 60 class ServiceWorkerShutdownBlocker; 61 struct CookieListItem; 62 63 class ServiceWorkerUpdateFinishCallback { 64 protected: 65 virtual ~ServiceWorkerUpdateFinishCallback() = default; 66 67 public: 68 NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback) 69 70 virtual void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) = 0; 71 72 virtual void UpdateFailed(ErrorResult& aStatus) = 0; 73 }; 74 75 #define NS_SERVICEWORKERMANAGER_IMPL_IID \ 76 {/* f4f8755a-69ca-46e8-a65d-775745535990 */ \ 77 0xf4f8755a, \ 78 0x69ca, \ 79 0x46e8, \ 80 {0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90}} 81 82 class ETPPermissionObserver final : public nsIObserver { 83 public: 84 NS_DECL_ISUPPORTS 85 NS_DECL_NSIOBSERVER 86 87 ETPPermissionObserver(); 88 89 private: 90 ~ETPPermissionObserver(); 91 92 void RegisterObserverEvents(); 93 void UnregisterObserverEvents(); 94 }; 95 96 /* 97 * The ServiceWorkerManager is a per-process global that deals with the 98 * installation, querying and event dispatch of ServiceWorkers for all the 99 * origins in the process. 100 * 101 * NOTE: the following documentation is a WIP: 102 * 103 * The ServiceWorkerManager (SWM) is a main-thread, parent-process singleton 104 * that encapsulates the browser-global state of service workers. This state 105 * includes, but is not limited to, all service worker registrations and all 106 * controlled service worker clients. The SWM also provides methods to read and 107 * mutate this state and to dispatch operations (e.g. DOM events such as a 108 * FetchEvent) to service workers. 109 * 110 * Example usage: 111 * 112 * MOZ_ASSERT(NS_IsMainThread(), "SWM is main-thread only"); 113 * 114 * RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 115 * 116 * // Nullness must be checked by code that possibly executes during browser 117 * // shutdown, which is when the SWM is destroyed. 118 * if (swm) { 119 * // Do something with the SWM. 120 * } 121 */ 122 class ServiceWorkerManager final : public nsIServiceWorkerManager, 123 public nsIObserver { 124 friend class GetRegistrationsRunnable; 125 friend class GetRegistrationRunnable; 126 friend class ServiceWorkerInfo; 127 friend class ServiceWorkerJob; 128 friend class ServiceWorkerRegistrationInfo; 129 friend class ServiceWorkerShutdownBlocker; 130 friend class ServiceWorkerUnregisterJob; 131 friend class ServiceWorkerUpdateJob; 132 friend class UpdateTimerCallback; 133 134 public: 135 NS_DECL_ISUPPORTS 136 NS_DECL_NSISERVICEWORKERMANAGER 137 NS_DECL_NSIOBSERVER 138 139 // Determine the correct lifetime extension to use for a given client. This 140 // will work for ServiceWorker clients, but ideally you should have a 141 // ServiceWorkerDescriptor in that case. 142 ServiceWorkerLifetimeExtension DetermineLifetimeForClient( 143 const ClientInfo& aClientInfo); 144 145 // Determine the correct lifetime extension to use for a given client. This 146 // will work for ServiceWorker clients, but ideally you should have a 147 // ServiceWorkerDescriptor in that case. 148 ServiceWorkerLifetimeExtension DetermineLifetimeForServiceWorker( 149 const ServiceWorkerDescriptor& aServiceWorker); 150 151 // Return true if the given principal and URI matches a registered service 152 // worker which handles fetch event. 153 // If there is a matched service worker but doesn't handle fetch events, this 154 // method will try to set the matched service worker as the controller of the 155 // passed in channel. Then also schedule a soft-update job for the service 156 // worker. 157 bool IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI, 158 nsIChannel* aChannel); 159 160 void DispatchFetchEvent(nsIInterceptedChannel* aChannel, ErrorResult& aRv); 161 162 void Update(const ClientInfo& aClientInfo, nsIPrincipal* aPrincipal, 163 const nsACString& aScope, nsCString aNewestWorkerScriptUrl, 164 ServiceWorkerUpdateFinishCallback* aCallback); 165 166 void UpdateInternal(const ClientInfo& aClientInfo, nsIPrincipal* aPrincipal, 167 const nsACString& aScope, 168 nsCString&& aNewestWorkerScriptUrl, 169 ServiceWorkerUpdateFinishCallback* aCallback); 170 171 void SoftUpdate(const OriginAttributes& aOriginAttributes, 172 const nsACString& aScope); 173 174 void SoftUpdateInternal(const OriginAttributes& aOriginAttributes, 175 const nsACString& aScope, 176 ServiceWorkerUpdateFinishCallback* aCallback); 177 178 RefPtr<ServiceWorkerRegistrationPromise> Register( 179 const ClientInfo& aClientInfo, const nsACString& aScopeURL, 180 const WorkerType& aType, const nsACString& aScriptURL, 181 ServiceWorkerUpdateViaCache aUpdateViaCache); 182 183 RefPtr<ServiceWorkerRegistrationPromise> GetRegistration( 184 const ClientInfo& aClientInfo, const nsACString& aURL) const; 185 186 RefPtr<ServiceWorkerRegistrationListPromise> GetRegistrations( 187 const ClientInfo& aClientInfo) const; 188 189 already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration( 190 nsIPrincipal* aPrincipal, const nsACString& aScope) const; 191 192 already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration( 193 const mozilla::ipc::PrincipalInfo& aPrincipal, 194 const nsACString& aScope) const; 195 196 already_AddRefed<ServiceWorkerRegistrationInfo> CreateNewRegistration( 197 const nsCString& aScope, const WorkerType& aType, 198 nsIPrincipal* aPrincipal, ServiceWorkerUpdateViaCache aUpdateViaCache, 199 IPCNavigationPreloadState aNavigationPreloadState = 200 IPCNavigationPreloadState(false, "true"_ns)); 201 202 void RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration); 203 204 void StoreRegistration(nsIPrincipal* aPrincipal, 205 ServiceWorkerRegistrationInfo* aRegistration); 206 207 /** 208 * Report an error for the given scope to any window we think might be 209 * interested, failing over to the Browser Console if we couldn't find any. 210 * 211 * Error messages should be localized, so you probably want to call 212 * LocalizeAndReportToAllClients instead, which in turn calls us after 213 * localizing the error. 214 */ 215 void ReportToAllClients(const nsCString& aScope, const nsString& aMessage, 216 const nsCString& aFilename, const nsString& aLine, 217 uint32_t aLineNumber, uint32_t aColumnNumber, 218 uint32_t aFlags); 219 220 /** 221 * Report a localized error for the given scope to any window we think might 222 * be interested. 223 * 224 * Note that this method takes an nsTArray<nsString> for the parameters, not 225 * bare chart16_t*[]. You can use a std::initializer_list constructor inline 226 * so that argument might look like: nsTArray<nsString> { some_nsString, 227 * PromiseFlatString(some_nsSubString_aka_nsAString), 228 * NS_ConvertUTF8toUTF16(some_nsCString_or_nsCSubString), 229 * u"some literal"_ns }. If you have anything else, like a 230 * number, you can use an nsAutoString with AppendInt/friends. 231 * 232 * @param [aFlags] 233 * The nsIScriptError flag, one of errorFlag (0x0), warningFlag (0x1), 234 * infoFlag (0x8). We default to error if omitted because usually we're 235 * logging exceptional and/or obvious breakage. 236 */ 237 static void LocalizeAndReportToAllClients( 238 const nsCString& aScope, const char* aStringKey, 239 const nsTArray<nsString>& aParamArray, uint32_t aFlags = 0x0, 240 const nsCString& aFilename = ""_ns, const nsString& aLine = u""_ns, 241 uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0); 242 243 // Always consumes the error by reporting to consoles of all controlled 244 // documents. 245 void HandleError(JSContext* aCx, nsIPrincipal* aPrincipal, 246 const nsCString& aScope, const nsCString& aWorkerURL, 247 const nsString& aMessage, const nsCString& aFilename, 248 const nsString& aLine, uint32_t aLineNumber, 249 uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType); 250 251 [[nodiscard]] RefPtr<GenericErrorResultPromise> MaybeClaimClient( 252 const ClientInfo& aClientInfo, 253 ServiceWorkerRegistrationInfo* aWorkerRegistration); 254 255 [[nodiscard]] RefPtr<GenericErrorResultPromise> MaybeClaimClient( 256 const ClientInfo& aClientInfo, 257 const ServiceWorkerDescriptor& aServiceWorker); 258 259 static already_AddRefed<ServiceWorkerManager> GetInstance(); 260 261 void LoadRegistration(const ServiceWorkerRegistrationData& aRegistration); 262 263 void LoadRegistrations( 264 const nsTArray<ServiceWorkerRegistrationData>& aRegistrations); 265 266 void MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo); 267 268 nsresult SendCookieChangeEvent(const OriginAttributes& aOriginAttributes, 269 const nsACString& aScope, 270 const net::CookieStruct& aCookie, 271 bool aCookieDeleted); 272 273 nsresult SendPushEvent(const nsACString& aOriginAttributes, 274 const nsACString& aScope, const nsAString& aMessageId, 275 const Maybe<nsTArray<uint8_t>>& aData); 276 277 void WorkerIsIdle(ServiceWorkerInfo* aWorker); 278 279 RefPtr<ServiceWorkerRegistrationPromise> WhenReady( 280 const ClientInfo& aClientInfo); 281 282 void CheckPendingReadyPromises(); 283 284 void RemovePendingReadyPromise(const ClientInfo& aClientInfo); 285 286 void NoteInheritedController(const ClientInfo& aClientInfo, 287 const ServiceWorkerDescriptor& aController); 288 289 void BlockShutdownOn(GenericNonExclusivePromise* aPromise, 290 uint32_t aShutdownStateId); 291 292 nsresult GetClientRegistration( 293 const ClientInfo& aClientInfo, 294 ServiceWorkerRegistrationInfo** aRegistrationInfo); 295 296 int32_t GetPrincipalQuotaUsageCheckCount(nsIPrincipal* aPrincipal); 297 298 void CheckPrincipalQuotaUsage(nsIPrincipal* aPrincipal, 299 const nsACString& aScope); 300 301 // Returns the shutdown state ID (may be an invalid ID if an 302 // nsIAsyncShutdownBlocker is not used). 303 uint32_t MaybeInitServiceWorkerShutdownProgress() const; 304 305 void ReportServiceWorkerShutdownProgress( 306 uint32_t aShutdownStateId, 307 ServiceWorkerShutdownState::Progress aProgress) const; 308 309 // Record periodic telemetry on number of running ServiceWorkers. When 310 // the number of running ServiceWorkers changes (or on shutdown), 311 // ServiceWorkerPrivateImpl will call RecordTelemetry with the number of 312 // running serviceworkers and those supporting Fetch. We use 313 // mTelemetryLastChange to determine how many datapoints to inject into 314 // Telemetry, and dispatch a background runnable to call 315 // RecordTelemetryGap() and Accumulate them. 316 void RecordTelemetry(uint32_t aNumber, uint32_t aFetch); 317 318 void EvictFromBFCache(ServiceWorkerRegistrationInfo* aRegistration); 319 320 nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> 321 GetRegistrations(nsIPrincipal* aPrincipal); 322 323 private: 324 struct RegistrationDataPerPrincipal; 325 326 static bool FindScopeForPath(const nsACString& aScopeKey, 327 const nsACString& aPath, 328 RegistrationDataPerPrincipal** aData, 329 nsACString& aMatch); 330 331 ServiceWorkerManager(); 332 ~ServiceWorkerManager(); 333 334 void Init(ServiceWorkerRegistrar* aRegistrar); 335 336 RefPtr<GenericErrorResultPromise> StartControllingClient( 337 const ClientInfo& aClientInfo, 338 ServiceWorkerRegistrationInfo* aRegistrationInfo, 339 bool aControlClientHandle = true); 340 341 void StopControllingClient(const ClientInfo& aClientInfo); 342 343 void MaybeStartShutdown(); 344 345 void MaybeFinishShutdown(); 346 347 already_AddRefed<ServiceWorkerJobQueue> GetOrCreateJobQueue( 348 const nsACString& aOriginSuffix, const nsACString& aScope); 349 350 void MaybeRemoveRegistrationInfo(const nsACString& aScopeKey); 351 352 already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration( 353 const nsACString& aScopeKey, const nsACString& aScope) const; 354 355 void AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration); 356 357 nsresult Update(ServiceWorkerRegistrationInfo* aRegistration); 358 359 ServiceWorkerInfo* GetActiveWorkerInfoForScope( 360 const OriginAttributes& aOriginAttributes, const nsACString& aScope); 361 362 // Given the ClientInfo for a ServiceWorker global, return the corresponding 363 // ServiceWorkerInfo, nullptr otherwise. Do not use this for clients for 364 // globals that are not ServiceWorkers (and ideally you should be using 365 // GetServiceWorkerByDescriptor instead). 366 ServiceWorkerInfo* GetServiceWorkerByClientInfo( 367 const ClientInfo& aClientInfo) const; 368 369 // Given the ServiceWorkerDescriptor for a ServiceWorker, return the 370 // corresponding nullptr otherwise. 371 ServiceWorkerInfo* GetServiceWorkerByDescriptor( 372 const ServiceWorkerDescriptor& aServiceWorker) const; 373 374 void StopControllingRegistration( 375 ServiceWorkerRegistrationInfo* aRegistration); 376 377 // Find the ServiceWorkerRegistration whose scope best matches the URL of the 378 // given window or worker client (for the origin of the client based on its 379 // principal). This cannot be used with ServiceWorker ClientInfos. 380 already_AddRefed<ServiceWorkerRegistrationInfo> 381 GetServiceWorkerRegistrationInfo(const ClientInfo& aClientInfo) const; 382 383 // Find the ServiceWorkerRegistration whose scope best matches the provided 384 // URL (for the origin of the given principal). 385 // 386 // Note that `GetRegistration` should be used in cases where you already have 387 // an exact scope. 388 already_AddRefed<ServiceWorkerRegistrationInfo> 389 GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, 390 nsIURI* aURI) const; 391 392 // Find the ServiceWorkerRegistration whose scope best matches the provided 393 // URL for the origin encoded as a scope key that has been obtained from 394 // PrincipToScopeKey. 395 already_AddRefed<ServiceWorkerRegistrationInfo> 396 GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey, 397 nsIURI* aURI) const; 398 399 static nsresult PrincipalToScopeKey(nsIPrincipal* aPrincipal, 400 nsACString& aKey); 401 402 static nsresult PrincipalInfoToScopeKey( 403 const mozilla::ipc::PrincipalInfo& aPrincipalInfo, nsACString& aKey); 404 405 static void AddScopeAndRegistration( 406 const nsACString& aScope, ServiceWorkerRegistrationInfo* aRegistation); 407 408 static bool HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope); 409 410 static void RemoveScopeAndRegistration( 411 ServiceWorkerRegistrationInfo* aRegistration); 412 413 void QueueFireEventOnServiceWorkerRegistrations( 414 ServiceWorkerRegistrationInfo* aRegistration, const nsAString& aName); 415 416 void UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration); 417 418 void MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration); 419 420 void PurgeServiceWorker(const ServiceWorkerRegistrationData& aRegistration, 421 nsIPrincipal* aPrincipal); 422 423 RefPtr<ServiceWorkerManagerChild> mActor; 424 425 bool mShuttingDown; 426 427 nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> mListeners; 428 429 void NotifyListenersOnRegister( 430 nsIServiceWorkerRegistrationInfo* aRegistration); 431 432 void NotifyListenersOnUnregister( 433 nsIServiceWorkerRegistrationInfo* aRegistration); 434 435 void NotifyListenersOnQuotaUsageCheckFinish( 436 nsIServiceWorkerRegistrationInfo* aRegistration); 437 438 void ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope); 439 440 void UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope); 441 442 void MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope); 443 444 // Used by remove() and removeAll() when clearing history. 445 // MUST ONLY BE CALLED FROM UnregisterIfMatchesHost! 446 void ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData, 447 ServiceWorkerRegistrationInfo* aRegistration); 448 449 // An "orphaned" registration is one that is unregistered and not controlling 450 // clients. The ServiceWorkerManager must know about all orphaned 451 // registrations to forcefully shutdown all Service Workers during browser 452 // shutdown. 453 void AddOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration); 454 455 void RemoveOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration); 456 457 HashSet<RefPtr<ServiceWorkerRegistrationInfo>, 458 PointerHasher<ServiceWorkerRegistrationInfo*>> 459 mOrphanedRegistrations; 460 461 RefPtr<ServiceWorkerShutdownBlocker> mShutdownBlocker; 462 463 nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> 464 mRegistrationInfos; 465 466 struct ControlledClientData { 467 RefPtr<ClientHandle> mClientHandle; 468 RefPtr<ServiceWorkerRegistrationInfo> mRegistrationInfo; 469 470 ControlledClientData(ClientHandle* aClientHandle, 471 ServiceWorkerRegistrationInfo* aRegistrationInfo) 472 : mClientHandle(aClientHandle), mRegistrationInfo(aRegistrationInfo) {} 473 }; 474 475 nsClassHashtable<nsIDHashKey, ControlledClientData> mControlledClients; 476 477 struct PendingReadyData { 478 RefPtr<ClientHandle> mClientHandle; 479 RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise; 480 481 explicit PendingReadyData(ClientHandle* aClientHandle) 482 : mClientHandle(aClientHandle), 483 mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)) {} 484 }; 485 486 nsTArray<UniquePtr<PendingReadyData>> mPendingReadyList; 487 488 const uint32_t mTelemetryPeriodMs = 5 * 1000; 489 TimeStamp mTelemetryLastChange; 490 RefPtr<ETPPermissionObserver> mETPPermissionObserver; 491 }; 492 493 } // namespace dom 494 } // namespace mozilla 495 496 #endif // mozilla_dom_workers_serviceworkermanager_h