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_serviceworkermanager_h
8 #define mozilla_dom_workers_serviceworkermanager_h
9 
10 #include <cstdint>
11 #include "ErrorList.h"
12 #include "ServiceWorkerShutdownState.h"
13 #include "js/ErrorReport.h"
14 #include "mozilla/AlreadyAddRefed.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/HashTable.h"
17 #include "mozilla/MozPromise.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/UniquePtr.h"
20 #include "mozilla/dom/ClientHandle.h"
21 #include "mozilla/dom/ClientOpPromise.h"
22 #include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
23 #include "mozilla/dom/ServiceWorkerRegistrationInfo.h"
24 #include "mozilla/dom/ServiceWorkerUtils.h"
25 #include "mozilla/mozalloc.h"
26 #include "nsClassHashtable.h"
27 #include "nsContentUtils.h"
28 #include "nsHashKeys.h"
29 #include "nsIObserver.h"
30 #include "nsIServiceWorkerManager.h"
31 #include "nsISupports.h"
32 #include "nsStringFwd.h"
33 #include "nsTArray.h"
34 
35 class nsIConsoleReportCollector;
36 
37 namespace mozilla {
38 
39 class OriginAttributes;
40 
41 namespace ipc {
42 class PrincipalInfo;
43 }  // namespace ipc
44 
45 namespace dom {
46 
47 class ContentParent;
48 class ServiceWorkerInfo;
49 class ServiceWorkerJobQueue;
50 class ServiceWorkerManagerChild;
51 class ServiceWorkerPrivate;
52 class ServiceWorkerRegistrar;
53 class ServiceWorkerShutdownBlocker;
54 
55 class ServiceWorkerUpdateFinishCallback {
56  protected:
57   virtual ~ServiceWorkerUpdateFinishCallback() = default;
58 
59  public:
60   NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback)
61 
62   virtual void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) = 0;
63 
64   virtual void UpdateFailed(ErrorResult& aStatus) = 0;
65 };
66 
67 #define NS_SERVICEWORKERMANAGER_IMPL_IID             \
68   { /* f4f8755a-69ca-46e8-a65d-775745535990 */       \
69     0xf4f8755a, 0x69ca, 0x46e8, {                    \
70       0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 \
71     }                                                \
72   }
73 
74 /*
75  * The ServiceWorkerManager is a per-process global that deals with the
76  * installation, querying and event dispatch of ServiceWorkers for all the
77  * origins in the process.
78  *
79  * NOTE: the following documentation is a WIP:
80  *
81  * The ServiceWorkerManager (SWM) is a main-thread, parent-process singleton
82  * that encapsulates the browser-global state of service workers. This state
83  * includes, but is not limited to, all service worker registrations and all
84  * controlled service worker clients. The SWM also provides methods to read and
85  * mutate this state and to dispatch operations (e.g. DOM events such as a
86  * FetchEvent) to service workers.
87  *
88  * Example usage:
89  *
90  * MOZ_ASSERT(NS_IsMainThread(), "SWM is main-thread only");
91  *
92  * RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
93  *
94  * // Nullness must be checked by code that possibly executes during browser
95  * // shutdown, which is when the SWM is destroyed.
96  * if (swm) {
97  *   // Do something with the SWM.
98  * }
99  */
100 class ServiceWorkerManager final : public nsIServiceWorkerManager,
101                                    public nsIObserver {
102   friend class GetRegistrationsRunnable;
103   friend class GetRegistrationRunnable;
104   friend class ServiceWorkerJob;
105   friend class ServiceWorkerRegistrationInfo;
106   friend class ServiceWorkerShutdownBlocker;
107   friend class ServiceWorkerUnregisterJob;
108   friend class ServiceWorkerUpdateJob;
109   friend class UpdateTimerCallback;
110 
111  public:
112   NS_DECL_ISUPPORTS
113   NS_DECL_NSISERVICEWORKERMANAGER
114   NS_DECL_NSIOBSERVER
115 
116   // Return true if the given principal and URI matches a registered service
117   // worker which handles fetch event.
118   // If there is a matched service worker but doesn't handle fetch events, this
119   // method will try to set the matched service worker as the controller of the
120   // passed in channel. Then also schedule a soft-update job for the service
121   // worker.
122   bool IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI,
123                    nsIChannel* aChannel);
124 
125   void DispatchFetchEvent(nsIInterceptedChannel* aChannel, ErrorResult& aRv);
126 
127   void Update(nsIPrincipal* aPrincipal, const nsACString& aScope,
128               nsCString aNewestWorkerScriptUrl,
129               ServiceWorkerUpdateFinishCallback* aCallback);
130 
131   void UpdateInternal(nsIPrincipal* aPrincipal, const nsACString& aScope,
132                       nsCString&& aNewestWorkerScriptUrl,
133                       ServiceWorkerUpdateFinishCallback* aCallback);
134 
135   void SoftUpdate(const OriginAttributes& aOriginAttributes,
136                   const nsACString& aScope);
137 
138   void SoftUpdateInternal(const OriginAttributes& aOriginAttributes,
139                           const nsACString& aScope,
140                           ServiceWorkerUpdateFinishCallback* aCallback);
141 
142   RefPtr<ServiceWorkerRegistrationPromise> Register(
143       const ClientInfo& aClientInfo, const nsACString& aScopeURL,
144       const nsACString& aScriptURL,
145       ServiceWorkerUpdateViaCache aUpdateViaCache);
146 
147   RefPtr<ServiceWorkerRegistrationPromise> GetRegistration(
148       const ClientInfo& aClientInfo, const nsACString& aURL) const;
149 
150   RefPtr<ServiceWorkerRegistrationListPromise> GetRegistrations(
151       const ClientInfo& aClientInfo) const;
152 
153   already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration(
154       nsIPrincipal* aPrincipal, const nsACString& aScope) const;
155 
156   already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration(
157       const mozilla::ipc::PrincipalInfo& aPrincipal,
158       const nsACString& aScope) const;
159 
160   already_AddRefed<ServiceWorkerRegistrationInfo> CreateNewRegistration(
161       const nsCString& aScope, nsIPrincipal* aPrincipal,
162       ServiceWorkerUpdateViaCache aUpdateViaCache,
163       IPCNavigationPreloadState aNavigationPreloadState =
164           IPCNavigationPreloadState(false, "true"_ns));
165 
166   void RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
167 
168   void StoreRegistration(nsIPrincipal* aPrincipal,
169                          ServiceWorkerRegistrationInfo* aRegistration);
170 
171   /**
172    * Report an error for the given scope to any window we think might be
173    * interested, failing over to the Browser Console if we couldn't find any.
174    *
175    * Error messages should be localized, so you probably want to call
176    * LocalizeAndReportToAllClients instead, which in turn calls us after
177    * localizing the error.
178    */
179   void ReportToAllClients(const nsCString& aScope, const nsString& aMessage,
180                           const nsString& aFilename, const nsString& aLine,
181                           uint32_t aLineNumber, uint32_t aColumnNumber,
182                           uint32_t aFlags);
183 
184   /**
185    * Report a localized error for the given scope to any window we think might
186    * be interested.
187    *
188    * Note that this method takes an nsTArray<nsString> for the parameters, not
189    * bare chart16_t*[].  You can use a std::initializer_list constructor inline
190    * so that argument might look like: nsTArray<nsString> { some_nsString,
191    * PromiseFlatString(some_nsSubString_aka_nsAString),
192    * NS_ConvertUTF8toUTF16(some_nsCString_or_nsCSubString),
193    * u"some literal"_ns }.  If you have anything else, like a
194    * number, you can use an nsAutoString with AppendInt/friends.
195    *
196    * @param [aFlags]
197    *   The nsIScriptError flag, one of errorFlag (0x0), warningFlag (0x1),
198    *   infoFlag (0x8).  We default to error if omitted because usually we're
199    *   logging exceptional and/or obvious breakage.
200    */
201   static void LocalizeAndReportToAllClients(
202       const nsCString& aScope, const char* aStringKey,
203       const nsTArray<nsString>& aParamArray, uint32_t aFlags = 0x0,
204       const nsString& aFilename = u""_ns, const nsString& aLine = u""_ns,
205       uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0);
206 
207   // Always consumes the error by reporting to consoles of all controlled
208   // documents.
209   void HandleError(JSContext* aCx, nsIPrincipal* aPrincipal,
210                    const nsCString& aScope, const nsString& aWorkerURL,
211                    const nsString& aMessage, const nsString& aFilename,
212                    const nsString& aLine, uint32_t aLineNumber,
213                    uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType);
214 
215   [[nodiscard]] RefPtr<GenericErrorResultPromise> MaybeClaimClient(
216       const ClientInfo& aClientInfo,
217       ServiceWorkerRegistrationInfo* aWorkerRegistration);
218 
219   [[nodiscard]] RefPtr<GenericErrorResultPromise> MaybeClaimClient(
220       const ClientInfo& aClientInfo,
221       const ServiceWorkerDescriptor& aServiceWorker);
222 
223   static already_AddRefed<ServiceWorkerManager> GetInstance();
224 
225   void LoadRegistration(const ServiceWorkerRegistrationData& aRegistration);
226 
227   void LoadRegistrations(
228       const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
229 
230   void MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo);
231 
232   nsresult SendPushEvent(const nsACString& aOriginAttributes,
233                          const nsACString& aScope, const nsAString& aMessageId,
234                          const Maybe<nsTArray<uint8_t>>& aData);
235 
236   void WorkerIsIdle(ServiceWorkerInfo* aWorker);
237 
238   RefPtr<ServiceWorkerRegistrationPromise> WhenReady(
239       const ClientInfo& aClientInfo);
240 
241   void CheckPendingReadyPromises();
242 
243   void RemovePendingReadyPromise(const ClientInfo& aClientInfo);
244 
245   void NoteInheritedController(const ClientInfo& aClientInfo,
246                                const ServiceWorkerDescriptor& aController);
247 
248   void BlockShutdownOn(GenericNonExclusivePromise* aPromise,
249                        uint32_t aShutdownStateId);
250 
251   nsresult GetClientRegistration(
252       const ClientInfo& aClientInfo,
253       ServiceWorkerRegistrationInfo** aRegistrationInfo);
254 
255   int32_t GetPrincipalQuotaUsageCheckCount(nsIPrincipal* aPrincipal);
256 
257   // Returns the shutdown state ID (may be an invalid ID if an
258   // nsIAsyncShutdownBlocker is not used).
259   uint32_t MaybeInitServiceWorkerShutdownProgress() const;
260 
261   void ReportServiceWorkerShutdownProgress(
262       uint32_t aShutdownStateId,
263       ServiceWorkerShutdownState::Progress aProgress) const;
264 
265  private:
266   struct RegistrationDataPerPrincipal;
267 
268   static bool FindScopeForPath(const nsACString& aScopeKey,
269                                const nsACString& aPath,
270                                RegistrationDataPerPrincipal** aData,
271                                nsACString& aMatch);
272 
273   ServiceWorkerManager();
274   ~ServiceWorkerManager();
275 
276   void Init(ServiceWorkerRegistrar* aRegistrar);
277 
278   RefPtr<GenericErrorResultPromise> StartControllingClient(
279       const ClientInfo& aClientInfo,
280       ServiceWorkerRegistrationInfo* aRegistrationInfo,
281       bool aControlClientHandle = true);
282 
283   void StopControllingClient(const ClientInfo& aClientInfo);
284 
285   void MaybeStartShutdown();
286 
287   void MaybeFinishShutdown();
288 
289   already_AddRefed<ServiceWorkerJobQueue> GetOrCreateJobQueue(
290       const nsACString& aOriginSuffix, const nsACString& aScope);
291 
292   void MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);
293 
294   already_AddRefed<ServiceWorkerRegistrationInfo> GetRegistration(
295       const nsACString& aScopeKey, const nsACString& aScope) const;
296 
297   void AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration);
298 
299   nsresult Update(ServiceWorkerRegistrationInfo* aRegistration);
300 
301   ServiceWorkerInfo* GetActiveWorkerInfoForScope(
302       const OriginAttributes& aOriginAttributes, const nsACString& aScope);
303 
304   void StopControllingRegistration(
305       ServiceWorkerRegistrationInfo* aRegistration);
306 
307   already_AddRefed<ServiceWorkerRegistrationInfo>
308   GetServiceWorkerRegistrationInfo(const ClientInfo& aClientInfo) const;
309 
310   already_AddRefed<ServiceWorkerRegistrationInfo>
311   GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
312                                    nsIURI* aURI) const;
313 
314   already_AddRefed<ServiceWorkerRegistrationInfo>
315   GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey,
316                                    nsIURI* aURI) const;
317 
318   // This method generates a key using isInElementBrowser from the principal. We
319   // don't use the origin because it can change during the loading.
320   static nsresult PrincipalToScopeKey(nsIPrincipal* aPrincipal,
321                                       nsACString& aKey);
322 
323   static nsresult PrincipalInfoToScopeKey(
324       const mozilla::ipc::PrincipalInfo& aPrincipalInfo, nsACString& aKey);
325 
326   static void AddScopeAndRegistration(
327       const nsACString& aScope, ServiceWorkerRegistrationInfo* aRegistation);
328 
329   static bool HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope);
330 
331   static void RemoveScopeAndRegistration(
332       ServiceWorkerRegistrationInfo* aRegistration);
333 
334   void QueueFireEventOnServiceWorkerRegistrations(
335       ServiceWorkerRegistrationInfo* aRegistration, const nsAString& aName);
336 
337   void UpdateClientControllers(ServiceWorkerRegistrationInfo* aRegistration);
338 
339   void MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
340 
341   RefPtr<ServiceWorkerManagerChild> mActor;
342 
343   bool mShuttingDown;
344 
345   nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> mListeners;
346 
347   void NotifyListenersOnRegister(
348       nsIServiceWorkerRegistrationInfo* aRegistration);
349 
350   void NotifyListenersOnUnregister(
351       nsIServiceWorkerRegistrationInfo* aRegistration);
352 
353   void ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope);
354 
355   void UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
356 
357   void MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
358 
359   nsresult SendNotificationEvent(const nsAString& aEventName,
360                                  const nsACString& aOriginSuffix,
361                                  const nsACString& aScope, const nsAString& aID,
362                                  const nsAString& aTitle, const nsAString& aDir,
363                                  const nsAString& aLang, const nsAString& aBody,
364                                  const nsAString& aTag, const nsAString& aIcon,
365                                  const nsAString& aData,
366                                  const nsAString& aBehavior);
367 
368   // Used by remove() and removeAll() when clearing history.
369   // MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!
370   void ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData,
371                        ServiceWorkerRegistrationInfo* aRegistration);
372 
373   // An "orphaned" registration is one that is unregistered and not controlling
374   // clients. The ServiceWorkerManager must know about all orphaned
375   // registrations to forcefully shutdown all Service Workers during browser
376   // shutdown.
377   void AddOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration);
378 
379   void RemoveOrphanedRegistration(ServiceWorkerRegistrationInfo* aRegistration);
380 
381   HashSet<RefPtr<ServiceWorkerRegistrationInfo>,
382           PointerHasher<ServiceWorkerRegistrationInfo*>>
383       mOrphanedRegistrations;
384 
385   RefPtr<ServiceWorkerShutdownBlocker> mShutdownBlocker;
386 
387   nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal>
388       mRegistrationInfos;
389 
390   struct ControlledClientData {
391     RefPtr<ClientHandle> mClientHandle;
392     RefPtr<ServiceWorkerRegistrationInfo> mRegistrationInfo;
393 
ControlledClientDataControlledClientData394     ControlledClientData(ClientHandle* aClientHandle,
395                          ServiceWorkerRegistrationInfo* aRegistrationInfo)
396         : mClientHandle(aClientHandle), mRegistrationInfo(aRegistrationInfo) {}
397   };
398 
399   nsClassHashtable<nsIDHashKey, ControlledClientData> mControlledClients;
400 
401   struct PendingReadyData {
402     RefPtr<ClientHandle> mClientHandle;
403     RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
404 
PendingReadyDataPendingReadyData405     explicit PendingReadyData(ClientHandle* aClientHandle)
406         : mClientHandle(aClientHandle),
407           mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)) {}
408   };
409 
410   nsTArray<UniquePtr<PendingReadyData>> mPendingReadyList;
411 };
412 
413 }  // namespace dom
414 }  // namespace mozilla
415 
416 #endif  // mozilla_dom_workers_serviceworkermanager_h
417