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 #include "ServiceWorkerManager.h"
8 #include "ServiceWorkerPrivateImpl.h"
9 
10 #include <algorithm>
11 
12 #include "nsCOMPtr.h"
13 #include "nsICookieJarSettings.h"
14 #include "nsIHttpChannel.h"
15 #include "nsIHttpChannelInternal.h"
16 #include "nsINamed.h"
17 #include "nsINetworkInterceptController.h"
18 #include "nsIMutableArray.h"
19 #include "nsIPrincipal.h"
20 #include "nsITimer.h"
21 #include "nsIUploadChannel2.h"
22 #include "nsServiceManagerUtils.h"
23 #include "nsDebug.h"
24 #include "nsIPermissionManager.h"
25 #include "nsXULAppAPI.h"
26 
27 #include "jsapi.h"
28 
29 #include "mozilla/AppShutdown.h"
30 #include "mozilla/BasePrincipal.h"
31 #include "mozilla/ClearOnShutdown.h"
32 #include "mozilla/ErrorNames.h"
33 #include "mozilla/LoadContext.h"
34 #include "mozilla/MozPromise.h"
35 #include "mozilla/Result.h"
36 #include "mozilla/ResultExtensions.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/dom/BindingUtils.h"
39 #include "mozilla/dom/ClientHandle.h"
40 #include "mozilla/dom/ClientManager.h"
41 #include "mozilla/dom/ClientSource.h"
42 #include "mozilla/dom/ConsoleUtils.h"
43 #include "mozilla/dom/ContentParent.h"
44 #include "mozilla/dom/ErrorEvent.h"
45 #include "mozilla/dom/Headers.h"
46 #include "mozilla/dom/InternalHeaders.h"
47 #include "mozilla/dom/Navigator.h"
48 #include "mozilla/dom/NotificationEvent.h"
49 #include "mozilla/dom/Promise.h"
50 #include "mozilla/dom/PromiseNativeHandler.h"
51 #include "mozilla/dom/Request.h"
52 #include "mozilla/dom/RootedDictionary.h"
53 #include "mozilla/dom/TypedArray.h"
54 #include "mozilla/dom/SharedWorker.h"
55 #include "mozilla/dom/WorkerPrivate.h"
56 #include "mozilla/dom/WorkerRunnable.h"
57 #include "mozilla/dom/WorkerScope.h"
58 #include "mozilla/extensions/WebExtensionPolicy.h"
59 #include "mozilla/ipc/BackgroundChild.h"
60 #include "mozilla/ipc/PBackgroundChild.h"
61 #include "mozilla/ipc/PBackgroundSharedTypes.h"
62 #include "mozilla/dom/ScriptLoader.h"
63 #include "mozilla/PermissionManager.h"
64 #include "mozilla/ScopeExit.h"
65 #include "mozilla/StaticPrefs_extensions.h"
66 #include "mozilla/StaticPrefs_privacy.h"
67 #include "mozilla/StoragePrincipalHelper.h"
68 #include "mozilla/Unused.h"
69 #include "mozilla/EnumSet.h"
70 
71 #include "nsComponentManagerUtils.h"
72 #include "nsContentUtils.h"
73 #include "nsIDUtils.h"
74 #include "nsNetUtil.h"
75 #include "nsProxyRelease.h"
76 #include "nsQueryObject.h"
77 #include "nsTArray.h"
78 
79 #include "ServiceWorker.h"
80 #include "ServiceWorkerContainer.h"
81 #include "ServiceWorkerInfo.h"
82 #include "ServiceWorkerJobQueue.h"
83 #include "ServiceWorkerManagerChild.h"
84 #include "ServiceWorkerPrivate.h"
85 #include "ServiceWorkerRegisterJob.h"
86 #include "ServiceWorkerRegistrar.h"
87 #include "ServiceWorkerRegistration.h"
88 #include "ServiceWorkerScriptCache.h"
89 #include "ServiceWorkerShutdownBlocker.h"
90 #include "ServiceWorkerEvents.h"
91 #include "ServiceWorkerUnregisterJob.h"
92 #include "ServiceWorkerUpdateJob.h"
93 #include "ServiceWorkerUtils.h"
94 #include "ServiceWorkerQuotaUtils.h"
95 
96 #ifdef PostMessage
97 #  undef PostMessage
98 #endif
99 
100 mozilla::LazyLogModule sWorkerTelemetryLog("WorkerTelemetry");
101 
102 #ifdef LOG
103 #  undef LOG
104 #endif
105 #define LOG(_args) MOZ_LOG(sWorkerTelemetryLog, LogLevel::Debug, _args);
106 
107 using namespace mozilla;
108 using namespace mozilla::dom;
109 using namespace mozilla::ipc;
110 
111 namespace mozilla {
112 namespace dom {
113 
114 // Counts the number of registered ServiceWorkers, and the number that
115 // handle Fetch, for reporting in Telemetry
116 uint32_t gServiceWorkersRegistered = 0;
117 uint32_t gServiceWorkersRegisteredFetch = 0;
118 
119 static_assert(
120     nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN ==
121         static_cast<uint32_t>(RequestMode::Same_origin),
122     "RequestMode enumeration value should match Necko CORS mode value.");
123 static_assert(
124     nsIHttpChannelInternal::CORS_MODE_NO_CORS ==
125         static_cast<uint32_t>(RequestMode::No_cors),
126     "RequestMode enumeration value should match Necko CORS mode value.");
127 static_assert(
128     nsIHttpChannelInternal::CORS_MODE_CORS ==
129         static_cast<uint32_t>(RequestMode::Cors),
130     "RequestMode enumeration value should match Necko CORS mode value.");
131 static_assert(
132     nsIHttpChannelInternal::CORS_MODE_NAVIGATE ==
133         static_cast<uint32_t>(RequestMode::Navigate),
134     "RequestMode enumeration value should match Necko CORS mode value.");
135 
136 static_assert(
137     nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW ==
138         static_cast<uint32_t>(RequestRedirect::Follow),
139     "RequestRedirect enumeration value should make Necko Redirect mode value.");
140 static_assert(
141     nsIHttpChannelInternal::REDIRECT_MODE_ERROR ==
142         static_cast<uint32_t>(RequestRedirect::Error),
143     "RequestRedirect enumeration value should make Necko Redirect mode value.");
144 static_assert(
145     nsIHttpChannelInternal::REDIRECT_MODE_MANUAL ==
146         static_cast<uint32_t>(RequestRedirect::Manual),
147     "RequestRedirect enumeration value should make Necko Redirect mode value.");
148 static_assert(
149     3 == RequestRedirectValues::Count,
150     "RequestRedirect enumeration value should make Necko Redirect mode value.");
151 
152 static_assert(
153     nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT ==
154         static_cast<uint32_t>(RequestCache::Default),
155     "RequestCache enumeration value should match Necko Cache mode value.");
156 static_assert(
157     nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE ==
158         static_cast<uint32_t>(RequestCache::No_store),
159     "RequestCache enumeration value should match Necko Cache mode value.");
160 static_assert(
161     nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD ==
162         static_cast<uint32_t>(RequestCache::Reload),
163     "RequestCache enumeration value should match Necko Cache mode value.");
164 static_assert(
165     nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE ==
166         static_cast<uint32_t>(RequestCache::No_cache),
167     "RequestCache enumeration value should match Necko Cache mode value.");
168 static_assert(
169     nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE ==
170         static_cast<uint32_t>(RequestCache::Force_cache),
171     "RequestCache enumeration value should match Necko Cache mode value.");
172 static_assert(
173     nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED ==
174         static_cast<uint32_t>(RequestCache::Only_if_cached),
175     "RequestCache enumeration value should match Necko Cache mode value.");
176 static_assert(
177     6 == RequestCacheValues::Count,
178     "RequestCache enumeration value should match Necko Cache mode value.");
179 
180 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) ==
181                   nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
182               "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
183               " should match ServiceWorkerUpdateViaCache enumeration.");
184 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::All) ==
185                   nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
186               "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
187               " should match ServiceWorkerUpdateViaCache enumeration.");
188 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
189                   nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE,
190               "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
191               " should match ServiceWorkerUpdateViaCache enumeration.");
192 
193 static StaticRefPtr<ServiceWorkerManager> gInstance;
194 
195 namespace {
196 
PopulateRegistrationData(nsIPrincipal * aPrincipal,const ServiceWorkerRegistrationInfo * aRegistration,ServiceWorkerRegistrationData & aData)197 nsresult PopulateRegistrationData(
198     nsIPrincipal* aPrincipal,
199     const ServiceWorkerRegistrationInfo* aRegistration,
200     ServiceWorkerRegistrationData& aData) {
201   MOZ_ASSERT(aPrincipal);
202   MOZ_ASSERT(aRegistration);
203 
204   if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal())) {
205     return NS_ERROR_FAILURE;
206   }
207 
208   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
209   if (NS_WARN_IF(NS_FAILED(rv))) {
210     return rv;
211   }
212 
213   aData.scope() = aRegistration->Scope();
214 
215   // TODO: When bug 1426401 is implemented we will need to handle more
216   //       than just the active worker here.
217   RefPtr<ServiceWorkerInfo> active = aRegistration->GetActive();
218   MOZ_ASSERT(active);
219   if (NS_WARN_IF(!active)) {
220     return NS_ERROR_FAILURE;
221   }
222 
223   aData.currentWorkerURL() = active->ScriptSpec();
224   aData.cacheName() = active->CacheName();
225   aData.currentWorkerHandlesFetch() = active->HandlesFetch();
226 
227   aData.currentWorkerInstalledTime() = active->GetInstalledTime();
228   aData.currentWorkerActivatedTime() = active->GetActivatedTime();
229 
230   aData.updateViaCache() =
231       static_cast<uint32_t>(aRegistration->GetUpdateViaCache());
232 
233   aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();
234 
235   aData.navigationPreloadState() = aRegistration->GetNavigationPreloadState();
236 
237   MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));
238 
239   return NS_OK;
240 }
241 
242 class TeardownRunnable final : public Runnable {
243  public:
TeardownRunnable(ServiceWorkerManagerChild * aActor)244   explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
245       : Runnable("dom::ServiceWorkerManager::TeardownRunnable"),
246         mActor(aActor) {
247     MOZ_ASSERT(mActor);
248   }
249 
Run()250   NS_IMETHOD Run() override {
251     MOZ_ASSERT(mActor);
252     PServiceWorkerManagerChild::Send__delete__(mActor);
253     return NS_OK;
254   }
255 
256  private:
257   ~TeardownRunnable() = default;
258 
259   RefPtr<ServiceWorkerManagerChild> mActor;
260 };
261 
262 constexpr char kFinishShutdownTopic[] = "profile-before-change-qm";
263 
GetAsyncShutdownBarrier()264 already_AddRefed<nsIAsyncShutdownClient> GetAsyncShutdownBarrier() {
265   AssertIsOnMainThread();
266 
267   nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
268   MOZ_ASSERT(svc);
269 
270   nsCOMPtr<nsIAsyncShutdownClient> barrier;
271   DebugOnly<nsresult> rv =
272       svc->GetProfileChangeTeardown(getter_AddRefs(barrier));
273   MOZ_ASSERT(NS_SUCCEEDED(rv));
274 
275   return barrier.forget();
276 }
277 
ScopeToPrincipal(nsIURI * aScopeURI,const OriginAttributes & aOriginAttributes)278 Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
279     nsIURI* aScopeURI, const OriginAttributes& aOriginAttributes) {
280   MOZ_ASSERT(aScopeURI);
281 
282   nsCOMPtr<nsIPrincipal> principal =
283       BasePrincipal::CreateContentPrincipal(aScopeURI, aOriginAttributes);
284   if (NS_WARN_IF(!principal)) {
285     return Err(NS_ERROR_FAILURE);
286   }
287 
288   return principal;
289 }
290 
ScopeToPrincipal(const nsACString & aScope,const OriginAttributes & aOriginAttributes)291 Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
292     const nsACString& aScope, const OriginAttributes& aOriginAttributes) {
293   MOZ_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));
294 
295   nsCOMPtr<nsIURI> scopeURI;
296   MOZ_TRY(NS_NewURI(getter_AddRefs(scopeURI), aScope));
297 
298   return ScopeToPrincipal(scopeURI, aOriginAttributes);
299 }
300 
301 }  // namespace
302 
303 struct ServiceWorkerManager::RegistrationDataPerPrincipal final {
304   // Implements a container of keys for the "scope to registration map":
305   // https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
306   //
307   // where each key is an absolute URL.
308   //
309   // The properties of this map that the spec uses are
310   // 1) insertion,
311   // 2) removal,
312   // 3) iteration of scopes in FIFO order (excluding removed scopes),
313   // 4) and finding, for a given path, the maximal length scope which is a
314   //    prefix of the path.
315   //
316   // Additionally, because this is a container of keys for a map, there
317   // shouldn't be duplicate scopes.
318   //
319   // The current implementation uses a dynamic array as the underlying
320   // container, which is not optimal for unbounded container sizes (all
321   // supported operations are in linear time) but may be superior for small
322   // container sizes.
323   //
324   // If this is proven to be too slow, the underlying storage should be replaced
325   // with a linked list of scopes in combination with an ordered map that maps
326   // scopes to linked list elements/iterators. This would reduce all of the
327   // above operations besides iteration (necessarily linear) to logarithmic
328   // time.
329   class ScopeContainer final : private nsTArray<nsCString> {
330     using Base = nsTArray<nsCString>;
331 
332    public:
333     using Base::Contains;
334     using Base::IsEmpty;
335     using Base::Length;
336 
337     // No using-declaration to avoid importing the non-const overload.
operator [](Base::index_type aIndex) const338     decltype(auto) operator[](Base::index_type aIndex) const {
339       return Base::operator[](aIndex);
340     }
341 
InsertScope(const nsACString & aScope)342     void InsertScope(const nsACString& aScope) {
343       MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));
344 
345       if (Contains(aScope)) {
346         return;
347       }
348 
349       AppendElement(aScope);
350     }
351 
RemoveScope(const nsACString & aScope)352     void RemoveScope(const nsACString& aScope) {
353       MOZ_ALWAYS_TRUE(RemoveElement(aScope));
354     }
355 
356     // Implements most of "Match Service Worker Registration":
357     // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
MatchScope(const nsACString & aClientUrl) const358     Maybe<nsCString> MatchScope(const nsACString& aClientUrl) const {
359       Maybe<nsCString> match;
360 
361       for (const nsCString& scope : *this) {
362         if (StringBeginsWith(aClientUrl, scope)) {
363           if (!match || scope.Length() > match->Length()) {
364             match = Some(scope);
365           }
366         }
367       }
368 
369       // Step 7.2:
370       // "Assert: matchingScope’s origin and clientURL’s origin are same
371       // origin."
372       MOZ_DIAGNOSTIC_ASSERT_IF(match, IsSameOrigin(*match, aClientUrl));
373 
374       return match;
375     }
376 
377    private:
IsSameOrigin(const nsACString & aMatchingScope,const nsACString & aClientUrl) const378     bool IsSameOrigin(const nsACString& aMatchingScope,
379                       const nsACString& aClientUrl) const {
380       auto parseResult = ScopeToPrincipal(aMatchingScope, OriginAttributes());
381 
382       if (NS_WARN_IF(parseResult.isErr())) {
383         return false;
384       }
385 
386       auto scopePrincipal = parseResult.unwrap();
387 
388       parseResult = ScopeToPrincipal(aClientUrl, OriginAttributes());
389 
390       if (NS_WARN_IF(parseResult.isErr())) {
391         return false;
392       }
393 
394       auto clientPrincipal = parseResult.unwrap();
395 
396       bool equals = false;
397 
398       if (NS_WARN_IF(
399               NS_FAILED(scopePrincipal->Equals(clientPrincipal, &equals)))) {
400         return false;
401       }
402 
403       return equals;
404     }
405   };
406 
407   ScopeContainer mScopeContainer;
408 
409   // Scope to registration.
410   // The scope should be a fully qualified valid URL.
411   nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
412 
413   // Maps scopes to job queues.
414   nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
415 
416   // Map scopes to scheduled update timers.
417   nsInterfaceHashtable<nsCStringHashKey, nsITimer> mUpdateTimers;
418 
419   // The number of times we have done a quota usage check for this origin for
420   // mitigation purposes.  See the docs on nsIServiceWorkerRegistrationInfo,
421   // where this value is exposed.
422   int32_t mQuotaUsageCheckCount = 0;
423 };
424 
425 //////////////////////////
426 // ServiceWorkerManager //
427 //////////////////////////
428 
429 NS_IMPL_ADDREF(ServiceWorkerManager)
NS_IMPL_RELEASE(ServiceWorkerManager)430 NS_IMPL_RELEASE(ServiceWorkerManager)
431 
432 NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
433   NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
434   NS_INTERFACE_MAP_ENTRY(nsIObserver)
435   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager)
436 NS_INTERFACE_MAP_END
437 
438 ServiceWorkerManager::ServiceWorkerManager()
439     : mActor(nullptr), mShuttingDown(false) {}
440 
~ServiceWorkerManager()441 ServiceWorkerManager::~ServiceWorkerManager() {
442   // The map will assert if it is not empty when destroyed.
443   mRegistrationInfos.Clear();
444 
445   // This can happen if the browser is started up in ProfileManager mode, in
446   // which case XPCOM will startup and shutdown, but there won't be any
447   // profile-* topic notifications. The shutdown blocker expects to be in a
448   // NotAcceptingPromises state when it's destroyed, and this transition
449   // normally happens in the "profile-change-teardown" notification callback
450   // (which won't be called in ProfileManager mode).
451   if (!mShuttingDown && mShutdownBlocker) {
452     mShutdownBlocker->StopAcceptingPromises();
453   }
454 }
455 
BlockShutdownOn(GenericNonExclusivePromise * aPromise,uint32_t aShutdownStateId)456 void ServiceWorkerManager::BlockShutdownOn(GenericNonExclusivePromise* aPromise,
457                                            uint32_t aShutdownStateId) {
458   AssertIsOnMainThread();
459 
460   MOZ_ASSERT(mShutdownBlocker);
461   MOZ_ASSERT(aPromise);
462 
463   mShutdownBlocker->WaitOnPromise(aPromise, aShutdownStateId);
464 }
465 
Init(ServiceWorkerRegistrar * aRegistrar)466 void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) {
467   // ServiceWorkers now only support parent intercept.  In parent intercept
468   // mode, only the parent process ServiceWorkerManager has any state or does
469   // anything.
470   //
471   // It is our goal to completely eliminate support for content process
472   // ServiceWorkerManager instances and make getting a SWM instance trigger a
473   // fatal assertion.  But until we've reached that point, we make
474   // initialization a no-op so that content process ServiceWorkerManager
475   // instances will simply have no state and no registrations.
476   if (!XRE_IsParentProcess()) {
477     return;
478   }
479 
480   nsCOMPtr<nsIAsyncShutdownClient> shutdownBarrier = GetAsyncShutdownBarrier();
481 
482   if (shutdownBarrier) {
483     mShutdownBlocker = ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
484         *shutdownBarrier, *this);
485     MOZ_ASSERT(mShutdownBlocker);
486   }
487 
488   MOZ_DIAGNOSTIC_ASSERT(aRegistrar);
489 
490   nsTArray<ServiceWorkerRegistrationData> data;
491   aRegistrar->GetRegistrations(data);
492   LoadRegistrations(data);
493 
494   PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
495   if (NS_WARN_IF(!actorChild)) {
496     MaybeStartShutdown();
497     return;
498   }
499 
500   PServiceWorkerManagerChild* actor =
501       actorChild->SendPServiceWorkerManagerConstructor();
502   if (!actor) {
503     MaybeStartShutdown();
504     return;
505   }
506 
507   mActor = static_cast<ServiceWorkerManagerChild*>(actor);
508 
509   mTelemetryLastChange = TimeStamp::Now();
510 }
511 
RecordTelemetry(uint32_t aNumber,uint32_t aFetch)512 void ServiceWorkerManager::RecordTelemetry(uint32_t aNumber, uint32_t aFetch) {
513   // Submit N value pairs to Telemetry for the time we were at those values
514   auto now = TimeStamp::Now();
515   // round down, with a minimum of 1 repeat.  In theory this gives
516   // inaccuracy if there are frequent changes, but that's uncommon.
517   uint32_t repeats = (uint32_t)((now - mTelemetryLastChange).ToMilliseconds()) /
518                      mTelemetryPeriodMs;
519   mTelemetryLastChange = now;
520   if (repeats == 0) {
521     repeats = 1;
522   }
523   nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
524       "ServiceWorkerTelemetryRunnable", [aNumber, aFetch, repeats]() {
525         LOG(("ServiceWorkers running: %u samples of %u/%u", repeats, aNumber,
526              aFetch));
527         // Don't allocate infinitely huge arrays if someone visits a SW site
528         // after a few months running. 1 month is about 500K repeats @ 5s
529         // sampling
530         uint32_t num_repeats = std::min(repeats, 1000000U);  // 4MB max
531         nsTArray<uint32_t> values;
532 
533         uint32_t* array = values.AppendElements(num_repeats);
534         for (uint32_t i = 0; i < num_repeats; i++) {
535           array[i] = aNumber;
536         }
537         Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "All"_ns,
538                               values);
539 
540         for (uint32_t i = 0; i < num_repeats; i++) {
541           array[i] = aFetch;
542         }
543         Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "Fetch"_ns,
544                               values);
545       });
546   NS_DispatchBackgroundTask(runnable.forget(), nsIEventTarget::DISPATCH_NORMAL);
547 }
548 
StartControllingClient(const ClientInfo & aClientInfo,ServiceWorkerRegistrationInfo * aRegistrationInfo,bool aControlClientHandle)549 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::StartControllingClient(
550     const ClientInfo& aClientInfo,
551     ServiceWorkerRegistrationInfo* aRegistrationInfo,
552     bool aControlClientHandle) {
553   MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());
554 
555   // XXX We can't use a generic lambda (accepting auto&& entry) like elsewhere
556   // with WithEntryHandle, since we get linker errors then using clang+lld. This
557   // might be a toolchain issue?
558   return mControlledClients.WithEntryHandle(
559       aClientInfo.Id(),
560       [&](decltype(mControlledClients)::EntryHandle&& entry)
561           -> RefPtr<GenericErrorResultPromise> {
562         const RefPtr<ServiceWorkerManager> self = this;
563 
564         const ServiceWorkerDescriptor& active =
565             aRegistrationInfo->GetActive()->Descriptor();
566 
567         if (entry) {
568           const RefPtr<ServiceWorkerRegistrationInfo> old =
569               std::move(entry.Data()->mRegistrationInfo);
570 
571           const RefPtr<GenericErrorResultPromise> promise =
572               aControlClientHandle
573                   ? entry.Data()->mClientHandle->Control(active)
574                   : GenericErrorResultPromise::CreateAndResolve(false,
575                                                                 __func__);
576 
577           entry.Data()->mRegistrationInfo = aRegistrationInfo;
578 
579           if (old != aRegistrationInfo) {
580             StopControllingRegistration(old);
581             aRegistrationInfo->StartControllingClient();
582           }
583 
584           Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS,
585                                 1);
586 
587           // Always check to see if we failed to actually control the client. In
588           // that case remove the client from our list of controlled clients.
589           return promise->Then(
590               GetMainThreadSerialEventTarget(), __func__,
591               [](bool) {
592                 // do nothing on success
593                 return GenericErrorResultPromise::CreateAndResolve(true,
594                                                                    __func__);
595               },
596               [self, aClientInfo](const CopyableErrorResult& aRv) {
597                 // failed to control, forget about this client
598                 self->StopControllingClient(aClientInfo);
599                 return GenericErrorResultPromise::CreateAndReject(aRv,
600                                                                   __func__);
601               });
602         }
603 
604         RefPtr<ClientHandle> clientHandle = ClientManager::CreateHandle(
605             aClientInfo, GetMainThreadSerialEventTarget());
606 
607         const RefPtr<GenericErrorResultPromise> promise =
608             aControlClientHandle
609                 ? clientHandle->Control(active)
610                 : GenericErrorResultPromise::CreateAndResolve(false, __func__);
611 
612         aRegistrationInfo->StartControllingClient();
613 
614         entry.Insert(
615             MakeUnique<ControlledClientData>(clientHandle, aRegistrationInfo));
616 
617         clientHandle->OnDetach()->Then(
618             GetMainThreadSerialEventTarget(), __func__,
619             [self, aClientInfo] { self->StopControllingClient(aClientInfo); });
620 
621         Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS,
622                               1);
623 
624         // Always check to see if we failed to actually control the client.  In
625         // that case removed the client from our list of controlled clients.
626         return promise->Then(
627             GetMainThreadSerialEventTarget(), __func__,
628             [](bool) {
629               // do nothing on success
630               return GenericErrorResultPromise::CreateAndResolve(true,
631                                                                  __func__);
632             },
633             [self, aClientInfo](const CopyableErrorResult& aRv) {
634               // failed to control, forget about this client
635               self->StopControllingClient(aClientInfo);
636               return GenericErrorResultPromise::CreateAndReject(aRv, __func__);
637             });
638       });
639 }
640 
StopControllingClient(const ClientInfo & aClientInfo)641 void ServiceWorkerManager::StopControllingClient(
642     const ClientInfo& aClientInfo) {
643   auto entry = mControlledClients.Lookup(aClientInfo.Id());
644   if (!entry) {
645     return;
646   }
647 
648   RefPtr<ServiceWorkerRegistrationInfo> reg =
649       std::move(entry.Data()->mRegistrationInfo);
650 
651   entry.Remove();
652 
653   StopControllingRegistration(reg);
654 }
655 
MaybeStartShutdown()656 void ServiceWorkerManager::MaybeStartShutdown() {
657   MOZ_ASSERT(NS_IsMainThread());
658 
659   if (mShuttingDown) {
660     return;
661   }
662 
663   mShuttingDown = true;
664 
665   for (const auto& dataPtr : mRegistrationInfos.Values()) {
666     for (const auto& timerEntry : dataPtr->mUpdateTimers.Values()) {
667       timerEntry->Cancel();
668     }
669     dataPtr->mUpdateTimers.Clear();
670 
671     for (const auto& queueEntry : dataPtr->mJobQueues.Values()) {
672       queueEntry->CancelAll();
673     }
674     dataPtr->mJobQueues.Clear();
675 
676     for (const auto& registrationEntry : dataPtr->mInfos.Values()) {
677       registrationEntry->ShutdownWorkers();
678     }
679 
680     // ServiceWorkerCleanup may try to unregister registrations, so don't clear
681     // mInfos.
682   }
683 
684   for (const auto& entry : mControlledClients.Values()) {
685     entry->mRegistrationInfo->ShutdownWorkers();
686   }
687 
688   for (auto iter = mOrphanedRegistrations.iter(); !iter.done(); iter.next()) {
689     iter.get()->ShutdownWorkers();
690   }
691 
692   if (mShutdownBlocker) {
693     mShutdownBlocker->StopAcceptingPromises();
694   }
695 
696   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
697   if (obs) {
698     obs->AddObserver(this, kFinishShutdownTopic, false);
699     return;
700   }
701 
702   MaybeFinishShutdown();
703 }
704 
MaybeFinishShutdown()705 void ServiceWorkerManager::MaybeFinishShutdown() {
706   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
707   if (obs) {
708     obs->RemoveObserver(this, kFinishShutdownTopic);
709   }
710 
711   if (!mActor) {
712     return;
713   }
714 
715   mActor->ManagerShuttingDown();
716 
717   RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
718   nsresult rv = NS_DispatchToMainThread(runnable);
719   Unused << NS_WARN_IF(NS_FAILED(rv));
720   mActor = nullptr;
721 
722   // This also submits final telemetry
723   ServiceWorkerPrivateImpl::RunningShutdown();
724 }
725 
726 class ServiceWorkerResolveWindowPromiseOnRegisterCallback final
727     : public ServiceWorkerJob::Callback {
728  public:
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerResolveWindowPromiseOnRegisterCallback,override)729   NS_INLINE_DECL_REFCOUNTING(
730       ServiceWorkerResolveWindowPromiseOnRegisterCallback, override)
731 
732   virtual void JobFinished(ServiceWorkerJob* aJob,
733                            ErrorResult& aStatus) override {
734     MOZ_ASSERT(NS_IsMainThread());
735     MOZ_ASSERT(aJob);
736 
737     if (aStatus.Failed()) {
738       mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
739       return;
740     }
741 
742     MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Register);
743     RefPtr<ServiceWorkerRegisterJob> registerJob =
744         static_cast<ServiceWorkerRegisterJob*>(aJob);
745     RefPtr<ServiceWorkerRegistrationInfo> reg = registerJob->GetRegistration();
746 
747     mPromiseHolder.Resolve(reg->Descriptor(), __func__);
748   }
749 
JobDiscarded(ErrorResult & aStatus)750   virtual void JobDiscarded(ErrorResult& aStatus) override {
751     MOZ_ASSERT(NS_IsMainThread());
752 
753     mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
754   }
755 
Promise()756   RefPtr<ServiceWorkerRegistrationPromise> Promise() {
757     MOZ_ASSERT(NS_IsMainThread());
758     return mPromiseHolder.Ensure(__func__);
759   }
760 
761  private:
762   ~ServiceWorkerResolveWindowPromiseOnRegisterCallback() = default;
763 
764   MozPromiseHolder<ServiceWorkerRegistrationPromise> mPromiseHolder;
765 };
766 
767 NS_IMETHODIMP
RegisterForTest(nsIPrincipal * aPrincipal,const nsAString & aScopeURL,const nsAString & aScriptURL,JSContext * aCx,mozilla::dom::Promise ** aPromise)768 ServiceWorkerManager::RegisterForTest(nsIPrincipal* aPrincipal,
769                                       const nsAString& aScopeURL,
770                                       const nsAString& aScriptURL,
771                                       JSContext* aCx,
772                                       mozilla::dom::Promise** aPromise) {
773   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
774   if (NS_WARN_IF(!global)) {
775     return NS_ERROR_FAILURE;
776   }
777 
778   ErrorResult erv;
779   RefPtr<Promise> outer = Promise::Create(global, erv);
780   if (NS_WARN_IF(erv.Failed())) {
781     return erv.StealNSResult();
782   }
783 
784   if (!StaticPrefs::dom_serviceWorkers_testing_enabled()) {
785     outer->MaybeRejectWithAbortError(
786         "registerForTest only allowed when dom.serviceWorkers.testing.enabled "
787         "is true");
788     outer.forget(aPromise);
789     return NS_OK;
790   }
791 
792   if (aPrincipal == nullptr) {
793     outer->MaybeRejectWithAbortError("Missing principal");
794     outer.forget(aPromise);
795     return NS_OK;
796   }
797 
798   if (aScriptURL.IsEmpty()) {
799     outer->MaybeRejectWithAbortError("Missing script url");
800     outer.forget(aPromise);
801     return NS_OK;
802   }
803 
804   if (aScopeURL.IsEmpty()) {
805     outer->MaybeRejectWithAbortError("Missing scope url");
806     outer.forget(aPromise);
807     return NS_OK;
808   }
809 
810   // The ClientType isn't really used here, but ClientType::Window
811   // is the least bad choice since this is happening on the main thread.
812   Maybe<ClientInfo> clientInfo =
813       dom::ClientManager::CreateInfo(ClientType::Window, aPrincipal);
814 
815   if (!clientInfo.isSome()) {
816     outer->MaybeRejectWithUnknownError("Error creating clientInfo");
817     outer.forget(aPromise);
818     return NS_OK;
819   }
820 
821   auto scope = NS_ConvertUTF16toUTF8(aScopeURL);
822   auto scriptURL = NS_ConvertUTF16toUTF8(aScriptURL);
823 
824   auto regPromise = Register(clientInfo.ref(), scope, scriptURL,
825                              dom::ServiceWorkerUpdateViaCache::Imports);
826   const RefPtr<ServiceWorkerManager> self(this);
827   const nsCOMPtr<nsIPrincipal> principal(aPrincipal);
828   regPromise->Then(
829       GetMainThreadSerialEventTarget(), __func__,
830       [self, outer, principal,
831        scope](const ServiceWorkerRegistrationDescriptor& regDesc) {
832         RefPtr<ServiceWorkerRegistrationInfo> registration =
833             self->GetRegistration(principal, NS_ConvertUTF16toUTF8(scope));
834         if (registration) {
835           outer->MaybeResolve(registration);
836         } else {
837           outer->MaybeRejectWithUnknownError(
838               "Failed to retrieve ServiceWorkerRegistrationInfo");
839         }
840       },
841       [outer](const mozilla::CopyableErrorResult& err) {
842         CopyableErrorResult result(err);
843         outer->MaybeReject(std::move(result));
844       });
845 
846   outer.forget(aPromise);
847 
848   return NS_OK;
849 }
850 
Register(const ClientInfo & aClientInfo,const nsACString & aScopeURL,const nsACString & aScriptURL,ServiceWorkerUpdateViaCache aUpdateViaCache)851 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::Register(
852     const ClientInfo& aClientInfo, const nsACString& aScopeURL,
853     const nsACString& aScriptURL, ServiceWorkerUpdateViaCache aUpdateViaCache) {
854   nsCOMPtr<nsIURI> scopeURI;
855   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScopeURL);
856   if (NS_FAILED(rv)) {
857     // Odd, since it was serialiazed from an nsIURI.
858     CopyableErrorResult err;
859     err.ThrowInvalidStateError("Scope URL cannot be parsed");
860     return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
861   }
862 
863   nsCOMPtr<nsIURI> scriptURI;
864   rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL);
865   if (NS_FAILED(rv)) {
866     // Odd, since it was serialiazed from an nsIURI.
867     CopyableErrorResult err;
868     err.ThrowInvalidStateError("Script URL cannot be parsed");
869     return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
870   }
871 
872   IgnoredErrorResult err;
873   ServiceWorkerScopeAndScriptAreValid(aClientInfo, scopeURI, scriptURI, err);
874   if (err.Failed()) {
875     return ServiceWorkerRegistrationPromise::CreateAndReject(
876         CopyableErrorResult(std::move(err)), __func__);
877   }
878 
879   // If the previous validation step passed then we must have a principal.
880   auto principalOrErr = aClientInfo.GetPrincipal();
881 
882   if (NS_WARN_IF(principalOrErr.isErr())) {
883     return ServiceWorkerRegistrationPromise::CreateAndReject(
884         CopyableErrorResult(principalOrErr.unwrapErr()), __func__);
885   }
886 
887   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
888   nsAutoCString scopeKey;
889   rv = PrincipalToScopeKey(principal, scopeKey);
890   if (NS_WARN_IF(NS_FAILED(rv))) {
891     return ServiceWorkerRegistrationPromise::CreateAndReject(
892         CopyableErrorResult(rv), __func__);
893   }
894 
895   RefPtr<ServiceWorkerJobQueue> queue =
896       GetOrCreateJobQueue(scopeKey, aScopeURL);
897 
898   RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
899       new ServiceWorkerResolveWindowPromiseOnRegisterCallback();
900 
901   RefPtr<ServiceWorkerRegisterJob> job = new ServiceWorkerRegisterJob(
902       principal, aScopeURL, aScriptURL,
903       static_cast<ServiceWorkerUpdateViaCache>(aUpdateViaCache));
904 
905   job->AppendResultCallback(cb);
906   queue->ScheduleJob(job);
907 
908   MOZ_ASSERT(NS_IsMainThread());
909 
910   return cb->Promise();
911 }
912 
913 /*
914  * Implements the async aspects of the getRegistrations algorithm.
915  */
916 class GetRegistrationsRunnable final : public Runnable {
917   const ClientInfo mClientInfo;
918   RefPtr<ServiceWorkerRegistrationListPromise::Private> mPromise;
919 
920  public:
GetRegistrationsRunnable(const ClientInfo & aClientInfo)921   explicit GetRegistrationsRunnable(const ClientInfo& aClientInfo)
922       : Runnable("dom::ServiceWorkerManager::GetRegistrationsRunnable"),
923         mClientInfo(aClientInfo),
924         mPromise(new ServiceWorkerRegistrationListPromise::Private(__func__)) {}
925 
Promise() const926   RefPtr<ServiceWorkerRegistrationListPromise> Promise() const {
927     return mPromise;
928   }
929 
930   NS_IMETHOD
Run()931   Run() override {
932     auto scopeExit = MakeScopeExit(
933         [&] { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); });
934 
935     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
936     if (!swm) {
937       return NS_OK;
938     }
939 
940     auto principalOrErr = mClientInfo.GetPrincipal();
941     if (NS_WARN_IF(principalOrErr.isErr())) {
942       return NS_OK;
943     }
944 
945     nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
946 
947     nsTArray<ServiceWorkerRegistrationDescriptor> array;
948 
949     if (NS_WARN_IF(!BasePrincipal::Cast(principal)->IsContentPrincipal())) {
950       return NS_OK;
951     }
952 
953     nsAutoCString scopeKey;
954     nsresult rv = swm->PrincipalToScopeKey(principal, scopeKey);
955     if (NS_WARN_IF(NS_FAILED(rv))) {
956       return rv;
957     }
958 
959     ServiceWorkerManager::RegistrationDataPerPrincipal* data;
960     if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
961       scopeExit.release();
962       mPromise->Resolve(array, __func__);
963       return NS_OK;
964     }
965 
966     for (uint32_t i = 0; i < data->mScopeContainer.Length(); ++i) {
967       RefPtr<ServiceWorkerRegistrationInfo> info =
968           data->mInfos.GetWeak(data->mScopeContainer[i]);
969 
970       NS_ConvertUTF8toUTF16 scope(data->mScopeContainer[i]);
971 
972       nsCOMPtr<nsIURI> scopeURI;
973       nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
974       if (NS_WARN_IF(NS_FAILED(rv))) {
975         break;
976       }
977 
978       // Unfortunately we don't seem to have an obvious window id here; in
979       // particular ClientInfo does not have one, and neither do service worker
980       // registrations, as far as I can tell.
981       rv = principal->CheckMayLoadWithReporting(
982           scopeURI, false /* allowIfInheritsPrincipal */,
983           0 /* innerWindowID */);
984       if (NS_WARN_IF(NS_FAILED(rv))) {
985         continue;
986       }
987 
988       array.AppendElement(info->Descriptor());
989     }
990 
991     scopeExit.release();
992     mPromise->Resolve(array, __func__);
993 
994     return NS_OK;
995   }
996 };
997 
998 RefPtr<ServiceWorkerRegistrationListPromise>
GetRegistrations(const ClientInfo & aClientInfo) const999 ServiceWorkerManager::GetRegistrations(const ClientInfo& aClientInfo) const {
1000   RefPtr<GetRegistrationsRunnable> runnable =
1001       new GetRegistrationsRunnable(aClientInfo);
1002   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
1003   return runnable->Promise();
1004 }
1005 
1006 /*
1007  * Implements the async aspects of the getRegistration algorithm.
1008  */
1009 class GetRegistrationRunnable final : public Runnable {
1010   const ClientInfo mClientInfo;
1011   RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
1012   nsCString mURL;
1013 
1014  public:
GetRegistrationRunnable(const ClientInfo & aClientInfo,const nsACString & aURL)1015   GetRegistrationRunnable(const ClientInfo& aClientInfo, const nsACString& aURL)
1016       : Runnable("dom::ServiceWorkerManager::GetRegistrationRunnable"),
1017         mClientInfo(aClientInfo),
1018         mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)),
1019         mURL(aURL) {}
1020 
Promise() const1021   RefPtr<ServiceWorkerRegistrationPromise> Promise() const { return mPromise; }
1022 
1023   NS_IMETHOD
Run()1024   Run() override {
1025     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1026     if (!swm) {
1027       mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
1028       return NS_OK;
1029     }
1030 
1031     auto principalOrErr = mClientInfo.GetPrincipal();
1032     if (NS_WARN_IF(principalOrErr.isErr())) {
1033       mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
1034       return NS_OK;
1035     }
1036 
1037     nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1038     nsCOMPtr<nsIURI> uri;
1039     nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
1040     if (NS_WARN_IF(NS_FAILED(rv))) {
1041       mPromise->Reject(rv, __func__);
1042       return NS_OK;
1043     }
1044 
1045     // Unfortunately we don't seem to have an obvious window id here; in
1046     // particular ClientInfo does not have one, and neither do service worker
1047     // registrations, as far as I can tell.
1048     rv = principal->CheckMayLoadWithReporting(
1049         uri, false /* allowIfInheritsPrincipal */, 0 /* innerWindowID */);
1050     if (NS_FAILED(rv)) {
1051       mPromise->Reject(NS_ERROR_DOM_SECURITY_ERR, __func__);
1052       return NS_OK;
1053     }
1054 
1055     RefPtr<ServiceWorkerRegistrationInfo> registration =
1056         swm->GetServiceWorkerRegistrationInfo(principal, uri);
1057 
1058     if (!registration) {
1059       // Reject with NS_OK means "not found".
1060       mPromise->Reject(NS_OK, __func__);
1061       return NS_OK;
1062     }
1063 
1064     mPromise->Resolve(registration->Descriptor(), __func__);
1065 
1066     return NS_OK;
1067   }
1068 };
1069 
GetRegistration(const ClientInfo & aClientInfo,const nsACString & aURL) const1070 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::GetRegistration(
1071     const ClientInfo& aClientInfo, const nsACString& aURL) const {
1072   MOZ_ASSERT(NS_IsMainThread());
1073 
1074   RefPtr<GetRegistrationRunnable> runnable =
1075       new GetRegistrationRunnable(aClientInfo, aURL);
1076   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
1077 
1078   return runnable->Promise();
1079 }
1080 
1081 NS_IMETHODIMP
SendPushEvent(const nsACString & aOriginAttributes,const nsACString & aScope,const nsTArray<uint8_t> & aDataBytes,uint8_t optional_argc)1082 ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
1083                                     const nsACString& aScope,
1084                                     const nsTArray<uint8_t>& aDataBytes,
1085                                     uint8_t optional_argc) {
1086   if (optional_argc == 1) {
1087     // This does one copy here (while constructing the Maybe) and another when
1088     // we end up copying into the SendPushEventRunnable.  We could fix that to
1089     // only do one copy by making things between here and there take
1090     // Maybe<nsTArray<uint8_t>>&&, but then we'd need to copy before we know
1091     // whether we really need to in PushMessageDispatcher::NotifyWorkers.  Since
1092     // in practice this only affects JS callers that pass data, and we don't
1093     // have any right now, let's not worry about it.
1094     return SendPushEvent(aOriginAttributes, aScope, u""_ns,
1095                          Some(aDataBytes.Clone()));
1096   }
1097   MOZ_ASSERT(optional_argc == 0);
1098   return SendPushEvent(aOriginAttributes, aScope, u""_ns, Nothing());
1099 }
1100 
SendPushEvent(const nsACString & aOriginAttributes,const nsACString & aScope,const nsAString & aMessageId,const Maybe<nsTArray<uint8_t>> & aData)1101 nsresult ServiceWorkerManager::SendPushEvent(
1102     const nsACString& aOriginAttributes, const nsACString& aScope,
1103     const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) {
1104   OriginAttributes attrs;
1105   if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
1106     return NS_ERROR_INVALID_ARG;
1107   }
1108 
1109   nsCOMPtr<nsIPrincipal> principal;
1110   MOZ_TRY_VAR(principal, ScopeToPrincipal(aScope, attrs));
1111 
1112   // The registration handling a push notification must have an exact scope
1113   // match. This will try to find an exact match, unlike how fetch may find the
1114   // registration with the longest scope that's a prefix of the fetched URL.
1115   RefPtr<ServiceWorkerRegistrationInfo> registration =
1116       GetRegistration(principal, aScope);
1117   if (NS_WARN_IF(!registration)) {
1118     return NS_ERROR_FAILURE;
1119   }
1120 
1121   MOZ_DIAGNOSTIC_ASSERT(registration->Scope().Equals(aScope));
1122 
1123   ServiceWorkerInfo* serviceWorker = registration->GetActive();
1124   if (NS_WARN_IF(!serviceWorker)) {
1125     return NS_ERROR_FAILURE;
1126   }
1127 
1128   return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
1129                                                        registration);
1130 }
1131 
1132 NS_IMETHODIMP
SendPushSubscriptionChangeEvent(const nsACString & aOriginAttributes,const nsACString & aScope)1133 ServiceWorkerManager::SendPushSubscriptionChangeEvent(
1134     const nsACString& aOriginAttributes, const nsACString& aScope) {
1135   OriginAttributes attrs;
1136   if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
1137     return NS_ERROR_INVALID_ARG;
1138   }
1139 
1140   ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
1141   if (!info) {
1142     return NS_ERROR_FAILURE;
1143   }
1144   return info->WorkerPrivate()->SendPushSubscriptionChangeEvent();
1145 }
1146 
SendNotificationEvent(const nsAString & aEventName,const nsACString & aOriginSuffix,const nsACString & aScope,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)1147 nsresult ServiceWorkerManager::SendNotificationEvent(
1148     const nsAString& aEventName, const nsACString& aOriginSuffix,
1149     const nsACString& aScope, const nsAString& aID, const nsAString& aTitle,
1150     const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
1151     const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
1152     const nsAString& aBehavior) {
1153   OriginAttributes attrs;
1154   if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
1155     return NS_ERROR_INVALID_ARG;
1156   }
1157 
1158   ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
1159   if (!info) {
1160     return NS_ERROR_FAILURE;
1161   }
1162 
1163   ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
1164   return workerPrivate->SendNotificationEvent(
1165       aEventName, aID, aTitle, aDir, aLang, aBody, aTag, aIcon, aData,
1166       aBehavior, NS_ConvertUTF8toUTF16(aScope));
1167 }
1168 
1169 NS_IMETHODIMP
SendNotificationClickEvent(const nsACString & aOriginSuffix,const nsACString & aScope,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)1170 ServiceWorkerManager::SendNotificationClickEvent(
1171     const nsACString& aOriginSuffix, const nsACString& aScope,
1172     const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
1173     const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
1174     const nsAString& aIcon, const nsAString& aData,
1175     const nsAString& aBehavior) {
1176   return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLICK_EVENT_NAME),
1177                                aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
1178                                aBody, aTag, aIcon, aData, aBehavior);
1179 }
1180 
1181 NS_IMETHODIMP
SendNotificationCloseEvent(const nsACString & aOriginSuffix,const nsACString & aScope,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)1182 ServiceWorkerManager::SendNotificationCloseEvent(
1183     const nsACString& aOriginSuffix, const nsACString& aScope,
1184     const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
1185     const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
1186     const nsAString& aIcon, const nsAString& aData,
1187     const nsAString& aBehavior) {
1188   return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLOSE_EVENT_NAME),
1189                                aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
1190                                aBody, aTag, aIcon, aData, aBehavior);
1191 }
1192 
WhenReady(const ClientInfo & aClientInfo)1193 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::WhenReady(
1194     const ClientInfo& aClientInfo) {
1195   AssertIsOnMainThread();
1196 
1197   for (auto& prd : mPendingReadyList) {
1198     if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
1199         prd->mClientHandle->Info().PrincipalInfo() ==
1200             aClientInfo.PrincipalInfo()) {
1201       return prd->mPromise;
1202     }
1203   }
1204 
1205   RefPtr<ServiceWorkerRegistrationInfo> reg =
1206       GetServiceWorkerRegistrationInfo(aClientInfo);
1207   if (reg && reg->GetActive()) {
1208     return ServiceWorkerRegistrationPromise::CreateAndResolve(reg->Descriptor(),
1209                                                               __func__);
1210   }
1211 
1212   nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
1213 
1214   RefPtr<ClientHandle> handle =
1215       ClientManager::CreateHandle(aClientInfo, target);
1216   mPendingReadyList.AppendElement(MakeUnique<PendingReadyData>(handle));
1217 
1218   RefPtr<ServiceWorkerManager> self(this);
1219   handle->OnDetach()->Then(target, __func__,
1220                            [self = std::move(self), aClientInfo] {
1221                              self->RemovePendingReadyPromise(aClientInfo);
1222                            });
1223 
1224   return mPendingReadyList.LastElement()->mPromise;
1225 }
1226 
CheckPendingReadyPromises()1227 void ServiceWorkerManager::CheckPendingReadyPromises() {
1228   nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
1229       std::move(mPendingReadyList);
1230   for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
1231     UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));
1232 
1233     RefPtr<ServiceWorkerRegistrationInfo> reg =
1234         GetServiceWorkerRegistrationInfo(prd->mClientHandle->Info());
1235 
1236     if (reg && reg->GetActive()) {
1237       prd->mPromise->Resolve(reg->Descriptor(), __func__);
1238     } else {
1239       mPendingReadyList.AppendElement(std::move(prd));
1240     }
1241   }
1242 }
1243 
RemovePendingReadyPromise(const ClientInfo & aClientInfo)1244 void ServiceWorkerManager::RemovePendingReadyPromise(
1245     const ClientInfo& aClientInfo) {
1246   nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
1247       std::move(mPendingReadyList);
1248   for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
1249     UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));
1250 
1251     if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
1252         prd->mClientHandle->Info().PrincipalInfo() ==
1253             aClientInfo.PrincipalInfo()) {
1254       prd->mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
1255     } else {
1256       mPendingReadyList.AppendElement(std::move(prd));
1257     }
1258   }
1259 }
1260 
NoteInheritedController(const ClientInfo & aClientInfo,const ServiceWorkerDescriptor & aController)1261 void ServiceWorkerManager::NoteInheritedController(
1262     const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aController) {
1263   MOZ_ASSERT(NS_IsMainThread());
1264 
1265   auto principalOrErr = PrincipalInfoToPrincipal(aController.PrincipalInfo());
1266 
1267   if (NS_WARN_IF(principalOrErr.isErr())) {
1268     return;
1269   }
1270 
1271   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1272   nsCOMPtr<nsIURI> scope;
1273   nsresult rv = NS_NewURI(getter_AddRefs(scope), aController.Scope());
1274   NS_ENSURE_SUCCESS_VOID(rv);
1275 
1276   RefPtr<ServiceWorkerRegistrationInfo> registration =
1277       GetServiceWorkerRegistrationInfo(principal, scope);
1278   NS_ENSURE_TRUE_VOID(registration);
1279   NS_ENSURE_TRUE_VOID(registration->GetActive());
1280 
1281   StartControllingClient(aClientInfo, registration,
1282                          false /* aControlClientHandle */);
1283 }
1284 
GetActiveWorkerInfoForScope(const OriginAttributes & aOriginAttributes,const nsACString & aScope)1285 ServiceWorkerInfo* ServiceWorkerManager::GetActiveWorkerInfoForScope(
1286     const OriginAttributes& aOriginAttributes, const nsACString& aScope) {
1287   MOZ_ASSERT(NS_IsMainThread());
1288 
1289   nsCOMPtr<nsIURI> scopeURI;
1290   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
1291   if (NS_FAILED(rv)) {
1292     return nullptr;
1293   }
1294 
1295   auto result = ScopeToPrincipal(scopeURI, aOriginAttributes);
1296   if (NS_WARN_IF(result.isErr())) {
1297     return nullptr;
1298   }
1299 
1300   auto principal = result.unwrap();
1301 
1302   RefPtr<ServiceWorkerRegistrationInfo> registration =
1303       GetServiceWorkerRegistrationInfo(principal, scopeURI);
1304   if (!registration) {
1305     return nullptr;
1306   }
1307 
1308   return registration->GetActive();
1309 }
1310 
1311 namespace {
1312 
1313 class UnregisterJobCallback final : public ServiceWorkerJob::Callback {
1314   nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
1315 
~UnregisterJobCallback()1316   ~UnregisterJobCallback() { MOZ_ASSERT(!mCallback); }
1317 
1318  public:
UnregisterJobCallback(nsIServiceWorkerUnregisterCallback * aCallback)1319   explicit UnregisterJobCallback(nsIServiceWorkerUnregisterCallback* aCallback)
1320       : mCallback(aCallback) {
1321     MOZ_ASSERT(NS_IsMainThread());
1322     MOZ_ASSERT(mCallback);
1323   }
1324 
JobFinished(ServiceWorkerJob * aJob,ErrorResult & aStatus)1325   void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override {
1326     MOZ_ASSERT(NS_IsMainThread());
1327     MOZ_ASSERT(aJob);
1328     MOZ_ASSERT(mCallback);
1329 
1330     auto scopeExit = MakeScopeExit([&]() { mCallback = nullptr; });
1331 
1332     if (aStatus.Failed()) {
1333       mCallback->UnregisterFailed();
1334       return;
1335     }
1336 
1337     MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Unregister);
1338     RefPtr<ServiceWorkerUnregisterJob> unregisterJob =
1339         static_cast<ServiceWorkerUnregisterJob*>(aJob);
1340     mCallback->UnregisterSucceeded(unregisterJob->GetResult());
1341   }
1342 
JobDiscarded(ErrorResult &)1343   void JobDiscarded(ErrorResult&) override {
1344     MOZ_ASSERT(NS_IsMainThread());
1345     MOZ_ASSERT(mCallback);
1346 
1347     mCallback->UnregisterFailed();
1348     mCallback = nullptr;
1349   }
1350 
1351   NS_INLINE_DECL_REFCOUNTING(UnregisterJobCallback, override)
1352 };
1353 
1354 }  // anonymous namespace
1355 
1356 NS_IMETHODIMP
Unregister(nsIPrincipal * aPrincipal,nsIServiceWorkerUnregisterCallback * aCallback,const nsAString & aScope)1357 ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
1358                                  nsIServiceWorkerUnregisterCallback* aCallback,
1359                                  const nsAString& aScope) {
1360   MOZ_ASSERT(NS_IsMainThread());
1361 
1362   if (!aPrincipal) {
1363     return NS_ERROR_FAILURE;
1364   }
1365 
1366   nsresult rv;
1367 
1368 // This is not accessible by content, and callers should always ensure scope is
1369 // a correct URI, so this is wrapped in DEBUG
1370 #ifdef DEBUG
1371   nsCOMPtr<nsIURI> scopeURI;
1372   rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
1373   if (NS_WARN_IF(NS_FAILED(rv))) {
1374     return NS_ERROR_DOM_SECURITY_ERR;
1375   }
1376 #endif
1377 
1378   nsAutoCString scopeKey;
1379   rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1380   if (NS_WARN_IF(NS_FAILED(rv))) {
1381     return rv;
1382   }
1383 
1384   NS_ConvertUTF16toUTF8 scope(aScope);
1385   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);
1386 
1387   RefPtr<ServiceWorkerUnregisterJob> job = new ServiceWorkerUnregisterJob(
1388       aPrincipal, scope, true /* send to parent */);
1389 
1390   if (aCallback) {
1391     RefPtr<UnregisterJobCallback> cb = new UnregisterJobCallback(aCallback);
1392     job->AppendResultCallback(cb);
1393   }
1394 
1395   queue->ScheduleJob(job);
1396   return NS_OK;
1397 }
1398 
WorkerIsIdle(ServiceWorkerInfo * aWorker)1399 void ServiceWorkerManager::WorkerIsIdle(ServiceWorkerInfo* aWorker) {
1400   MOZ_ASSERT(NS_IsMainThread());
1401   MOZ_DIAGNOSTIC_ASSERT(aWorker);
1402 
1403   RefPtr<ServiceWorkerRegistrationInfo> reg =
1404       GetRegistration(aWorker->Principal(), aWorker->Scope());
1405   if (!reg) {
1406     return;
1407   }
1408 
1409   if (reg->GetActive() != aWorker) {
1410     return;
1411   }
1412 
1413   reg->TryToActivateAsync();
1414 }
1415 
1416 already_AddRefed<ServiceWorkerJobQueue>
GetOrCreateJobQueue(const nsACString & aKey,const nsACString & aScope)1417 ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey,
1418                                           const nsACString& aScope) {
1419   MOZ_ASSERT(!aKey.IsEmpty());
1420   ServiceWorkerManager::RegistrationDataPerPrincipal* data;
1421   // XXX we could use WithEntryHandle here to avoid a hashtable lookup, except
1422   // that leads to a false positive assertion, see bug 1370674 comment 7.
1423   if (!mRegistrationInfos.Get(aKey, &data)) {
1424     data = mRegistrationInfos
1425                .InsertOrUpdate(aKey, MakeUnique<RegistrationDataPerPrincipal>())
1426                .get();
1427   }
1428 
1429   RefPtr queue = data->mJobQueues.GetOrInsertNew(aScope);
1430   return queue.forget();
1431 }
1432 
1433 /* static */
GetInstance()1434 already_AddRefed<ServiceWorkerManager> ServiceWorkerManager::GetInstance() {
1435   if (!gInstance) {
1436     RefPtr<ServiceWorkerRegistrar> swr;
1437 
1438     // XXX: Substitute this with an assertion. See comment in Init.
1439     if (XRE_IsParentProcess()) {
1440       // Don't (re-)create the ServiceWorkerManager if we are already shutting
1441       // down.
1442       if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
1443         return nullptr;
1444       }
1445       // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar
1446       // is initialized.
1447       swr = ServiceWorkerRegistrar::Get();
1448       if (!swr) {
1449         return nullptr;
1450       }
1451     }
1452 
1453     MOZ_ASSERT(NS_IsMainThread());
1454 
1455     gInstance = new ServiceWorkerManager();
1456     gInstance->Init(swr);
1457     ClearOnShutdown(&gInstance);
1458   }
1459   RefPtr<ServiceWorkerManager> copy = gInstance.get();
1460   return copy.forget();
1461 }
1462 
ReportToAllClients(const nsCString & aScope,const nsString & aMessage,const nsString & aFilename,const nsString & aLine,uint32_t aLineNumber,uint32_t aColumnNumber,uint32_t aFlags)1463 void ServiceWorkerManager::ReportToAllClients(
1464     const nsCString& aScope, const nsString& aMessage,
1465     const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1466     uint32_t aColumnNumber, uint32_t aFlags) {
1467   ConsoleUtils::ReportForServiceWorkerScope(
1468       NS_ConvertUTF8toUTF16(aScope), aMessage, aFilename, aLineNumber,
1469       aColumnNumber, ConsoleUtils::eError);
1470 }
1471 
1472 /* static */
LocalizeAndReportToAllClients(const nsCString & aScope,const char * aStringKey,const nsTArray<nsString> & aParamArray,uint32_t aFlags,const nsString & aFilename,const nsString & aLine,uint32_t aLineNumber,uint32_t aColumnNumber)1473 void ServiceWorkerManager::LocalizeAndReportToAllClients(
1474     const nsCString& aScope, const char* aStringKey,
1475     const nsTArray<nsString>& aParamArray, uint32_t aFlags,
1476     const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1477     uint32_t aColumnNumber) {
1478   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1479   if (!swm) {
1480     return;
1481   }
1482 
1483   nsresult rv;
1484   nsAutoString message;
1485   rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
1486                                              aStringKey, aParamArray, message);
1487   if (NS_SUCCEEDED(rv)) {
1488     swm->ReportToAllClients(aScope, message, aFilename, aLine, aLineNumber,
1489                             aColumnNumber, aFlags);
1490   } else {
1491     NS_WARNING("Failed to format and therefore report localized error.");
1492   }
1493 }
1494 
HandleError(JSContext * aCx,nsIPrincipal * aPrincipal,const nsCString & aScope,const nsString & aWorkerURL,const nsString & aMessage,const nsString & aFilename,const nsString & aLine,uint32_t aLineNumber,uint32_t aColumnNumber,uint32_t aFlags,JSExnType aExnType)1495 void ServiceWorkerManager::HandleError(
1496     JSContext* aCx, nsIPrincipal* aPrincipal, const nsCString& aScope,
1497     const nsString& aWorkerURL, const nsString& aMessage,
1498     const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1499     uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType) {
1500   MOZ_ASSERT(NS_IsMainThread());
1501   MOZ_ASSERT(aPrincipal);
1502 
1503   nsAutoCString scopeKey;
1504   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1505   if (NS_WARN_IF(NS_FAILED(rv))) {
1506     return;
1507   }
1508 
1509   ServiceWorkerManager::RegistrationDataPerPrincipal* data;
1510   if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) {
1511     return;
1512   }
1513 
1514   // Always report any uncaught exceptions or errors to the console of
1515   // each client.
1516   ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
1517                      aColumnNumber, aFlags);
1518 }
1519 
LoadRegistration(const ServiceWorkerRegistrationData & aRegistration)1520 void ServiceWorkerManager::LoadRegistration(
1521     const ServiceWorkerRegistrationData& aRegistration) {
1522   MOZ_ASSERT(NS_IsMainThread());
1523 
1524   auto principalOrErr = PrincipalInfoToPrincipal(aRegistration.principal());
1525   if (NS_WARN_IF(principalOrErr.isErr())) {
1526     return;
1527   }
1528   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1529 
1530   // Purge extensions registrations if they are disabled by prefs.
1531   if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
1532     nsCOMPtr<nsIURI> uri = principal->GetURI();
1533 
1534     // We do check the URI scheme here because when this is going to run
1535     // the extension may not have been loaded yet and the WebExtensionPolicy
1536     // may not exist yet.
1537     if (uri->SchemeIs("moz-extension")) {
1538       const auto& cacheName = aRegistration.cacheName();
1539       serviceWorkerScriptCache::PurgeCache(principal, cacheName);
1540       return;
1541     }
1542   }
1543 
1544   RefPtr<ServiceWorkerRegistrationInfo> registration =
1545       GetRegistration(principal, aRegistration.scope());
1546   if (!registration) {
1547     registration =
1548         CreateNewRegistration(aRegistration.scope(), principal,
1549                               static_cast<ServiceWorkerUpdateViaCache>(
1550                                   aRegistration.updateViaCache()),
1551                               aRegistration.navigationPreloadState());
1552   } else {
1553     // If active worker script matches our expectations for a "current worker",
1554     // then we are done. Since scripts with the same URL might have different
1555     // contents such as updated scripts or scripts with different LoadFlags, we
1556     // use the CacheName to judge whether the two scripts are identical, where
1557     // the CacheName is an UUID generated when a new script is found.
1558     if (registration->GetActive() &&
1559         registration->GetActive()->CacheName() == aRegistration.cacheName()) {
1560       // No needs for updates.
1561       return;
1562     }
1563   }
1564 
1565   registration->SetLastUpdateTime(aRegistration.lastUpdateTime());
1566 
1567   nsLoadFlags importsLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
1568   if (aRegistration.updateViaCache() !=
1569       static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None)) {
1570     importsLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
1571   }
1572 
1573   const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
1574   if (!currentWorkerURL.IsEmpty()) {
1575     registration->SetActive(new ServiceWorkerInfo(
1576         registration->Principal(), registration->Scope(), registration->Id(),
1577         registration->Version(), currentWorkerURL, aRegistration.cacheName(),
1578         importsLoadFlags));
1579     registration->GetActive()->SetHandlesFetch(
1580         aRegistration.currentWorkerHandlesFetch());
1581     registration->GetActive()->SetInstalledTime(
1582         aRegistration.currentWorkerInstalledTime());
1583     registration->GetActive()->SetActivatedTime(
1584         aRegistration.currentWorkerActivatedTime());
1585   }
1586 }
1587 
LoadRegistrations(const nsTArray<ServiceWorkerRegistrationData> & aRegistrations)1588 void ServiceWorkerManager::LoadRegistrations(
1589     const nsTArray<ServiceWorkerRegistrationData>& aRegistrations) {
1590   MOZ_ASSERT(NS_IsMainThread());
1591   uint32_t fetch = 0;
1592   for (uint32_t i = 0, len = aRegistrations.Length(); i < len; ++i) {
1593     LoadRegistration(aRegistrations[i]);
1594     if (aRegistrations[i].currentWorkerHandlesFetch()) {
1595       fetch++;
1596     }
1597   }
1598   gServiceWorkersRegistered = aRegistrations.Length();
1599   gServiceWorkersRegisteredFetch = fetch;
1600   Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_REGISTRATIONS,
1601                        u"All"_ns, gServiceWorkersRegistered);
1602   Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_REGISTRATIONS,
1603                        u"Fetch"_ns, gServiceWorkersRegisteredFetch);
1604   LOG(("LoadRegistrations: %u, fetch %u\n", gServiceWorkersRegistered,
1605        gServiceWorkersRegisteredFetch));
1606 }
1607 
StoreRegistration(nsIPrincipal * aPrincipal,ServiceWorkerRegistrationInfo * aRegistration)1608 void ServiceWorkerManager::StoreRegistration(
1609     nsIPrincipal* aPrincipal, ServiceWorkerRegistrationInfo* aRegistration) {
1610   MOZ_ASSERT(aPrincipal);
1611   MOZ_ASSERT(aRegistration);
1612 
1613   if (mShuttingDown) {
1614     return;
1615   }
1616 
1617   // Do not store a registration for addons that are not installed, not enabled
1618   // or installed temporarily.
1619   //
1620   // If the dom.serviceWorkers.testing.persistTemporaryInstalledAddons is set
1621   // to true, the registration for a temporary installed addon will still be
1622   // persisted (only meant to be used to make it easier to test some particular
1623   // scenario with a temporary installed addon which doesn't need to be signed
1624   // to be installed on release channel builds).
1625   if (aPrincipal->SchemeIs("moz-extension")) {
1626     RefPtr<extensions::WebExtensionPolicy> addonPolicy =
1627         BasePrincipal::Cast(aPrincipal)->AddonPolicy();
1628     if (!addonPolicy || !addonPolicy->Active() ||
1629         (addonPolicy->TemporarilyInstalled() &&
1630          !StaticPrefs::
1631              dom_serviceWorkers_testing_persistTemporarilyInstalledAddons())) {
1632       return;
1633     }
1634   }
1635 
1636   ServiceWorkerRegistrationData data;
1637   nsresult rv = PopulateRegistrationData(aPrincipal, aRegistration, data);
1638   if (NS_WARN_IF(NS_FAILED(rv))) {
1639     return;
1640   }
1641 
1642   PrincipalInfo principalInfo;
1643   if (NS_WARN_IF(
1644           NS_FAILED(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)))) {
1645     return;
1646   }
1647 
1648   mActor->SendRegister(data);
1649 }
1650 
1651 already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(const ClientInfo & aClientInfo) const1652 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
1653     const ClientInfo& aClientInfo) const {
1654   auto principalOrErr = aClientInfo.GetPrincipal();
1655   if (NS_WARN_IF(principalOrErr.isErr())) {
1656     return nullptr;
1657   }
1658 
1659   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1660   nsCOMPtr<nsIURI> uri;
1661   nsresult rv = NS_NewURI(getter_AddRefs(uri), aClientInfo.URL());
1662   NS_ENSURE_SUCCESS(rv, nullptr);
1663 
1664   return GetServiceWorkerRegistrationInfo(principal, uri);
1665 }
1666 
1667 already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(nsIPrincipal * aPrincipal,nsIURI * aURI) const1668 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
1669                                                        nsIURI* aURI) const {
1670   MOZ_ASSERT(aPrincipal);
1671   MOZ_ASSERT(aURI);
1672 
1673   nsAutoCString scopeKey;
1674   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1675   if (NS_FAILED(rv)) {
1676     return nullptr;
1677   }
1678 
1679   return GetServiceWorkerRegistrationInfo(scopeKey, aURI);
1680 }
1681 
1682 already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(const nsACString & aScopeKey,nsIURI * aURI) const1683 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
1684     const nsACString& aScopeKey, nsIURI* aURI) const {
1685   MOZ_ASSERT(aURI);
1686 
1687   nsAutoCString spec;
1688   nsresult rv = aURI->GetSpec(spec);
1689   if (NS_WARN_IF(NS_FAILED(rv))) {
1690     return nullptr;
1691   }
1692 
1693   nsAutoCString scope;
1694   RegistrationDataPerPrincipal* data;
1695   if (!FindScopeForPath(aScopeKey, spec, &data, scope)) {
1696     return nullptr;
1697   }
1698 
1699   MOZ_ASSERT(data);
1700 
1701   RefPtr<ServiceWorkerRegistrationInfo> registration;
1702   data->mInfos.Get(scope, getter_AddRefs(registration));
1703   // ordered scopes and registrations better be in sync.
1704   MOZ_ASSERT(registration);
1705 
1706 #ifdef DEBUG
1707   nsAutoCString origin;
1708   rv = registration->Principal()->GetOrigin(origin);
1709   MOZ_ASSERT(NS_SUCCEEDED(rv));
1710   MOZ_ASSERT(origin.Equals(aScopeKey));
1711 #endif
1712 
1713   return registration.forget();
1714 }
1715 
1716 /* static */
PrincipalToScopeKey(nsIPrincipal * aPrincipal,nsACString & aKey)1717 nsresult ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal* aPrincipal,
1718                                                    nsACString& aKey) {
1719   MOZ_ASSERT(aPrincipal);
1720 
1721   if (!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal()) {
1722     return NS_ERROR_FAILURE;
1723   }
1724 
1725   nsresult rv = aPrincipal->GetOrigin(aKey);
1726   if (NS_WARN_IF(NS_FAILED(rv))) {
1727     return rv;
1728   }
1729 
1730   return NS_OK;
1731 }
1732 
1733 /* static */
PrincipalInfoToScopeKey(const PrincipalInfo & aPrincipalInfo,nsACString & aKey)1734 nsresult ServiceWorkerManager::PrincipalInfoToScopeKey(
1735     const PrincipalInfo& aPrincipalInfo, nsACString& aKey) {
1736   if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
1737     return NS_ERROR_FAILURE;
1738   }
1739 
1740   auto content = aPrincipalInfo.get_ContentPrincipalInfo();
1741 
1742   nsAutoCString suffix;
1743   content.attrs().CreateSuffix(suffix);
1744 
1745   aKey = content.originNoSuffix();
1746   aKey.Append(suffix);
1747 
1748   return NS_OK;
1749 }
1750 
1751 /* static */
AddScopeAndRegistration(const nsACString & aScope,ServiceWorkerRegistrationInfo * aInfo)1752 void ServiceWorkerManager::AddScopeAndRegistration(
1753     const nsACString& aScope, ServiceWorkerRegistrationInfo* aInfo) {
1754   MOZ_ASSERT(aInfo);
1755   MOZ_ASSERT(aInfo->Principal());
1756   MOZ_ASSERT(!aInfo->IsUnregistered());
1757 
1758   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1759   if (!swm) {
1760     // browser shutdown
1761     return;
1762   }
1763 
1764   nsAutoCString scopeKey;
1765   nsresult rv = swm->PrincipalToScopeKey(aInfo->Principal(), scopeKey);
1766   if (NS_WARN_IF(NS_FAILED(rv))) {
1767     return;
1768   }
1769 
1770   MOZ_ASSERT(!scopeKey.IsEmpty());
1771 
1772   auto* const data = swm->mRegistrationInfos.GetOrInsertNew(scopeKey);
1773   data->mScopeContainer.InsertScope(aScope);
1774   data->mInfos.InsertOrUpdate(aScope, RefPtr{aInfo});
1775   swm->NotifyListenersOnRegister(aInfo);
1776 }
1777 
1778 /* static */
FindScopeForPath(const nsACString & aScopeKey,const nsACString & aPath,RegistrationDataPerPrincipal ** aData,nsACString & aMatch)1779 bool ServiceWorkerManager::FindScopeForPath(
1780     const nsACString& aScopeKey, const nsACString& aPath,
1781     RegistrationDataPerPrincipal** aData, nsACString& aMatch) {
1782   MOZ_ASSERT(aData);
1783 
1784   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1785 
1786   if (!swm || !swm->mRegistrationInfos.Get(aScopeKey, aData)) {
1787     return false;
1788   }
1789 
1790   Maybe<nsCString> scope = (*aData)->mScopeContainer.MatchScope(aPath);
1791 
1792   if (scope) {
1793     // scope.isSome() will still truen true after this; we are just moving the
1794     // string inside the Maybe, so the Maybe will contain an empty string.
1795     aMatch = std::move(*scope);
1796   }
1797 
1798   return scope.isSome();
1799 }
1800 
1801 /* static */
HasScope(nsIPrincipal * aPrincipal,const nsACString & aScope)1802 bool ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
1803                                     const nsACString& aScope) {
1804   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1805   if (!swm) {
1806     return false;
1807   }
1808 
1809   nsAutoCString scopeKey;
1810   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1811   if (NS_WARN_IF(NS_FAILED(rv))) {
1812     return false;
1813   }
1814 
1815   RegistrationDataPerPrincipal* data;
1816   if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
1817     return false;
1818   }
1819 
1820   return data->mScopeContainer.Contains(aScope);
1821 }
1822 
1823 /* static */
RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo * aRegistration)1824 void ServiceWorkerManager::RemoveScopeAndRegistration(
1825     ServiceWorkerRegistrationInfo* aRegistration) {
1826   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1827   if (!swm) {
1828     return;
1829   }
1830 
1831   nsAutoCString scopeKey;
1832   nsresult rv = swm->PrincipalToScopeKey(aRegistration->Principal(), scopeKey);
1833   if (NS_WARN_IF(NS_FAILED(rv))) {
1834     return;
1835   }
1836 
1837   RegistrationDataPerPrincipal* data;
1838   if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
1839     return;
1840   }
1841 
1842   if (auto entry = data->mUpdateTimers.Lookup(aRegistration->Scope())) {
1843     entry.Data()->Cancel();
1844     entry.Remove();
1845   }
1846 
1847   // Verify there are no controlled clients for the purged registration.
1848   for (auto iter = swm->mControlledClients.Iter(); !iter.Done(); iter.Next()) {
1849     auto& reg = iter.UserData()->mRegistrationInfo;
1850     if (reg->Scope().Equals(aRegistration->Scope()) &&
1851         reg->Principal()->Equals(aRegistration->Principal()) &&
1852         reg->IsCorrupt()) {
1853       iter.Remove();
1854     }
1855   }
1856 
1857   RefPtr<ServiceWorkerRegistrationInfo> info;
1858   data->mInfos.Remove(aRegistration->Scope(), getter_AddRefs(info));
1859   aRegistration->SetUnregistered();
1860   data->mScopeContainer.RemoveScope(aRegistration->Scope());
1861   swm->NotifyListenersOnUnregister(info);
1862 
1863   swm->MaybeRemoveRegistrationInfo(scopeKey);
1864 }
1865 
MaybeRemoveRegistrationInfo(const nsACString & aScopeKey)1866 void ServiceWorkerManager::MaybeRemoveRegistrationInfo(
1867     const nsACString& aScopeKey) {
1868   if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) {
1869     if (entry.Data()->mScopeContainer.IsEmpty() &&
1870         entry.Data()->mJobQueues.Count() == 0) {
1871       entry.Remove();
1872 
1873       // Need to reset the mQuotaUsageCheckCount, if
1874       // RegistrationDataPerPrincipal:: mScopeContainer is empty. This
1875       // RegistrationDataPerPrincipal might be reused, such that quota usage
1876       // mitigation can be triggered for the new added registration.
1877     } else if (entry.Data()->mScopeContainer.IsEmpty() &&
1878                entry.Data()->mQuotaUsageCheckCount) {
1879       entry.Data()->mQuotaUsageCheckCount = 0;
1880     }
1881   }
1882 }
1883 
StartControlling(const ClientInfo & aClientInfo,const ServiceWorkerDescriptor & aServiceWorker)1884 bool ServiceWorkerManager::StartControlling(
1885     const ClientInfo& aClientInfo,
1886     const ServiceWorkerDescriptor& aServiceWorker) {
1887   MOZ_ASSERT(NS_IsMainThread());
1888 
1889   auto principalOrErr =
1890       PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
1891 
1892   if (NS_WARN_IF(principalOrErr.isErr())) {
1893     return false;
1894   }
1895 
1896   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1897 
1898   nsCOMPtr<nsIURI> scope;
1899   nsresult rv = NS_NewURI(getter_AddRefs(scope), aServiceWorker.Scope());
1900   NS_ENSURE_SUCCESS(rv, false);
1901 
1902   RefPtr<ServiceWorkerRegistrationInfo> registration =
1903       GetServiceWorkerRegistrationInfo(principal, scope);
1904   NS_ENSURE_TRUE(registration, false);
1905   NS_ENSURE_TRUE(registration->GetActive(), false);
1906 
1907   StartControllingClient(aClientInfo, registration);
1908 
1909   return true;
1910 }
1911 
MaybeCheckNavigationUpdate(const ClientInfo & aClientInfo)1912 void ServiceWorkerManager::MaybeCheckNavigationUpdate(
1913     const ClientInfo& aClientInfo) {
1914   MOZ_ASSERT(NS_IsMainThread());
1915   // We perform these success path navigation update steps when the
1916   // document tells us its more or less done loading.  This avoids
1917   // slowing down page load and also lets pages consistently get
1918   // updatefound events when they fire.
1919   //
1920   // 9.8.20 If respondWithEntered is false, then:
1921   // 9.8.22 Else: (respondWith was entered and succeeded)
1922   //    If request is a non-subresource request, then: Invoke Soft Update
1923   //    algorithm.
1924   ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
1925   if (data && data->mRegistrationInfo) {
1926     data->mRegistrationInfo->MaybeScheduleUpdate();
1927   }
1928 }
1929 
StopControllingRegistration(ServiceWorkerRegistrationInfo * aRegistration)1930 void ServiceWorkerManager::StopControllingRegistration(
1931     ServiceWorkerRegistrationInfo* aRegistration) {
1932   aRegistration->StopControllingClient();
1933   if (aRegistration->IsControllingClients()) {
1934     return;
1935   }
1936 
1937   if (aRegistration->IsUnregistered()) {
1938     if (aRegistration->IsIdle()) {
1939       aRegistration->Clear();
1940     } else {
1941       aRegistration->ClearWhenIdle();
1942     }
1943     return;
1944   }
1945 
1946   // We use to aggressively terminate the worker at this point, but it
1947   // caused problems.  There are more uses for a service worker than actively
1948   // controlled documents.  We need to let the worker naturally terminate
1949   // in case its handling push events, message events, etc.
1950   aRegistration->TryToActivateAsync();
1951 }
1952 
1953 NS_IMETHODIMP
GetScopeForUrl(nsIPrincipal * aPrincipal,const nsAString & aUrl,nsAString & aScope)1954 ServiceWorkerManager::GetScopeForUrl(nsIPrincipal* aPrincipal,
1955                                      const nsAString& aUrl, nsAString& aScope) {
1956   MOZ_ASSERT(aPrincipal);
1957 
1958   nsCOMPtr<nsIURI> uri;
1959   nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl);
1960   if (NS_WARN_IF(NS_FAILED(rv))) {
1961     return NS_ERROR_FAILURE;
1962   }
1963 
1964   RefPtr<ServiceWorkerRegistrationInfo> r =
1965       GetServiceWorkerRegistrationInfo(aPrincipal, uri);
1966   if (!r) {
1967     return NS_ERROR_FAILURE;
1968   }
1969 
1970   CopyUTF8toUTF16(r->Scope(), aScope);
1971   return NS_OK;
1972 }
1973 
1974 namespace {
1975 
1976 class ContinueDispatchFetchEventRunnable : public Runnable {
1977   RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
1978   nsCOMPtr<nsIInterceptedChannel> mChannel;
1979   nsCOMPtr<nsILoadGroup> mLoadGroup;
1980 
1981  public:
ContinueDispatchFetchEventRunnable(ServiceWorkerPrivate * aServiceWorkerPrivate,nsIInterceptedChannel * aChannel,nsILoadGroup * aLoadGroup)1982   ContinueDispatchFetchEventRunnable(
1983       ServiceWorkerPrivate* aServiceWorkerPrivate,
1984       nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup)
1985       : Runnable(
1986             "dom::ServiceWorkerManager::ContinueDispatchFetchEventRunnable"),
1987         mServiceWorkerPrivate(aServiceWorkerPrivate),
1988         mChannel(aChannel),
1989         mLoadGroup(aLoadGroup) {
1990     MOZ_ASSERT(aServiceWorkerPrivate);
1991     MOZ_ASSERT(aChannel);
1992   }
1993 
HandleError()1994   void HandleError() {
1995     MOZ_ASSERT(NS_IsMainThread());
1996     NS_WARNING("Unexpected error while dispatching fetch event!");
1997     nsresult rv = mChannel->ResetInterception(false);
1998     if (NS_FAILED(rv)) {
1999       NS_WARNING("Failed to resume intercepted network request");
2000       mChannel->CancelInterception(rv);
2001     }
2002   }
2003 
2004   NS_IMETHOD
Run()2005   Run() override {
2006     MOZ_ASSERT(NS_IsMainThread());
2007 
2008     nsCOMPtr<nsIChannel> channel;
2009     nsresult rv = mChannel->GetChannel(getter_AddRefs(channel));
2010     if (NS_WARN_IF(NS_FAILED(rv))) {
2011       HandleError();
2012       return NS_OK;
2013     }
2014 
2015     // The channel might have encountered an unexpected error while ensuring
2016     // the upload stream is cloneable.  Check here and reset the interception
2017     // if that happens.
2018     nsresult status;
2019     rv = channel->GetStatus(&status);
2020     if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) {
2021       HandleError();
2022       return NS_OK;
2023     }
2024 
2025     nsString clientId;
2026     nsString resultingClientId;
2027     nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2028     Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
2029     if (clientInfo.isSome()) {
2030       clientId = NSID_TrimBracketsUTF16(clientInfo->Id());
2031     }
2032 
2033     // Having an initial or reserved client are mutually exclusive events:
2034     // either an initial client is used upon navigating an about:blank
2035     // iframe, or a new, reserved environment/client is created (e.g.
2036     // upon a top-level navigation). See step 4 of
2037     // https://html.spec.whatwg.org/#process-a-navigate-fetch as well as
2038     // https://github.com/w3c/ServiceWorker/issues/1228#issuecomment-345132444
2039     Maybe<ClientInfo> resulting = loadInfo->GetInitialClientInfo();
2040 
2041     if (resulting.isNothing()) {
2042       resulting = loadInfo->GetReservedClientInfo();
2043     } else {
2044       MOZ_ASSERT(loadInfo->GetReservedClientInfo().isNothing());
2045     }
2046 
2047     if (resulting.isSome()) {
2048       resultingClientId = NSID_TrimBracketsUTF16(resulting->Id());
2049     }
2050 
2051     rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup, clientId,
2052                                                resultingClientId);
2053     if (NS_WARN_IF(NS_FAILED(rv))) {
2054       HandleError();
2055     }
2056 
2057     return NS_OK;
2058   }
2059 };
2060 
2061 }  // anonymous namespace
2062 
DispatchFetchEvent(nsIInterceptedChannel * aChannel,ErrorResult & aRv)2063 void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel* aChannel,
2064                                               ErrorResult& aRv) {
2065   MOZ_ASSERT(aChannel);
2066   MOZ_ASSERT(NS_IsMainThread());
2067 
2068   nsCOMPtr<nsIChannel> internalChannel;
2069   aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
2070   if (NS_WARN_IF(aRv.Failed())) {
2071     return;
2072   }
2073 
2074   nsCOMPtr<nsILoadGroup> loadGroup;
2075   aRv = internalChannel->GetLoadGroup(getter_AddRefs(loadGroup));
2076   if (NS_WARN_IF(aRv.Failed())) {
2077     return;
2078   }
2079 
2080   nsCOMPtr<nsILoadInfo> loadInfo = internalChannel->LoadInfo();
2081   RefPtr<ServiceWorkerInfo> serviceWorker;
2082 
2083   if (!nsContentUtils::IsNonSubresourceRequest(internalChannel)) {
2084     const Maybe<ServiceWorkerDescriptor>& controller =
2085         loadInfo->GetController();
2086     if (NS_WARN_IF(controller.isNothing())) {
2087       aRv.Throw(NS_ERROR_FAILURE);
2088       return;
2089     }
2090 
2091     RefPtr<ServiceWorkerRegistrationInfo> registration;
2092     nsresult rv = GetClientRegistration(loadInfo->GetClientInfo().ref(),
2093                                         getter_AddRefs(registration));
2094     if (NS_WARN_IF(NS_FAILED(rv))) {
2095       aRv.Throw(rv);
2096       return;
2097     }
2098 
2099     serviceWorker = registration->GetActive();
2100     if (NS_WARN_IF(!serviceWorker) ||
2101         NS_WARN_IF(serviceWorker->Descriptor().Id() != controller.ref().Id())) {
2102       aRv.Throw(NS_ERROR_FAILURE);
2103       return;
2104     }
2105   } else {
2106     nsCOMPtr<nsIURI> uri;
2107     aRv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
2108     if (NS_WARN_IF(aRv.Failed())) {
2109       return;
2110     }
2111 
2112     // non-subresource request means the URI contains the principal
2113     OriginAttributes attrs = loadInfo->GetOriginAttributes();
2114     if (StaticPrefs::privacy_partition_serviceWorkers()) {
2115       StoragePrincipalHelper::GetOriginAttributes(
2116           internalChannel, attrs,
2117           StoragePrincipalHelper::eForeignPartitionedPrincipal);
2118     }
2119 
2120     nsCOMPtr<nsIPrincipal> principal =
2121         BasePrincipal::CreateContentPrincipal(uri, attrs);
2122 
2123     RefPtr<ServiceWorkerRegistrationInfo> registration =
2124         GetServiceWorkerRegistrationInfo(principal, uri);
2125     if (NS_WARN_IF(!registration)) {
2126       aRv.Throw(NS_ERROR_FAILURE);
2127       return;
2128     }
2129 
2130     // While we only enter this method if IsAvailable() previously saw
2131     // an active worker, it is possible for that worker to be removed
2132     // before we get to this point.  Therefore we must handle a nullptr
2133     // active worker here.
2134     serviceWorker = registration->GetActive();
2135     if (NS_WARN_IF(!serviceWorker)) {
2136       aRv.Throw(NS_ERROR_FAILURE);
2137       return;
2138     }
2139 
2140     // If there is a reserved client it should be marked as controlled before
2141     // the FetchEvent is dispatched.
2142     Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo();
2143 
2144     // Also override the initial about:blank controller since the real
2145     // network load may be intercepted by a different service worker.  If
2146     // the intial about:blank has a controller here its simply been
2147     // inherited from its parent.
2148     if (clientInfo.isNothing()) {
2149       clientInfo = loadInfo->GetInitialClientInfo();
2150 
2151       // TODO: We need to handle the case where the initial about:blank is
2152       //       controlled, but the final document load is not.  Right now
2153       //       the spec does not really say what to do.  There currently
2154       //       is no way for the controller to be cleared from a client in
2155       //       the spec or our implementation.  We may want to force a
2156       //       new inner window to be created instead of reusing the
2157       //       initial about:blank global.  See bug 1419620 and the spec
2158       //       issue here: https://github.com/w3c/ServiceWorker/issues/1232
2159     }
2160 
2161     if (clientInfo.isSome()) {
2162       // ClientChannelHelper is not called for STS upgrades that get
2163       // intercepted by a service worker when interception occurs in
2164       // the content process.  Therefore the reserved client is not
2165       // properly cleared in that case leading to a situation where
2166       // a ClientSource with an http:// principal is controlled by
2167       // a ServiceWorker with an https:// principal.
2168       //
2169       // This does not occur when interception is handled by the
2170       // simpler InterceptedHttpChannel approach in the parent.
2171       //
2172       // As a temporary work around check for this principal mismatch
2173       // here and perform the ClientChannelHelper's replacement of
2174       // reserved client automatically.
2175       if (!XRE_IsParentProcess()) {
2176         auto clientPrincipalOrErr = clientInfo.ref().GetPrincipal();
2177 
2178         nsCOMPtr<nsIPrincipal> clientPrincipal;
2179         if (clientPrincipalOrErr.isOk()) {
2180           clientPrincipal = clientPrincipalOrErr.unwrap();
2181         }
2182 
2183         if (!clientPrincipal || !clientPrincipal->Equals(principal)) {
2184           UniquePtr<ClientSource> reservedClient =
2185               loadInfo->TakeReservedClientSource();
2186 
2187           nsCOMPtr<nsISerialEventTarget> target =
2188               reservedClient ? reservedClient->EventTarget()
2189                              : GetMainThreadSerialEventTarget();
2190 
2191           reservedClient.reset();
2192           reservedClient = ClientManager::CreateSource(ClientType::Window,
2193                                                        target, principal);
2194 
2195           loadInfo->GiveReservedClientSource(std::move(reservedClient));
2196 
2197           clientInfo = loadInfo->GetReservedClientInfo();
2198         }
2199       }
2200 
2201       // First, attempt to mark the reserved client controlled directly.  This
2202       // will update the controlled status in the ClientManagerService in the
2203       // parent.  It will also eventually propagate back to the ClientSource.
2204       StartControllingClient(clientInfo.ref(), registration);
2205     }
2206 
2207     uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
2208     nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(internalChannel);
2209     MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
2210 
2211     // Synthetic redirects for non-subresource requests with a "follow"
2212     // redirect mode may switch controllers.  This is basically worker
2213     // scripts right now.  In this case we need to explicitly clear the
2214     // controller to avoid assertions on the SetController() below.
2215     if (redirectMode == nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
2216       loadInfo->ClearController();
2217     }
2218 
2219     // But we also note the reserved state on the LoadInfo.  This allows the
2220     // ClientSource to be updated immediately after the nsIChannel starts.
2221     // This is necessary to have the correct controller in place for immediate
2222     // follow-on requests.
2223     loadInfo->SetController(serviceWorker->Descriptor());
2224   }
2225 
2226   MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
2227 
2228   RefPtr<ContinueDispatchFetchEventRunnable> continueRunnable =
2229       new ContinueDispatchFetchEventRunnable(serviceWorker->WorkerPrivate(),
2230                                              aChannel, loadGroup);
2231 
2232   // When this service worker was registered, we also sent down the permissions
2233   // for the runnable. They should have arrived by now, but we still need to
2234   // wait for them if they have not.
2235   nsCOMPtr<nsIRunnable> permissionsRunnable = NS_NewRunnableFunction(
2236       "dom::ServiceWorkerManager::DispatchFetchEvent", [=]() {
2237         RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance();
2238         if (permMgr) {
2239           permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
2240                                             continueRunnable);
2241         } else {
2242           continueRunnable->HandleError();
2243         }
2244       });
2245 
2246   nsCOMPtr<nsIUploadChannel2> uploadChannel =
2247       do_QueryInterface(internalChannel);
2248 
2249   // If there is no upload stream, then continue immediately
2250   if (!uploadChannel) {
2251     MOZ_ALWAYS_SUCCEEDS(permissionsRunnable->Run());
2252     return;
2253   }
2254   // Otherwise, ensure the upload stream can be cloned directly.  This may
2255   // require some async copying, so provide a callback.
2256   aRv = uploadChannel->EnsureUploadStreamIsCloneable(permissionsRunnable);
2257 }
2258 
IsAvailable(nsIPrincipal * aPrincipal,nsIURI * aURI,nsIChannel * aChannel)2259 bool ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI,
2260                                        nsIChannel* aChannel) {
2261   MOZ_ASSERT(aPrincipal);
2262   MOZ_ASSERT(aURI);
2263   MOZ_ASSERT(aChannel);
2264 
2265   RefPtr<ServiceWorkerRegistrationInfo> registration =
2266       GetServiceWorkerRegistrationInfo(aPrincipal, aURI);
2267 
2268   if (!registration || !registration->GetActive()) {
2269     return false;
2270   }
2271 
2272   // Checking if the matched service worker handles fetch events or not.
2273   // If it does, directly return true and handle the client controlling logic
2274   // in DispatchFetchEvent(). otherwise, do followings then return false.
2275   // 1. Set the matched service worker as the controller of LoadInfo and
2276   //    correspoinding ClinetInfo
2277   // 2. Maybe schedule a soft update
2278   if (!registration->GetActive()->HandlesFetch()) {
2279     // Checkin if the channel is not allowed for the service worker.
2280     auto storageAccess = StorageAllowedForChannel(aChannel);
2281     nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
2282 
2283     if (storageAccess != StorageAccess::eAllow) {
2284       if (!StaticPrefs::privacy_partition_serviceWorkers()) {
2285         return false;
2286       }
2287 
2288       nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2289       loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
2290 
2291       if (!StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
2292         return false;
2293       }
2294     }
2295 
2296     // ServiceWorkerInterceptController::ShouldPrepareForIntercept() handles the
2297     // subresource cases. Must be non-subresource case here.
2298     MOZ_ASSERT(nsContentUtils::IsNonSubresourceRequest(aChannel));
2299 
2300     Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo();
2301     if (clientInfo.isNothing()) {
2302       clientInfo = loadInfo->GetInitialClientInfo();
2303     }
2304 
2305     if (clientInfo.isSome()) {
2306       StartControllingClient(clientInfo.ref(), registration);
2307     }
2308 
2309     uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
2310     nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aChannel);
2311     MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
2312 
2313     // Synthetic redirects for non-subresource requests with a "follow"
2314     // redirect mode may switch controllers.  This is basically worker
2315     // scripts right now.  In this case we need to explicitly clear the
2316     // controller to avoid assertions on the SetController() below.
2317     if (redirectMode == nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
2318       loadInfo->ClearController();
2319     }
2320 
2321     loadInfo->SetController(registration->GetActive()->Descriptor());
2322 
2323     // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm 17.1
2324     // try schedule a soft-update for non-subresource case.
2325     registration->MaybeScheduleUpdate();
2326     return false;
2327   }
2328   // Found a matching service worker which handles fetch events, return true.
2329   return true;
2330 }
2331 
GetClientRegistration(const ClientInfo & aClientInfo,ServiceWorkerRegistrationInfo ** aRegistrationInfo)2332 nsresult ServiceWorkerManager::GetClientRegistration(
2333     const ClientInfo& aClientInfo,
2334     ServiceWorkerRegistrationInfo** aRegistrationInfo) {
2335   ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
2336   if (!data || !data->mRegistrationInfo) {
2337     return NS_ERROR_NOT_AVAILABLE;
2338   }
2339 
2340   // If the document is controlled, the current worker MUST be non-null.
2341   if (!data->mRegistrationInfo->GetActive()) {
2342     return NS_ERROR_NOT_AVAILABLE;
2343   }
2344 
2345   RefPtr<ServiceWorkerRegistrationInfo> ref = data->mRegistrationInfo;
2346   ref.forget(aRegistrationInfo);
2347   return NS_OK;
2348 }
2349 
GetPrincipalQuotaUsageCheckCount(nsIPrincipal * aPrincipal)2350 int32_t ServiceWorkerManager::GetPrincipalQuotaUsageCheckCount(
2351     nsIPrincipal* aPrincipal) {
2352   nsAutoCString scopeKey;
2353   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2354   if (NS_WARN_IF(NS_FAILED(rv))) {
2355     return -1;
2356   }
2357 
2358   RegistrationDataPerPrincipal* data;
2359   if (!mRegistrationInfos.Get(scopeKey, &data)) {
2360     return -1;
2361   }
2362 
2363   return data->mQuotaUsageCheckCount;
2364 }
2365 
CheckPrincipalQuotaUsage(nsIPrincipal * aPrincipal,const nsACString & aScope)2366 void ServiceWorkerManager::CheckPrincipalQuotaUsage(nsIPrincipal* aPrincipal,
2367                                                     const nsACString& aScope) {
2368   MOZ_ASSERT(NS_IsMainThread());
2369   MOZ_ASSERT(aPrincipal);
2370 
2371   nsAutoCString scopeKey;
2372   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2373   if (NS_WARN_IF(NS_FAILED(rv))) {
2374     return;
2375   }
2376 
2377   RegistrationDataPerPrincipal* data;
2378   if (!mRegistrationInfos.Get(scopeKey, &data)) {
2379     return;
2380   }
2381 
2382   // Had already schedule a quota usage check.
2383   if (data->mQuotaUsageCheckCount != 0) {
2384     return;
2385   }
2386 
2387   ++data->mQuotaUsageCheckCount;
2388 
2389   // Get the corresponding ServiceWorkerRegistrationInfo here. Unregisteration
2390   // might be triggered later, should get it here before it be removed from
2391   // data.mInfos, such that NotifyListenersOnQuotaCheckFinish() can notify the
2392   // corresponding ServiceWorkerRegistrationInfo after asynchronous quota
2393   // checking finish.
2394   RefPtr<ServiceWorkerRegistrationInfo> info;
2395   data->mInfos.Get(aScope, getter_AddRefs(info));
2396   MOZ_ASSERT(info);
2397 
2398   RefPtr<ServiceWorkerManager> self = this;
2399 
2400   ClearQuotaUsageIfNeeded(aPrincipal, [self, info](bool aResult) {
2401     MOZ_ASSERT(NS_IsMainThread());
2402     self->NotifyListenersOnQuotaUsageCheckFinish(info);
2403   });
2404 }
2405 
SoftUpdate(const OriginAttributes & aOriginAttributes,const nsACString & aScope)2406 void ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes,
2407                                       const nsACString& aScope) {
2408   MOZ_ASSERT(NS_IsMainThread());
2409 
2410   if (mShuttingDown) {
2411     return;
2412   }
2413 
2414   SoftUpdateInternal(aOriginAttributes, aScope, nullptr);
2415 }
2416 
2417 namespace {
2418 
2419 class UpdateJobCallback final : public ServiceWorkerJob::Callback {
2420   RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
2421 
~UpdateJobCallback()2422   ~UpdateJobCallback() { MOZ_ASSERT(!mCallback); }
2423 
2424  public:
UpdateJobCallback(ServiceWorkerUpdateFinishCallback * aCallback)2425   explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback)
2426       : mCallback(aCallback) {
2427     MOZ_ASSERT(NS_IsMainThread());
2428     MOZ_ASSERT(mCallback);
2429   }
2430 
JobFinished(ServiceWorkerJob * aJob,ErrorResult & aStatus)2431   void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override {
2432     MOZ_ASSERT(NS_IsMainThread());
2433     MOZ_ASSERT(aJob);
2434     MOZ_ASSERT(mCallback);
2435 
2436     auto scopeExit = MakeScopeExit([&]() { mCallback = nullptr; });
2437 
2438     if (aStatus.Failed()) {
2439       mCallback->UpdateFailed(aStatus);
2440       return;
2441     }
2442 
2443     MOZ_DIAGNOSTIC_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update);
2444     RefPtr<ServiceWorkerUpdateJob> updateJob =
2445         static_cast<ServiceWorkerUpdateJob*>(aJob);
2446     RefPtr<ServiceWorkerRegistrationInfo> reg = updateJob->GetRegistration();
2447     mCallback->UpdateSucceeded(reg);
2448   }
2449 
JobDiscarded(ErrorResult & aStatus)2450   void JobDiscarded(ErrorResult& aStatus) override {
2451     MOZ_ASSERT(NS_IsMainThread());
2452     MOZ_ASSERT(mCallback);
2453 
2454     mCallback->UpdateFailed(aStatus);
2455     mCallback = nullptr;
2456   }
2457 
2458   NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback, override)
2459 };
2460 
2461 }  // anonymous namespace
2462 
SoftUpdateInternal(const OriginAttributes & aOriginAttributes,const nsACString & aScope,ServiceWorkerUpdateFinishCallback * aCallback)2463 void ServiceWorkerManager::SoftUpdateInternal(
2464     const OriginAttributes& aOriginAttributes, const nsACString& aScope,
2465     ServiceWorkerUpdateFinishCallback* aCallback) {
2466   MOZ_ASSERT(NS_IsMainThread());
2467 
2468   if (mShuttingDown) {
2469     return;
2470   }
2471 
2472   auto result = ScopeToPrincipal(aScope, aOriginAttributes);
2473   if (NS_WARN_IF(result.isErr())) {
2474     return;
2475   }
2476 
2477   auto principal = result.unwrap();
2478 
2479   nsAutoCString scopeKey;
2480   nsresult rv = PrincipalToScopeKey(principal, scopeKey);
2481   if (NS_WARN_IF(NS_FAILED(rv))) {
2482     return;
2483   }
2484 
2485   RefPtr<ServiceWorkerRegistrationInfo> registration =
2486       GetRegistration(scopeKey, aScope);
2487   if (NS_WARN_IF(!registration)) {
2488     return;
2489   }
2490 
2491   // "If registration's installing worker is not null, abort these steps."
2492   if (registration->GetInstalling()) {
2493     return;
2494   }
2495 
2496   // "Let newestWorker be the result of running Get Newest Worker algorithm
2497   // passing registration as its argument.
2498   // If newestWorker is null, abort these steps."
2499   RefPtr<ServiceWorkerInfo> newest = registration->Newest();
2500   if (!newest) {
2501     return;
2502   }
2503 
2504   // "If the registration queue for registration is empty, invoke Update
2505   // algorithm, or its equivalent, with client, registration as its argument."
2506   // TODO(catalinb): We don't implement the force bypass cache flag.
2507   // See: https://github.com/slightlyoff/ServiceWorker/issues/759
2508   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
2509 
2510   RefPtr<ServiceWorkerUpdateJob> job = new ServiceWorkerUpdateJob(
2511       principal, registration->Scope(), newest->ScriptSpec(),
2512       registration->GetUpdateViaCache());
2513 
2514   if (aCallback) {
2515     RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
2516     job->AppendResultCallback(cb);
2517   }
2518 
2519   queue->ScheduleJob(job);
2520 }
2521 
Update(nsIPrincipal * aPrincipal,const nsACString & aScope,nsCString aNewestWorkerScriptUrl,ServiceWorkerUpdateFinishCallback * aCallback)2522 void ServiceWorkerManager::Update(
2523     nsIPrincipal* aPrincipal, const nsACString& aScope,
2524     nsCString aNewestWorkerScriptUrl,
2525     ServiceWorkerUpdateFinishCallback* aCallback) {
2526   MOZ_ASSERT(NS_IsMainThread());
2527   MOZ_ASSERT(!aNewestWorkerScriptUrl.IsEmpty());
2528 
2529   UpdateInternal(aPrincipal, aScope, std::move(aNewestWorkerScriptUrl),
2530                  aCallback);
2531 }
2532 
UpdateInternal(nsIPrincipal * aPrincipal,const nsACString & aScope,nsCString && aNewestWorkerScriptUrl,ServiceWorkerUpdateFinishCallback * aCallback)2533 void ServiceWorkerManager::UpdateInternal(
2534     nsIPrincipal* aPrincipal, const nsACString& aScope,
2535     nsCString&& aNewestWorkerScriptUrl,
2536     ServiceWorkerUpdateFinishCallback* aCallback) {
2537   MOZ_ASSERT(aPrincipal);
2538   MOZ_ASSERT(aCallback);
2539   MOZ_ASSERT(!aNewestWorkerScriptUrl.IsEmpty());
2540 
2541   nsAutoCString scopeKey;
2542   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2543   if (NS_WARN_IF(NS_FAILED(rv))) {
2544     return;
2545   }
2546 
2547   RefPtr<ServiceWorkerRegistrationInfo> registration =
2548       GetRegistration(scopeKey, aScope);
2549   if (NS_WARN_IF(!registration)) {
2550     ErrorResult error;
2551     error.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(aScope, "uninstalled");
2552     aCallback->UpdateFailed(error);
2553 
2554     // In case the callback does not consume the exception
2555     error.SuppressException();
2556     return;
2557   }
2558 
2559   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
2560 
2561   // "Let job be the result of running Create Job with update, registration’s
2562   // scope url, newestWorker’s script url, promise, and the context object’s
2563   // relevant settings object."
2564   RefPtr<ServiceWorkerUpdateJob> job = new ServiceWorkerUpdateJob(
2565       aPrincipal, registration->Scope(), std::move(aNewestWorkerScriptUrl),
2566       registration->GetUpdateViaCache());
2567 
2568   RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
2569   job->AppendResultCallback(cb);
2570 
2571   // "Invoke Schedule Job with job."
2572   queue->ScheduleJob(job);
2573 }
2574 
MaybeClaimClient(const ClientInfo & aClientInfo,ServiceWorkerRegistrationInfo * aWorkerRegistration)2575 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::MaybeClaimClient(
2576     const ClientInfo& aClientInfo,
2577     ServiceWorkerRegistrationInfo* aWorkerRegistration) {
2578   MOZ_DIAGNOSTIC_ASSERT(aWorkerRegistration);
2579 
2580   if (!aWorkerRegistration->GetActive()) {
2581     CopyableErrorResult rv;
2582     rv.ThrowInvalidStateError("Worker is not active");
2583     return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2584   }
2585 
2586   // Same origin check
2587   auto principalOrErr = aClientInfo.GetPrincipal();
2588 
2589   if (NS_WARN_IF(principalOrErr.isErr())) {
2590     CopyableErrorResult rv;
2591     rv.ThrowSecurityError("Could not extract client's principal");
2592     return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2593   }
2594 
2595   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
2596   if (!aWorkerRegistration->Principal()->Equals(principal)) {
2597     CopyableErrorResult rv;
2598     rv.ThrowSecurityError("Worker is for a different origin");
2599     return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2600   }
2601 
2602   // The registration that should be controlling the client
2603   RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
2604       GetServiceWorkerRegistrationInfo(aClientInfo);
2605 
2606   // The registration currently controlling the client
2607   RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
2608   GetClientRegistration(aClientInfo, getter_AddRefs(controllingRegistration));
2609 
2610   if (aWorkerRegistration != matchingRegistration ||
2611       aWorkerRegistration == controllingRegistration) {
2612     return GenericErrorResultPromise::CreateAndResolve(true, __func__);
2613   }
2614 
2615   return StartControllingClient(aClientInfo, aWorkerRegistration);
2616 }
2617 
MaybeClaimClient(const ClientInfo & aClientInfo,const ServiceWorkerDescriptor & aServiceWorker)2618 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::MaybeClaimClient(
2619     const ClientInfo& aClientInfo,
2620     const ServiceWorkerDescriptor& aServiceWorker) {
2621   auto principalOrErr = aServiceWorker.GetPrincipal();
2622   if (NS_WARN_IF(principalOrErr.isErr())) {
2623     return GenericErrorResultPromise::CreateAndResolve(false, __func__);
2624   }
2625 
2626   nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
2627 
2628   RefPtr<ServiceWorkerRegistrationInfo> registration =
2629       GetRegistration(principal, aServiceWorker.Scope());
2630 
2631   // While ServiceWorkerManager is distributed across child processes its
2632   // possible for us to sometimes get a claim for a new worker that has
2633   // not propagated to this process yet.  For now, simply note that we
2634   // are done.  The fix for this is to move the SWM to the parent process
2635   // so there are no consistency errors.
2636   if (NS_WARN_IF(!registration) || NS_WARN_IF(!registration->GetActive())) {
2637     return GenericErrorResultPromise::CreateAndResolve(false, __func__);
2638   }
2639 
2640   return MaybeClaimClient(aClientInfo, registration);
2641 }
2642 
UpdateClientControllers(ServiceWorkerRegistrationInfo * aRegistration)2643 void ServiceWorkerManager::UpdateClientControllers(
2644     ServiceWorkerRegistrationInfo* aRegistration) {
2645   MOZ_ASSERT(NS_IsMainThread());
2646 
2647   RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
2648   MOZ_DIAGNOSTIC_ASSERT(activeWorker);
2649 
2650   AutoTArray<RefPtr<ClientHandle>, 16> handleList;
2651   for (const auto& client : mControlledClients.Values()) {
2652     if (client->mRegistrationInfo != aRegistration) {
2653       continue;
2654     }
2655 
2656     handleList.AppendElement(client->mClientHandle);
2657   }
2658 
2659   // Fire event after iterating mControlledClients is done to prevent
2660   // modification by reentering from the event handlers during iteration.
2661   for (auto& handle : handleList) {
2662     RefPtr<GenericErrorResultPromise> p =
2663         handle->Control(activeWorker->Descriptor());
2664 
2665     RefPtr<ServiceWorkerManager> self = this;
2666 
2667     // If we fail to control the client, then automatically remove it
2668     // from our list of controlled clients.
2669     p->Then(
2670         GetMainThreadSerialEventTarget(), __func__,
2671         [](bool) {
2672           // do nothing on success
2673         },
2674         [self, clientInfo = handle->Info()](const CopyableErrorResult& aRv) {
2675           // failed to control, forget about this client
2676           self->StopControllingClient(clientInfo);
2677         });
2678   }
2679 }
2680 
2681 already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(nsIPrincipal * aPrincipal,const nsACString & aScope) const2682 ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
2683                                       const nsACString& aScope) const {
2684   MOZ_ASSERT(aPrincipal);
2685 
2686   nsAutoCString scopeKey;
2687   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2688   if (NS_WARN_IF(NS_FAILED(rv))) {
2689     return nullptr;
2690   }
2691 
2692   return GetRegistration(scopeKey, aScope);
2693 }
2694 
2695 already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(const PrincipalInfo & aPrincipalInfo,const nsACString & aScope) const2696 ServiceWorkerManager::GetRegistration(const PrincipalInfo& aPrincipalInfo,
2697                                       const nsACString& aScope) const {
2698   nsAutoCString scopeKey;
2699   nsresult rv = PrincipalInfoToScopeKey(aPrincipalInfo, scopeKey);
2700   if (NS_WARN_IF(NS_FAILED(rv))) {
2701     return nullptr;
2702   }
2703 
2704   return GetRegistration(scopeKey, aScope);
2705 }
2706 
2707 NS_IMETHODIMP
ReloadRegistrationsForTest()2708 ServiceWorkerManager::ReloadRegistrationsForTest() {
2709   if (NS_WARN_IF(!StaticPrefs::dom_serviceWorkers_testing_enabled())) {
2710     return NS_ERROR_FAILURE;
2711   }
2712 
2713   // Let's keep it simple and fail if there are any controlled client,
2714   // the test case can take care of making sure there is none when this
2715   // method will be called.
2716   if (NS_WARN_IF(!mControlledClients.IsEmpty())) {
2717     return NS_ERROR_FAILURE;
2718   }
2719 
2720   for (const auto& info : mRegistrationInfos.Values()) {
2721     for (ServiceWorkerRegistrationInfo* reg : info->mInfos.Values()) {
2722       MOZ_ASSERT(reg);
2723       reg->ForceShutdown();
2724     }
2725   }
2726 
2727   mRegistrationInfos.Clear();
2728 
2729   nsTArray<ServiceWorkerRegistrationData> data;
2730   RefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get();
2731   if (NS_WARN_IF(!swr->ReloadDataForTest())) {
2732     return NS_ERROR_FAILURE;
2733   }
2734   swr->GetRegistrations(data);
2735   LoadRegistrations(data);
2736 
2737   return NS_OK;
2738 }
2739 
2740 NS_IMETHODIMP
RegisterForAddonPrincipal(nsIPrincipal * aPrincipal,JSContext * aCx,dom::Promise ** aPromise)2741 ServiceWorkerManager::RegisterForAddonPrincipal(nsIPrincipal* aPrincipal,
2742                                                 JSContext* aCx,
2743                                                 dom::Promise** aPromise) {
2744   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
2745   if (NS_WARN_IF(!global)) {
2746     return NS_ERROR_FAILURE;
2747   }
2748 
2749   ErrorResult erv;
2750   RefPtr<Promise> outer = Promise::Create(global, erv);
2751   if (NS_WARN_IF(erv.Failed())) {
2752     return erv.StealNSResult();
2753   }
2754 
2755   auto enabled =
2756       StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2757   if (!enabled) {
2758     outer->MaybeRejectWithNotAllowedError(
2759         "Disabled. extensions.backgroundServiceWorker.enabled is false");
2760     outer.forget(aPromise);
2761     return NS_OK;
2762   }
2763 
2764   MOZ_ASSERT(aPrincipal);
2765   auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
2766   if (!addonPolicy) {
2767     outer->MaybeRejectWithNotAllowedError("Not an extension principal");
2768     outer.forget(aPromise);
2769     return NS_OK;
2770   }
2771 
2772   nsCString scope;
2773   auto result = addonPolicy->GetURL(u""_ns);
2774   if (result.isOk()) {
2775     scope.Assign(NS_ConvertUTF16toUTF8(result.unwrap()));
2776   } else {
2777     outer->MaybeRejectWithUnknownError("Unable to resolve addon scope URL");
2778     outer.forget(aPromise);
2779     return NS_OK;
2780   }
2781 
2782   nsString scriptURL;
2783   addonPolicy->GetBackgroundWorker(scriptURL);
2784 
2785   if (scriptURL.IsEmpty()) {
2786     outer->MaybeRejectWithNotFoundError("Missing background worker script url");
2787     outer.forget(aPromise);
2788     return NS_OK;
2789   }
2790 
2791   Maybe<ClientInfo> clientInfo =
2792       dom::ClientManager::CreateInfo(ClientType::All, aPrincipal);
2793 
2794   if (!clientInfo.isSome()) {
2795     outer->MaybeRejectWithUnknownError("Error creating clientInfo");
2796     outer.forget(aPromise);
2797     return NS_OK;
2798   }
2799 
2800   auto regPromise =
2801       Register(clientInfo.ref(), scope, NS_ConvertUTF16toUTF8(scriptURL),
2802                dom::ServiceWorkerUpdateViaCache::Imports);
2803   const RefPtr<ServiceWorkerManager> self(this);
2804   const nsCOMPtr<nsIPrincipal> principal(aPrincipal);
2805   regPromise->Then(
2806       GetMainThreadSerialEventTarget(), __func__,
2807       [self, outer, principal,
2808        scope](const ServiceWorkerRegistrationDescriptor& regDesc) {
2809         RefPtr<ServiceWorkerRegistrationInfo> registration =
2810             self->GetRegistration(principal, scope);
2811         if (registration) {
2812           outer->MaybeResolve(registration);
2813         } else {
2814           outer->MaybeRejectWithUnknownError(
2815               "Failed to retrieve ServiceWorkerRegistrationInfo");
2816         }
2817       },
2818       [outer](const mozilla::CopyableErrorResult& err) {
2819         CopyableErrorResult result(err);
2820         outer->MaybeReject(std::move(result));
2821       });
2822 
2823   outer.forget(aPromise);
2824 
2825   return NS_OK;
2826 }
2827 
2828 NS_IMETHODIMP
GetRegistrationForAddonPrincipal(nsIPrincipal * aPrincipal,nsIServiceWorkerRegistrationInfo ** aInfo)2829 ServiceWorkerManager::GetRegistrationForAddonPrincipal(
2830     nsIPrincipal* aPrincipal, nsIServiceWorkerRegistrationInfo** aInfo) {
2831   MOZ_ASSERT(aPrincipal);
2832 
2833   MOZ_ASSERT(aPrincipal);
2834   auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
2835   if (!addonPolicy) {
2836     return NS_ERROR_FAILURE;
2837   }
2838 
2839   nsCString scope;
2840   auto result = addonPolicy->GetURL(u""_ns);
2841   if (result.isOk()) {
2842     scope.Assign(NS_ConvertUTF16toUTF8(result.unwrap()));
2843   } else {
2844     return NS_ERROR_FAILURE;
2845   }
2846 
2847   nsCOMPtr<nsIURI> scopeURI;
2848   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
2849   if (NS_FAILED(rv)) {
2850     return NS_ERROR_FAILURE;
2851   }
2852 
2853   RefPtr<ServiceWorkerRegistrationInfo> info =
2854       GetServiceWorkerRegistrationInfo(aPrincipal, scopeURI);
2855   if (!info) {
2856     aInfo = nullptr;
2857     return NS_OK;
2858   }
2859   info.forget(aInfo);
2860   return NS_OK;
2861 }
2862 
2863 NS_IMETHODIMP
WakeForExtensionAPIEvent(const nsAString & aExtensionBaseURL,const nsAString & aAPINamespace,const nsAString & aAPIEventName,JSContext * aCx,dom::Promise ** aPromise)2864 ServiceWorkerManager::WakeForExtensionAPIEvent(
2865     const nsAString& aExtensionBaseURL, const nsAString& aAPINamespace,
2866     const nsAString& aAPIEventName, JSContext* aCx, dom::Promise** aPromise) {
2867   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
2868   if (NS_WARN_IF(!global)) {
2869     return NS_ERROR_FAILURE;
2870   }
2871 
2872   ErrorResult erv;
2873   RefPtr<Promise> outer = Promise::Create(global, erv);
2874   if (NS_WARN_IF(erv.Failed())) {
2875     return erv.StealNSResult();
2876   }
2877 
2878   auto enabled =
2879       StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2880   if (!enabled) {
2881     outer->MaybeRejectWithNotAllowedError(
2882         "Disabled. extensions.backgroundServiceWorker.enabled is false");
2883     outer.forget(aPromise);
2884     return NS_OK;
2885   }
2886 
2887   nsCOMPtr<nsIURI> scopeURI;
2888   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aExtensionBaseURL);
2889   if (NS_FAILED(rv)) {
2890     outer->MaybeReject(rv);
2891     outer.forget(aPromise);
2892     return NS_OK;
2893   }
2894 
2895   nsCOMPtr<nsIPrincipal> principal;
2896   MOZ_TRY_VAR(principal, ScopeToPrincipal(scopeURI, {}));
2897 
2898   auto* addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
2899   if (NS_WARN_IF(!addonPolicy)) {
2900     outer->MaybeRejectWithNotAllowedError(
2901         "Not an extension principal or extension disabled");
2902     outer.forget(aPromise);
2903     return NS_OK;
2904   }
2905 
2906   OriginAttributes attrs;
2907   ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(
2908       attrs, NS_ConvertUTF16toUTF8(aExtensionBaseURL));
2909   if (NS_WARN_IF(!info)) {
2910     outer->MaybeRejectWithInvalidStateError(
2911         "No active worker for the extension background service worker");
2912     outer.forget(aPromise);
2913     return NS_OK;
2914   }
2915 
2916   ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
2917   auto result =
2918       workerPrivate->WakeForExtensionAPIEvent(aAPINamespace, aAPIEventName);
2919   if (result.isErr()) {
2920     outer->MaybeReject(result.propagateErr());
2921     outer.forget(aPromise);
2922     return NS_OK;
2923   }
2924 
2925   RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener> innerPromise =
2926       result.unwrap();
2927 
2928   innerPromise->Then(
2929       GetMainThreadSerialEventTarget(), __func__,
2930       [outer](bool aSubscribedEvent) { outer->MaybeResolve(aSubscribedEvent); },
2931       [outer](nsresult aErrorResult) { outer->MaybeReject(aErrorResult); });
2932 
2933   outer.forget(aPromise);
2934   return NS_OK;
2935 }
2936 
2937 NS_IMETHODIMP
GetRegistrationByPrincipal(nsIPrincipal * aPrincipal,const nsAString & aScope,nsIServiceWorkerRegistrationInfo ** aInfo)2938 ServiceWorkerManager::GetRegistrationByPrincipal(
2939     nsIPrincipal* aPrincipal, const nsAString& aScope,
2940     nsIServiceWorkerRegistrationInfo** aInfo) {
2941   MOZ_ASSERT(aPrincipal);
2942   MOZ_ASSERT(aInfo);
2943 
2944   nsCOMPtr<nsIURI> scopeURI;
2945   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
2946   if (NS_FAILED(rv)) {
2947     return NS_ERROR_FAILURE;
2948   }
2949 
2950   RefPtr<ServiceWorkerRegistrationInfo> info =
2951       GetServiceWorkerRegistrationInfo(aPrincipal, scopeURI);
2952   if (!info) {
2953     return NS_ERROR_FAILURE;
2954   }
2955   info.forget(aInfo);
2956 
2957   return NS_OK;
2958 }
2959 
2960 already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(const nsACString & aScopeKey,const nsACString & aScope) const2961 ServiceWorkerManager::GetRegistration(const nsACString& aScopeKey,
2962                                       const nsACString& aScope) const {
2963   RefPtr<ServiceWorkerRegistrationInfo> reg;
2964 
2965   RegistrationDataPerPrincipal* data;
2966   if (!mRegistrationInfos.Get(aScopeKey, &data)) {
2967     return reg.forget();
2968   }
2969 
2970   data->mInfos.Get(aScope, getter_AddRefs(reg));
2971   return reg.forget();
2972 }
2973 
2974 already_AddRefed<ServiceWorkerRegistrationInfo>
CreateNewRegistration(const nsCString & aScope,nsIPrincipal * aPrincipal,ServiceWorkerUpdateViaCache aUpdateViaCache,IPCNavigationPreloadState aNavigationPreloadState)2975 ServiceWorkerManager::CreateNewRegistration(
2976     const nsCString& aScope, nsIPrincipal* aPrincipal,
2977     ServiceWorkerUpdateViaCache aUpdateViaCache,
2978     IPCNavigationPreloadState aNavigationPreloadState) {
2979 #ifdef DEBUG
2980   MOZ_ASSERT(NS_IsMainThread());
2981   nsCOMPtr<nsIURI> scopeURI;
2982   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
2983   MOZ_ASSERT(NS_SUCCEEDED(rv));
2984 
2985   RefPtr<ServiceWorkerRegistrationInfo> tmp =
2986       GetRegistration(aPrincipal, aScope);
2987   MOZ_ASSERT(!tmp);
2988 #endif
2989 
2990   RefPtr<ServiceWorkerRegistrationInfo> registration =
2991       new ServiceWorkerRegistrationInfo(aScope, aPrincipal, aUpdateViaCache,
2992                                         std::move(aNavigationPreloadState));
2993 
2994   // From now on ownership of registration is with
2995   // mServiceWorkerRegistrationInfos.
2996   AddScopeAndRegistration(aScope, registration);
2997   return registration.forget();
2998 }
2999 
MaybeRemoveRegistration(ServiceWorkerRegistrationInfo * aRegistration)3000 void ServiceWorkerManager::MaybeRemoveRegistration(
3001     ServiceWorkerRegistrationInfo* aRegistration) {
3002   MOZ_ASSERT(aRegistration);
3003   RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
3004   if (!newest && HasScope(aRegistration->Principal(), aRegistration->Scope())) {
3005     RemoveRegistration(aRegistration);
3006   }
3007 }
3008 
RemoveRegistration(ServiceWorkerRegistrationInfo * aRegistration)3009 void ServiceWorkerManager::RemoveRegistration(
3010     ServiceWorkerRegistrationInfo* aRegistration) {
3011   // Note, we do not need to call mActor->SendUnregister() here.  There are a
3012   // few ways we can get here: 1) Through a normal unregister which calls
3013   // SendUnregister() in the
3014   //    unregister job Start() method.
3015   // 2) Through origin storage being purged.  These result in ForceUnregister()
3016   //    starting unregister jobs which in turn call SendUnregister().
3017   // 3) Through the failure to install a new service worker.  Since we don't
3018   //    store the registration until install succeeds, we do not need to call
3019   //    SendUnregister here.
3020   MOZ_ASSERT(HasScope(aRegistration->Principal(), aRegistration->Scope()));
3021 
3022   RemoveScopeAndRegistration(aRegistration);
3023 }
3024 
3025 NS_IMETHODIMP
GetAllRegistrations(nsIArray ** aResult)3026 ServiceWorkerManager::GetAllRegistrations(nsIArray** aResult) {
3027   MOZ_ASSERT(NS_IsMainThread());
3028 
3029   nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
3030   if (!array) {
3031     return NS_ERROR_OUT_OF_MEMORY;
3032   }
3033 
3034   for (const auto& info : mRegistrationInfos.Values()) {
3035     for (ServiceWorkerRegistrationInfo* reg : info->mInfos.Values()) {
3036       MOZ_ASSERT(reg);
3037 
3038       array->AppendElement(reg);
3039     }
3040   }
3041 
3042   array.forget(aResult);
3043   return NS_OK;
3044 }
3045 
3046 NS_IMETHODIMP
RemoveRegistrationsByOriginAttributes(const nsAString & aPattern)3047 ServiceWorkerManager::RemoveRegistrationsByOriginAttributes(
3048     const nsAString& aPattern) {
3049   MOZ_ASSERT(XRE_IsParentProcess());
3050   MOZ_ASSERT(NS_IsMainThread());
3051 
3052   MOZ_ASSERT(!aPattern.IsEmpty());
3053 
3054   OriginAttributesPattern pattern;
3055   MOZ_ALWAYS_TRUE(pattern.Init(aPattern));
3056 
3057   for (const auto& data : mRegistrationInfos.Values()) {
3058     // We can use iteration because ForceUnregister (and Unregister) are
3059     // async. Otherwise doing some R/W operations on an hashtable during
3060     // iteration will crash.
3061     for (ServiceWorkerRegistrationInfo* reg : data->mInfos.Values()) {
3062       MOZ_ASSERT(reg);
3063       MOZ_ASSERT(reg->Principal());
3064 
3065       bool matches = pattern.Matches(reg->Principal()->OriginAttributesRef());
3066       if (!matches) {
3067         continue;
3068       }
3069 
3070       ForceUnregister(data.get(), reg);
3071     }
3072   }
3073 
3074   return NS_OK;
3075 }
3076 
ForceUnregister(RegistrationDataPerPrincipal * aRegistrationData,ServiceWorkerRegistrationInfo * aRegistration)3077 void ServiceWorkerManager::ForceUnregister(
3078     RegistrationDataPerPrincipal* aRegistrationData,
3079     ServiceWorkerRegistrationInfo* aRegistration) {
3080   MOZ_ASSERT(aRegistrationData);
3081   MOZ_ASSERT(aRegistration);
3082 
3083   RefPtr<ServiceWorkerJobQueue> queue;
3084   aRegistrationData->mJobQueues.Get(aRegistration->Scope(),
3085                                     getter_AddRefs(queue));
3086   if (queue) {
3087     queue->CancelAll();
3088   }
3089 
3090   if (auto entry =
3091           aRegistrationData->mUpdateTimers.Lookup(aRegistration->Scope())) {
3092     entry.Data()->Cancel();
3093     entry.Remove();
3094   }
3095 
3096   // Since Unregister is async, it is ok to call it in an enumeration.
3097   Unregister(aRegistration->Principal(), nullptr,
3098              NS_ConvertUTF8toUTF16(aRegistration->Scope()));
3099 }
3100 
3101 NS_IMETHODIMP
AddListener(nsIServiceWorkerManagerListener * aListener)3102 ServiceWorkerManager::AddListener(nsIServiceWorkerManagerListener* aListener) {
3103   MOZ_ASSERT(NS_IsMainThread());
3104 
3105   if (!aListener || mListeners.Contains(aListener)) {
3106     return NS_ERROR_INVALID_ARG;
3107   }
3108 
3109   mListeners.AppendElement(aListener);
3110 
3111   return NS_OK;
3112 }
3113 
3114 NS_IMETHODIMP
RemoveListener(nsIServiceWorkerManagerListener * aListener)3115 ServiceWorkerManager::RemoveListener(
3116     nsIServiceWorkerManagerListener* aListener) {
3117   MOZ_ASSERT(NS_IsMainThread());
3118 
3119   if (!aListener || !mListeners.Contains(aListener)) {
3120     return NS_ERROR_INVALID_ARG;
3121   }
3122 
3123   mListeners.RemoveElement(aListener);
3124 
3125   return NS_OK;
3126 }
3127 
3128 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)3129 ServiceWorkerManager::Observe(nsISupports* aSubject, const char* aTopic,
3130                               const char16_t* aData) {
3131   if (strcmp(aTopic, kFinishShutdownTopic) == 0) {
3132     MaybeFinishShutdown();
3133     return NS_OK;
3134   }
3135 
3136   MOZ_CRASH("Received message we aren't supposed to be registered for!");
3137   return NS_OK;
3138 }
3139 
3140 NS_IMETHODIMP
PropagateUnregister(nsIPrincipal * aPrincipal,nsIServiceWorkerUnregisterCallback * aCallback,const nsAString & aScope)3141 ServiceWorkerManager::PropagateUnregister(
3142     nsIPrincipal* aPrincipal, nsIServiceWorkerUnregisterCallback* aCallback,
3143     const nsAString& aScope) {
3144   MOZ_ASSERT(NS_IsMainThread());
3145   MOZ_ASSERT(aPrincipal);
3146 
3147   // Return earlier with an explicit failure if this xpcom method is called
3148   // when the ServiceWorkerManager is not initialized yet or it is already
3149   // shutting down.
3150   if (NS_WARN_IF(!mActor)) {
3151     return NS_ERROR_FAILURE;
3152   }
3153 
3154   PrincipalInfo principalInfo;
3155   if (NS_WARN_IF(
3156           NS_FAILED(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)))) {
3157     return NS_ERROR_FAILURE;
3158   }
3159 
3160   mActor->SendPropagateUnregister(principalInfo, nsString(aScope));
3161 
3162   nsresult rv = Unregister(aPrincipal, aCallback, aScope);
3163   if (NS_WARN_IF(NS_FAILED(rv))) {
3164     return rv;
3165   }
3166 
3167   return NS_OK;
3168 }
3169 
NotifyListenersOnRegister(nsIServiceWorkerRegistrationInfo * aInfo)3170 void ServiceWorkerManager::NotifyListenersOnRegister(
3171     nsIServiceWorkerRegistrationInfo* aInfo) {
3172   nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3173       mListeners.Clone());
3174   for (size_t index = 0; index < listeners.Length(); ++index) {
3175     listeners[index]->OnRegister(aInfo);
3176   }
3177 }
3178 
NotifyListenersOnUnregister(nsIServiceWorkerRegistrationInfo * aInfo)3179 void ServiceWorkerManager::NotifyListenersOnUnregister(
3180     nsIServiceWorkerRegistrationInfo* aInfo) {
3181   nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3182       mListeners.Clone());
3183   for (size_t index = 0; index < listeners.Length(); ++index) {
3184     listeners[index]->OnUnregister(aInfo);
3185   }
3186 }
3187 
NotifyListenersOnQuotaUsageCheckFinish(nsIServiceWorkerRegistrationInfo * aRegistration)3188 void ServiceWorkerManager::NotifyListenersOnQuotaUsageCheckFinish(
3189     nsIServiceWorkerRegistrationInfo* aRegistration) {
3190   nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3191       mListeners.Clone());
3192   for (size_t index = 0; index < listeners.Length(); ++index) {
3193     listeners[index]->OnQuotaUsageCheckFinish(aRegistration);
3194   }
3195 }
3196 
3197 class UpdateTimerCallback final : public nsITimerCallback, public nsINamed {
3198   nsCOMPtr<nsIPrincipal> mPrincipal;
3199   const nsCString mScope;
3200 
3201   ~UpdateTimerCallback() = default;
3202 
3203  public:
UpdateTimerCallback(nsIPrincipal * aPrincipal,const nsACString & aScope)3204   UpdateTimerCallback(nsIPrincipal* aPrincipal, const nsACString& aScope)
3205       : mPrincipal(aPrincipal), mScope(aScope) {
3206     MOZ_ASSERT(NS_IsMainThread());
3207     MOZ_ASSERT(mPrincipal);
3208     MOZ_ASSERT(!mScope.IsEmpty());
3209   }
3210 
3211   NS_IMETHOD
Notify(nsITimer * aTimer)3212   Notify(nsITimer* aTimer) override {
3213     MOZ_ASSERT(NS_IsMainThread());
3214 
3215     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
3216     if (!swm) {
3217       // shutting down, do nothing
3218       return NS_OK;
3219     }
3220 
3221     swm->UpdateTimerFired(mPrincipal, mScope);
3222     return NS_OK;
3223   }
3224 
3225   NS_IMETHOD
GetName(nsACString & aName)3226   GetName(nsACString& aName) override {
3227     aName.AssignLiteral("UpdateTimerCallback");
3228     return NS_OK;
3229   }
3230 
3231   NS_DECL_ISUPPORTS
3232 };
3233 
NS_IMPL_ISUPPORTS(UpdateTimerCallback,nsITimerCallback,nsINamed)3234 NS_IMPL_ISUPPORTS(UpdateTimerCallback, nsITimerCallback, nsINamed)
3235 
3236 void ServiceWorkerManager::ScheduleUpdateTimer(nsIPrincipal* aPrincipal,
3237                                                const nsACString& aScope) {
3238   MOZ_ASSERT(NS_IsMainThread());
3239   MOZ_ASSERT(aPrincipal);
3240   MOZ_ASSERT(!aScope.IsEmpty());
3241 
3242   if (mShuttingDown) {
3243     return;
3244   }
3245 
3246   nsAutoCString scopeKey;
3247   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
3248   if (NS_WARN_IF(NS_FAILED(rv))) {
3249     return;
3250   }
3251 
3252   RegistrationDataPerPrincipal* data;
3253   if (!mRegistrationInfos.Get(scopeKey, &data)) {
3254     return;
3255   }
3256 
3257   data->mUpdateTimers.WithEntryHandle(
3258       aScope, [&aPrincipal, &aScope](auto&& entry) {
3259         if (entry) {
3260           // In case there is already a timer scheduled, just use the original
3261           // schedule time.  We don't want to push it out to a later time since
3262           // that could allow updates to be starved forever if events are
3263           // continuously fired.
3264           return;
3265         }
3266 
3267         nsCOMPtr<nsITimerCallback> callback =
3268             new UpdateTimerCallback(aPrincipal, aScope);
3269 
3270         const uint32_t UPDATE_DELAY_MS = 1000;
3271 
3272         nsCOMPtr<nsITimer> timer;
3273 
3274         const nsresult rv =
3275             NS_NewTimerWithCallback(getter_AddRefs(timer), callback,
3276                                     UPDATE_DELAY_MS, nsITimer::TYPE_ONE_SHOT);
3277 
3278         if (NS_WARN_IF(NS_FAILED(rv))) {
3279           return;
3280         }
3281 
3282         entry.Insert(std::move(timer));
3283       });
3284 }
3285 
UpdateTimerFired(nsIPrincipal * aPrincipal,const nsACString & aScope)3286 void ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal,
3287                                             const nsACString& aScope) {
3288   MOZ_ASSERT(NS_IsMainThread());
3289   MOZ_ASSERT(aPrincipal);
3290   MOZ_ASSERT(!aScope.IsEmpty());
3291 
3292   if (mShuttingDown) {
3293     return;
3294   }
3295 
3296   // First cleanup the timer.
3297   nsAutoCString scopeKey;
3298   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
3299   if (NS_WARN_IF(NS_FAILED(rv))) {
3300     return;
3301   }
3302 
3303   RegistrationDataPerPrincipal* data;
3304   if (!mRegistrationInfos.Get(scopeKey, &data)) {
3305     return;
3306   }
3307 
3308   if (auto entry = data->mUpdateTimers.Lookup(aScope)) {
3309     entry.Data()->Cancel();
3310     entry.Remove();
3311   }
3312 
3313   RefPtr<ServiceWorkerRegistrationInfo> registration;
3314   data->mInfos.Get(aScope, getter_AddRefs(registration));
3315   if (!registration) {
3316     return;
3317   }
3318 
3319   if (!registration->CheckAndClearIfUpdateNeeded()) {
3320     return;
3321   }
3322 
3323   OriginAttributes attrs = aPrincipal->OriginAttributesRef();
3324 
3325   SoftUpdate(attrs, aScope);
3326 }
3327 
MaybeSendUnregister(nsIPrincipal * aPrincipal,const nsACString & aScope)3328 void ServiceWorkerManager::MaybeSendUnregister(nsIPrincipal* aPrincipal,
3329                                                const nsACString& aScope) {
3330   MOZ_ASSERT(NS_IsMainThread());
3331   MOZ_ASSERT(aPrincipal);
3332   MOZ_ASSERT(!aScope.IsEmpty());
3333 
3334   if (!mActor) {
3335     return;
3336   }
3337 
3338   PrincipalInfo principalInfo;
3339   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
3340   if (NS_WARN_IF(NS_FAILED(rv))) {
3341     return;
3342   }
3343 
3344   Unused << mActor->SendUnregister(principalInfo,
3345                                    NS_ConvertUTF8toUTF16(aScope));
3346 }
3347 
AddOrphanedRegistration(ServiceWorkerRegistrationInfo * aRegistration)3348 void ServiceWorkerManager::AddOrphanedRegistration(
3349     ServiceWorkerRegistrationInfo* aRegistration) {
3350   MOZ_ASSERT(NS_IsMainThread());
3351   MOZ_ASSERT(aRegistration);
3352   MOZ_ASSERT(aRegistration->IsUnregistered());
3353   MOZ_ASSERT(!aRegistration->IsControllingClients());
3354   MOZ_ASSERT(!aRegistration->IsIdle());
3355   MOZ_ASSERT(!mOrphanedRegistrations.has(aRegistration));
3356 
3357   MOZ_ALWAYS_TRUE(mOrphanedRegistrations.putNew(aRegistration));
3358 }
3359 
RemoveOrphanedRegistration(ServiceWorkerRegistrationInfo * aRegistration)3360 void ServiceWorkerManager::RemoveOrphanedRegistration(
3361     ServiceWorkerRegistrationInfo* aRegistration) {
3362   MOZ_ASSERT(NS_IsMainThread());
3363   MOZ_ASSERT(aRegistration);
3364   MOZ_ASSERT(aRegistration->IsUnregistered());
3365   MOZ_ASSERT(!aRegistration->IsControllingClients());
3366   MOZ_ASSERT(aRegistration->IsIdle());
3367   MOZ_ASSERT(mOrphanedRegistrations.has(aRegistration));
3368 
3369   mOrphanedRegistrations.remove(aRegistration);
3370 }
3371 
MaybeInitServiceWorkerShutdownProgress() const3372 uint32_t ServiceWorkerManager::MaybeInitServiceWorkerShutdownProgress() const {
3373   if (!mShutdownBlocker) {
3374     return ServiceWorkerShutdownBlocker::kInvalidShutdownStateId;
3375   }
3376 
3377   return mShutdownBlocker->CreateShutdownState();
3378 }
3379 
ReportServiceWorkerShutdownProgress(uint32_t aShutdownStateId,ServiceWorkerShutdownState::Progress aProgress) const3380 void ServiceWorkerManager::ReportServiceWorkerShutdownProgress(
3381     uint32_t aShutdownStateId,
3382     ServiceWorkerShutdownState::Progress aProgress) const {
3383   MOZ_ASSERT(mShutdownBlocker);
3384   mShutdownBlocker->ReportShutdownProgress(aShutdownStateId, aProgress);
3385 }
3386 
3387 }  // namespace dom
3388 }  // namespace mozilla
3389