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