/* -*- 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_serviceworkerprivate_h #define mozilla_dom_serviceworkerprivate_h #include #include #include "mozilla/Attributes.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/FetchService.h" #include "mozilla/dom/RemoteWorkerController.h" #include "mozilla/dom/RemoteWorkerTypes.h" #include "mozilla/dom/ServiceWorkerOpArgs.h" #include "nsCOMPtr.h" #include "nsISupportsImpl.h" #include "nsTArray.h" #define NOTIFICATION_CLICK_EVENT_NAME u"notificationclick" #define NOTIFICATION_CLOSE_EVENT_NAME u"notificationclose" class nsIInterceptedChannel; class nsIWorkerDebugger; namespace mozilla { template class Maybe; class JSObjectHolder; namespace dom { class ClientInfoAndState; class RemoteWorkerControllerChild; class ServiceWorkerCloneData; class ServiceWorkerInfo; class ServiceWorkerPrivate; class ServiceWorkerRegistrationInfo; namespace ipc { class StructuredCloneData; } // namespace ipc class LifeCycleEventCallback : public Runnable { public: LifeCycleEventCallback() : Runnable("dom::LifeCycleEventCallback") {} // Called on the worker thread. virtual void SetResult(bool aResult) = 0; }; // Used to keep track of pending waitUntil as well as in-flight extendable // events. When the last token is released, we attempt to terminate the worker. class KeepAliveToken final : public nsISupports { public: NS_DECL_ISUPPORTS explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate); private: ~KeepAliveToken(); RefPtr mPrivate; }; class ServiceWorkerPrivate final : public RemoteWorkerObserver { friend class KeepAliveToken; public: NS_INLINE_DECL_REFCOUNTING(ServiceWorkerPrivate, override); using PromiseExtensionWorkerHasListener = MozPromise; public: explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo); nsresult SendMessageEvent(RefPtr&& aData, const ClientInfoAndState& aClientInfoAndState); // This is used to validate the worker script and continue the installation // process. nsresult CheckScriptEvaluation(RefPtr aCallback); nsresult SendLifeCycleEvent(const nsAString& aEventType, RefPtr aCallback); nsresult SendPushEvent(const nsAString& aMessageId, const Maybe>& aData, RefPtr aRegistration); nsresult SendPushSubscriptionChangeEvent(); nsresult SendNotificationEvent(const nsAString& aEventName, 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, const nsAString& aScope); nsresult SendFetchEvent(nsCOMPtr aChannel, nsILoadGroup* aLoadGroup, const nsAString& aClientId, const nsAString& aResultingClientId); Result, nsresult> WakeForExtensionAPIEvent(const nsAString& aExtensionAPINamespace, const nsAString& aEXtensionAPIEventName); // This will terminate the current running worker thread and drop the // workerPrivate reference. // Called by ServiceWorkerInfo when [[Clear Registration]] is invoked // or whenever the spec mandates that we terminate the worker. // This is a no-op if the worker has already been stopped. void TerminateWorker(); void NoteDeadServiceWorkerInfo(); void NoteStoppedControllingDocuments(); void UpdateState(ServiceWorkerState aState); nsresult GetDebugger(nsIWorkerDebugger** aResult); nsresult AttachDebugger(); nsresult DetachDebugger(); bool IsIdle() const; // This promise is used schedule clearing of the owning registrations and its // associated Service Workers if that registration becomes "unreachable" by // the ServiceWorkerManager. This occurs under two conditions, which are the // preconditions to calling this method: // - The owning registration must be unregistered. // - The associated Service Worker must *not* be controlling clients. // // Additionally, perhaps stating the obvious, the associated Service Worker // must *not* be idle (whatever must be done "when idle" can just be done // immediately). RefPtr GetIdlePromise(); void SetHandlesFetch(bool aValue); RefPtr SetSkipWaitingFlag(); static void RunningShutdown() { // Force a final update of the number of running ServiceWorkers UpdateRunning(0, 0); MOZ_ASSERT(sRunningServiceWorkers == 0); MOZ_ASSERT(sRunningServiceWorkersFetch == 0); } /** * Update Telemetry for # of running ServiceWorkers */ static void UpdateRunning(int32_t aDelta, int32_t aFetchDelta); private: // Timer callbacks void NoteIdleWorkerCallback(nsITimer* aTimer); void TerminateWorkerCallback(nsITimer* aTimer); void RenewKeepAliveToken(); void ResetIdleTimeout(); void AddToken(); void ReleaseToken(); already_AddRefed CreateEventKeepAliveToken(); nsresult SpawnWorkerIfNeeded(); ~ServiceWorkerPrivate(); nsresult Initialize(); /** * RemoteWorkerObserver */ void CreationFailed() override; void CreationSucceeded() override; void ErrorReceived(const ErrorValue& aError) override; void LockNotified(bool aCreated) final { // no-op for service workers } void WebTransportNotified(bool aCreated) final { // no-op for service workers } void Terminated() override; // Refreshes only the parts of mRemoteWorkerData that may change over time. void RefreshRemoteWorkerData( const RefPtr& aRegistration); nsresult SendPushEventInternal( RefPtr&& aRegistration, ServiceWorkerPushEventOpArgs&& aArgs); // Setup the navigation preload by the intercepted channel and the // RegistrationInfo. RefPtr SetupNavigationPreload( nsCOMPtr& aChannel, const RefPtr& aRegistration); nsresult SendFetchEventInternal( RefPtr&& aRegistration, ParentToParentServiceWorkerFetchEventOpArgs&& aArgs, nsCOMPtr&& aChannel, RefPtr&& aPreloadResponseReadyPromises); void Shutdown(); RefPtr ShutdownInternal( uint32_t aShutdownStateId); nsresult ExecServiceWorkerOp( ServiceWorkerOpArgs&& aArgs, std::function&& aSuccessCallback, std::function&& aFailureCallback = [] {}); class PendingFunctionalEvent { public: PendingFunctionalEvent( ServiceWorkerPrivate* aOwner, RefPtr&& aRegistration); virtual ~PendingFunctionalEvent(); virtual nsresult Send() = 0; protected: ServiceWorkerPrivate* const MOZ_NON_OWNING_REF mOwner; RefPtr mRegistration; }; class PendingPushEvent final : public PendingFunctionalEvent { public: PendingPushEvent(ServiceWorkerPrivate* aOwner, RefPtr&& aRegistration, ServiceWorkerPushEventOpArgs&& aArgs); nsresult Send() override; private: ServiceWorkerPushEventOpArgs mArgs; }; class PendingFetchEvent final : public PendingFunctionalEvent { public: PendingFetchEvent( ServiceWorkerPrivate* aOwner, RefPtr&& aRegistration, ParentToParentServiceWorkerFetchEventOpArgs&& aArgs, nsCOMPtr&& aChannel, RefPtr&& aPreloadResponseReadyPromises); nsresult Send() override; ~PendingFetchEvent(); private: ParentToParentServiceWorkerFetchEventOpArgs mArgs; nsCOMPtr mChannel; // The promises from FetchService. It indicates if the preload response is // ready or not. The promise's resolve/reject value should be handled in // FetchEventOpChild, such that the preload result can be propagated to the // ServiceWorker through IPC. However, FetchEventOpChild creation could be // pending here, so this member is needed. And it will be forwarded to // FetchEventOpChild when crearting the FetchEventOpChild. RefPtr mPreloadResponseReadyPromises; }; nsTArray> mPendingFunctionalEvents; /** * It's possible that there are still in-progress operations when a * a termination operation is issued. In this case, it's important to keep * the RemoteWorkerControllerChild actor alive until all pending operations * have completed before destroying it with Send__delete__(). * * RAIIActorPtrHolder holds a singular, owning reference to a * RemoteWorkerControllerChild actor and is responsible for destroying the * actor in its (i.e. the holder's) destructor. This implies that all * in-progress operations must maintain a strong reference to their * corresponding holders and release the reference once completed/canceled. * * Additionally a RAIIActorPtrHolder must be initialized with a non-null actor * and cannot be moved or copied. Therefore, the identities of two held * actors can be compared by simply comparing their holders' addresses. */ class RAIIActorPtrHolder final { public: NS_INLINE_DECL_REFCOUNTING(RAIIActorPtrHolder) explicit RAIIActorPtrHolder( already_AddRefed aActor); RAIIActorPtrHolder(const RAIIActorPtrHolder& aOther) = delete; RAIIActorPtrHolder& operator=(const RAIIActorPtrHolder& aOther) = delete; RAIIActorPtrHolder(RAIIActorPtrHolder&& aOther) = delete; RAIIActorPtrHolder& operator=(RAIIActorPtrHolder&& aOther) = delete; RemoteWorkerControllerChild* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN; RemoteWorkerControllerChild* get() const; RefPtr OnDestructor(); private: ~RAIIActorPtrHolder(); MozPromiseHolder mDestructorPromiseHolder; const RefPtr mActor; }; RefPtr mControllerChild; RemoteWorkerData mRemoteWorkerData; TimeStamp mServiceWorkerLaunchTimeStart; // Counters for Telemetry - totals running simultaneously, and those that // handle Fetch, plus Max values for each static uint32_t sRunningServiceWorkers; static uint32_t sRunningServiceWorkersFetch; static uint32_t sRunningServiceWorkersMax; static uint32_t sRunningServiceWorkersFetchMax; // We know the state after we've evaluated the worker, and we then store // it in the registration. The only valid state transition should be // from Unknown to Enabled or Disabled. enum { Unknown, Enabled, Disabled } mHandlesFetch{Unknown}; // The info object owns us. It is possible to outlive it for a brief period // of time if there are pending waitUntil promises, in which case it // will be null and |SpawnWorkerIfNeeded| will always fail. ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo; nsCOMPtr mIdleWorkerTimer; // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the // worker a grace period after each event. RefPtr mIdleKeepAliveToken; uint64_t mDebuggerCount; uint64_t mTokenCount; // Used by the owning `ServiceWorkerRegistrationInfo` when it wants to call // `Clear` after being unregistered and isn't controlling any clients but this // worker (i.e. the registration's active worker) isn't idle yet. Note that // such an event should happen at most once in a // `ServiceWorkerRegistrationInfo`s lifetime, so this promise should also only // be obtained at most once. MozPromiseHolder mIdlePromiseHolder; #ifdef DEBUG bool mIdlePromiseObtained = false; #endif }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_serviceworkerprivate_h