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_serviceworkerprivate_h 8 #define mozilla_dom_serviceworkerprivate_h 9 10 #include <type_traits> 11 12 #include "nsCOMPtr.h" 13 #include "mozilla/dom/WorkerPrivate.h" 14 #include "mozilla/MozPromise.h" 15 16 #define NOTIFICATION_CLICK_EVENT_NAME u"notificationclick" 17 #define NOTIFICATION_CLOSE_EVENT_NAME u"notificationclose" 18 19 class nsIInterceptedChannel; 20 class nsIWorkerDebugger; 21 22 namespace mozilla { 23 24 class JSObjectHolder; 25 26 namespace dom { 27 28 class ClientInfoAndState; 29 class ServiceWorkerCloneData; 30 class ServiceWorkerInfo; 31 class ServiceWorkerPrivate; 32 class ServiceWorkerPrivateImpl; 33 class ServiceWorkerRegistrationInfo; 34 35 namespace ipc { 36 class StructuredCloneData; 37 } // namespace ipc 38 39 class LifeCycleEventCallback : public Runnable { 40 public: LifeCycleEventCallback()41 LifeCycleEventCallback() : Runnable("dom::LifeCycleEventCallback") {} 42 43 // Called on the worker thread. 44 virtual void SetResult(bool aResult) = 0; 45 }; 46 47 // Used to keep track of pending waitUntil as well as in-flight extendable 48 // events. When the last token is released, we attempt to terminate the worker. 49 class KeepAliveToken final : public nsISupports { 50 public: 51 NS_DECL_ISUPPORTS 52 53 explicit KeepAliveToken(ServiceWorkerPrivate* aPrivate); 54 55 private: 56 ~KeepAliveToken(); 57 58 RefPtr<ServiceWorkerPrivate> mPrivate; 59 }; 60 61 // ServiceWorkerPrivate is a wrapper for managing the on-demand aspect of 62 // service workers. It handles all event dispatching to the worker and ensures 63 // the worker thread is running when needed. 64 // 65 // Lifetime management: To spin up the worker thread we own a |WorkerPrivate| 66 // object which can be cancelled if no events are received for a certain 67 // amount of time. The worker is kept alive by holding a |KeepAliveToken| 68 // reference. 69 // 70 // Extendable events hold tokens for the duration of their handler execution 71 // and until their waitUntil promise is resolved, while ServiceWorkerPrivate 72 // will hold a token for |dom.serviceWorkers.idle_timeout| seconds after each 73 // new event. 74 // 75 // Note: All timer events must be handled on the main thread because the 76 // worker may block indefinitely the worker thread (e. g. infinite loop in the 77 // script). 78 // 79 // There are 3 cases where we may ignore keep alive tokens: 80 // 1. When ServiceWorkerPrivate's token expired, if there are still waitUntil 81 // handlers holding tokens, we wait another 82 // |dom.serviceWorkers.idle_extended_timeout| seconds before forcibly 83 // terminating the worker. 84 // 2. If the worker stopped controlling documents and it is not handling push 85 // events. 86 // 3. The content process is shutting down. 87 // 88 // Adding an API function for a new event requires calling |SpawnWorkerIfNeeded| 89 // with an appropriate reason before any runnable is dispatched to the worker. 90 // If the event is extendable then the runnable should inherit 91 // ExtendableEventWorkerRunnable. 92 class ServiceWorkerPrivate final { 93 friend class KeepAliveToken; 94 friend class ServiceWorkerPrivateImpl; 95 96 public: 97 NS_IMETHOD_(MozExternalRefCountType) AddRef(); 98 NS_IMETHOD_(MozExternalRefCountType) Release(); 99 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ServiceWorkerPrivate) 100 101 using HasThreadSafeRefCnt = std::false_type; 102 103 protected: 104 nsCycleCollectingAutoRefCnt mRefCnt; 105 NS_DECL_OWNINGTHREAD 106 107 public: 108 // TODO: remove this class. There's one (and only should be one) concrete 109 // class that derives this abstract base class. 110 class Inner { 111 public: 112 NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING 113 114 virtual nsresult SendMessageEvent( 115 RefPtr<ServiceWorkerCloneData>&& aData, 116 const ClientInfoAndState& aClientInfoAndState) = 0; 117 118 virtual nsresult CheckScriptEvaluation( 119 RefPtr<LifeCycleEventCallback> aScriptEvaluationCallback) = 0; 120 121 virtual nsresult SendLifeCycleEvent( 122 const nsAString& aEventName, 123 RefPtr<LifeCycleEventCallback> aCallback) = 0; 124 125 virtual nsresult SendPushEvent( 126 RefPtr<ServiceWorkerRegistrationInfo> aRegistration, 127 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) = 0; 128 129 virtual nsresult SendPushSubscriptionChangeEvent() = 0; 130 131 virtual nsresult SendNotificationEvent( 132 const nsAString& aEventName, const nsAString& aID, 133 const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang, 134 const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon, 135 const nsAString& aData, const nsAString& aBehavior, 136 const nsAString& aScope, uint32_t aDisableOpenClickDelay) = 0; 137 138 virtual nsresult SendFetchEvent( 139 RefPtr<ServiceWorkerRegistrationInfo> aRegistration, 140 nsCOMPtr<nsIInterceptedChannel> aChannel, const nsAString& aClientId, 141 const nsAString& aResultingClientId) = 0; 142 143 virtual nsresult SpawnWorkerIfNeeded() = 0; 144 145 virtual void TerminateWorker() = 0; 146 147 virtual void UpdateState(ServiceWorkerState aState) = 0; 148 149 virtual void NoteDeadOuter() = 0; 150 151 virtual bool WorkerIsDead() const = 0; 152 }; 153 154 explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo); 155 156 nsresult SendMessageEvent(RefPtr<ServiceWorkerCloneData>&& aData, 157 const ClientInfoAndState& aClientInfoAndState); 158 159 // This is used to validate the worker script and continue the installation 160 // process. 161 nsresult CheckScriptEvaluation(LifeCycleEventCallback* aCallback); 162 163 nsresult SendLifeCycleEvent(const nsAString& aEventType, 164 LifeCycleEventCallback* aCallback); 165 166 nsresult SendPushEvent(const nsAString& aMessageId, 167 const Maybe<nsTArray<uint8_t>>& aData, 168 ServiceWorkerRegistrationInfo* aRegistration); 169 170 nsresult SendPushSubscriptionChangeEvent(); 171 172 nsresult SendNotificationEvent(const nsAString& aEventName, 173 const nsAString& aID, const nsAString& aTitle, 174 const nsAString& aDir, const nsAString& aLang, 175 const nsAString& aBody, const nsAString& aTag, 176 const nsAString& aIcon, const nsAString& aData, 177 const nsAString& aBehavior, 178 const nsAString& aScope); 179 180 nsresult SendFetchEvent(nsIInterceptedChannel* aChannel, 181 nsILoadGroup* aLoadGroup, const nsAString& aClientId, 182 const nsAString& aResultingClientId); 183 184 bool MaybeStoreISupports(nsISupports* aSupports); 185 186 void RemoveISupports(nsISupports* aSupports); 187 188 // This will terminate the current running worker thread and drop the 189 // workerPrivate reference. 190 // Called by ServiceWorkerInfo when [[Clear Registration]] is invoked 191 // or whenever the spec mandates that we terminate the worker. 192 // This is a no-op if the worker has already been stopped. 193 void TerminateWorker(); 194 195 void NoteDeadServiceWorkerInfo(); 196 197 void NoteStoppedControllingDocuments(); 198 199 void UpdateState(ServiceWorkerState aState); 200 201 nsresult GetDebugger(nsIWorkerDebugger** aResult); 202 203 nsresult AttachDebugger(); 204 205 nsresult DetachDebugger(); 206 207 bool IsIdle() const; 208 209 // This promise is used schedule clearing of the owning registrations and its 210 // associated Service Workers if that registration becomes "unreachable" by 211 // the ServiceWorkerManager. This occurs under two conditions, which are the 212 // preconditions to calling this method: 213 // - The owning registration must be unregistered. 214 // - The associated Service Worker must *not* be controlling clients. 215 // 216 // Additionally, perhaps stating the obvious, the associated Service Worker 217 // must *not* be idle (whatever must be done "when idle" can just be done 218 // immediately). 219 RefPtr<GenericPromise> GetIdlePromise(); 220 221 void SetHandlesFetch(bool aValue); 222 223 private: 224 enum WakeUpReason { 225 FetchEvent = 0, 226 PushEvent, 227 PushSubscriptionChangeEvent, 228 MessageEvent, 229 NotificationClickEvent, 230 NotificationCloseEvent, 231 LifeCycleEvent, 232 AttachEvent, 233 Unknown 234 }; 235 236 // Timer callbacks 237 void NoteIdleWorkerCallback(nsITimer* aTimer); 238 239 void TerminateWorkerCallback(nsITimer* aTimer); 240 241 void RenewKeepAliveToken(WakeUpReason aWhy); 242 243 void ResetIdleTimeout(); 244 245 void AddToken(); 246 247 void ReleaseToken(); 248 249 nsresult SpawnWorkerIfNeeded(WakeUpReason aWhy, 250 bool* aNewWorkerCreated = nullptr, 251 nsILoadGroup* aLoadGroup = nullptr); 252 253 ~ServiceWorkerPrivate(); 254 255 already_AddRefed<KeepAliveToken> CreateEventKeepAliveToken(); 256 257 // The info object owns us. It is possible to outlive it for a brief period 258 // of time if there are pending waitUntil promises, in which case it 259 // will be null and |SpawnWorkerIfNeeded| will always fail. 260 ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo; 261 262 // The WorkerPrivate object can only be closed by this class or by the 263 // RuntimeService class if gecko is shutting down. Closing the worker 264 // multiple times is OK, since the second attempt will be a no-op. 265 RefPtr<WorkerPrivate> mWorkerPrivate; 266 267 nsCOMPtr<nsITimer> mIdleWorkerTimer; 268 269 // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the 270 // worker a grace period after each event. 271 RefPtr<KeepAliveToken> mIdleKeepAliveToken; 272 273 uint64_t mDebuggerCount; 274 275 uint64_t mTokenCount; 276 277 // Meant for keeping objects alive while handling requests from the worker 278 // on the main thread. Access to this array is provided through 279 // |StoreISupports| and |RemoveISupports|. Note that the array is also 280 // cleared whenever the worker is terminated. 281 nsTArray<nsCOMPtr<nsISupports>> mSupportsArray; 282 283 // Array of function event worker runnables that are pending due to 284 // the worker activating. Main thread only. 285 nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents; 286 287 RefPtr<Inner> mInner; 288 289 // Used by the owning `ServiceWorkerRegistrationInfo` when it wants to call 290 // `Clear` after being unregistered and isn't controlling any clients but this 291 // worker (i.e. the registration's active worker) isn't idle yet. Note that 292 // such an event should happen at most once in a 293 // `ServiceWorkerRegistrationInfo`s lifetime, so this promise should also only 294 // be obtained at most once. 295 MozPromiseHolder<GenericPromise> mIdlePromiseHolder; 296 297 #ifdef DEBUG 298 bool mIdlePromiseObtained = false; 299 #endif 300 }; 301 302 } // namespace dom 303 } // namespace mozilla 304 305 #endif // mozilla_dom_serviceworkerprivate_h 306