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