/* -*- 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/. */ #ifndef mozilla_dom_workers_serviceworkermanager_h #define mozilla_dom_workers_serviceworkermanager_h #include #include "ErrorList.h" #include "ServiceWorkerShutdownState.h" #include "js/ErrorReport.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Assertions.h" #include "mozilla/HashTable.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/ClientHandle.h" #include "mozilla/dom/ClientOpPromise.h" #include "mozilla/dom/ServiceWorkerRegistrationBinding.h" #include "mozilla/dom/ServiceWorkerRegistrationInfo.h" #include "mozilla/dom/ServiceWorkerUtils.h" #include "mozilla/mozalloc.h" #include "nsClassHashtable.h" #include "nsContentUtils.h" #include "nsHashKeys.h" #include "nsIObserver.h" #include "nsIServiceWorkerManager.h" #include "nsISupports.h" #include "nsStringFwd.h" #include "nsTArray.h" class nsIConsoleReportCollector; namespace mozilla { class OriginAttributes; namespace ipc { class PrincipalInfo; } // namespace ipc namespace dom { extern uint32_t gServiceWorkersRegistered; extern uint32_t gServiceWorkersRegisteredFetch; class ContentParent; class ServiceWorkerInfo; class ServiceWorkerJobQueue; class ServiceWorkerManagerChild; class ServiceWorkerPrivate; class ServiceWorkerRegistrar; class ServiceWorkerShutdownBlocker; class ServiceWorkerUpdateFinishCallback { protected: virtual ~ServiceWorkerUpdateFinishCallback() = default; public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback) virtual void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) = 0; virtual void UpdateFailed(ErrorResult& aStatus) = 0; }; #define NS_SERVICEWORKERMANAGER_IMPL_IID \ { /* f4f8755a-69ca-46e8-a65d-775745535990 */ \ 0xf4f8755a, 0x69ca, 0x46e8, { \ 0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 \ } \ } /* * The ServiceWorkerManager is a per-process global that deals with the * installation, querying and event dispatch of ServiceWorkers for all the * origins in the process. * * NOTE: the following documentation is a WIP: * * The ServiceWorkerManager (SWM) is a main-thread, parent-process singleton * that encapsulates the browser-global state of service workers. This state * includes, but is not limited to, all service worker registrations and all * controlled service worker clients. The SWM also provides methods to read and * mutate this state and to dispatch operations (e.g. DOM events such as a * FetchEvent) to service workers. * * Example usage: * * MOZ_ASSERT(NS_IsMainThread(), "SWM is main-thread only"); * * RefPtr swm = ServiceWorkerManager::GetInstance(); * * // Nullness must be checked by code that possibly executes during browser * // shutdown, which is when the SWM is destroyed. * if (swm) { * // Do something with the SWM. * } */ class ServiceWorkerManager final : public nsIServiceWorkerManager, public nsIObserver { friend class GetRegistrationsRunnable; friend class GetRegistrationRunnable; friend class ServiceWorkerJob; friend class ServiceWorkerRegistrationInfo; friend class ServiceWorkerShutdownBlocker; friend class ServiceWorkerUnregisterJob; friend class ServiceWorkerUpdateJob; friend class UpdateTimerCallback; public: NS_DECL_ISUPPORTS NS_DECL_NSISERVICEWORKERMANAGER NS_DECL_NSIOBSERVER // Return true if the given principal and URI matches a registered service // worker which handles fetch event. // If there is a matched service worker but doesn't handle fetch events, this // method will try to set the matched service worker as the controller of the // passed in channel. Then also schedule a soft-update job for the service // worker. bool IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI, nsIChannel* aChannel); void DispatchFetchEvent(nsIInterceptedChannel* aChannel, ErrorResult& aRv); void Update(nsIPrincipal* aPrincipal, const nsACString& aScope, nsCString aNewestWorkerScriptUrl, ServiceWorkerUpdateFinishCallback* aCallback); void UpdateInternal(nsIPrincipal* aPrincipal, const nsACString& aScope, nsCString&& aNewestWorkerScriptUrl, ServiceWorkerUpdateFinishCallback* aCallback); void SoftUpdate(const OriginAttributes& aOriginAttributes, const nsACString& aScope); void SoftUpdateInternal(const OriginAttributes& aOriginAttributes, const nsACString& aScope, ServiceWorkerUpdateFinishCallback* aCallback); RefPtr Register( const ClientInfo& aClientInfo, const nsACString& aScopeURL, const nsACString& aScriptURL, ServiceWorkerUpdateViaCache aUpdateViaCache); RefPtr GetRegistration( const ClientInfo& aClientInfo, const nsACString& aURL) const; RefPtr GetRegistrations( const ClientInfo& aClientInfo) const; already_AddRefed GetRegistration( nsIPrincipal* aPrincipal, const nsACString& aScope) const; already_AddRefed GetRegistration( const mozilla::ipc::PrincipalInfo& aPrincipal, const nsACString& aScope) const; already_AddRefed CreateNewRegistration( const nsCString& aScope, nsIPrincipal* aPrincipal, ServiceWorkerUpdateViaCache aUpdateViaCache, IPCNavigationPreloadState aNavigationPreloadState = IPCNavigationPreloadState(false, "true"_ns)); void RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration); void StoreRegistration(nsIPrincipal* aPrincipal, ServiceWorkerRegistrationInfo* aRegistration); /** * Report an error for the given scope to any window we think might be * interested, failing over to the Browser Console if we couldn't find any. * * Error messages should be localized, so you probably want to call * LocalizeAndReportToAllClients instead, which in turn calls us after * localizing the error. */ void ReportToAllClients(const nsCString& aScope, const nsString& aMessage, const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber, uint32_t aColumnNumber, uint32_t aFlags); /** * Report a localized error for the given scope to any window we think might * be interested. * * Note that this method takes an nsTArray for the parameters, not * bare chart16_t*[]. You can use a std::initializer_list constructor inline * so that argument might look like: nsTArray { some_nsString, * PromiseFlatString(some_nsSubString_aka_nsAString), * NS_ConvertUTF8toUTF16(some_nsCString_or_nsCSubString), * u"some literal"_ns }. If you have anything else, like a * number, you can use an nsAutoString with AppendInt/friends. * * @param [aFlags] * The nsIScriptError flag, one of errorFlag (0x0), warningFlag (0x1), * infoFlag (0x8). We default to error if omitted because usually we're * logging exceptional and/or obvious breakage. */ static void LocalizeAndReportToAllClients( const nsCString& aScope, const char* aStringKey, const nsTArray& aParamArray, uint32_t aFlags = 0x0, const nsString& aFilename = u""_ns, const nsString& aLine = u""_ns, uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0); // Always consumes the error by reporting to consoles of all controlled // documents. void HandleError(JSContext* aCx, nsIPrincipal* aPrincipal, const nsCString& aScope, const nsString& aWorkerURL, const nsString& aMessage, const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber, uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType); [[nodiscard]] RefPtr MaybeClaimClient( const ClientInfo& aClientInfo, ServiceWorkerRegistrationInfo* aWorkerRegistration); [[nodiscard]] RefPtr MaybeClaimClient( const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aServiceWorker); static already_AddRefed GetInstance(); void LoadRegistration(const ServiceWorkerRegistrationData& aRegistration); void LoadRegistrations( const nsTArray& aRegistrations); void MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo); nsresult SendPushEvent(const nsACString& aOriginAttributes, const nsACString& aScope, const nsAString& aMessageId, const Maybe>& aData); void WorkerIsIdle(ServiceWorkerInfo* aWorker); RefPtr WhenReady( const ClientInfo& aClientInfo); void CheckPendingReadyPromises(); void RemovePendingReadyPromise(const ClientInfo& aClientInfo); void NoteInheritedController(const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aController); void BlockShutdownOn(GenericNonExclusivePromise* aPromise, uint32_t aShutdownStateId); nsresult GetClientRegistration( const ClientInfo& aClientInfo, ServiceWorkerRegistrationInfo** aRegistrationInfo); int32_t GetPrincipalQuotaUsageCheckCount(nsIPrincipal* aPrincipal); void CheckPrincipalQuotaUsage(nsIPrincipal* aPrincipal, const nsACString& aScope); // Returns the shutdown state ID (may be an invalid ID if an // nsIAsyncShutdownBlocker is not used). uint32_t MaybeInitServiceWorkerShutdownProgress() const; void ReportServiceWorkerShutdownProgress( uint32_t aShutdownStateId, ServiceWorkerShutdownState::Progress aProgress) const; // Record periodic telemetry on number of running ServiceWorkers. When // the number of running ServiceWorkers changes (or on shutdown), // ServiceWorkerPrivateImpl will call RecordTelemetry with the number of // running serviceworkers and those supporting Fetch. We use // mTelemetryLastChange to determine how many datapoints to inject into // Telemetry, and dispatch a background runnable to call // RecordTelemetryGap() and Accumulate them. void RecordTelemetry(uint32_t aNumber, uint32_t aFetch); void EvictFromBFCache(ServiceWorkerRegistrationInfo* aRegistration); private: struct RegistrationDataPerPrincipal; static bool FindScopeForPath(const nsACString& aScopeKey, const nsACString& aPath, RegistrationDataPerPrincipal** aData, nsACString& aMatch); ServiceWorkerManager(); ~ServiceWorkerManager(); void Init(ServiceWorkerRegistrar* aRegistrar); RefPtr StartControllingClient( const ClientInfo& aClientInfo, ServiceWorkerRegistrationInfo* aRegistrationInfo, bool aControlClientHandle = true); void StopControllingClient(const ClientInfo& aClientInfo); void MaybeStartShutdown(); void MaybeFinishShutdown(); already_AddRefed GetOrCreateJobQueue( const nsACString& aOriginSuffix, const nsACString& aScope); void MaybeRemoveRegistrationInfo(const nsACString& aScopeKey); already_AddRefed GetRegistration( const nsACString& aScopeKey, const nsACString& aScope) const; void AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration); nsresult Update(ServiceWorkerRegistrationInfo* aRegistration); ServiceWorkerInfo* GetActiveWorkerInfoForScope( const OriginAttributes& aOriginAttributes, const nsACString& aScope); void StopControllingRegistration( ServiceWorkerRegistrationInfo* aRegistration); already_AddRefed GetServiceWorkerRegistrationInfo(const ClientInfo& aClientInfo) const; already_AddRefed GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, nsIURI* aURI) const; already_AddRefed GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey, nsIURI* aURI) const; // This method generates a key using isInElementBrowser from the principal. We // don't use the origin because it can change during the loading. static nsresult PrincipalToScopeKey(nsIPrincipal* aPrincipal, nsACString& aKey); static nsresult PrincipalInfoToScopeKey( const mozilla::ipc::PrincipalInfo& aPrincipalInfo, nsACString& aKey); static void AddScopeAndRegistration( const nsACString& aScope, ServiceWorkerRegistrationInfo* aRegistation); static bool HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope); static void RemoveScopeAndRegistration( ServiceWorkerRegistrationInfo* aRegistration); void QueueFireEventOnServiceWorkerRegistrations( ServiceWorkerRegistrationInfo* aRegistration, const nsAString& aName); void UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration); void MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration); RefPtr mActor; bool mShuttingDown; nsTArray> mListeners; void NotifyListenersOnRegister( nsIServiceWorkerRegistrationInfo* aRegistration); void NotifyListenersOnUnregister( nsIServiceWorkerRegistrationInfo* aRegistration); void NotifyListenersOnQuotaUsageCheckFinish( nsIServiceWorkerRegistrationInfo* aRegistration); void ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope); void UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope); void MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope); nsresult SendNotificationEvent(const nsAString& aEventName, const nsACString& aOriginSuffix, const nsACString& aScope, const nsAString& aID, const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang, const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon, const nsAString& aData, const nsAString& aBehavior); // Used by remove() and removeAll() when clearing history. // MUST ONLY BE CALLED FROM UnregisterIfMatchesHost! void ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData, ServiceWorkerRegistrationInfo* aRegistration); // An "orphaned" registration is one that is unregistered and not controlling // clients. The ServiceWorkerManager must know about all orphaned // registrations to forcefully shutdown all Service Workers during browser // shutdown. void AddOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration); void RemoveOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration); HashSet, PointerHasher> mOrphanedRegistrations; RefPtr mShutdownBlocker; nsClassHashtable mRegistrationInfos; struct ControlledClientData { RefPtr mClientHandle; RefPtr mRegistrationInfo; ControlledClientData(ClientHandle* aClientHandle, ServiceWorkerRegistrationInfo* aRegistrationInfo) : mClientHandle(aClientHandle), mRegistrationInfo(aRegistrationInfo) {} }; nsClassHashtable mControlledClients; struct PendingReadyData { RefPtr mClientHandle; RefPtr mPromise; explicit PendingReadyData(ClientHandle* aClientHandle) : mClientHandle(aClientHandle), mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)) {} }; nsTArray> mPendingReadyList; const uint32_t mTelemetryPeriodMs = 5 * 1000; TimeStamp mTelemetryLastChange; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_workers_serviceworkermanager_h