/* -*- 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/ServiceWorkerLifetimeExtension.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 net { class CookieStruct; } namespace dom { class ContentParent; class ServiceWorkerInfo; class ServiceWorkerJobQueue; class ServiceWorkerManagerChild; class ServiceWorkerPrivate; class ServiceWorkerRegistrar; class ServiceWorkerShutdownBlocker; struct CookieListItem; 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}} class ETPPermissionObserver final : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER ETPPermissionObserver(); private: ~ETPPermissionObserver(); void RegisterObserverEvents(); void UnregisterObserverEvents(); }; /* * 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 ServiceWorkerInfo; 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 // Determine the correct lifetime extension to use for a given client. This // will work for ServiceWorker clients, but ideally you should have a // ServiceWorkerDescriptor in that case. ServiceWorkerLifetimeExtension DetermineLifetimeForClient( const ClientInfo& aClientInfo); // Determine the correct lifetime extension to use for a given client. This // will work for ServiceWorker clients, but ideally you should have a // ServiceWorkerDescriptor in that case. ServiceWorkerLifetimeExtension DetermineLifetimeForServiceWorker( const ServiceWorkerDescriptor& aServiceWorker); // 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(const ClientInfo& aClientInfo, nsIPrincipal* aPrincipal, const nsACString& aScope, nsCString aNewestWorkerScriptUrl, ServiceWorkerUpdateFinishCallback* aCallback); void UpdateInternal(const ClientInfo& aClientInfo, 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 nsCString& 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 nsCString& aFilename = ""_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 nsCString& aWorkerURL, const nsString& aMessage, const nsCString& 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 SendCookieChangeEvent(const OriginAttributes& aOriginAttributes, const nsACString& aScope, const net::CookieStruct& aCookie, bool aCookieDeleted); 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); nsRefPtrHashtable GetRegistrations(nsIPrincipal* aPrincipal); 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); // Given the ClientInfo for a ServiceWorker global, return the corresponding // ServiceWorkerInfo, nullptr otherwise. Do not use this for clients for // globals that are not ServiceWorkers (and ideally you should be using // GetServiceWorkerByDescriptor instead). ServiceWorkerInfo* GetServiceWorkerByClientInfo( const ClientInfo& aClientInfo) const; // Given the ServiceWorkerDescriptor for a ServiceWorker, return the // corresponding nullptr otherwise. ServiceWorkerInfo* GetServiceWorkerByDescriptor( const ServiceWorkerDescriptor& aServiceWorker) const; void StopControllingRegistration( ServiceWorkerRegistrationInfo* aRegistration); // Find the ServiceWorkerRegistration whose scope best matches the URL of the // given window or worker client (for the origin of the client based on its // principal). This cannot be used with ServiceWorker ClientInfos. already_AddRefed GetServiceWorkerRegistrationInfo(const ClientInfo& aClientInfo) const; // Find the ServiceWorkerRegistration whose scope best matches the provided // URL (for the origin of the given principal). // // Note that `GetRegistration` should be used in cases where you already have // an exact scope. already_AddRefed GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, nsIURI* aURI) const; // Find the ServiceWorkerRegistration whose scope best matches the provided // URL for the origin encoded as a scope key that has been obtained from // PrincipToScopeKey. already_AddRefed GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey, nsIURI* aURI) const; 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); void PurgeServiceWorker(const ServiceWorkerRegistrationData& aRegistration, nsIPrincipal* aPrincipal); 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); // 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; RefPtr mETPPermissionObserver; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_workers_serviceworkermanager_h