no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / serviceworkers / ServiceWorkerManager.cpp
blob842939d78511d9bbe016334764991d0c64314743
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/. */
7 #include "ServiceWorkerManager.h"
9 #include <algorithm>
11 #include "nsCOMPtr.h"
12 #include "nsICookieJarSettings.h"
13 #include "nsIHttpChannel.h"
14 #include "nsIHttpChannelInternal.h"
15 #include "nsINamed.h"
16 #include "nsINetworkInterceptController.h"
17 #include "nsIMutableArray.h"
18 #include "nsIPrincipal.h"
19 #include "nsITimer.h"
20 #include "nsIUploadChannel2.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsDebug.h"
23 #include "nsIPermissionManager.h"
24 #include "nsXULAppAPI.h"
26 #include "jsapi.h"
28 #include "mozilla/AppShutdown.h"
29 #include "mozilla/BasePrincipal.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "mozilla/ErrorNames.h"
32 #include "mozilla/LoadContext.h"
33 #include "mozilla/MozPromise.h"
34 #include "mozilla/Result.h"
35 #include "mozilla/ResultExtensions.h"
36 #include "mozilla/Telemetry.h"
37 #include "mozilla/dom/BindingUtils.h"
38 #include "mozilla/dom/ClientHandle.h"
39 #include "mozilla/dom/ClientManager.h"
40 #include "mozilla/dom/ClientSource.h"
41 #include "mozilla/dom/ConsoleUtils.h"
42 #include "mozilla/dom/ContentParent.h"
43 #include "mozilla/dom/ErrorEvent.h"
44 #include "mozilla/dom/Headers.h"
45 #include "mozilla/dom/InternalHeaders.h"
46 #include "mozilla/dom/Navigator.h"
47 #include "mozilla/dom/NotificationEvent.h"
48 #include "mozilla/dom/Promise.h"
49 #include "mozilla/dom/PromiseNativeHandler.h"
50 #include "mozilla/dom/Request.h"
51 #include "mozilla/dom/RootedDictionary.h"
52 #include "mozilla/dom/TypedArray.h"
53 #include "mozilla/dom/SharedWorker.h"
54 #include "mozilla/dom/WorkerPrivate.h"
55 #include "mozilla/dom/WorkerRunnable.h"
56 #include "mozilla/dom/WorkerScope.h"
57 #include "mozilla/extensions/WebExtensionPolicy.h"
58 #include "mozilla/ipc/BackgroundChild.h"
59 #include "mozilla/ipc/PBackgroundChild.h"
60 #include "mozilla/ipc/PBackgroundSharedTypes.h"
61 #include "mozilla/dom/ScriptLoader.h"
62 #include "mozilla/PermissionManager.h"
63 #include "mozilla/ScopeExit.h"
64 #include "mozilla/StaticPrefs_extensions.h"
65 #include "mozilla/StaticPrefs_privacy.h"
66 #include "mozilla/StoragePrincipalHelper.h"
67 #include "mozilla/Unused.h"
68 #include "mozilla/EnumSet.h"
70 #include "nsComponentManagerUtils.h"
71 #include "nsContentUtils.h"
72 #include "nsIDUtils.h"
73 #include "nsNetUtil.h"
74 #include "nsProxyRelease.h"
75 #include "nsQueryObject.h"
76 #include "nsTArray.h"
78 #include "ServiceWorker.h"
79 #include "ServiceWorkerContainer.h"
80 #include "ServiceWorkerInfo.h"
81 #include "ServiceWorkerJobQueue.h"
82 #include "ServiceWorkerManagerChild.h"
83 #include "ServiceWorkerPrivate.h"
84 #include "ServiceWorkerRegisterJob.h"
85 #include "ServiceWorkerRegistrar.h"
86 #include "ServiceWorkerRegistration.h"
87 #include "ServiceWorkerScriptCache.h"
88 #include "ServiceWorkerShutdownBlocker.h"
89 #include "ServiceWorkerEvents.h"
90 #include "ServiceWorkerUnregisterJob.h"
91 #include "ServiceWorkerUpdateJob.h"
92 #include "ServiceWorkerUtils.h"
93 #include "ServiceWorkerQuotaUtils.h"
95 #ifdef PostMessage
96 # undef PostMessage
97 #endif
99 mozilla::LazyLogModule sWorkerTelemetryLog("WorkerTelemetry");
101 #ifdef LOG
102 # undef LOG
103 #endif
104 #define LOG(_args) MOZ_LOG(sWorkerTelemetryLog, LogLevel::Debug, _args);
106 using namespace mozilla;
107 using namespace mozilla::dom;
108 using namespace mozilla::ipc;
110 namespace mozilla::dom {
112 // Counts the number of registered ServiceWorkers, and the number that
113 // handle Fetch, for reporting in Telemetry
114 uint32_t gServiceWorkersRegistered = 0;
115 uint32_t gServiceWorkersRegisteredFetch = 0;
117 static_assert(
118 nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW ==
119 static_cast<uint32_t>(RequestRedirect::Follow),
120 "RequestRedirect enumeration value should make Necko Redirect mode value.");
121 static_assert(
122 nsIHttpChannelInternal::REDIRECT_MODE_ERROR ==
123 static_cast<uint32_t>(RequestRedirect::Error),
124 "RequestRedirect enumeration value should make Necko Redirect mode value.");
125 static_assert(
126 nsIHttpChannelInternal::REDIRECT_MODE_MANUAL ==
127 static_cast<uint32_t>(RequestRedirect::Manual),
128 "RequestRedirect enumeration value should make Necko Redirect mode value.");
129 static_assert(
130 3 == ContiguousEnumSize<RequestRedirect>::value,
131 "RequestRedirect enumeration value should make Necko Redirect mode value.");
133 static_assert(
134 nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT ==
135 static_cast<uint32_t>(RequestCache::Default),
136 "RequestCache enumeration value should match Necko Cache mode value.");
137 static_assert(
138 nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE ==
139 static_cast<uint32_t>(RequestCache::No_store),
140 "RequestCache enumeration value should match Necko Cache mode value.");
141 static_assert(
142 nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD ==
143 static_cast<uint32_t>(RequestCache::Reload),
144 "RequestCache enumeration value should match Necko Cache mode value.");
145 static_assert(
146 nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE ==
147 static_cast<uint32_t>(RequestCache::No_cache),
148 "RequestCache enumeration value should match Necko Cache mode value.");
149 static_assert(
150 nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE ==
151 static_cast<uint32_t>(RequestCache::Force_cache),
152 "RequestCache enumeration value should match Necko Cache mode value.");
153 static_assert(
154 nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED ==
155 static_cast<uint32_t>(RequestCache::Only_if_cached),
156 "RequestCache enumeration value should match Necko Cache mode value.");
157 static_assert(
158 6 == ContiguousEnumSize<RequestCache>::value,
159 "RequestCache enumeration value should match Necko Cache mode value.");
161 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) ==
162 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
163 "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
164 " should match ServiceWorkerUpdateViaCache enumeration.");
165 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::All) ==
166 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
167 "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
168 " should match ServiceWorkerUpdateViaCache enumeration.");
169 static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
170 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE,
171 "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
172 " should match ServiceWorkerUpdateViaCache enumeration.");
174 static StaticRefPtr<ServiceWorkerManager> gInstance;
176 namespace {
178 nsresult PopulateRegistrationData(
179 nsIPrincipal* aPrincipal,
180 const ServiceWorkerRegistrationInfo* aRegistration,
181 ServiceWorkerRegistrationData& aData) {
182 MOZ_ASSERT(aPrincipal);
183 MOZ_ASSERT(aRegistration);
185 if (NS_WARN_IF(!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal())) {
186 return NS_ERROR_FAILURE;
189 nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
190 if (NS_WARN_IF(NS_FAILED(rv))) {
191 return rv;
194 aData.scope() = aRegistration->Scope();
196 // TODO: When bug 1426401 is implemented we will need to handle more
197 // than just the active worker here.
198 RefPtr<ServiceWorkerInfo> active = aRegistration->GetActive();
199 MOZ_ASSERT(active);
200 if (NS_WARN_IF(!active)) {
201 return NS_ERROR_FAILURE;
204 aData.currentWorkerURL() = active->ScriptSpec();
205 aData.cacheName() = active->CacheName();
206 aData.currentWorkerHandlesFetch() = active->HandlesFetch();
208 aData.currentWorkerInstalledTime() = active->GetInstalledTime();
209 aData.currentWorkerActivatedTime() = active->GetActivatedTime();
211 aData.updateViaCache() =
212 static_cast<uint32_t>(aRegistration->GetUpdateViaCache());
214 aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();
216 aData.navigationPreloadState() = aRegistration->GetNavigationPreloadState();
218 MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));
220 return NS_OK;
223 class TeardownRunnable final : public Runnable {
224 public:
225 explicit TeardownRunnable(ServiceWorkerManagerChild* aActor)
226 : Runnable("dom::ServiceWorkerManager::TeardownRunnable"),
227 mActor(aActor) {
228 MOZ_ASSERT(mActor);
231 NS_IMETHOD Run() override {
232 MOZ_ASSERT(mActor);
233 PServiceWorkerManagerChild::Send__delete__(mActor);
234 return NS_OK;
237 private:
238 ~TeardownRunnable() = default;
240 RefPtr<ServiceWorkerManagerChild> mActor;
243 constexpr char kFinishShutdownTopic[] = "profile-before-change-qm";
245 already_AddRefed<nsIAsyncShutdownClient> GetAsyncShutdownBarrier() {
246 AssertIsOnMainThread();
248 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
249 MOZ_ASSERT(svc);
251 nsCOMPtr<nsIAsyncShutdownClient> barrier;
252 DebugOnly<nsresult> rv =
253 svc->GetProfileChangeTeardown(getter_AddRefs(barrier));
254 MOZ_ASSERT(NS_SUCCEEDED(rv));
256 return barrier.forget();
259 Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
260 nsIURI* aScopeURI, const OriginAttributes& aOriginAttributes) {
261 MOZ_ASSERT(aScopeURI);
263 nsCOMPtr<nsIPrincipal> principal =
264 BasePrincipal::CreateContentPrincipal(aScopeURI, aOriginAttributes);
265 if (NS_WARN_IF(!principal)) {
266 return Err(NS_ERROR_FAILURE);
269 return principal;
272 Result<nsCOMPtr<nsIPrincipal>, nsresult> ScopeToPrincipal(
273 const nsACString& aScope, const OriginAttributes& aOriginAttributes) {
274 MOZ_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));
276 nsCOMPtr<nsIURI> scopeURI;
277 MOZ_TRY(NS_NewURI(getter_AddRefs(scopeURI), aScope));
279 return ScopeToPrincipal(scopeURI, aOriginAttributes);
282 } // namespace
284 struct ServiceWorkerManager::RegistrationDataPerPrincipal final {
285 // Implements a container of keys for the "scope to registration map":
286 // https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
288 // where each key is an absolute URL.
290 // The properties of this map that the spec uses are
291 // 1) insertion,
292 // 2) removal,
293 // 3) iteration of scopes in FIFO order (excluding removed scopes),
294 // 4) and finding, for a given path, the maximal length scope which is a
295 // prefix of the path.
297 // Additionally, because this is a container of keys for a map, there
298 // shouldn't be duplicate scopes.
300 // The current implementation uses a dynamic array as the underlying
301 // container, which is not optimal for unbounded container sizes (all
302 // supported operations are in linear time) but may be superior for small
303 // container sizes.
305 // If this is proven to be too slow, the underlying storage should be replaced
306 // with a linked list of scopes in combination with an ordered map that maps
307 // scopes to linked list elements/iterators. This would reduce all of the
308 // above operations besides iteration (necessarily linear) to logarithmic
309 // time.
310 class ScopeContainer final : private nsTArray<nsCString> {
311 using Base = nsTArray<nsCString>;
313 public:
314 using Base::Contains;
315 using Base::IsEmpty;
316 using Base::Length;
318 // No using-declaration to avoid importing the non-const overload.
319 decltype(auto) operator[](Base::index_type aIndex) const {
320 return Base::operator[](aIndex);
323 void InsertScope(const nsACString& aScope) {
324 MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsAbsoluteURL(aScope));
326 if (Contains(aScope)) {
327 return;
330 AppendElement(aScope);
333 void RemoveScope(const nsACString& aScope) {
334 MOZ_ALWAYS_TRUE(RemoveElement(aScope));
337 // Implements most of "Match Service Worker Registration":
338 // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
339 Maybe<nsCString> MatchScope(const nsACString& aClientUrl) const {
340 Maybe<nsCString> match;
342 for (const nsCString& scope : *this) {
343 if (StringBeginsWith(aClientUrl, scope)) {
344 if (!match || scope.Length() > match->Length()) {
345 match = Some(scope);
350 // Step 7.2:
351 // "Assert: matchingScope’s origin and clientURL’s origin are same
352 // origin."
353 MOZ_DIAGNOSTIC_ASSERT_IF(match, IsSameOrigin(*match, aClientUrl));
355 return match;
358 private:
359 bool IsSameOrigin(const nsACString& aMatchingScope,
360 const nsACString& aClientUrl) const {
361 auto parseResult = ScopeToPrincipal(aMatchingScope, OriginAttributes());
363 if (NS_WARN_IF(parseResult.isErr())) {
364 return false;
367 auto scopePrincipal = parseResult.unwrap();
369 parseResult = ScopeToPrincipal(aClientUrl, OriginAttributes());
371 if (NS_WARN_IF(parseResult.isErr())) {
372 return false;
375 auto clientPrincipal = parseResult.unwrap();
377 bool equals = false;
379 if (NS_WARN_IF(
380 NS_FAILED(scopePrincipal->Equals(clientPrincipal, &equals)))) {
381 return false;
384 return equals;
388 ScopeContainer mScopeContainer;
390 // Scope to registration.
391 // The scope should be a fully qualified valid URL.
392 nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
394 // Maps scopes to job queues.
395 nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
397 // Map scopes to scheduled update timers.
398 nsInterfaceHashtable<nsCStringHashKey, nsITimer> mUpdateTimers;
400 // The number of times we have done a quota usage check for this origin for
401 // mitigation purposes. See the docs on nsIServiceWorkerRegistrationInfo,
402 // where this value is exposed.
403 int32_t mQuotaUsageCheckCount = 0;
406 //////////////////////////
407 // ServiceWorkerManager //
408 //////////////////////////
410 NS_IMPL_ADDREF(ServiceWorkerManager)
411 NS_IMPL_RELEASE(ServiceWorkerManager)
413 NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
414 NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
415 NS_INTERFACE_MAP_ENTRY(nsIObserver)
416 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager)
417 NS_INTERFACE_MAP_END
419 ServiceWorkerManager::ServiceWorkerManager()
420 : mActor(nullptr), mShuttingDown(false) {}
422 ServiceWorkerManager::~ServiceWorkerManager() {
423 // The map will assert if it is not empty when destroyed.
424 mRegistrationInfos.Clear();
426 // This can happen if the browser is started up in ProfileManager mode, in
427 // which case XPCOM will startup and shutdown, but there won't be any
428 // profile-* topic notifications. The shutdown blocker expects to be in a
429 // NotAcceptingPromises state when it's destroyed, and this transition
430 // normally happens in the "profile-change-teardown" notification callback
431 // (which won't be called in ProfileManager mode).
432 if (!mShuttingDown && mShutdownBlocker) {
433 mShutdownBlocker->StopAcceptingPromises();
437 void ServiceWorkerManager::BlockShutdownOn(GenericNonExclusivePromise* aPromise,
438 uint32_t aShutdownStateId) {
439 AssertIsOnMainThread();
441 MOZ_ASSERT(mShutdownBlocker);
442 MOZ_ASSERT(aPromise);
444 mShutdownBlocker->WaitOnPromise(aPromise, aShutdownStateId);
447 void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) {
448 // ServiceWorkers now only support parent intercept. In parent intercept
449 // mode, only the parent process ServiceWorkerManager has any state or does
450 // anything.
452 // It is our goal to completely eliminate support for content process
453 // ServiceWorkerManager instances and make getting a SWM instance trigger a
454 // fatal assertion. But until we've reached that point, we make
455 // initialization a no-op so that content process ServiceWorkerManager
456 // instances will simply have no state and no registrations.
457 if (!XRE_IsParentProcess()) {
458 return;
461 nsCOMPtr<nsIAsyncShutdownClient> shutdownBarrier = GetAsyncShutdownBarrier();
463 if (shutdownBarrier) {
464 mShutdownBlocker = ServiceWorkerShutdownBlocker::CreateAndRegisterOn(
465 *shutdownBarrier, *this);
466 MOZ_ASSERT(mShutdownBlocker);
469 MOZ_DIAGNOSTIC_ASSERT(aRegistrar);
471 PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
472 if (NS_WARN_IF(!actorChild)) {
473 MaybeStartShutdown();
474 return;
477 PServiceWorkerManagerChild* actor =
478 actorChild->SendPServiceWorkerManagerConstructor();
479 if (!actor) {
480 MaybeStartShutdown();
481 return;
484 mActor = static_cast<ServiceWorkerManagerChild*>(actor);
486 // mActor must be set before LoadRegistrations is called because it can purge
487 // service workers if preferences are disabled.
488 nsTArray<ServiceWorkerRegistrationData> data;
489 aRegistrar->GetRegistrations(data);
490 LoadRegistrations(data);
492 mTelemetryLastChange = TimeStamp::Now();
495 void ServiceWorkerManager::RecordTelemetry(uint32_t aNumber, uint32_t aFetch) {
496 // Submit N value pairs to Telemetry for the time we were at those values
497 auto now = TimeStamp::Now();
498 // round down, with a minimum of 1 repeat. In theory this gives
499 // inaccuracy if there are frequent changes, but that's uncommon.
500 uint32_t repeats = (uint32_t)((now - mTelemetryLastChange).ToMilliseconds()) /
501 mTelemetryPeriodMs;
502 mTelemetryLastChange = now;
503 if (repeats == 0) {
504 repeats = 1;
506 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
507 "ServiceWorkerTelemetryRunnable", [aNumber, aFetch, repeats]() {
508 LOG(("ServiceWorkers running: %u samples of %u/%u", repeats, aNumber,
509 aFetch));
510 // Don't allocate infinitely huge arrays if someone visits a SW site
511 // after a few months running. 1 month is about 500K repeats @ 5s
512 // sampling
513 uint32_t num_repeats = std::min(repeats, 1000000U); // 4MB max
514 nsTArray<uint32_t> values;
516 uint32_t* array = values.AppendElements(num_repeats);
517 for (uint32_t i = 0; i < num_repeats; i++) {
518 array[i] = aNumber;
520 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "All"_ns,
521 values);
523 for (uint32_t i = 0; i < num_repeats; i++) {
524 array[i] = aFetch;
526 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "Fetch"_ns,
527 values);
529 NS_DispatchBackgroundTask(runnable.forget(), nsIEventTarget::DISPATCH_NORMAL);
532 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::StartControllingClient(
533 const ClientInfo& aClientInfo,
534 ServiceWorkerRegistrationInfo* aRegistrationInfo,
535 bool aControlClientHandle) {
536 MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());
538 // XXX We can't use a generic lambda (accepting auto&& entry) like elsewhere
539 // with WithEntryHandle, since we get linker errors then using clang+lld. This
540 // might be a toolchain issue?
541 return mControlledClients.WithEntryHandle(
542 aClientInfo.Id(),
543 [&](decltype(mControlledClients)::EntryHandle&& entry)
544 -> RefPtr<GenericErrorResultPromise> {
545 const RefPtr<ServiceWorkerManager> self = this;
547 const ServiceWorkerDescriptor& active =
548 aRegistrationInfo->GetActive()->Descriptor();
550 if (entry) {
551 const RefPtr<ServiceWorkerRegistrationInfo> old =
552 std::move(entry.Data()->mRegistrationInfo);
554 const RefPtr<GenericErrorResultPromise> promise =
555 aControlClientHandle
556 ? entry.Data()->mClientHandle->Control(active)
557 : GenericErrorResultPromise::CreateAndResolve(false,
558 __func__);
560 entry.Data()->mRegistrationInfo = aRegistrationInfo;
562 if (old != aRegistrationInfo) {
563 StopControllingRegistration(old);
564 aRegistrationInfo->StartControllingClient();
567 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS,
570 // Always check to see if we failed to actually control the client. In
571 // that case remove the client from our list of controlled clients.
572 return promise->Then(
573 GetMainThreadSerialEventTarget(), __func__,
574 [](bool) {
575 // do nothing on success
576 return GenericErrorResultPromise::CreateAndResolve(true,
577 __func__);
579 [self, aClientInfo](const CopyableErrorResult& aRv) {
580 // failed to control, forget about this client
581 self->StopControllingClient(aClientInfo);
582 return GenericErrorResultPromise::CreateAndReject(aRv,
583 __func__);
587 RefPtr<ClientHandle> clientHandle = ClientManager::CreateHandle(
588 aClientInfo, GetMainThreadSerialEventTarget());
590 const RefPtr<GenericErrorResultPromise> promise =
591 aControlClientHandle
592 ? clientHandle->Control(active)
593 : GenericErrorResultPromise::CreateAndResolve(false, __func__);
595 aRegistrationInfo->StartControllingClient();
597 entry.Insert(
598 MakeUnique<ControlledClientData>(clientHandle, aRegistrationInfo));
600 clientHandle->OnDetach()->Then(
601 GetMainThreadSerialEventTarget(), __func__,
602 [self, aClientInfo] { self->StopControllingClient(aClientInfo); });
604 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS,
607 // Always check to see if we failed to actually control the client. In
608 // that case removed the client from our list of controlled clients.
609 return promise->Then(
610 GetMainThreadSerialEventTarget(), __func__,
611 [](bool) {
612 // do nothing on success
613 return GenericErrorResultPromise::CreateAndResolve(true,
614 __func__);
616 [self, aClientInfo](const CopyableErrorResult& aRv) {
617 // failed to control, forget about this client
618 self->StopControllingClient(aClientInfo);
619 return GenericErrorResultPromise::CreateAndReject(aRv, __func__);
624 void ServiceWorkerManager::StopControllingClient(
625 const ClientInfo& aClientInfo) {
626 auto entry = mControlledClients.Lookup(aClientInfo.Id());
627 if (!entry) {
628 return;
631 RefPtr<ServiceWorkerRegistrationInfo> reg =
632 std::move(entry.Data()->mRegistrationInfo);
634 entry.Remove();
636 StopControllingRegistration(reg);
639 void ServiceWorkerManager::MaybeStartShutdown() {
640 MOZ_ASSERT(NS_IsMainThread());
642 if (mShuttingDown) {
643 return;
646 mShuttingDown = true;
648 for (const auto& dataPtr : mRegistrationInfos.Values()) {
649 for (const auto& timerEntry : dataPtr->mUpdateTimers.Values()) {
650 timerEntry->Cancel();
652 dataPtr->mUpdateTimers.Clear();
654 for (const auto& queueEntry : dataPtr->mJobQueues.Values()) {
655 queueEntry->CancelAll();
657 dataPtr->mJobQueues.Clear();
659 for (const auto& registrationEntry : dataPtr->mInfos.Values()) {
660 registrationEntry->ShutdownWorkers();
663 // ServiceWorkerCleanup may try to unregister registrations, so don't clear
664 // mInfos.
667 for (const auto& entry : mControlledClients.Values()) {
668 entry->mRegistrationInfo->ShutdownWorkers();
671 for (auto iter = mOrphanedRegistrations.iter(); !iter.done(); iter.next()) {
672 iter.get()->ShutdownWorkers();
675 if (mShutdownBlocker) {
676 mShutdownBlocker->StopAcceptingPromises();
679 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
680 if (obs) {
681 obs->AddObserver(this, kFinishShutdownTopic, false);
682 return;
685 MaybeFinishShutdown();
688 void ServiceWorkerManager::MaybeFinishShutdown() {
689 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
690 if (obs) {
691 obs->RemoveObserver(this, kFinishShutdownTopic);
694 if (!mActor) {
695 return;
698 mActor->ManagerShuttingDown();
700 RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
701 nsresult rv = NS_DispatchToMainThread(runnable);
702 Unused << NS_WARN_IF(NS_FAILED(rv));
703 mActor = nullptr;
705 // This also submits final telemetry
706 ServiceWorkerPrivate::RunningShutdown();
709 class ServiceWorkerResolveWindowPromiseOnRegisterCallback final
710 : public ServiceWorkerJob::Callback {
711 public:
712 NS_INLINE_DECL_REFCOUNTING(
713 ServiceWorkerResolveWindowPromiseOnRegisterCallback, override)
715 virtual void JobFinished(ServiceWorkerJob* aJob,
716 ErrorResult& aStatus) override {
717 MOZ_ASSERT(NS_IsMainThread());
718 MOZ_ASSERT(aJob);
720 if (aStatus.Failed()) {
721 mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
722 return;
725 MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Register);
726 RefPtr<ServiceWorkerRegisterJob> registerJob =
727 static_cast<ServiceWorkerRegisterJob*>(aJob);
728 RefPtr<ServiceWorkerRegistrationInfo> reg = registerJob->GetRegistration();
730 mPromiseHolder.Resolve(reg->Descriptor(), __func__);
733 virtual void JobDiscarded(ErrorResult& aStatus) override {
734 MOZ_ASSERT(NS_IsMainThread());
736 mPromiseHolder.Reject(CopyableErrorResult(aStatus), __func__);
739 RefPtr<ServiceWorkerRegistrationPromise> Promise() {
740 MOZ_ASSERT(NS_IsMainThread());
741 return mPromiseHolder.Ensure(__func__);
744 private:
745 ~ServiceWorkerResolveWindowPromiseOnRegisterCallback() = default;
747 MozPromiseHolder<ServiceWorkerRegistrationPromise> mPromiseHolder;
750 NS_IMETHODIMP
751 ServiceWorkerManager::RegisterForTest(nsIPrincipal* aPrincipal,
752 const nsAString& aScopeURL,
753 const nsAString& aScriptURL,
754 JSContext* aCx,
755 mozilla::dom::Promise** aPromise) {
756 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
757 if (NS_WARN_IF(!global)) {
758 return NS_ERROR_FAILURE;
761 ErrorResult erv;
762 RefPtr<Promise> outer = Promise::Create(global, erv);
763 if (NS_WARN_IF(erv.Failed())) {
764 return erv.StealNSResult();
767 if (!StaticPrefs::dom_serviceWorkers_testing_enabled()) {
768 outer->MaybeRejectWithAbortError(
769 "registerForTest only allowed when dom.serviceWorkers.testing.enabled "
770 "is true");
771 outer.forget(aPromise);
772 return NS_OK;
775 if (aPrincipal == nullptr) {
776 outer->MaybeRejectWithAbortError("Missing principal");
777 outer.forget(aPromise);
778 return NS_OK;
781 if (aScriptURL.IsEmpty()) {
782 outer->MaybeRejectWithAbortError("Missing script url");
783 outer.forget(aPromise);
784 return NS_OK;
787 if (aScopeURL.IsEmpty()) {
788 outer->MaybeRejectWithAbortError("Missing scope url");
789 outer.forget(aPromise);
790 return NS_OK;
793 // The ClientType isn't really used here, but ClientType::Window
794 // is the least bad choice since this is happening on the main thread.
795 Maybe<ClientInfo> clientInfo =
796 dom::ClientManager::CreateInfo(ClientType::Window, aPrincipal);
798 if (!clientInfo.isSome()) {
799 outer->MaybeRejectWithUnknownError("Error creating clientInfo");
800 outer.forget(aPromise);
801 return NS_OK;
804 auto scope = NS_ConvertUTF16toUTF8(aScopeURL);
805 auto scriptURL = NS_ConvertUTF16toUTF8(aScriptURL);
807 auto regPromise = Register(clientInfo.ref(), scope, scriptURL,
808 dom::ServiceWorkerUpdateViaCache::Imports);
809 const RefPtr<ServiceWorkerManager> self(this);
810 const nsCOMPtr<nsIPrincipal> principal(aPrincipal);
811 regPromise->Then(
812 GetMainThreadSerialEventTarget(), __func__,
813 [self, outer, principal,
814 scope](const ServiceWorkerRegistrationDescriptor& regDesc) {
815 RefPtr<ServiceWorkerRegistrationInfo> registration =
816 self->GetRegistration(principal, NS_ConvertUTF16toUTF8(scope));
817 if (registration) {
818 outer->MaybeResolve(registration);
819 } else {
820 outer->MaybeRejectWithUnknownError(
821 "Failed to retrieve ServiceWorkerRegistrationInfo");
824 [outer](const mozilla::CopyableErrorResult& err) {
825 CopyableErrorResult result(err);
826 outer->MaybeReject(std::move(result));
829 outer.forget(aPromise);
831 return NS_OK;
834 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::Register(
835 const ClientInfo& aClientInfo, const nsACString& aScopeURL,
836 const nsACString& aScriptURL, ServiceWorkerUpdateViaCache aUpdateViaCache) {
837 nsCOMPtr<nsIURI> scopeURI;
838 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScopeURL);
839 if (NS_FAILED(rv)) {
840 // Odd, since it was serialiazed from an nsIURI.
841 CopyableErrorResult err;
842 err.ThrowInvalidStateError("Scope URL cannot be parsed");
843 return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
846 nsCOMPtr<nsIURI> scriptURI;
847 rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL);
848 if (NS_FAILED(rv)) {
849 // Odd, since it was serialiazed from an nsIURI.
850 CopyableErrorResult err;
851 err.ThrowInvalidStateError("Script URL cannot be parsed");
852 return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
855 IgnoredErrorResult err;
856 ServiceWorkerScopeAndScriptAreValid(aClientInfo, scopeURI, scriptURI, err);
857 if (err.Failed()) {
858 return ServiceWorkerRegistrationPromise::CreateAndReject(
859 CopyableErrorResult(std::move(err)), __func__);
862 // If the previous validation step passed then we must have a principal.
863 auto principalOrErr = aClientInfo.GetPrincipal();
865 if (NS_WARN_IF(principalOrErr.isErr())) {
866 return ServiceWorkerRegistrationPromise::CreateAndReject(
867 CopyableErrorResult(principalOrErr.unwrapErr()), __func__);
870 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
871 nsAutoCString scopeKey;
872 rv = PrincipalToScopeKey(principal, scopeKey);
873 if (NS_WARN_IF(NS_FAILED(rv))) {
874 return ServiceWorkerRegistrationPromise::CreateAndReject(
875 CopyableErrorResult(rv), __func__);
878 RefPtr<ServiceWorkerJobQueue> queue =
879 GetOrCreateJobQueue(scopeKey, aScopeURL);
881 RefPtr<ServiceWorkerResolveWindowPromiseOnRegisterCallback> cb =
882 new ServiceWorkerResolveWindowPromiseOnRegisterCallback();
884 RefPtr<ServiceWorkerRegisterJob> job = new ServiceWorkerRegisterJob(
885 principal, aScopeURL, aScriptURL,
886 static_cast<ServiceWorkerUpdateViaCache>(aUpdateViaCache));
888 job->AppendResultCallback(cb);
889 queue->ScheduleJob(job);
891 MOZ_ASSERT(NS_IsMainThread());
893 return cb->Promise();
897 * Implements the async aspects of the getRegistrations algorithm.
899 class GetRegistrationsRunnable final : public Runnable {
900 const ClientInfo mClientInfo;
901 RefPtr<ServiceWorkerRegistrationListPromise::Private> mPromise;
903 public:
904 explicit GetRegistrationsRunnable(const ClientInfo& aClientInfo)
905 : Runnable("dom::ServiceWorkerManager::GetRegistrationsRunnable"),
906 mClientInfo(aClientInfo),
907 mPromise(new ServiceWorkerRegistrationListPromise::Private(__func__)) {}
909 RefPtr<ServiceWorkerRegistrationListPromise> Promise() const {
910 return mPromise;
913 NS_IMETHOD
914 Run() override {
915 auto scopeExit = MakeScopeExit(
916 [&] { mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); });
918 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
919 if (!swm) {
920 return NS_OK;
923 auto principalOrErr = mClientInfo.GetPrincipal();
924 if (NS_WARN_IF(principalOrErr.isErr())) {
925 return NS_OK;
928 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
930 nsTArray<ServiceWorkerRegistrationDescriptor> array;
932 if (NS_WARN_IF(!BasePrincipal::Cast(principal)->IsContentPrincipal())) {
933 return NS_OK;
936 nsAutoCString scopeKey;
937 nsresult rv = swm->PrincipalToScopeKey(principal, scopeKey);
938 if (NS_WARN_IF(NS_FAILED(rv))) {
939 return rv;
942 ServiceWorkerManager::RegistrationDataPerPrincipal* data;
943 if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
944 scopeExit.release();
945 mPromise->Resolve(array, __func__);
946 return NS_OK;
949 for (uint32_t i = 0; i < data->mScopeContainer.Length(); ++i) {
950 RefPtr<ServiceWorkerRegistrationInfo> info =
951 data->mInfos.GetWeak(data->mScopeContainer[i]);
953 NS_ConvertUTF8toUTF16 scope(data->mScopeContainer[i]);
955 nsCOMPtr<nsIURI> scopeURI;
956 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
957 if (NS_WARN_IF(NS_FAILED(rv))) {
958 break;
961 // Unfortunately we don't seem to have an obvious window id here; in
962 // particular ClientInfo does not have one, and neither do service worker
963 // registrations, as far as I can tell.
964 rv = principal->CheckMayLoadWithReporting(
965 scopeURI, false /* allowIfInheritsPrincipal */,
966 0 /* innerWindowID */);
967 if (NS_WARN_IF(NS_FAILED(rv))) {
968 continue;
971 array.AppendElement(info->Descriptor());
974 scopeExit.release();
975 mPromise->Resolve(array, __func__);
977 return NS_OK;
981 RefPtr<ServiceWorkerRegistrationListPromise>
982 ServiceWorkerManager::GetRegistrations(const ClientInfo& aClientInfo) const {
983 RefPtr<GetRegistrationsRunnable> runnable =
984 new GetRegistrationsRunnable(aClientInfo);
985 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
986 return runnable->Promise();
990 * Implements the async aspects of the getRegistration algorithm.
992 class GetRegistrationRunnable final : public Runnable {
993 const ClientInfo mClientInfo;
994 RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
995 nsCString mURL;
997 public:
998 GetRegistrationRunnable(const ClientInfo& aClientInfo, const nsACString& aURL)
999 : Runnable("dom::ServiceWorkerManager::GetRegistrationRunnable"),
1000 mClientInfo(aClientInfo),
1001 mPromise(new ServiceWorkerRegistrationPromise::Private(__func__)),
1002 mURL(aURL) {}
1004 RefPtr<ServiceWorkerRegistrationPromise> Promise() const { return mPromise; }
1006 NS_IMETHOD
1007 Run() override {
1008 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1009 if (!swm) {
1010 mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
1011 return NS_OK;
1014 auto principalOrErr = mClientInfo.GetPrincipal();
1015 if (NS_WARN_IF(principalOrErr.isErr())) {
1016 mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
1017 return NS_OK;
1020 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1021 nsCOMPtr<nsIURI> uri;
1022 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
1023 if (NS_WARN_IF(NS_FAILED(rv))) {
1024 mPromise->Reject(rv, __func__);
1025 return NS_OK;
1028 // Unfortunately we don't seem to have an obvious window id here; in
1029 // particular ClientInfo does not have one, and neither do service worker
1030 // registrations, as far as I can tell.
1031 rv = principal->CheckMayLoadWithReporting(
1032 uri, false /* allowIfInheritsPrincipal */, 0 /* innerWindowID */);
1033 if (NS_FAILED(rv)) {
1034 mPromise->Reject(NS_ERROR_DOM_SECURITY_ERR, __func__);
1035 return NS_OK;
1038 RefPtr<ServiceWorkerRegistrationInfo> registration =
1039 swm->GetServiceWorkerRegistrationInfo(principal, uri);
1041 if (!registration) {
1042 // Reject with NS_OK means "not found".
1043 mPromise->Reject(NS_OK, __func__);
1044 return NS_OK;
1047 mPromise->Resolve(registration->Descriptor(), __func__);
1049 return NS_OK;
1053 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::GetRegistration(
1054 const ClientInfo& aClientInfo, const nsACString& aURL) const {
1055 MOZ_ASSERT(NS_IsMainThread());
1057 RefPtr<GetRegistrationRunnable> runnable =
1058 new GetRegistrationRunnable(aClientInfo, aURL);
1059 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
1061 return runnable->Promise();
1064 NS_IMETHODIMP
1065 ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
1066 const nsACString& aScope,
1067 const nsTArray<uint8_t>& aDataBytes,
1068 uint8_t optional_argc) {
1069 if (optional_argc == 1) {
1070 // This does one copy here (while constructing the Maybe) and another when
1071 // we end up copying into the SendPushEventRunnable. We could fix that to
1072 // only do one copy by making things between here and there take
1073 // Maybe<nsTArray<uint8_t>>&&, but then we'd need to copy before we know
1074 // whether we really need to in PushMessageDispatcher::NotifyWorkers. Since
1075 // in practice this only affects JS callers that pass data, and we don't
1076 // have any right now, let's not worry about it.
1077 return SendPushEvent(aOriginAttributes, aScope, u""_ns,
1078 Some(aDataBytes.Clone()));
1080 MOZ_ASSERT(optional_argc == 0);
1081 return SendPushEvent(aOriginAttributes, aScope, u""_ns, Nothing());
1084 nsresult ServiceWorkerManager::SendPushEvent(
1085 const nsACString& aOriginAttributes, const nsACString& aScope,
1086 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) {
1087 OriginAttributes attrs;
1088 if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
1089 return NS_ERROR_INVALID_ARG;
1092 nsCOMPtr<nsIPrincipal> principal;
1093 MOZ_TRY_VAR(principal, ScopeToPrincipal(aScope, attrs));
1095 // The registration handling a push notification must have an exact scope
1096 // match. This will try to find an exact match, unlike how fetch may find the
1097 // registration with the longest scope that's a prefix of the fetched URL.
1098 RefPtr<ServiceWorkerRegistrationInfo> registration =
1099 GetRegistration(principal, aScope);
1100 if (NS_WARN_IF(!registration)) {
1101 return NS_ERROR_FAILURE;
1104 MOZ_DIAGNOSTIC_ASSERT(registration->Scope().Equals(aScope));
1106 ServiceWorkerInfo* serviceWorker = registration->GetActive();
1107 if (NS_WARN_IF(!serviceWorker)) {
1108 return NS_ERROR_FAILURE;
1111 return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
1112 registration);
1115 NS_IMETHODIMP
1116 ServiceWorkerManager::SendPushSubscriptionChangeEvent(
1117 const nsACString& aOriginAttributes, const nsACString& aScope) {
1118 OriginAttributes attrs;
1119 if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
1120 return NS_ERROR_INVALID_ARG;
1123 ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
1124 if (!info) {
1125 return NS_ERROR_FAILURE;
1127 return info->WorkerPrivate()->SendPushSubscriptionChangeEvent();
1130 nsresult ServiceWorkerManager::SendNotificationEvent(
1131 const nsAString& aEventName, const nsACString& aOriginSuffix,
1132 const nsACString& aScope, const nsAString& aID, const nsAString& aTitle,
1133 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
1134 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
1135 const nsAString& aBehavior) {
1136 OriginAttributes attrs;
1137 if (!attrs.PopulateFromSuffix(aOriginSuffix)) {
1138 return NS_ERROR_INVALID_ARG;
1141 ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(attrs, aScope);
1142 if (!info) {
1143 return NS_ERROR_FAILURE;
1146 ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
1147 return workerPrivate->SendNotificationEvent(
1148 aEventName, aID, aTitle, aDir, aLang, aBody, aTag, aIcon, aData,
1149 aBehavior, NS_ConvertUTF8toUTF16(aScope));
1152 NS_IMETHODIMP
1153 ServiceWorkerManager::SendNotificationClickEvent(
1154 const nsACString& aOriginSuffix, const nsACString& aScope,
1155 const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
1156 const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
1157 const nsAString& aIcon, const nsAString& aData,
1158 const nsAString& aBehavior) {
1159 return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLICK_EVENT_NAME),
1160 aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
1161 aBody, aTag, aIcon, aData, aBehavior);
1164 NS_IMETHODIMP
1165 ServiceWorkerManager::SendNotificationCloseEvent(
1166 const nsACString& aOriginSuffix, const nsACString& aScope,
1167 const nsAString& aID, const nsAString& aTitle, const nsAString& aDir,
1168 const nsAString& aLang, const nsAString& aBody, const nsAString& aTag,
1169 const nsAString& aIcon, const nsAString& aData,
1170 const nsAString& aBehavior) {
1171 return SendNotificationEvent(nsLiteralString(NOTIFICATION_CLOSE_EVENT_NAME),
1172 aOriginSuffix, aScope, aID, aTitle, aDir, aLang,
1173 aBody, aTag, aIcon, aData, aBehavior);
1176 RefPtr<ServiceWorkerRegistrationPromise> ServiceWorkerManager::WhenReady(
1177 const ClientInfo& aClientInfo) {
1178 AssertIsOnMainThread();
1180 for (auto& prd : mPendingReadyList) {
1181 if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
1182 prd->mClientHandle->Info().PrincipalInfo() ==
1183 aClientInfo.PrincipalInfo()) {
1184 return prd->mPromise;
1188 RefPtr<ServiceWorkerRegistrationInfo> reg =
1189 GetServiceWorkerRegistrationInfo(aClientInfo);
1190 if (reg && reg->GetActive()) {
1191 return ServiceWorkerRegistrationPromise::CreateAndResolve(reg->Descriptor(),
1192 __func__);
1195 nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
1197 RefPtr<ClientHandle> handle =
1198 ClientManager::CreateHandle(aClientInfo, target);
1199 mPendingReadyList.AppendElement(MakeUnique<PendingReadyData>(handle));
1201 RefPtr<ServiceWorkerManager> self(this);
1202 handle->OnDetach()->Then(target, __func__,
1203 [self = std::move(self), aClientInfo] {
1204 self->RemovePendingReadyPromise(aClientInfo);
1207 return mPendingReadyList.LastElement()->mPromise;
1210 void ServiceWorkerManager::CheckPendingReadyPromises() {
1211 nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
1212 std::move(mPendingReadyList);
1213 for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
1214 UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));
1216 RefPtr<ServiceWorkerRegistrationInfo> reg =
1217 GetServiceWorkerRegistrationInfo(prd->mClientHandle->Info());
1219 if (reg && reg->GetActive()) {
1220 prd->mPromise->Resolve(reg->Descriptor(), __func__);
1221 } else {
1222 mPendingReadyList.AppendElement(std::move(prd));
1227 void ServiceWorkerManager::RemovePendingReadyPromise(
1228 const ClientInfo& aClientInfo) {
1229 nsTArray<UniquePtr<PendingReadyData>> pendingReadyList =
1230 std::move(mPendingReadyList);
1231 for (uint32_t i = 0; i < pendingReadyList.Length(); ++i) {
1232 UniquePtr<PendingReadyData> prd(std::move(pendingReadyList[i]));
1234 if (prd->mClientHandle->Info().Id() == aClientInfo.Id() &&
1235 prd->mClientHandle->Info().PrincipalInfo() ==
1236 aClientInfo.PrincipalInfo()) {
1237 prd->mPromise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
1238 } else {
1239 mPendingReadyList.AppendElement(std::move(prd));
1244 void ServiceWorkerManager::NoteInheritedController(
1245 const ClientInfo& aClientInfo, const ServiceWorkerDescriptor& aController) {
1246 MOZ_ASSERT(NS_IsMainThread());
1248 auto principalOrErr = PrincipalInfoToPrincipal(aController.PrincipalInfo());
1250 if (NS_WARN_IF(principalOrErr.isErr())) {
1251 return;
1254 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1255 nsCOMPtr<nsIURI> scope;
1256 nsresult rv = NS_NewURI(getter_AddRefs(scope), aController.Scope());
1257 NS_ENSURE_SUCCESS_VOID(rv);
1259 RefPtr<ServiceWorkerRegistrationInfo> registration =
1260 GetServiceWorkerRegistrationInfo(principal, scope);
1261 NS_ENSURE_TRUE_VOID(registration);
1262 NS_ENSURE_TRUE_VOID(registration->GetActive());
1264 StartControllingClient(aClientInfo, registration,
1265 false /* aControlClientHandle */);
1268 ServiceWorkerInfo* ServiceWorkerManager::GetActiveWorkerInfoForScope(
1269 const OriginAttributes& aOriginAttributes, const nsACString& aScope) {
1270 MOZ_ASSERT(NS_IsMainThread());
1272 nsCOMPtr<nsIURI> scopeURI;
1273 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
1274 if (NS_FAILED(rv)) {
1275 return nullptr;
1278 auto result = ScopeToPrincipal(scopeURI, aOriginAttributes);
1279 if (NS_WARN_IF(result.isErr())) {
1280 return nullptr;
1283 auto principal = result.unwrap();
1285 RefPtr<ServiceWorkerRegistrationInfo> registration =
1286 GetServiceWorkerRegistrationInfo(principal, scopeURI);
1287 if (!registration) {
1288 return nullptr;
1291 return registration->GetActive();
1294 namespace {
1296 class UnregisterJobCallback final : public ServiceWorkerJob::Callback {
1297 nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
1299 ~UnregisterJobCallback() { MOZ_ASSERT(!mCallback); }
1301 public:
1302 explicit UnregisterJobCallback(nsIServiceWorkerUnregisterCallback* aCallback)
1303 : mCallback(aCallback) {
1304 MOZ_ASSERT(NS_IsMainThread());
1305 MOZ_ASSERT(mCallback);
1308 void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override {
1309 MOZ_ASSERT(NS_IsMainThread());
1310 MOZ_ASSERT(aJob);
1311 MOZ_ASSERT(mCallback);
1313 auto scopeExit = MakeScopeExit([&]() { mCallback = nullptr; });
1315 if (aStatus.Failed()) {
1316 mCallback->UnregisterFailed();
1317 return;
1320 MOZ_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Unregister);
1321 RefPtr<ServiceWorkerUnregisterJob> unregisterJob =
1322 static_cast<ServiceWorkerUnregisterJob*>(aJob);
1323 mCallback->UnregisterSucceeded(unregisterJob->GetResult());
1326 void JobDiscarded(ErrorResult&) override {
1327 MOZ_ASSERT(NS_IsMainThread());
1328 MOZ_ASSERT(mCallback);
1330 mCallback->UnregisterFailed();
1331 mCallback = nullptr;
1334 NS_INLINE_DECL_REFCOUNTING(UnregisterJobCallback, override)
1337 } // anonymous namespace
1339 NS_IMETHODIMP
1340 ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
1341 nsIServiceWorkerUnregisterCallback* aCallback,
1342 const nsAString& aScope) {
1343 MOZ_ASSERT(NS_IsMainThread());
1345 if (!aPrincipal) {
1346 return NS_ERROR_FAILURE;
1349 nsresult rv;
1351 // This is not accessible by content, and callers should always ensure scope is
1352 // a correct URI, so this is wrapped in DEBUG
1353 #ifdef DEBUG
1354 nsCOMPtr<nsIURI> scopeURI;
1355 rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
1356 if (NS_WARN_IF(NS_FAILED(rv))) {
1357 return NS_ERROR_DOM_SECURITY_ERR;
1359 #endif
1361 nsAutoCString scopeKey;
1362 rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1363 if (NS_WARN_IF(NS_FAILED(rv))) {
1364 return rv;
1367 NS_ConvertUTF16toUTF8 scope(aScope);
1368 RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, scope);
1370 RefPtr<ServiceWorkerUnregisterJob> job =
1371 new ServiceWorkerUnregisterJob(aPrincipal, scope);
1373 if (aCallback) {
1374 RefPtr<UnregisterJobCallback> cb = new UnregisterJobCallback(aCallback);
1375 job->AppendResultCallback(cb);
1378 queue->ScheduleJob(job);
1379 return NS_OK;
1382 void ServiceWorkerManager::WorkerIsIdle(ServiceWorkerInfo* aWorker) {
1383 MOZ_ASSERT(NS_IsMainThread());
1384 MOZ_DIAGNOSTIC_ASSERT(aWorker);
1386 RefPtr<ServiceWorkerRegistrationInfo> reg =
1387 GetRegistration(aWorker->Principal(), aWorker->Scope());
1388 if (!reg) {
1389 return;
1392 if (reg->GetActive() != aWorker) {
1393 return;
1396 reg->TryToActivateAsync();
1399 already_AddRefed<ServiceWorkerJobQueue>
1400 ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey,
1401 const nsACString& aScope) {
1402 MOZ_ASSERT(!aKey.IsEmpty());
1403 ServiceWorkerManager::RegistrationDataPerPrincipal* data;
1404 // XXX we could use WithEntryHandle here to avoid a hashtable lookup, except
1405 // that leads to a false positive assertion, see bug 1370674 comment 7.
1406 if (!mRegistrationInfos.Get(aKey, &data)) {
1407 data = mRegistrationInfos
1408 .InsertOrUpdate(aKey, MakeUnique<RegistrationDataPerPrincipal>())
1409 .get();
1412 RefPtr queue = data->mJobQueues.GetOrInsertNew(aScope);
1413 return queue.forget();
1416 /* static */
1417 already_AddRefed<ServiceWorkerManager> ServiceWorkerManager::GetInstance() {
1418 if (!gInstance) {
1419 RefPtr<ServiceWorkerRegistrar> swr;
1421 // XXX: Substitute this with an assertion. See comment in Init.
1422 if (XRE_IsParentProcess()) {
1423 // Don't (re-)create the ServiceWorkerManager if we are already shutting
1424 // down.
1425 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
1426 return nullptr;
1428 // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar
1429 // is initialized.
1430 swr = ServiceWorkerRegistrar::Get();
1431 if (!swr) {
1432 return nullptr;
1436 MOZ_ASSERT(NS_IsMainThread());
1438 gInstance = new ServiceWorkerManager();
1439 gInstance->Init(swr);
1440 ClearOnShutdown(&gInstance);
1442 RefPtr<ServiceWorkerManager> copy = gInstance.get();
1443 return copy.forget();
1446 void ServiceWorkerManager::ReportToAllClients(
1447 const nsCString& aScope, const nsString& aMessage,
1448 const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1449 uint32_t aColumnNumber, uint32_t aFlags) {
1450 ConsoleUtils::ReportForServiceWorkerScope(
1451 NS_ConvertUTF8toUTF16(aScope), aMessage, aFilename, aLineNumber,
1452 aColumnNumber, ConsoleUtils::eError);
1455 /* static */
1456 void ServiceWorkerManager::LocalizeAndReportToAllClients(
1457 const nsCString& aScope, const char* aStringKey,
1458 const nsTArray<nsString>& aParamArray, uint32_t aFlags,
1459 const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1460 uint32_t aColumnNumber) {
1461 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1462 if (!swm) {
1463 return;
1466 nsresult rv;
1467 nsAutoString message;
1468 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
1469 aStringKey, aParamArray, message);
1470 if (NS_SUCCEEDED(rv)) {
1471 swm->ReportToAllClients(aScope, message, aFilename, aLine, aLineNumber,
1472 aColumnNumber, aFlags);
1473 } else {
1474 NS_WARNING("Failed to format and therefore report localized error.");
1478 void ServiceWorkerManager::HandleError(
1479 JSContext* aCx, nsIPrincipal* aPrincipal, const nsCString& aScope,
1480 const nsString& aWorkerURL, const nsString& aMessage,
1481 const nsString& aFilename, const nsString& aLine, uint32_t aLineNumber,
1482 uint32_t aColumnNumber, uint32_t aFlags, JSExnType aExnType) {
1483 MOZ_ASSERT(NS_IsMainThread());
1484 MOZ_ASSERT(aPrincipal);
1486 nsAutoCString scopeKey;
1487 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1488 if (NS_WARN_IF(NS_FAILED(rv))) {
1489 return;
1492 ServiceWorkerManager::RegistrationDataPerPrincipal* data;
1493 if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) {
1494 return;
1497 // Always report any uncaught exceptions or errors to the console of
1498 // each client.
1499 ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
1500 aColumnNumber, aFlags);
1503 void ServiceWorkerManager::PurgeServiceWorker(
1504 const ServiceWorkerRegistrationData& aRegistration,
1505 nsIPrincipal* aPrincipal) {
1506 MOZ_ASSERT(mActor);
1507 serviceWorkerScriptCache::PurgeCache(aPrincipal, aRegistration.cacheName());
1508 MaybeSendUnregister(aPrincipal, aRegistration.scope());
1511 void ServiceWorkerManager::LoadRegistration(
1512 const ServiceWorkerRegistrationData& aRegistration) {
1513 MOZ_ASSERT(NS_IsMainThread());
1515 auto principalOrErr = PrincipalInfoToPrincipal(aRegistration.principal());
1516 if (NS_WARN_IF(principalOrErr.isErr())) {
1517 return;
1519 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1521 if (!StaticPrefs::dom_serviceWorkers_enabled()) {
1522 // If service workers are disabled, remove the registration from disk
1523 // instead of loading.
1524 PurgeServiceWorker(aRegistration, principal);
1525 return;
1528 // Purge extensions registrations if they are disabled by prefs.
1529 if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
1530 nsCOMPtr<nsIURI> uri = principal->GetURI();
1532 // We do check the URI scheme here because when this is going to run
1533 // the extension may not have been loaded yet and the WebExtensionPolicy
1534 // may not exist yet.
1535 if (uri->SchemeIs("moz-extension")) {
1536 PurgeServiceWorker(aRegistration, principal);
1537 return;
1541 RefPtr<ServiceWorkerRegistrationInfo> registration =
1542 GetRegistration(principal, aRegistration.scope());
1543 if (!registration) {
1544 registration =
1545 CreateNewRegistration(aRegistration.scope(), principal,
1546 static_cast<ServiceWorkerUpdateViaCache>(
1547 aRegistration.updateViaCache()),
1548 aRegistration.navigationPreloadState());
1549 } else {
1550 // If active worker script matches our expectations for a "current worker",
1551 // then we are done. Since scripts with the same URL might have different
1552 // contents such as updated scripts or scripts with different LoadFlags, we
1553 // use the CacheName to judge whether the two scripts are identical, where
1554 // the CacheName is an UUID generated when a new script is found.
1555 if (registration->GetActive() &&
1556 registration->GetActive()->CacheName() == aRegistration.cacheName()) {
1557 // No needs for updates.
1558 return;
1562 registration->SetLastUpdateTime(aRegistration.lastUpdateTime());
1564 nsLoadFlags importsLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
1565 if (aRegistration.updateViaCache() !=
1566 static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None)) {
1567 importsLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
1570 const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
1571 if (!currentWorkerURL.IsEmpty()) {
1572 registration->SetActive(new ServiceWorkerInfo(
1573 registration->Principal(), registration->Scope(), registration->Id(),
1574 registration->Version(), currentWorkerURL, aRegistration.cacheName(),
1575 importsLoadFlags));
1576 registration->GetActive()->SetHandlesFetch(
1577 aRegistration.currentWorkerHandlesFetch());
1578 registration->GetActive()->SetInstalledTime(
1579 aRegistration.currentWorkerInstalledTime());
1580 registration->GetActive()->SetActivatedTime(
1581 aRegistration.currentWorkerActivatedTime());
1585 void ServiceWorkerManager::LoadRegistrations(
1586 const nsTArray<ServiceWorkerRegistrationData>& aRegistrations) {
1587 MOZ_ASSERT(NS_IsMainThread());
1588 uint32_t fetch = 0;
1589 for (uint32_t i = 0, len = aRegistrations.Length(); i < len; ++i) {
1590 LoadRegistration(aRegistrations[i]);
1591 if (aRegistrations[i].currentWorkerHandlesFetch()) {
1592 fetch++;
1595 gServiceWorkersRegistered = aRegistrations.Length();
1596 gServiceWorkersRegisteredFetch = fetch;
1597 Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_REGISTRATIONS,
1598 u"All"_ns, gServiceWorkersRegistered);
1599 Telemetry::ScalarSet(Telemetry::ScalarID::SERVICEWORKER_REGISTRATIONS,
1600 u"Fetch"_ns, gServiceWorkersRegisteredFetch);
1601 LOG(("LoadRegistrations: %u, fetch %u\n", gServiceWorkersRegistered,
1602 gServiceWorkersRegisteredFetch));
1605 void ServiceWorkerManager::StoreRegistration(
1606 nsIPrincipal* aPrincipal, ServiceWorkerRegistrationInfo* aRegistration) {
1607 MOZ_ASSERT(aPrincipal);
1608 MOZ_ASSERT(aRegistration);
1610 if (mShuttingDown) {
1611 return;
1614 // Do not store a registration for addons that are not installed, not enabled
1615 // or installed temporarily.
1617 // If the dom.serviceWorkers.testing.persistTemporaryInstalledAddons is set
1618 // to true, the registration for a temporary installed addon will still be
1619 // persisted (only meant to be used to make it easier to test some particular
1620 // scenario with a temporary installed addon which doesn't need to be signed
1621 // to be installed on release channel builds).
1622 if (aPrincipal->SchemeIs("moz-extension")) {
1623 RefPtr<extensions::WebExtensionPolicy> addonPolicy =
1624 BasePrincipal::Cast(aPrincipal)->AddonPolicy();
1625 if (!addonPolicy || !addonPolicy->Active() ||
1626 (addonPolicy->TemporarilyInstalled() &&
1627 !StaticPrefs::
1628 dom_serviceWorkers_testing_persistTemporarilyInstalledAddons())) {
1629 return;
1633 ServiceWorkerRegistrationData data;
1634 nsresult rv = PopulateRegistrationData(aPrincipal, aRegistration, data);
1635 if (NS_WARN_IF(NS_FAILED(rv))) {
1636 return;
1639 PrincipalInfo principalInfo;
1640 if (NS_WARN_IF(
1641 NS_FAILED(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)))) {
1642 return;
1645 mActor->SendRegister(data);
1648 already_AddRefed<ServiceWorkerRegistrationInfo>
1649 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
1650 const ClientInfo& aClientInfo) const {
1651 auto principalOrErr = aClientInfo.GetPrincipal();
1652 if (NS_WARN_IF(principalOrErr.isErr())) {
1653 return nullptr;
1656 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1657 nsCOMPtr<nsIURI> uri;
1658 nsresult rv = NS_NewURI(getter_AddRefs(uri), aClientInfo.URL());
1659 NS_ENSURE_SUCCESS(rv, nullptr);
1661 return GetServiceWorkerRegistrationInfo(principal, uri);
1664 already_AddRefed<ServiceWorkerRegistrationInfo>
1665 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
1666 nsIURI* aURI) const {
1667 MOZ_ASSERT(aPrincipal);
1668 MOZ_ASSERT(aURI);
1670 nsAutoCString scopeKey;
1671 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1672 if (NS_FAILED(rv)) {
1673 return nullptr;
1676 return GetServiceWorkerRegistrationInfo(scopeKey, aURI);
1679 already_AddRefed<ServiceWorkerRegistrationInfo>
1680 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
1681 const nsACString& aScopeKey, nsIURI* aURI) const {
1682 MOZ_ASSERT(aURI);
1684 nsAutoCString spec;
1685 nsresult rv = aURI->GetSpec(spec);
1686 if (NS_WARN_IF(NS_FAILED(rv))) {
1687 return nullptr;
1690 nsAutoCString scope;
1691 RegistrationDataPerPrincipal* data;
1692 if (!FindScopeForPath(aScopeKey, spec, &data, scope)) {
1693 return nullptr;
1696 MOZ_ASSERT(data);
1698 RefPtr<ServiceWorkerRegistrationInfo> registration;
1699 data->mInfos.Get(scope, getter_AddRefs(registration));
1700 // ordered scopes and registrations better be in sync.
1701 MOZ_ASSERT(registration);
1703 #ifdef DEBUG
1704 nsAutoCString origin;
1705 rv = registration->Principal()->GetOrigin(origin);
1706 MOZ_ASSERT(NS_SUCCEEDED(rv));
1707 MOZ_ASSERT(origin.Equals(aScopeKey));
1708 #endif
1710 return registration.forget();
1713 /* static */
1714 nsresult ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal* aPrincipal,
1715 nsACString& aKey) {
1716 MOZ_ASSERT(aPrincipal);
1718 if (!BasePrincipal::Cast(aPrincipal)->IsContentPrincipal()) {
1719 return NS_ERROR_FAILURE;
1722 nsresult rv = aPrincipal->GetOrigin(aKey);
1723 if (NS_WARN_IF(NS_FAILED(rv))) {
1724 return rv;
1727 return NS_OK;
1730 /* static */
1731 nsresult ServiceWorkerManager::PrincipalInfoToScopeKey(
1732 const PrincipalInfo& aPrincipalInfo, nsACString& aKey) {
1733 if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
1734 return NS_ERROR_FAILURE;
1737 auto content = aPrincipalInfo.get_ContentPrincipalInfo();
1739 nsAutoCString suffix;
1740 content.attrs().CreateSuffix(suffix);
1742 aKey = content.originNoSuffix();
1743 aKey.Append(suffix);
1745 return NS_OK;
1748 /* static */
1749 void ServiceWorkerManager::AddScopeAndRegistration(
1750 const nsACString& aScope, ServiceWorkerRegistrationInfo* aInfo) {
1751 MOZ_ASSERT(aInfo);
1752 MOZ_ASSERT(aInfo->Principal());
1753 MOZ_ASSERT(!aInfo->IsUnregistered());
1755 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1756 if (!swm) {
1757 // browser shutdown
1758 return;
1761 nsAutoCString scopeKey;
1762 nsresult rv = swm->PrincipalToScopeKey(aInfo->Principal(), scopeKey);
1763 if (NS_WARN_IF(NS_FAILED(rv))) {
1764 return;
1767 MOZ_ASSERT(!scopeKey.IsEmpty());
1769 auto* const data = swm->mRegistrationInfos.GetOrInsertNew(scopeKey);
1770 data->mScopeContainer.InsertScope(aScope);
1771 data->mInfos.InsertOrUpdate(aScope, RefPtr{aInfo});
1772 swm->NotifyListenersOnRegister(aInfo);
1775 /* static */
1776 bool ServiceWorkerManager::FindScopeForPath(
1777 const nsACString& aScopeKey, const nsACString& aPath,
1778 RegistrationDataPerPrincipal** aData, nsACString& aMatch) {
1779 MOZ_ASSERT(aData);
1781 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1783 if (!swm || !swm->mRegistrationInfos.Get(aScopeKey, aData)) {
1784 return false;
1787 Maybe<nsCString> scope = (*aData)->mScopeContainer.MatchScope(aPath);
1789 if (scope) {
1790 // scope.isSome() will still truen true after this; we are just moving the
1791 // string inside the Maybe, so the Maybe will contain an empty string.
1792 aMatch = std::move(*scope);
1795 return scope.isSome();
1798 /* static */
1799 bool ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal,
1800 const nsACString& aScope) {
1801 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1802 if (!swm) {
1803 return false;
1806 nsAutoCString scopeKey;
1807 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
1808 if (NS_WARN_IF(NS_FAILED(rv))) {
1809 return false;
1812 RegistrationDataPerPrincipal* data;
1813 if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
1814 return false;
1817 return data->mScopeContainer.Contains(aScope);
1820 /* static */
1821 void ServiceWorkerManager::RemoveScopeAndRegistration(
1822 ServiceWorkerRegistrationInfo* aRegistration) {
1823 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
1824 if (!swm) {
1825 return;
1828 nsAutoCString scopeKey;
1829 nsresult rv = swm->PrincipalToScopeKey(aRegistration->Principal(), scopeKey);
1830 if (NS_WARN_IF(NS_FAILED(rv))) {
1831 return;
1834 RegistrationDataPerPrincipal* data;
1835 if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
1836 return;
1839 if (auto entry = data->mUpdateTimers.Lookup(aRegistration->Scope())) {
1840 entry.Data()->Cancel();
1841 entry.Remove();
1844 // Verify there are no controlled clients for the purged registration.
1845 for (auto iter = swm->mControlledClients.Iter(); !iter.Done(); iter.Next()) {
1846 auto& reg = iter.UserData()->mRegistrationInfo;
1847 if (reg->Scope().Equals(aRegistration->Scope()) &&
1848 reg->Principal()->Equals(aRegistration->Principal()) &&
1849 reg->IsCorrupt()) {
1850 iter.Remove();
1854 RefPtr<ServiceWorkerRegistrationInfo> info;
1855 data->mInfos.Remove(aRegistration->Scope(), getter_AddRefs(info));
1856 aRegistration->SetUnregistered();
1857 data->mScopeContainer.RemoveScope(aRegistration->Scope());
1858 swm->NotifyListenersOnUnregister(info);
1860 swm->MaybeRemoveRegistrationInfo(scopeKey);
1863 void ServiceWorkerManager::MaybeRemoveRegistrationInfo(
1864 const nsACString& aScopeKey) {
1865 if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) {
1866 if (entry.Data()->mScopeContainer.IsEmpty() &&
1867 entry.Data()->mJobQueues.Count() == 0) {
1868 entry.Remove();
1870 // Need to reset the mQuotaUsageCheckCount, if
1871 // RegistrationDataPerPrincipal:: mScopeContainer is empty. This
1872 // RegistrationDataPerPrincipal might be reused, such that quota usage
1873 // mitigation can be triggered for the new added registration.
1874 } else if (entry.Data()->mScopeContainer.IsEmpty() &&
1875 entry.Data()->mQuotaUsageCheckCount) {
1876 entry.Data()->mQuotaUsageCheckCount = 0;
1881 bool ServiceWorkerManager::StartControlling(
1882 const ClientInfo& aClientInfo,
1883 const ServiceWorkerDescriptor& aServiceWorker) {
1884 MOZ_ASSERT(NS_IsMainThread());
1886 auto principalOrErr =
1887 PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
1889 if (NS_WARN_IF(principalOrErr.isErr())) {
1890 return false;
1893 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
1895 nsCOMPtr<nsIURI> scope;
1896 nsresult rv = NS_NewURI(getter_AddRefs(scope), aServiceWorker.Scope());
1897 NS_ENSURE_SUCCESS(rv, false);
1899 RefPtr<ServiceWorkerRegistrationInfo> registration =
1900 GetServiceWorkerRegistrationInfo(principal, scope);
1901 NS_ENSURE_TRUE(registration, false);
1902 NS_ENSURE_TRUE(registration->GetActive(), false);
1904 StartControllingClient(aClientInfo, registration);
1906 return true;
1909 void ServiceWorkerManager::MaybeCheckNavigationUpdate(
1910 const ClientInfo& aClientInfo) {
1911 MOZ_ASSERT(NS_IsMainThread());
1912 // We perform these success path navigation update steps when the
1913 // document tells us its more or less done loading. This avoids
1914 // slowing down page load and also lets pages consistently get
1915 // updatefound events when they fire.
1917 // 9.8.20 If respondWithEntered is false, then:
1918 // 9.8.22 Else: (respondWith was entered and succeeded)
1919 // If request is a non-subresource request, then: Invoke Soft Update
1920 // algorithm.
1921 ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
1922 if (data && data->mRegistrationInfo) {
1923 data->mRegistrationInfo->MaybeScheduleUpdate();
1927 void ServiceWorkerManager::StopControllingRegistration(
1928 ServiceWorkerRegistrationInfo* aRegistration) {
1929 aRegistration->StopControllingClient();
1930 if (aRegistration->IsControllingClients()) {
1931 return;
1934 if (aRegistration->IsUnregistered()) {
1935 if (aRegistration->IsIdle()) {
1936 aRegistration->Clear();
1937 } else {
1938 aRegistration->ClearWhenIdle();
1940 return;
1943 // We use to aggressively terminate the worker at this point, but it
1944 // caused problems. There are more uses for a service worker than actively
1945 // controlled documents. We need to let the worker naturally terminate
1946 // in case its handling push events, message events, etc.
1947 aRegistration->TryToActivateAsync();
1950 NS_IMETHODIMP
1951 ServiceWorkerManager::GetScopeForUrl(nsIPrincipal* aPrincipal,
1952 const nsAString& aUrl, nsAString& aScope) {
1953 MOZ_ASSERT(aPrincipal);
1955 nsCOMPtr<nsIURI> uri;
1956 nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl);
1957 if (NS_WARN_IF(NS_FAILED(rv))) {
1958 return NS_ERROR_FAILURE;
1961 RefPtr<ServiceWorkerRegistrationInfo> r =
1962 GetServiceWorkerRegistrationInfo(aPrincipal, uri);
1963 if (!r) {
1964 return NS_ERROR_FAILURE;
1967 CopyUTF8toUTF16(r->Scope(), aScope);
1968 return NS_OK;
1971 namespace {
1973 class ContinueDispatchFetchEventRunnable : public Runnable {
1974 RefPtr<ServiceWorkerPrivate> mServiceWorkerPrivate;
1975 nsCOMPtr<nsIInterceptedChannel> mChannel;
1976 nsCOMPtr<nsILoadGroup> mLoadGroup;
1978 public:
1979 ContinueDispatchFetchEventRunnable(
1980 ServiceWorkerPrivate* aServiceWorkerPrivate,
1981 nsIInterceptedChannel* aChannel, nsILoadGroup* aLoadGroup)
1982 : Runnable(
1983 "dom::ServiceWorkerManager::ContinueDispatchFetchEventRunnable"),
1984 mServiceWorkerPrivate(aServiceWorkerPrivate),
1985 mChannel(aChannel),
1986 mLoadGroup(aLoadGroup) {
1987 MOZ_ASSERT(aServiceWorkerPrivate);
1988 MOZ_ASSERT(aChannel);
1991 void HandleError() {
1992 MOZ_ASSERT(NS_IsMainThread());
1993 NS_WARNING("Unexpected error while dispatching fetch event!");
1994 nsresult rv = mChannel->ResetInterception(false);
1995 if (NS_FAILED(rv)) {
1996 NS_WARNING("Failed to resume intercepted network request");
1997 mChannel->CancelInterception(rv);
2001 NS_IMETHOD
2002 Run() override {
2003 MOZ_ASSERT(NS_IsMainThread());
2005 nsCOMPtr<nsIChannel> channel;
2006 nsresult rv = mChannel->GetChannel(getter_AddRefs(channel));
2007 if (NS_WARN_IF(NS_FAILED(rv))) {
2008 HandleError();
2009 return NS_OK;
2012 // The channel might have encountered an unexpected error while ensuring
2013 // the upload stream is cloneable. Check here and reset the interception
2014 // if that happens.
2015 nsresult status;
2016 rv = channel->GetStatus(&status);
2017 if (NS_WARN_IF(NS_FAILED(rv) || NS_FAILED(status))) {
2018 HandleError();
2019 return NS_OK;
2022 nsString clientId;
2023 nsString resultingClientId;
2024 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
2025 Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
2026 if (clientInfo.isSome()) {
2027 clientId = NSID_TrimBracketsUTF16(clientInfo->Id());
2030 // Having an initial or reserved client are mutually exclusive events:
2031 // either an initial client is used upon navigating an about:blank
2032 // iframe, or a new, reserved environment/client is created (e.g.
2033 // upon a top-level navigation). See step 4 of
2034 // https://html.spec.whatwg.org/#process-a-navigate-fetch as well as
2035 // https://github.com/w3c/ServiceWorker/issues/1228#issuecomment-345132444
2036 Maybe<ClientInfo> resulting = loadInfo->GetInitialClientInfo();
2038 if (resulting.isNothing()) {
2039 resulting = loadInfo->GetReservedClientInfo();
2040 } else {
2041 MOZ_ASSERT(loadInfo->GetReservedClientInfo().isNothing());
2044 if (resulting.isSome()) {
2045 resultingClientId = NSID_TrimBracketsUTF16(resulting->Id());
2048 rv = mServiceWorkerPrivate->SendFetchEvent(mChannel, mLoadGroup, clientId,
2049 resultingClientId);
2050 if (NS_WARN_IF(NS_FAILED(rv))) {
2051 HandleError();
2054 return NS_OK;
2058 } // anonymous namespace
2060 void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel* aChannel,
2061 ErrorResult& aRv) {
2062 MOZ_ASSERT(aChannel);
2063 MOZ_ASSERT(NS_IsMainThread());
2064 MOZ_ASSERT(XRE_IsParentProcess());
2066 nsCOMPtr<nsIChannel> internalChannel;
2067 aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
2068 if (NS_WARN_IF(aRv.Failed())) {
2069 return;
2072 nsCOMPtr<nsILoadGroup> loadGroup;
2073 aRv = internalChannel->GetLoadGroup(getter_AddRefs(loadGroup));
2074 if (NS_WARN_IF(aRv.Failed())) {
2075 return;
2078 nsCOMPtr<nsILoadInfo> loadInfo = internalChannel->LoadInfo();
2079 RefPtr<ServiceWorkerInfo> serviceWorker;
2081 if (!nsContentUtils::IsNonSubresourceRequest(internalChannel)) {
2082 const Maybe<ServiceWorkerDescriptor>& controller =
2083 loadInfo->GetController();
2084 if (NS_WARN_IF(controller.isNothing())) {
2085 aRv.Throw(NS_ERROR_FAILURE);
2086 return;
2089 RefPtr<ServiceWorkerRegistrationInfo> registration;
2090 nsresult rv = GetClientRegistration(loadInfo->GetClientInfo().ref(),
2091 getter_AddRefs(registration));
2092 if (NS_WARN_IF(NS_FAILED(rv))) {
2093 aRv.Throw(rv);
2094 return;
2097 serviceWorker = registration->GetActive();
2098 if (NS_WARN_IF(!serviceWorker) ||
2099 NS_WARN_IF(serviceWorker->Descriptor().Id() != controller.ref().Id())) {
2100 aRv.Throw(NS_ERROR_FAILURE);
2101 return;
2103 } else {
2104 nsCOMPtr<nsIURI> uri;
2105 aRv = aChannel->GetSecureUpgradedChannelURI(getter_AddRefs(uri));
2106 if (NS_WARN_IF(aRv.Failed())) {
2107 return;
2110 // non-subresource request means the URI contains the principal
2111 OriginAttributes attrs = loadInfo->GetOriginAttributes();
2112 if (StaticPrefs::privacy_partition_serviceWorkers()) {
2113 StoragePrincipalHelper::GetOriginAttributes(
2114 internalChannel, attrs,
2115 StoragePrincipalHelper::eForeignPartitionedPrincipal);
2118 nsCOMPtr<nsIPrincipal> principal =
2119 BasePrincipal::CreateContentPrincipal(uri, attrs);
2121 RefPtr<ServiceWorkerRegistrationInfo> registration =
2122 GetServiceWorkerRegistrationInfo(principal, uri);
2123 if (NS_WARN_IF(!registration)) {
2124 aRv.Throw(NS_ERROR_FAILURE);
2125 return;
2128 // While we only enter this method if IsAvailable() previously saw
2129 // an active worker, it is possible for that worker to be removed
2130 // before we get to this point. Therefore we must handle a nullptr
2131 // active worker here.
2132 serviceWorker = registration->GetActive();
2133 if (NS_WARN_IF(!serviceWorker)) {
2134 aRv.Throw(NS_ERROR_FAILURE);
2135 return;
2138 // If there is a reserved client it should be marked as controlled before
2139 // the FetchEvent is dispatched.
2140 Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo();
2142 // Also override the initial about:blank controller since the real
2143 // network load may be intercepted by a different service worker. If
2144 // the intial about:blank has a controller here its simply been
2145 // inherited from its parent.
2146 if (clientInfo.isNothing()) {
2147 clientInfo = loadInfo->GetInitialClientInfo();
2149 // TODO: We need to handle the case where the initial about:blank is
2150 // controlled, but the final document load is not. Right now
2151 // the spec does not really say what to do. There currently
2152 // is no way for the controller to be cleared from a client in
2153 // the spec or our implementation. We may want to force a
2154 // new inner window to be created instead of reusing the
2155 // initial about:blank global. See bug 1419620 and the spec
2156 // issue here: https://github.com/w3c/ServiceWorker/issues/1232
2159 if (clientInfo.isSome()) {
2160 // ClientChannelHelper is not called for STS upgrades that get
2161 // intercepted by a service worker when interception occurs in
2162 // the content process. Therefore the reserved client is not
2163 // properly cleared in that case leading to a situation where
2164 // a ClientSource with an http:// principal is controlled by
2165 // a ServiceWorker with an https:// principal.
2167 // This does not occur when interception is handled by the
2168 // simpler InterceptedHttpChannel approach in the parent.
2170 // As a temporary work around check for this principal mismatch
2171 // here and perform the ClientChannelHelper's replacement of
2172 // reserved client automatically.
2173 if (!XRE_IsParentProcess()) {
2174 auto clientPrincipalOrErr = clientInfo.ref().GetPrincipal();
2176 nsCOMPtr<nsIPrincipal> clientPrincipal;
2177 if (clientPrincipalOrErr.isOk()) {
2178 clientPrincipal = clientPrincipalOrErr.unwrap();
2181 if (!clientPrincipal || !clientPrincipal->Equals(principal)) {
2182 UniquePtr<ClientSource> reservedClient =
2183 loadInfo->TakeReservedClientSource();
2185 nsCOMPtr<nsISerialEventTarget> target =
2186 reservedClient ? reservedClient->EventTarget()
2187 : GetMainThreadSerialEventTarget();
2189 reservedClient.reset();
2190 reservedClient = ClientManager::CreateSource(ClientType::Window,
2191 target, principal);
2193 loadInfo->GiveReservedClientSource(std::move(reservedClient));
2195 clientInfo = loadInfo->GetReservedClientInfo();
2199 // First, attempt to mark the reserved client controlled directly. This
2200 // will update the controlled status in the ClientManagerService in the
2201 // parent. It will also eventually propagate back to the ClientSource.
2202 StartControllingClient(clientInfo.ref(), registration);
2205 uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
2206 nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(internalChannel);
2207 MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
2209 // Synthetic redirects for non-subresource requests with a "follow"
2210 // redirect mode may switch controllers. This is basically worker
2211 // scripts right now. In this case we need to explicitly clear the
2212 // controller to avoid assertions on the SetController() below.
2213 if (redirectMode == nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
2214 loadInfo->ClearController();
2217 // But we also note the reserved state on the LoadInfo. This allows the
2218 // ClientSource to be updated immediately after the nsIChannel starts.
2219 // This is necessary to have the correct controller in place for immediate
2220 // follow-on requests.
2221 loadInfo->SetController(serviceWorker->Descriptor());
2224 MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
2226 RefPtr<ContinueDispatchFetchEventRunnable> continueRunnable =
2227 new ContinueDispatchFetchEventRunnable(serviceWorker->WorkerPrivate(),
2228 aChannel, loadGroup);
2230 // When this service worker was registered, we also sent down the permissions
2231 // for the runnable. They should have arrived by now, but we still need to
2232 // wait for them if they have not.
2233 RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance();
2234 if (permMgr) {
2235 permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
2236 continueRunnable);
2237 } else {
2238 continueRunnable->HandleError();
2242 bool ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI,
2243 nsIChannel* aChannel) {
2244 MOZ_ASSERT(aPrincipal);
2245 MOZ_ASSERT(aURI);
2246 MOZ_ASSERT(aChannel);
2248 RefPtr<ServiceWorkerRegistrationInfo> registration =
2249 GetServiceWorkerRegistrationInfo(aPrincipal, aURI);
2251 if (!registration || !registration->GetActive()) {
2252 return false;
2255 // Checking if the matched service worker handles fetch events or not.
2256 // If it does, directly return true and handle the client controlling logic
2257 // in DispatchFetchEvent(). otherwise, do followings then return false.
2258 // 1. Set the matched service worker as the controller of LoadInfo and
2259 // correspoinding ClinetInfo
2260 // 2. Maybe schedule a soft update
2261 if (!registration->GetActive()->HandlesFetch()) {
2262 // Checkin if the channel is not allowed for the service worker.
2263 auto storageAccess = StorageAllowedForChannel(aChannel);
2264 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
2266 if (storageAccess != StorageAccess::eAllow) {
2267 if (!StaticPrefs::privacy_partition_serviceWorkers()) {
2268 return false;
2271 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2272 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
2274 if (!StoragePartitioningEnabled(storageAccess, cookieJarSettings)) {
2275 return false;
2279 // ServiceWorkerInterceptController::ShouldPrepareForIntercept() handles the
2280 // subresource cases. Must be non-subresource case here.
2281 MOZ_ASSERT(nsContentUtils::IsNonSubresourceRequest(aChannel));
2283 Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo();
2284 if (clientInfo.isNothing()) {
2285 clientInfo = loadInfo->GetInitialClientInfo();
2288 if (clientInfo.isSome()) {
2289 StartControllingClient(clientInfo.ref(), registration);
2292 uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
2293 nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aChannel);
2294 MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
2296 // Synthetic redirects for non-subresource requests with a "follow"
2297 // redirect mode may switch controllers. This is basically worker
2298 // scripts right now. In this case we need to explicitly clear the
2299 // controller to avoid assertions on the SetController() below.
2300 if (redirectMode == nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
2301 loadInfo->ClearController();
2304 loadInfo->SetController(registration->GetActive()->Descriptor());
2306 // https://w3c.github.io/ServiceWorker/#on-fetch-request-algorithm 17.1
2307 // try schedule a soft-update for non-subresource case.
2308 registration->MaybeScheduleUpdate();
2309 return false;
2311 // Found a matching service worker which handles fetch events, return true.
2312 return true;
2315 nsresult ServiceWorkerManager::GetClientRegistration(
2316 const ClientInfo& aClientInfo,
2317 ServiceWorkerRegistrationInfo** aRegistrationInfo) {
2318 ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
2319 if (!data || !data->mRegistrationInfo) {
2320 return NS_ERROR_NOT_AVAILABLE;
2323 // If the document is controlled, the current worker MUST be non-null.
2324 if (!data->mRegistrationInfo->GetActive()) {
2325 return NS_ERROR_NOT_AVAILABLE;
2328 RefPtr<ServiceWorkerRegistrationInfo> ref = data->mRegistrationInfo;
2329 ref.forget(aRegistrationInfo);
2330 return NS_OK;
2333 int32_t ServiceWorkerManager::GetPrincipalQuotaUsageCheckCount(
2334 nsIPrincipal* aPrincipal) {
2335 nsAutoCString scopeKey;
2336 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2337 if (NS_WARN_IF(NS_FAILED(rv))) {
2338 return -1;
2341 RegistrationDataPerPrincipal* data;
2342 if (!mRegistrationInfos.Get(scopeKey, &data)) {
2343 return -1;
2346 return data->mQuotaUsageCheckCount;
2349 void ServiceWorkerManager::CheckPrincipalQuotaUsage(nsIPrincipal* aPrincipal,
2350 const nsACString& aScope) {
2351 MOZ_ASSERT(NS_IsMainThread());
2352 MOZ_ASSERT(aPrincipal);
2354 nsAutoCString scopeKey;
2355 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2356 if (NS_WARN_IF(NS_FAILED(rv))) {
2357 return;
2360 RegistrationDataPerPrincipal* data;
2361 if (!mRegistrationInfos.Get(scopeKey, &data)) {
2362 return;
2365 // Had already schedule a quota usage check.
2366 if (data->mQuotaUsageCheckCount != 0) {
2367 return;
2370 ++data->mQuotaUsageCheckCount;
2372 // Get the corresponding ServiceWorkerRegistrationInfo here. Unregisteration
2373 // might be triggered later, should get it here before it be removed from
2374 // data.mInfos, such that NotifyListenersOnQuotaCheckFinish() can notify the
2375 // corresponding ServiceWorkerRegistrationInfo after asynchronous quota
2376 // checking finish.
2377 RefPtr<ServiceWorkerRegistrationInfo> info;
2378 data->mInfos.Get(aScope, getter_AddRefs(info));
2379 MOZ_ASSERT(info);
2381 RefPtr<ServiceWorkerManager> self = this;
2383 ClearQuotaUsageIfNeeded(aPrincipal, [self, info](bool aResult) {
2384 MOZ_ASSERT(NS_IsMainThread());
2385 self->NotifyListenersOnQuotaUsageCheckFinish(info);
2389 void ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes,
2390 const nsACString& aScope) {
2391 MOZ_ASSERT(NS_IsMainThread());
2393 if (mShuttingDown) {
2394 return;
2397 SoftUpdateInternal(aOriginAttributes, aScope, nullptr);
2400 namespace {
2402 class UpdateJobCallback final : public ServiceWorkerJob::Callback {
2403 RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
2405 ~UpdateJobCallback() { MOZ_ASSERT(!mCallback); }
2407 public:
2408 explicit UpdateJobCallback(ServiceWorkerUpdateFinishCallback* aCallback)
2409 : mCallback(aCallback) {
2410 MOZ_ASSERT(NS_IsMainThread());
2411 MOZ_ASSERT(mCallback);
2414 void JobFinished(ServiceWorkerJob* aJob, ErrorResult& aStatus) override {
2415 MOZ_ASSERT(NS_IsMainThread());
2416 MOZ_ASSERT(aJob);
2417 MOZ_ASSERT(mCallback);
2419 auto scopeExit = MakeScopeExit([&]() { mCallback = nullptr; });
2421 if (aStatus.Failed()) {
2422 mCallback->UpdateFailed(aStatus);
2423 return;
2426 MOZ_DIAGNOSTIC_ASSERT(aJob->GetType() == ServiceWorkerJob::Type::Update);
2427 RefPtr<ServiceWorkerUpdateJob> updateJob =
2428 static_cast<ServiceWorkerUpdateJob*>(aJob);
2429 RefPtr<ServiceWorkerRegistrationInfo> reg = updateJob->GetRegistration();
2430 mCallback->UpdateSucceeded(reg);
2433 void JobDiscarded(ErrorResult& aStatus) override {
2434 MOZ_ASSERT(NS_IsMainThread());
2435 MOZ_ASSERT(mCallback);
2437 mCallback->UpdateFailed(aStatus);
2438 mCallback = nullptr;
2441 NS_INLINE_DECL_REFCOUNTING(UpdateJobCallback, override)
2444 } // anonymous namespace
2446 void ServiceWorkerManager::SoftUpdateInternal(
2447 const OriginAttributes& aOriginAttributes, const nsACString& aScope,
2448 ServiceWorkerUpdateFinishCallback* aCallback) {
2449 MOZ_ASSERT(NS_IsMainThread());
2451 if (mShuttingDown) {
2452 return;
2455 auto result = ScopeToPrincipal(aScope, aOriginAttributes);
2456 if (NS_WARN_IF(result.isErr())) {
2457 return;
2460 auto principal = result.unwrap();
2462 nsAutoCString scopeKey;
2463 nsresult rv = PrincipalToScopeKey(principal, scopeKey);
2464 if (NS_WARN_IF(NS_FAILED(rv))) {
2465 return;
2468 RefPtr<ServiceWorkerRegistrationInfo> registration =
2469 GetRegistration(scopeKey, aScope);
2470 if (NS_WARN_IF(!registration)) {
2471 return;
2474 // "If registration's installing worker is not null, abort these steps."
2475 if (registration->GetInstalling()) {
2476 return;
2479 // "Let newestWorker be the result of running Get Newest Worker algorithm
2480 // passing registration as its argument.
2481 // If newestWorker is null, abort these steps."
2482 RefPtr<ServiceWorkerInfo> newest = registration->Newest();
2483 if (!newest) {
2484 return;
2487 // "If the registration queue for registration is empty, invoke Update
2488 // algorithm, or its equivalent, with client, registration as its argument."
2489 // TODO(catalinb): We don't implement the force bypass cache flag.
2490 // See: https://github.com/slightlyoff/ServiceWorker/issues/759
2491 RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
2493 RefPtr<ServiceWorkerUpdateJob> job = new ServiceWorkerUpdateJob(
2494 principal, registration->Scope(), newest->ScriptSpec(),
2495 registration->GetUpdateViaCache());
2497 if (aCallback) {
2498 RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
2499 job->AppendResultCallback(cb);
2502 queue->ScheduleJob(job);
2505 void ServiceWorkerManager::Update(
2506 nsIPrincipal* aPrincipal, const nsACString& aScope,
2507 nsCString aNewestWorkerScriptUrl,
2508 ServiceWorkerUpdateFinishCallback* aCallback) {
2509 MOZ_ASSERT(NS_IsMainThread());
2510 MOZ_ASSERT(!aNewestWorkerScriptUrl.IsEmpty());
2512 UpdateInternal(aPrincipal, aScope, std::move(aNewestWorkerScriptUrl),
2513 aCallback);
2516 void ServiceWorkerManager::UpdateInternal(
2517 nsIPrincipal* aPrincipal, const nsACString& aScope,
2518 nsCString&& aNewestWorkerScriptUrl,
2519 ServiceWorkerUpdateFinishCallback* aCallback) {
2520 MOZ_ASSERT(aPrincipal);
2521 MOZ_ASSERT(aCallback);
2522 MOZ_ASSERT(!aNewestWorkerScriptUrl.IsEmpty());
2524 nsAutoCString scopeKey;
2525 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2526 if (NS_WARN_IF(NS_FAILED(rv))) {
2527 return;
2530 RefPtr<ServiceWorkerRegistrationInfo> registration =
2531 GetRegistration(scopeKey, aScope);
2532 if (NS_WARN_IF(!registration)) {
2533 ErrorResult error;
2534 error.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(aScope, "uninstalled");
2535 aCallback->UpdateFailed(error);
2537 // In case the callback does not consume the exception
2538 error.SuppressException();
2539 return;
2542 RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
2544 // "Let job be the result of running Create Job with update, registration’s
2545 // scope url, newestWorker’s script url, promise, and the context object’s
2546 // relevant settings object."
2547 RefPtr<ServiceWorkerUpdateJob> job = new ServiceWorkerUpdateJob(
2548 aPrincipal, registration->Scope(), std::move(aNewestWorkerScriptUrl),
2549 registration->GetUpdateViaCache());
2551 RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
2552 job->AppendResultCallback(cb);
2554 // "Invoke Schedule Job with job."
2555 queue->ScheduleJob(job);
2558 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::MaybeClaimClient(
2559 const ClientInfo& aClientInfo,
2560 ServiceWorkerRegistrationInfo* aWorkerRegistration) {
2561 MOZ_DIAGNOSTIC_ASSERT(aWorkerRegistration);
2563 if (!aWorkerRegistration->GetActive()) {
2564 CopyableErrorResult rv;
2565 rv.ThrowInvalidStateError("Worker is not active");
2566 return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2569 // Same origin check
2570 auto principalOrErr = aClientInfo.GetPrincipal();
2572 if (NS_WARN_IF(principalOrErr.isErr())) {
2573 CopyableErrorResult rv;
2574 rv.ThrowSecurityError("Could not extract client's principal");
2575 return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2578 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
2579 if (!aWorkerRegistration->Principal()->Equals(principal)) {
2580 CopyableErrorResult rv;
2581 rv.ThrowSecurityError("Worker is for a different origin");
2582 return GenericErrorResultPromise::CreateAndReject(rv, __func__);
2585 // The registration that should be controlling the client
2586 RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
2587 GetServiceWorkerRegistrationInfo(aClientInfo);
2589 // The registration currently controlling the client
2590 RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
2591 GetClientRegistration(aClientInfo, getter_AddRefs(controllingRegistration));
2593 if (aWorkerRegistration != matchingRegistration ||
2594 aWorkerRegistration == controllingRegistration) {
2595 return GenericErrorResultPromise::CreateAndResolve(true, __func__);
2598 return StartControllingClient(aClientInfo, aWorkerRegistration);
2601 RefPtr<GenericErrorResultPromise> ServiceWorkerManager::MaybeClaimClient(
2602 const ClientInfo& aClientInfo,
2603 const ServiceWorkerDescriptor& aServiceWorker) {
2604 auto principalOrErr = aServiceWorker.GetPrincipal();
2605 if (NS_WARN_IF(principalOrErr.isErr())) {
2606 return GenericErrorResultPromise::CreateAndResolve(false, __func__);
2609 nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
2611 RefPtr<ServiceWorkerRegistrationInfo> registration =
2612 GetRegistration(principal, aServiceWorker.Scope());
2614 // While ServiceWorkerManager is distributed across child processes its
2615 // possible for us to sometimes get a claim for a new worker that has
2616 // not propagated to this process yet. For now, simply note that we
2617 // are done. The fix for this is to move the SWM to the parent process
2618 // so there are no consistency errors.
2619 if (NS_WARN_IF(!registration) || NS_WARN_IF(!registration->GetActive())) {
2620 return GenericErrorResultPromise::CreateAndResolve(false, __func__);
2623 return MaybeClaimClient(aClientInfo, registration);
2626 void ServiceWorkerManager::UpdateClientControllers(
2627 ServiceWorkerRegistrationInfo* aRegistration) {
2628 MOZ_ASSERT(NS_IsMainThread());
2630 RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
2631 MOZ_DIAGNOSTIC_ASSERT(activeWorker);
2633 AutoTArray<RefPtr<ClientHandle>, 16> handleList;
2634 for (const auto& client : mControlledClients.Values()) {
2635 if (client->mRegistrationInfo != aRegistration) {
2636 continue;
2639 handleList.AppendElement(client->mClientHandle);
2642 // Fire event after iterating mControlledClients is done to prevent
2643 // modification by reentering from the event handlers during iteration.
2644 for (auto& handle : handleList) {
2645 RefPtr<GenericErrorResultPromise> p =
2646 handle->Control(activeWorker->Descriptor());
2648 RefPtr<ServiceWorkerManager> self = this;
2650 // If we fail to control the client, then automatically remove it
2651 // from our list of controlled clients.
2652 p->Then(
2653 GetMainThreadSerialEventTarget(), __func__,
2654 [](bool) {
2655 // do nothing on success
2657 [self, clientInfo = handle->Info()](const CopyableErrorResult& aRv) {
2658 // failed to control, forget about this client
2659 self->StopControllingClient(clientInfo);
2664 void ServiceWorkerManager::EvictFromBFCache(
2665 ServiceWorkerRegistrationInfo* aRegistration) {
2666 MOZ_ASSERT(NS_IsMainThread());
2667 for (const auto& client : mControlledClients.Values()) {
2668 if (client->mRegistrationInfo == aRegistration) {
2669 client->mClientHandle->EvictFromBFCache();
2674 already_AddRefed<ServiceWorkerRegistrationInfo>
2675 ServiceWorkerManager::GetRegistration(nsIPrincipal* aPrincipal,
2676 const nsACString& aScope) const {
2677 MOZ_ASSERT(aPrincipal);
2679 nsAutoCString scopeKey;
2680 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
2681 if (NS_WARN_IF(NS_FAILED(rv))) {
2682 return nullptr;
2685 return GetRegistration(scopeKey, aScope);
2688 already_AddRefed<ServiceWorkerRegistrationInfo>
2689 ServiceWorkerManager::GetRegistration(const PrincipalInfo& aPrincipalInfo,
2690 const nsACString& aScope) const {
2691 nsAutoCString scopeKey;
2692 nsresult rv = PrincipalInfoToScopeKey(aPrincipalInfo, scopeKey);
2693 if (NS_WARN_IF(NS_FAILED(rv))) {
2694 return nullptr;
2697 return GetRegistration(scopeKey, aScope);
2700 NS_IMETHODIMP
2701 ServiceWorkerManager::ReloadRegistrationsForTest() {
2702 if (NS_WARN_IF(!StaticPrefs::dom_serviceWorkers_testing_enabled())) {
2703 return NS_ERROR_FAILURE;
2706 // Let's keep it simple and fail if there are any controlled client,
2707 // the test case can take care of making sure there is none when this
2708 // method will be called.
2709 if (NS_WARN_IF(!mControlledClients.IsEmpty())) {
2710 return NS_ERROR_FAILURE;
2713 for (const auto& info : mRegistrationInfos.Values()) {
2714 for (ServiceWorkerRegistrationInfo* reg : info->mInfos.Values()) {
2715 MOZ_ASSERT(reg);
2716 reg->ForceShutdown();
2720 mRegistrationInfos.Clear();
2722 nsTArray<ServiceWorkerRegistrationData> data;
2723 RefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get();
2724 if (NS_WARN_IF(!swr->ReloadDataForTest())) {
2725 return NS_ERROR_FAILURE;
2727 swr->GetRegistrations(data);
2728 LoadRegistrations(data);
2730 return NS_OK;
2733 NS_IMETHODIMP
2734 ServiceWorkerManager::RegisterForAddonPrincipal(nsIPrincipal* aPrincipal,
2735 JSContext* aCx,
2736 dom::Promise** aPromise) {
2737 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
2738 if (NS_WARN_IF(!global)) {
2739 return NS_ERROR_FAILURE;
2742 ErrorResult erv;
2743 RefPtr<Promise> outer = Promise::Create(global, erv);
2744 if (NS_WARN_IF(erv.Failed())) {
2745 return erv.StealNSResult();
2748 auto enabled =
2749 StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2750 if (!enabled) {
2751 outer->MaybeRejectWithNotAllowedError(
2752 "Disabled. extensions.backgroundServiceWorker.enabled is false");
2753 outer.forget(aPromise);
2754 return NS_OK;
2757 MOZ_ASSERT(aPrincipal);
2758 auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
2759 if (!addonPolicy) {
2760 outer->MaybeRejectWithNotAllowedError("Not an extension principal");
2761 outer.forget(aPromise);
2762 return NS_OK;
2765 nsCString scope;
2766 auto result = addonPolicy->GetURL(u""_ns);
2767 if (result.isOk()) {
2768 scope.Assign(NS_ConvertUTF16toUTF8(result.unwrap()));
2769 } else {
2770 outer->MaybeRejectWithUnknownError("Unable to resolve addon scope URL");
2771 outer.forget(aPromise);
2772 return NS_OK;
2775 nsString scriptURL;
2776 addonPolicy->GetBackgroundWorker(scriptURL);
2778 if (scriptURL.IsEmpty()) {
2779 outer->MaybeRejectWithNotFoundError("Missing background worker script url");
2780 outer.forget(aPromise);
2781 return NS_OK;
2784 Maybe<ClientInfo> clientInfo =
2785 dom::ClientManager::CreateInfo(ClientType::All, aPrincipal);
2787 if (!clientInfo.isSome()) {
2788 outer->MaybeRejectWithUnknownError("Error creating clientInfo");
2789 outer.forget(aPromise);
2790 return NS_OK;
2793 auto regPromise =
2794 Register(clientInfo.ref(), scope, NS_ConvertUTF16toUTF8(scriptURL),
2795 dom::ServiceWorkerUpdateViaCache::Imports);
2796 const RefPtr<ServiceWorkerManager> self(this);
2797 const nsCOMPtr<nsIPrincipal> principal(aPrincipal);
2798 regPromise->Then(
2799 GetMainThreadSerialEventTarget(), __func__,
2800 [self, outer, principal,
2801 scope](const ServiceWorkerRegistrationDescriptor& regDesc) {
2802 RefPtr<ServiceWorkerRegistrationInfo> registration =
2803 self->GetRegistration(principal, scope);
2804 if (registration) {
2805 outer->MaybeResolve(registration);
2806 } else {
2807 outer->MaybeRejectWithUnknownError(
2808 "Failed to retrieve ServiceWorkerRegistrationInfo");
2811 [outer](const mozilla::CopyableErrorResult& err) {
2812 CopyableErrorResult result(err);
2813 outer->MaybeReject(std::move(result));
2816 outer.forget(aPromise);
2818 return NS_OK;
2821 NS_IMETHODIMP
2822 ServiceWorkerManager::GetRegistrationForAddonPrincipal(
2823 nsIPrincipal* aPrincipal, nsIServiceWorkerRegistrationInfo** aInfo) {
2824 MOZ_ASSERT(aPrincipal);
2826 MOZ_ASSERT(aPrincipal);
2827 auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
2828 if (!addonPolicy) {
2829 return NS_ERROR_FAILURE;
2832 nsCString scope;
2833 auto result = addonPolicy->GetURL(u""_ns);
2834 if (result.isOk()) {
2835 scope.Assign(NS_ConvertUTF16toUTF8(result.unwrap()));
2836 } else {
2837 return NS_ERROR_FAILURE;
2840 nsCOMPtr<nsIURI> scopeURI;
2841 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), scope);
2842 if (NS_FAILED(rv)) {
2843 return NS_ERROR_FAILURE;
2846 RefPtr<ServiceWorkerRegistrationInfo> info =
2847 GetServiceWorkerRegistrationInfo(aPrincipal, scopeURI);
2848 if (!info) {
2849 aInfo = nullptr;
2850 return NS_OK;
2852 info.forget(aInfo);
2853 return NS_OK;
2856 NS_IMETHODIMP
2857 ServiceWorkerManager::WakeForExtensionAPIEvent(
2858 const nsAString& aExtensionBaseURL, const nsAString& aAPINamespace,
2859 const nsAString& aAPIEventName, JSContext* aCx, dom::Promise** aPromise) {
2860 nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
2861 if (NS_WARN_IF(!global)) {
2862 return NS_ERROR_FAILURE;
2865 ErrorResult erv;
2866 RefPtr<Promise> outer = Promise::Create(global, erv);
2867 if (NS_WARN_IF(erv.Failed())) {
2868 return erv.StealNSResult();
2871 auto enabled =
2872 StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2873 if (!enabled) {
2874 outer->MaybeRejectWithNotAllowedError(
2875 "Disabled. extensions.backgroundServiceWorker.enabled is false");
2876 outer.forget(aPromise);
2877 return NS_OK;
2880 nsCOMPtr<nsIURI> scopeURI;
2881 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aExtensionBaseURL);
2882 if (NS_FAILED(rv)) {
2883 outer->MaybeReject(rv);
2884 outer.forget(aPromise);
2885 return NS_OK;
2888 nsCOMPtr<nsIPrincipal> principal;
2889 MOZ_TRY_VAR(principal, ScopeToPrincipal(scopeURI, {}));
2891 auto* addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
2892 if (NS_WARN_IF(!addonPolicy)) {
2893 outer->MaybeRejectWithNotAllowedError(
2894 "Not an extension principal or extension disabled");
2895 outer.forget(aPromise);
2896 return NS_OK;
2899 OriginAttributes attrs;
2900 ServiceWorkerInfo* info = GetActiveWorkerInfoForScope(
2901 attrs, NS_ConvertUTF16toUTF8(aExtensionBaseURL));
2902 if (NS_WARN_IF(!info)) {
2903 outer->MaybeRejectWithInvalidStateError(
2904 "No active worker for the extension background service worker");
2905 outer.forget(aPromise);
2906 return NS_OK;
2909 ServiceWorkerPrivate* workerPrivate = info->WorkerPrivate();
2910 auto result =
2911 workerPrivate->WakeForExtensionAPIEvent(aAPINamespace, aAPIEventName);
2912 if (result.isErr()) {
2913 outer->MaybeReject(result.propagateErr());
2914 outer.forget(aPromise);
2915 return NS_OK;
2918 RefPtr<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener> innerPromise =
2919 result.unwrap();
2921 innerPromise->Then(
2922 GetMainThreadSerialEventTarget(), __func__,
2923 [outer](bool aSubscribedEvent) { outer->MaybeResolve(aSubscribedEvent); },
2924 [outer](nsresult aErrorResult) { outer->MaybeReject(aErrorResult); });
2926 outer.forget(aPromise);
2927 return NS_OK;
2930 NS_IMETHODIMP
2931 ServiceWorkerManager::GetRegistrationByPrincipal(
2932 nsIPrincipal* aPrincipal, const nsAString& aScope,
2933 nsIServiceWorkerRegistrationInfo** aInfo) {
2934 MOZ_ASSERT(aPrincipal);
2935 MOZ_ASSERT(aInfo);
2937 nsCOMPtr<nsIURI> scopeURI;
2938 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
2939 if (NS_FAILED(rv)) {
2940 return NS_ERROR_FAILURE;
2943 RefPtr<ServiceWorkerRegistrationInfo> info =
2944 GetServiceWorkerRegistrationInfo(aPrincipal, scopeURI);
2945 if (!info) {
2946 return NS_ERROR_FAILURE;
2948 info.forget(aInfo);
2950 return NS_OK;
2953 already_AddRefed<ServiceWorkerRegistrationInfo>
2954 ServiceWorkerManager::GetRegistration(const nsACString& aScopeKey,
2955 const nsACString& aScope) const {
2956 RefPtr<ServiceWorkerRegistrationInfo> reg;
2958 RegistrationDataPerPrincipal* data;
2959 if (!mRegistrationInfos.Get(aScopeKey, &data)) {
2960 return reg.forget();
2963 data->mInfos.Get(aScope, getter_AddRefs(reg));
2964 return reg.forget();
2967 already_AddRefed<ServiceWorkerRegistrationInfo>
2968 ServiceWorkerManager::CreateNewRegistration(
2969 const nsCString& aScope, nsIPrincipal* aPrincipal,
2970 ServiceWorkerUpdateViaCache aUpdateViaCache,
2971 IPCNavigationPreloadState aNavigationPreloadState) {
2972 #ifdef DEBUG
2973 MOZ_ASSERT(NS_IsMainThread());
2974 nsCOMPtr<nsIURI> scopeURI;
2975 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
2976 MOZ_ASSERT(NS_SUCCEEDED(rv));
2978 RefPtr<ServiceWorkerRegistrationInfo> tmp =
2979 GetRegistration(aPrincipal, aScope);
2980 MOZ_ASSERT(!tmp);
2981 #endif
2983 RefPtr<ServiceWorkerRegistrationInfo> registration =
2984 new ServiceWorkerRegistrationInfo(aScope, aPrincipal, aUpdateViaCache,
2985 std::move(aNavigationPreloadState));
2987 // From now on ownership of registration is with
2988 // mServiceWorkerRegistrationInfos.
2989 AddScopeAndRegistration(aScope, registration);
2990 return registration.forget();
2993 void ServiceWorkerManager::MaybeRemoveRegistration(
2994 ServiceWorkerRegistrationInfo* aRegistration) {
2995 MOZ_ASSERT(aRegistration);
2996 RefPtr<ServiceWorkerInfo> newest = aRegistration->Newest();
2997 if (!newest && HasScope(aRegistration->Principal(), aRegistration->Scope())) {
2998 RemoveRegistration(aRegistration);
3002 void ServiceWorkerManager::RemoveRegistration(
3003 ServiceWorkerRegistrationInfo* aRegistration) {
3004 // Note, we do not need to call mActor->SendUnregister() here. There are a
3005 // few ways we can get here: 1) Through a normal unregister which calls
3006 // SendUnregister() in the
3007 // unregister job Start() method.
3008 // 2) Through origin storage being purged. These result in ForceUnregister()
3009 // starting unregister jobs which in turn call SendUnregister().
3010 // 3) Through the failure to install a new service worker. Since we don't
3011 // store the registration until install succeeds, we do not need to call
3012 // SendUnregister here.
3013 MOZ_ASSERT(HasScope(aRegistration->Principal(), aRegistration->Scope()));
3015 RemoveScopeAndRegistration(aRegistration);
3018 NS_IMETHODIMP
3019 ServiceWorkerManager::GetAllRegistrations(nsIArray** aResult) {
3020 MOZ_ASSERT(NS_IsMainThread());
3022 nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID));
3023 if (!array) {
3024 return NS_ERROR_OUT_OF_MEMORY;
3027 for (const auto& info : mRegistrationInfos.Values()) {
3028 for (ServiceWorkerRegistrationInfo* reg : info->mInfos.Values()) {
3029 MOZ_ASSERT(reg);
3031 array->AppendElement(reg);
3035 array.forget(aResult);
3036 return NS_OK;
3039 NS_IMETHODIMP
3040 ServiceWorkerManager::RemoveRegistrationsByOriginAttributes(
3041 const nsAString& aPattern) {
3042 MOZ_ASSERT(XRE_IsParentProcess());
3043 MOZ_ASSERT(NS_IsMainThread());
3045 MOZ_ASSERT(!aPattern.IsEmpty());
3047 OriginAttributesPattern pattern;
3048 MOZ_ALWAYS_TRUE(pattern.Init(aPattern));
3050 for (const auto& data : mRegistrationInfos.Values()) {
3051 // We can use iteration because ForceUnregister (and Unregister) are
3052 // async. Otherwise doing some R/W operations on an hashtable during
3053 // iteration will crash.
3054 for (ServiceWorkerRegistrationInfo* reg : data->mInfos.Values()) {
3055 MOZ_ASSERT(reg);
3056 MOZ_ASSERT(reg->Principal());
3058 bool matches = pattern.Matches(reg->Principal()->OriginAttributesRef());
3059 if (!matches) {
3060 continue;
3063 ForceUnregister(data.get(), reg);
3067 return NS_OK;
3070 void ServiceWorkerManager::ForceUnregister(
3071 RegistrationDataPerPrincipal* aRegistrationData,
3072 ServiceWorkerRegistrationInfo* aRegistration) {
3073 MOZ_ASSERT(aRegistrationData);
3074 MOZ_ASSERT(aRegistration);
3076 RefPtr<ServiceWorkerJobQueue> queue;
3077 aRegistrationData->mJobQueues.Get(aRegistration->Scope(),
3078 getter_AddRefs(queue));
3079 if (queue) {
3080 queue->CancelAll();
3083 if (auto entry =
3084 aRegistrationData->mUpdateTimers.Lookup(aRegistration->Scope())) {
3085 entry.Data()->Cancel();
3086 entry.Remove();
3089 // Since Unregister is async, it is ok to call it in an enumeration.
3090 Unregister(aRegistration->Principal(), nullptr,
3091 NS_ConvertUTF8toUTF16(aRegistration->Scope()));
3094 NS_IMETHODIMP
3095 ServiceWorkerManager::AddListener(nsIServiceWorkerManagerListener* aListener) {
3096 MOZ_ASSERT(NS_IsMainThread());
3098 if (!aListener || mListeners.Contains(aListener)) {
3099 return NS_ERROR_INVALID_ARG;
3102 mListeners.AppendElement(aListener);
3104 return NS_OK;
3107 NS_IMETHODIMP
3108 ServiceWorkerManager::RemoveListener(
3109 nsIServiceWorkerManagerListener* aListener) {
3110 MOZ_ASSERT(NS_IsMainThread());
3112 if (!aListener || !mListeners.Contains(aListener)) {
3113 return NS_ERROR_INVALID_ARG;
3116 mListeners.RemoveElement(aListener);
3118 return NS_OK;
3121 NS_IMETHODIMP
3122 ServiceWorkerManager::Observe(nsISupports* aSubject, const char* aTopic,
3123 const char16_t* aData) {
3124 if (strcmp(aTopic, kFinishShutdownTopic) == 0) {
3125 MaybeFinishShutdown();
3126 return NS_OK;
3129 MOZ_CRASH("Received message we aren't supposed to be registered for!");
3130 return NS_OK;
3133 NS_IMETHODIMP
3134 ServiceWorkerManager::PropagateUnregister(
3135 nsIPrincipal* aPrincipal, nsIServiceWorkerUnregisterCallback* aCallback,
3136 const nsAString& aScope) {
3137 MOZ_ASSERT(NS_IsMainThread());
3138 MOZ_ASSERT(aPrincipal);
3140 // Return earlier with an explicit failure if this xpcom method is called
3141 // when the ServiceWorkerManager is not initialized yet or it is already
3142 // shutting down.
3143 if (NS_WARN_IF(!mActor)) {
3144 return NS_ERROR_FAILURE;
3147 PrincipalInfo principalInfo;
3148 if (NS_WARN_IF(
3149 NS_FAILED(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)))) {
3150 return NS_ERROR_FAILURE;
3153 mActor->SendPropagateUnregister(principalInfo, aScope);
3155 nsresult rv = Unregister(aPrincipal, aCallback, aScope);
3156 if (NS_WARN_IF(NS_FAILED(rv))) {
3157 return rv;
3160 return NS_OK;
3163 void ServiceWorkerManager::NotifyListenersOnRegister(
3164 nsIServiceWorkerRegistrationInfo* aInfo) {
3165 nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3166 mListeners.Clone());
3167 for (size_t index = 0; index < listeners.Length(); ++index) {
3168 listeners[index]->OnRegister(aInfo);
3172 void ServiceWorkerManager::NotifyListenersOnUnregister(
3173 nsIServiceWorkerRegistrationInfo* aInfo) {
3174 nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3175 mListeners.Clone());
3176 for (size_t index = 0; index < listeners.Length(); ++index) {
3177 listeners[index]->OnUnregister(aInfo);
3181 void ServiceWorkerManager::NotifyListenersOnQuotaUsageCheckFinish(
3182 nsIServiceWorkerRegistrationInfo* aRegistration) {
3183 nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> listeners(
3184 mListeners.Clone());
3185 for (size_t index = 0; index < listeners.Length(); ++index) {
3186 listeners[index]->OnQuotaUsageCheckFinish(aRegistration);
3190 class UpdateTimerCallback final : public nsITimerCallback, public nsINamed {
3191 nsCOMPtr<nsIPrincipal> mPrincipal;
3192 const nsCString mScope;
3194 ~UpdateTimerCallback() = default;
3196 public:
3197 UpdateTimerCallback(nsIPrincipal* aPrincipal, const nsACString& aScope)
3198 : mPrincipal(aPrincipal), mScope(aScope) {
3199 MOZ_ASSERT(NS_IsMainThread());
3200 MOZ_ASSERT(mPrincipal);
3201 MOZ_ASSERT(!mScope.IsEmpty());
3204 NS_IMETHOD
3205 Notify(nsITimer* aTimer) override {
3206 MOZ_ASSERT(NS_IsMainThread());
3208 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
3209 if (!swm) {
3210 // shutting down, do nothing
3211 return NS_OK;
3214 swm->UpdateTimerFired(mPrincipal, mScope);
3215 return NS_OK;
3218 NS_IMETHOD
3219 GetName(nsACString& aName) override {
3220 aName.AssignLiteral("UpdateTimerCallback");
3221 return NS_OK;
3224 NS_DECL_ISUPPORTS
3227 NS_IMPL_ISUPPORTS(UpdateTimerCallback, nsITimerCallback, nsINamed)
3229 void ServiceWorkerManager::ScheduleUpdateTimer(nsIPrincipal* aPrincipal,
3230 const nsACString& aScope) {
3231 MOZ_ASSERT(NS_IsMainThread());
3232 MOZ_ASSERT(aPrincipal);
3233 MOZ_ASSERT(!aScope.IsEmpty());
3235 if (mShuttingDown) {
3236 return;
3239 nsAutoCString scopeKey;
3240 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
3241 if (NS_WARN_IF(NS_FAILED(rv))) {
3242 return;
3245 RegistrationDataPerPrincipal* data;
3246 if (!mRegistrationInfos.Get(scopeKey, &data)) {
3247 return;
3250 data->mUpdateTimers.WithEntryHandle(
3251 aScope, [&aPrincipal, &aScope](auto&& entry) {
3252 if (entry) {
3253 // In case there is already a timer scheduled, just use the original
3254 // schedule time. We don't want to push it out to a later time since
3255 // that could allow updates to be starved forever if events are
3256 // continuously fired.
3257 return;
3260 nsCOMPtr<nsITimerCallback> callback =
3261 new UpdateTimerCallback(aPrincipal, aScope);
3263 const uint32_t UPDATE_DELAY_MS = 1000;
3265 nsCOMPtr<nsITimer> timer;
3267 const nsresult rv =
3268 NS_NewTimerWithCallback(getter_AddRefs(timer), callback,
3269 UPDATE_DELAY_MS, nsITimer::TYPE_ONE_SHOT);
3271 if (NS_WARN_IF(NS_FAILED(rv))) {
3272 return;
3275 entry.Insert(std::move(timer));
3279 void ServiceWorkerManager::UpdateTimerFired(nsIPrincipal* aPrincipal,
3280 const nsACString& aScope) {
3281 MOZ_ASSERT(NS_IsMainThread());
3282 MOZ_ASSERT(aPrincipal);
3283 MOZ_ASSERT(!aScope.IsEmpty());
3285 if (mShuttingDown) {
3286 return;
3289 // First cleanup the timer.
3290 nsAutoCString scopeKey;
3291 nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
3292 if (NS_WARN_IF(NS_FAILED(rv))) {
3293 return;
3296 RegistrationDataPerPrincipal* data;
3297 if (!mRegistrationInfos.Get(scopeKey, &data)) {
3298 return;
3301 if (auto entry = data->mUpdateTimers.Lookup(aScope)) {
3302 entry.Data()->Cancel();
3303 entry.Remove();
3306 RefPtr<ServiceWorkerRegistrationInfo> registration;
3307 data->mInfos.Get(aScope, getter_AddRefs(registration));
3308 if (!registration) {
3309 return;
3312 if (!registration->CheckAndClearIfUpdateNeeded()) {
3313 return;
3316 OriginAttributes attrs = aPrincipal->OriginAttributesRef();
3318 SoftUpdate(attrs, aScope);
3321 void ServiceWorkerManager::MaybeSendUnregister(nsIPrincipal* aPrincipal,
3322 const nsACString& aScope) {
3323 MOZ_ASSERT(NS_IsMainThread());
3324 MOZ_ASSERT(aPrincipal);
3325 MOZ_ASSERT(!aScope.IsEmpty());
3327 if (!mActor) {
3328 return;
3331 PrincipalInfo principalInfo;
3332 nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
3333 if (NS_WARN_IF(NS_FAILED(rv))) {
3334 return;
3337 Unused << mActor->SendUnregister(principalInfo,
3338 NS_ConvertUTF8toUTF16(aScope));
3341 void ServiceWorkerManager::AddOrphanedRegistration(
3342 ServiceWorkerRegistrationInfo* aRegistration) {
3343 MOZ_ASSERT(NS_IsMainThread());
3344 MOZ_ASSERT(aRegistration);
3345 MOZ_ASSERT(aRegistration->IsUnregistered());
3346 MOZ_ASSERT(!aRegistration->IsControllingClients());
3347 MOZ_ASSERT(!aRegistration->IsIdle());
3348 MOZ_ASSERT(!mOrphanedRegistrations.has(aRegistration));
3350 MOZ_ALWAYS_TRUE(mOrphanedRegistrations.putNew(aRegistration));
3353 void ServiceWorkerManager::RemoveOrphanedRegistration(
3354 ServiceWorkerRegistrationInfo* aRegistration) {
3355 MOZ_ASSERT(NS_IsMainThread());
3356 MOZ_ASSERT(aRegistration);
3357 MOZ_ASSERT(aRegistration->IsUnregistered());
3358 MOZ_ASSERT(!aRegistration->IsControllingClients());
3359 MOZ_ASSERT(aRegistration->IsIdle());
3360 MOZ_ASSERT(mOrphanedRegistrations.has(aRegistration));
3362 mOrphanedRegistrations.remove(aRegistration);
3365 uint32_t ServiceWorkerManager::MaybeInitServiceWorkerShutdownProgress() const {
3366 if (!mShutdownBlocker) {
3367 return ServiceWorkerShutdownBlocker::kInvalidShutdownStateId;
3370 return mShutdownBlocker->CreateShutdownState();
3373 void ServiceWorkerManager::ReportServiceWorkerShutdownProgress(
3374 uint32_t aShutdownStateId,
3375 ServiceWorkerShutdownState::Progress aProgress) const {
3376 MOZ_ASSERT(mShutdownBlocker);
3377 mShutdownBlocker->ReportShutdownProgress(aShutdownStateId, aProgress);
3380 } // namespace mozilla::dom