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