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_workers_serviceworkerprivate_h
8 #define mozilla_dom_workers_serviceworkerprivate_h
9 
10 #include "nsCOMPtr.h"
11 
12 #include "WorkerPrivate.h"
13 
14 #define NOTIFICATION_CLICK_EVENT_NAME "notificationclick"
15 #define NOTIFICATION_CLOSE_EVENT_NAME "notificationclose"
16 
17 class nsIInterceptedChannel;
18 
19 namespace mozilla {
20 namespace dom {
21 namespace workers {
22 
23 class ServiceWorkerInfo;
24 class ServiceWorkerRegistrationInfo;
25 class KeepAliveToken;
26 
27 class LifeCycleEventCallback : public Runnable
28 {
29 public:
30   // Called on the worker thread.
31   virtual void
32   SetResult(bool aResult) = 0;
33 };
34 
35 // ServiceWorkerPrivate is a wrapper for managing the on-demand aspect of
36 // service workers. It handles all event dispatching to the worker and ensures
37 // the worker thread is running when needed.
38 //
39 // Lifetime management: To spin up the worker thread we own a |WorkerPrivate|
40 // object which can be cancelled if no events are received for a certain
41 // amount of time. The worker is kept alive by holding a |KeepAliveToken|
42 // reference.
43 //
44 // Extendable events hold tokens for the duration of their handler execution
45 // and until their waitUntil promise is resolved, while ServiceWorkerPrivate
46 // will hold a token for |dom.serviceWorkers.idle_timeout| seconds after each
47 // new event.
48 //
49 // Note: All timer events must be handled on the main thread because the
50 // worker may block indefinitely the worker thread (e. g. infinite loop in the
51 // script).
52 //
53 // There are 3 cases where we may ignore keep alive tokens:
54 // 1. When ServiceWorkerPrivate's token expired, if there are still waitUntil
55 // handlers holding tokens, we wait another |dom.serviceWorkers.idle_extended_timeout|
56 // seconds before forcibly terminating the worker.
57 // 2. If the worker stopped controlling documents and it is not handling push
58 // events.
59 // 3. The content process is shutting down.
60 //
61 // Adding an API function for a new event requires calling |SpawnWorkerIfNeeded|
62 // with an appropriate reason before any runnable is dispatched to the worker.
63 // If the event is extendable then the runnable should inherit
64 // ExtendableEventWorkerRunnable.
65 class ServiceWorkerPrivate final : public nsIObserver
66 {
67   friend class KeepAliveToken;
68 
69 public:
70   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
71   NS_DECL_CYCLE_COLLECTION_CLASS(ServiceWorkerPrivate)
72   NS_DECL_NSIOBSERVER
73 
74   explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
75 
76   nsresult
77   SendMessageEvent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
78                    const Optional<Sequence<JS::Value>>& aTransferable,
79                    UniquePtr<ServiceWorkerClientInfo>&& aClientInfo);
80 
81   // This is used to validate the worker script and continue the installation
82   // process.
83   nsresult
84   CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
85 
86   nsresult
87   SendLifeCycleEvent(const nsAString& aEventType,
88                      LifeCycleEventCallback* aCallback,
89                      nsIRunnable* aLoadFailure);
90 
91   nsresult
92   SendPushEvent(const nsAString& aMessageId,
93                 const Maybe<nsTArray<uint8_t>>& aData,
94                 ServiceWorkerRegistrationInfo* aRegistration);
95 
96   nsresult
97   SendPushSubscriptionChangeEvent();
98 
99   nsresult
100   SendNotificationEvent(const nsAString& aEventName,
101                         const nsAString& aID,
102                         const nsAString& aTitle,
103                         const nsAString& aDir,
104                         const nsAString& aLang,
105                         const nsAString& aBody,
106                         const nsAString& aTag,
107                         const nsAString& aIcon,
108                         const nsAString& aData,
109                         const nsAString& aBehavior,
110                         const nsAString& aScope);
111 
112   nsresult
113   SendFetchEvent(nsIInterceptedChannel* aChannel,
114                  nsILoadGroup* aLoadGroup,
115                  const nsAString& aDocumentId,
116                  bool aIsReload);
117 
118   void
119   StoreISupports(nsISupports* aSupports);
120 
121   void
122   RemoveISupports(nsISupports* aSupports);
123 
124   // This will terminate the current running worker thread and drop the
125   // workerPrivate reference.
126   // Called by ServiceWorkerInfo when [[Clear Registration]] is invoked
127   // or whenever the spec mandates that we terminate the worker.
128   // This is a no-op if the worker has already been stopped.
129   void
130   TerminateWorker();
131 
132   void
133   NoteDeadServiceWorkerInfo();
134 
135   void
136   NoteStoppedControllingDocuments();
137 
138   void
139   Activated();
140 
141   nsresult
142   GetDebugger(nsIWorkerDebugger** aResult);
143 
144   nsresult
145   AttachDebugger();
146 
147   nsresult
148   DetachDebugger();
149 
150   bool
151   IsIdle() const;
152 
153   void
154   AddPendingWindow(Runnable* aPendingWindow);
155 
156 private:
157   enum WakeUpReason {
158     FetchEvent = 0,
159     PushEvent,
160     PushSubscriptionChangeEvent,
161     MessageEvent,
162     NotificationClickEvent,
163     NotificationCloseEvent,
164     LifeCycleEvent,
165     AttachEvent
166   };
167 
168   // Timer callbacks
169   void
170   NoteIdleWorkerCallback(nsITimer* aTimer);
171 
172   void
173   TerminateWorkerCallback(nsITimer* aTimer);
174 
175   void
176   RenewKeepAliveToken(WakeUpReason aWhy);
177 
178   void
179   ResetIdleTimeout();
180 
181   void
182   AddToken();
183 
184   void
185   ReleaseToken();
186 
187   // |aLoadFailedRunnable| is a runnable dispatched to the main thread
188   // if the script loader failed for some reason, but can be null.
189   nsresult
190   SpawnWorkerIfNeeded(WakeUpReason aWhy,
191                       nsIRunnable* aLoadFailedRunnable,
192                       nsILoadGroup* aLoadGroup = nullptr);
193 
194   ~ServiceWorkerPrivate();
195 
196   already_AddRefed<KeepAliveToken>
197   CreateEventKeepAliveToken();
198 
199   // The info object owns us. It is possible to outlive it for a brief period
200   // of time if there are pending waitUntil promises, in which case it
201   // will be null and |SpawnWorkerIfNeeded| will always fail.
202   ServiceWorkerInfo* MOZ_NON_OWNING_REF mInfo;
203 
204   // The WorkerPrivate object can only be closed by this class or by the
205   // RuntimeService class if gecko is shutting down. Closing the worker
206   // multiple times is OK, since the second attempt will be a no-op.
207   RefPtr<WorkerPrivate> mWorkerPrivate;
208 
209   nsCOMPtr<nsITimer> mIdleWorkerTimer;
210 
211   // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the
212   // worker a grace period after each event.
213   RefPtr<KeepAliveToken> mIdleKeepAliveToken;
214 
215   uint64_t mDebuggerCount;
216 
217   uint64_t mTokenCount;
218 
219   // Meant for keeping objects alive while handling requests from the worker
220   // on the main thread. Access to this array is provided through
221   // |StoreISupports| and |RemoveISupports|. Note that the array is also
222   // cleared whenever the worker is terminated.
223   nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
224 
225   // Array of function event worker runnables that are pending due to
226   // the worker activating.  Main thread only.
227   nsTArray<RefPtr<WorkerRunnable>> mPendingFunctionalEvents;
228 
229   nsTArray<Runnable*> pendingWindows;
230 };
231 
232 } // namespace workers
233 } // namespace dom
234 } // namespace mozilla
235 
236 #endif // mozilla_dom_workers_serviceworkerprivate_h
237