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"
12 #include "nsICookieJarSettings.h"
13 #include "nsIHttpChannel.h"
14 #include "nsIHttpChannelInternal.h"
16 #include "nsINetworkInterceptController.h"
17 #include "nsIMutableArray.h"
18 #include "nsIPrincipal.h"
20 #include "nsIUploadChannel2.h"
21 #include "nsServiceManagerUtils.h"
23 #include "nsIPermissionManager.h"
24 #include "nsXULAppAPI.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"
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"
99 mozilla::LazyLogModule
sWorkerTelemetryLog("WorkerTelemetry");
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;
118 nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW
==
119 static_cast<uint32_t>(RequestRedirect::Follow
),
120 "RequestRedirect enumeration value should make Necko Redirect mode value.");
122 nsIHttpChannelInternal::REDIRECT_MODE_ERROR
==
123 static_cast<uint32_t>(RequestRedirect::Error
),
124 "RequestRedirect enumeration value should make Necko Redirect mode value.");
126 nsIHttpChannelInternal::REDIRECT_MODE_MANUAL
==
127 static_cast<uint32_t>(RequestRedirect::Manual
),
128 "RequestRedirect enumeration value should make Necko Redirect mode value.");
130 3 == ContiguousEnumSize
<RequestRedirect
>::value
,
131 "RequestRedirect enumeration value should make Necko Redirect mode value.");
134 nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT
==
135 static_cast<uint32_t>(RequestCache::Default
),
136 "RequestCache enumeration value should match Necko Cache mode value.");
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.");
142 nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD
==
143 static_cast<uint32_t>(RequestCache::Reload
),
144 "RequestCache enumeration value should match Necko Cache mode value.");
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.");
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.");
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.");
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
;
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
))) {
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();
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
));
223 class TeardownRunnable final
: public Runnable
{
225 explicit TeardownRunnable(ServiceWorkerManagerChild
* aActor
)
226 : Runnable("dom::ServiceWorkerManager::TeardownRunnable"),
231 NS_IMETHOD
Run() override
{
233 PServiceWorkerManagerChild::Send__delete__(mActor
);
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();
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
);
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
);
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
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
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
310 class ScopeContainer final
: private nsTArray
<nsCString
> {
311 using Base
= nsTArray
<nsCString
>;
314 using Base::Contains
;
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
)) {
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()) {
351 // "Assert: matchingScope’s origin and clientURL’s origin are same
353 MOZ_DIAGNOSTIC_ASSERT_IF(match
, IsSameOrigin(*match
, aClientUrl
));
359 bool IsSameOrigin(const nsACString
& aMatchingScope
,
360 const nsACString
& aClientUrl
) const {
361 auto parseResult
= ScopeToPrincipal(aMatchingScope
, OriginAttributes());
363 if (NS_WARN_IF(parseResult
.isErr())) {
367 auto scopePrincipal
= parseResult
.unwrap();
369 parseResult
= ScopeToPrincipal(aClientUrl
, OriginAttributes());
371 if (NS_WARN_IF(parseResult
.isErr())) {
375 auto clientPrincipal
= parseResult
.unwrap();
380 NS_FAILED(scopePrincipal
->Equals(clientPrincipal
, &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
)
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
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()) {
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();
477 PServiceWorkerManagerChild
* actor
=
478 actorChild
->SendPServiceWorkerManagerConstructor();
480 MaybeStartShutdown();
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()) /
502 mTelemetryLastChange
= now
;
506 nsCOMPtr
<nsIRunnable
> runnable
= NS_NewRunnableFunction(
507 "ServiceWorkerTelemetryRunnable", [aNumber
, aFetch
, repeats
]() {
508 LOG(("ServiceWorkers running: %u samples of %u/%u", repeats
, aNumber
,
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
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
++) {
520 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING
, "All"_ns
,
523 for (uint32_t i
= 0; i
< num_repeats
; i
++) {
526 Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING
, "Fetch"_ns
,
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(
543 [&](decltype(mControlledClients
)::EntryHandle
&& entry
)
544 -> RefPtr
<GenericErrorResultPromise
> {
545 const RefPtr
<ServiceWorkerManager
> self
= this;
547 const ServiceWorkerDescriptor
& active
=
548 aRegistrationInfo
->GetActive()->Descriptor();
551 const RefPtr
<ServiceWorkerRegistrationInfo
> old
=
552 std::move(entry
.Data()->mRegistrationInfo
);
554 const RefPtr
<GenericErrorResultPromise
> promise
=
556 ? entry
.Data()->mClientHandle
->Control(active
)
557 : GenericErrorResultPromise::CreateAndResolve(false,
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__
,
575 // do nothing on success
576 return GenericErrorResultPromise::CreateAndResolve(true,
579 [self
, aClientInfo
](const CopyableErrorResult
& aRv
) {
580 // failed to control, forget about this client
581 self
->StopControllingClient(aClientInfo
);
582 return GenericErrorResultPromise::CreateAndReject(aRv
,
587 RefPtr
<ClientHandle
> clientHandle
= ClientManager::CreateHandle(
588 aClientInfo
, GetMainThreadSerialEventTarget());
590 const RefPtr
<GenericErrorResultPromise
> promise
=
592 ? clientHandle
->Control(active
)
593 : GenericErrorResultPromise::CreateAndResolve(false, __func__
);
595 aRegistrationInfo
->StartControllingClient();
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__
,
612 // do nothing on success
613 return GenericErrorResultPromise::CreateAndResolve(true,
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());
631 RefPtr
<ServiceWorkerRegistrationInfo
> reg
=
632 std::move(entry
.Data()->mRegistrationInfo
);
636 StopControllingRegistration(reg
);
639 void ServiceWorkerManager::MaybeStartShutdown() {
640 MOZ_ASSERT(NS_IsMainThread());
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
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();
681 obs
->AddObserver(this, kFinishShutdownTopic
, false);
685 MaybeFinishShutdown();
688 void ServiceWorkerManager::MaybeFinishShutdown() {
689 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
691 obs
->RemoveObserver(this, kFinishShutdownTopic
);
698 mActor
->ManagerShuttingDown();
700 RefPtr
<TeardownRunnable
> runnable
= new TeardownRunnable(mActor
);
701 nsresult rv
= NS_DispatchToMainThread(runnable
);
702 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
705 // This also submits final telemetry
706 ServiceWorkerPrivate::RunningShutdown();
709 class ServiceWorkerResolveWindowPromiseOnRegisterCallback final
710 : public ServiceWorkerJob::Callback
{
712 NS_INLINE_DECL_REFCOUNTING(
713 ServiceWorkerResolveWindowPromiseOnRegisterCallback
, override
)
715 virtual void JobFinished(ServiceWorkerJob
* aJob
,
716 ErrorResult
& aStatus
) override
{
717 MOZ_ASSERT(NS_IsMainThread());
720 if (aStatus
.Failed()) {
721 mPromiseHolder
.Reject(CopyableErrorResult(aStatus
), __func__
);
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__
);
745 ~ServiceWorkerResolveWindowPromiseOnRegisterCallback() = default;
747 MozPromiseHolder
<ServiceWorkerRegistrationPromise
> mPromiseHolder
;
751 ServiceWorkerManager::RegisterForTest(nsIPrincipal
* aPrincipal
,
752 const nsAString
& aScopeURL
,
753 const nsAString
& aScriptURL
,
755 mozilla::dom::Promise
** aPromise
) {
756 nsIGlobalObject
* global
= xpc::CurrentNativeGlobal(aCx
);
757 if (NS_WARN_IF(!global
)) {
758 return NS_ERROR_FAILURE
;
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 "
771 outer
.forget(aPromise
);
775 if (aPrincipal
== nullptr) {
776 outer
->MaybeRejectWithAbortError("Missing principal");
777 outer
.forget(aPromise
);
781 if (aScriptURL
.IsEmpty()) {
782 outer
->MaybeRejectWithAbortError("Missing script url");
783 outer
.forget(aPromise
);
787 if (aScopeURL
.IsEmpty()) {
788 outer
->MaybeRejectWithAbortError("Missing scope url");
789 outer
.forget(aPromise
);
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
);
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
);
812 GetMainThreadSerialEventTarget(), __func__
,
813 [self
, outer
, principal
,
814 scope
](const ServiceWorkerRegistrationDescriptor
& regDesc
) {
815 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
816 self
->GetRegistration(principal
, NS_ConvertUTF16toUTF8(scope
));
818 outer
->MaybeResolve(registration
);
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
);
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
);
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
);
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
);
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
;
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 {
915 auto scopeExit
= MakeScopeExit(
916 [&] { mPromise
->Reject(NS_ERROR_DOM_INVALID_STATE_ERR
, __func__
); });
918 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
923 auto principalOrErr
= mClientInfo
.GetPrincipal();
924 if (NS_WARN_IF(principalOrErr
.isErr())) {
928 nsCOMPtr
<nsIPrincipal
> principal
= principalOrErr
.unwrap();
930 nsTArray
<ServiceWorkerRegistrationDescriptor
> array
;
932 if (NS_WARN_IF(!BasePrincipal::Cast(principal
)->IsContentPrincipal())) {
936 nsAutoCString scopeKey
;
937 nsresult rv
= swm
->PrincipalToScopeKey(principal
, scopeKey
);
938 if (NS_WARN_IF(NS_FAILED(rv
))) {
942 ServiceWorkerManager::RegistrationDataPerPrincipal
* data
;
943 if (!swm
->mRegistrationInfos
.Get(scopeKey
, &data
)) {
945 mPromise
->Resolve(array
, __func__
);
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
))) {
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
))) {
971 array
.AppendElement(info
->Descriptor());
975 mPromise
->Resolve(array
, __func__
);
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
;
998 GetRegistrationRunnable(const ClientInfo
& aClientInfo
, const nsACString
& aURL
)
999 : Runnable("dom::ServiceWorkerManager::GetRegistrationRunnable"),
1000 mClientInfo(aClientInfo
),
1001 mPromise(new ServiceWorkerRegistrationPromise::Private(__func__
)),
1004 RefPtr
<ServiceWorkerRegistrationPromise
> Promise() const { return mPromise
; }
1008 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
1010 mPromise
->Reject(NS_ERROR_DOM_INVALID_STATE_ERR
, __func__
);
1014 auto principalOrErr
= mClientInfo
.GetPrincipal();
1015 if (NS_WARN_IF(principalOrErr
.isErr())) {
1016 mPromise
->Reject(NS_ERROR_DOM_INVALID_STATE_ERR
, __func__
);
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__
);
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__
);
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__
);
1047 mPromise
->Resolve(registration
->Descriptor(), __func__
);
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();
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
,
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
);
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
);
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
));
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
);
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(),
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__
);
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__
);
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())) {
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
)) {
1278 auto result
= ScopeToPrincipal(scopeURI
, aOriginAttributes
);
1279 if (NS_WARN_IF(result
.isErr())) {
1283 auto principal
= result
.unwrap();
1285 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
1286 GetServiceWorkerRegistrationInfo(principal
, scopeURI
);
1287 if (!registration
) {
1291 return registration
->GetActive();
1296 class UnregisterJobCallback final
: public ServiceWorkerJob::Callback
{
1297 nsCOMPtr
<nsIServiceWorkerUnregisterCallback
> mCallback
;
1299 ~UnregisterJobCallback() { MOZ_ASSERT(!mCallback
); }
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());
1311 MOZ_ASSERT(mCallback
);
1313 auto scopeExit
= MakeScopeExit([&]() { mCallback
= nullptr; });
1315 if (aStatus
.Failed()) {
1316 mCallback
->UnregisterFailed();
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
1340 ServiceWorkerManager::Unregister(nsIPrincipal
* aPrincipal
,
1341 nsIServiceWorkerUnregisterCallback
* aCallback
,
1342 const nsAString
& aScope
) {
1343 MOZ_ASSERT(NS_IsMainThread());
1346 return NS_ERROR_FAILURE
;
1351 // This is not accessible by content, and callers should always ensure scope is
1352 // a correct URI, so this is wrapped in 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
;
1361 nsAutoCString scopeKey
;
1362 rv
= PrincipalToScopeKey(aPrincipal
, scopeKey
);
1363 if (NS_WARN_IF(NS_FAILED(rv
))) {
1367 NS_ConvertUTF16toUTF8
scope(aScope
);
1368 RefPtr
<ServiceWorkerJobQueue
> queue
= GetOrCreateJobQueue(scopeKey
, scope
);
1370 RefPtr
<ServiceWorkerUnregisterJob
> job
=
1371 new ServiceWorkerUnregisterJob(aPrincipal
, scope
);
1374 RefPtr
<UnregisterJobCallback
> cb
= new UnregisterJobCallback(aCallback
);
1375 job
->AppendResultCallback(cb
);
1378 queue
->ScheduleJob(job
);
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());
1392 if (reg
->GetActive() != aWorker
) {
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
>())
1412 RefPtr queue
= data
->mJobQueues
.GetOrInsertNew(aScope
);
1413 return queue
.forget();
1417 already_AddRefed
<ServiceWorkerManager
> ServiceWorkerManager::GetInstance() {
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
1425 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
1428 // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar
1430 swr
= ServiceWorkerRegistrar::Get();
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
);
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();
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
);
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
))) {
1492 ServiceWorkerManager::RegistrationDataPerPrincipal
* data
;
1493 if (NS_WARN_IF(!mRegistrationInfos
.Get(scopeKey
, &data
))) {
1497 // Always report any uncaught exceptions or errors to the console of
1499 ReportToAllClients(aScope
, aMessage
, aFilename
, aLine
, aLineNumber
,
1500 aColumnNumber
, aFlags
);
1503 void ServiceWorkerManager::PurgeServiceWorker(
1504 const ServiceWorkerRegistrationData
& aRegistration
,
1505 nsIPrincipal
* aPrincipal
) {
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())) {
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
);
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
);
1541 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
1542 GetRegistration(principal
, aRegistration
.scope());
1543 if (!registration
) {
1545 CreateNewRegistration(aRegistration
.scope(), principal
,
1546 static_cast<ServiceWorkerUpdateViaCache
>(
1547 aRegistration
.updateViaCache()),
1548 aRegistration
.navigationPreloadState());
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.
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(),
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());
1589 for (uint32_t i
= 0, len
= aRegistrations
.Length(); i
< len
; ++i
) {
1590 LoadRegistration(aRegistrations
[i
]);
1591 if (aRegistrations
[i
].currentWorkerHandlesFetch()) {
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
) {
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() &&
1628 dom_serviceWorkers_testing_persistTemporarilyInstalledAddons())) {
1633 ServiceWorkerRegistrationData data
;
1634 nsresult rv
= PopulateRegistrationData(aPrincipal
, aRegistration
, data
);
1635 if (NS_WARN_IF(NS_FAILED(rv
))) {
1639 PrincipalInfo principalInfo
;
1641 NS_FAILED(PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
)))) {
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())) {
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
);
1670 nsAutoCString scopeKey
;
1671 nsresult rv
= PrincipalToScopeKey(aPrincipal
, scopeKey
);
1672 if (NS_FAILED(rv
)) {
1676 return GetServiceWorkerRegistrationInfo(scopeKey
, aURI
);
1679 already_AddRefed
<ServiceWorkerRegistrationInfo
>
1680 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(
1681 const nsACString
& aScopeKey
, nsIURI
* aURI
) const {
1685 nsresult rv
= aURI
->GetSpec(spec
);
1686 if (NS_WARN_IF(NS_FAILED(rv
))) {
1690 nsAutoCString scope
;
1691 RegistrationDataPerPrincipal
* data
;
1692 if (!FindScopeForPath(aScopeKey
, spec
, &data
, scope
)) {
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
);
1704 nsAutoCString origin
;
1705 rv
= registration
->Principal()->GetOrigin(origin
);
1706 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1707 MOZ_ASSERT(origin
.Equals(aScopeKey
));
1710 return registration
.forget();
1714 nsresult
ServiceWorkerManager::PrincipalToScopeKey(nsIPrincipal
* aPrincipal
,
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
))) {
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
);
1749 void ServiceWorkerManager::AddScopeAndRegistration(
1750 const nsACString
& aScope
, ServiceWorkerRegistrationInfo
* aInfo
) {
1752 MOZ_ASSERT(aInfo
->Principal());
1753 MOZ_ASSERT(!aInfo
->IsUnregistered());
1755 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
1761 nsAutoCString scopeKey
;
1762 nsresult rv
= swm
->PrincipalToScopeKey(aInfo
->Principal(), scopeKey
);
1763 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
1776 bool ServiceWorkerManager::FindScopeForPath(
1777 const nsACString
& aScopeKey
, const nsACString
& aPath
,
1778 RegistrationDataPerPrincipal
** aData
, nsACString
& aMatch
) {
1781 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
1783 if (!swm
|| !swm
->mRegistrationInfos
.Get(aScopeKey
, aData
)) {
1787 Maybe
<nsCString
> scope
= (*aData
)->mScopeContainer
.MatchScope(aPath
);
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();
1799 bool ServiceWorkerManager::HasScope(nsIPrincipal
* aPrincipal
,
1800 const nsACString
& aScope
) {
1801 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
1806 nsAutoCString scopeKey
;
1807 nsresult rv
= PrincipalToScopeKey(aPrincipal
, scopeKey
);
1808 if (NS_WARN_IF(NS_FAILED(rv
))) {
1812 RegistrationDataPerPrincipal
* data
;
1813 if (!swm
->mRegistrationInfos
.Get(scopeKey
, &data
)) {
1817 return data
->mScopeContainer
.Contains(aScope
);
1821 void ServiceWorkerManager::RemoveScopeAndRegistration(
1822 ServiceWorkerRegistrationInfo
* aRegistration
) {
1823 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
1828 nsAutoCString scopeKey
;
1829 nsresult rv
= swm
->PrincipalToScopeKey(aRegistration
->Principal(), scopeKey
);
1830 if (NS_WARN_IF(NS_FAILED(rv
))) {
1834 RegistrationDataPerPrincipal
* data
;
1835 if (!swm
->mRegistrationInfos
.Get(scopeKey
, &data
)) {
1839 if (auto entry
= data
->mUpdateTimers
.Lookup(aRegistration
->Scope())) {
1840 entry
.Data()->Cancel();
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()) &&
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) {
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())) {
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
);
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
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()) {
1934 if (aRegistration
->IsUnregistered()) {
1935 if (aRegistration
->IsIdle()) {
1936 aRegistration
->Clear();
1938 aRegistration
->ClearWhenIdle();
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();
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
);
1964 return NS_ERROR_FAILURE
;
1967 CopyUTF8toUTF16(r
->Scope(), aScope
);
1973 class ContinueDispatchFetchEventRunnable
: public Runnable
{
1974 RefPtr
<ServiceWorkerPrivate
> mServiceWorkerPrivate
;
1975 nsCOMPtr
<nsIInterceptedChannel
> mChannel
;
1976 nsCOMPtr
<nsILoadGroup
> mLoadGroup
;
1979 ContinueDispatchFetchEventRunnable(
1980 ServiceWorkerPrivate
* aServiceWorkerPrivate
,
1981 nsIInterceptedChannel
* aChannel
, nsILoadGroup
* aLoadGroup
)
1983 "dom::ServiceWorkerManager::ContinueDispatchFetchEventRunnable"),
1984 mServiceWorkerPrivate(aServiceWorkerPrivate
),
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
);
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
))) {
2012 // The channel might have encountered an unexpected error while ensuring
2013 // the upload stream is cloneable. Check here and reset the interception
2016 rv
= channel
->GetStatus(&status
);
2017 if (NS_WARN_IF(NS_FAILED(rv
) || NS_FAILED(status
))) {
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();
2041 MOZ_ASSERT(loadInfo
->GetReservedClientInfo().isNothing());
2044 if (resulting
.isSome()) {
2045 resultingClientId
= NSID_TrimBracketsUTF16(resulting
->Id());
2048 rv
= mServiceWorkerPrivate
->SendFetchEvent(mChannel
, mLoadGroup
, clientId
,
2050 if (NS_WARN_IF(NS_FAILED(rv
))) {
2058 } // anonymous namespace
2060 void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel
* aChannel
,
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())) {
2072 nsCOMPtr
<nsILoadGroup
> loadGroup
;
2073 aRv
= internalChannel
->GetLoadGroup(getter_AddRefs(loadGroup
));
2074 if (NS_WARN_IF(aRv
.Failed())) {
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
);
2089 RefPtr
<ServiceWorkerRegistrationInfo
> registration
;
2090 nsresult rv
= GetClientRegistration(loadInfo
->GetClientInfo().ref(),
2091 getter_AddRefs(registration
));
2092 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
2104 nsCOMPtr
<nsIURI
> uri
;
2105 aRv
= aChannel
->GetSecureUpgradedChannelURI(getter_AddRefs(uri
));
2106 if (NS_WARN_IF(aRv
.Failed())) {
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
);
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
);
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
,
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();
2235 permMgr
->WhenPermissionsAvailable(serviceWorker
->Principal(),
2238 continueRunnable
->HandleError();
2242 bool ServiceWorkerManager::IsAvailable(nsIPrincipal
* aPrincipal
, nsIURI
* aURI
,
2243 nsIChannel
* aChannel
) {
2244 MOZ_ASSERT(aPrincipal
);
2246 MOZ_ASSERT(aChannel
);
2248 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
2249 GetServiceWorkerRegistrationInfo(aPrincipal
, aURI
);
2251 if (!registration
|| !registration
->GetActive()) {
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()) {
2271 nsCOMPtr
<nsICookieJarSettings
> cookieJarSettings
;
2272 loadInfo
->GetCookieJarSettings(getter_AddRefs(cookieJarSettings
));
2274 if (!StoragePartitioningEnabled(storageAccess
, cookieJarSettings
)) {
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();
2311 // Found a matching service worker which handles fetch events, 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
);
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
))) {
2341 RegistrationDataPerPrincipal
* data
;
2342 if (!mRegistrationInfos
.Get(scopeKey
, &data
)) {
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
))) {
2360 RegistrationDataPerPrincipal
* data
;
2361 if (!mRegistrationInfos
.Get(scopeKey
, &data
)) {
2365 // Had already schedule a quota usage check.
2366 if (data
->mQuotaUsageCheckCount
!= 0) {
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
2377 RefPtr
<ServiceWorkerRegistrationInfo
> info
;
2378 data
->mInfos
.Get(aScope
, getter_AddRefs(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
) {
2397 SoftUpdateInternal(aOriginAttributes
, aScope
, nullptr);
2402 class UpdateJobCallback final
: public ServiceWorkerJob::Callback
{
2403 RefPtr
<ServiceWorkerUpdateFinishCallback
> mCallback
;
2405 ~UpdateJobCallback() { MOZ_ASSERT(!mCallback
); }
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());
2417 MOZ_ASSERT(mCallback
);
2419 auto scopeExit
= MakeScopeExit([&]() { mCallback
= nullptr; });
2421 if (aStatus
.Failed()) {
2422 mCallback
->UpdateFailed(aStatus
);
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
) {
2455 auto result
= ScopeToPrincipal(aScope
, aOriginAttributes
);
2456 if (NS_WARN_IF(result
.isErr())) {
2460 auto principal
= result
.unwrap();
2462 nsAutoCString scopeKey
;
2463 nsresult rv
= PrincipalToScopeKey(principal
, scopeKey
);
2464 if (NS_WARN_IF(NS_FAILED(rv
))) {
2468 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
2469 GetRegistration(scopeKey
, aScope
);
2470 if (NS_WARN_IF(!registration
)) {
2474 // "If registration's installing worker is not null, abort these steps."
2475 if (registration
->GetInstalling()) {
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();
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());
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
),
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
))) {
2530 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
2531 GetRegistration(scopeKey
, aScope
);
2532 if (NS_WARN_IF(!registration
)) {
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();
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
) {
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.
2653 GetMainThreadSerialEventTarget(), __func__
,
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
))) {
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
))) {
2697 return GetRegistration(scopeKey
, aScope
);
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()) {
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
);
2734 ServiceWorkerManager::RegisterForAddonPrincipal(nsIPrincipal
* aPrincipal
,
2736 dom::Promise
** aPromise
) {
2737 nsIGlobalObject
* global
= xpc::CurrentNativeGlobal(aCx
);
2738 if (NS_WARN_IF(!global
)) {
2739 return NS_ERROR_FAILURE
;
2743 RefPtr
<Promise
> outer
= Promise::Create(global
, erv
);
2744 if (NS_WARN_IF(erv
.Failed())) {
2745 return erv
.StealNSResult();
2749 StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2751 outer
->MaybeRejectWithNotAllowedError(
2752 "Disabled. extensions.backgroundServiceWorker.enabled is false");
2753 outer
.forget(aPromise
);
2757 MOZ_ASSERT(aPrincipal
);
2758 auto* addonPolicy
= BasePrincipal::Cast(aPrincipal
)->AddonPolicy();
2760 outer
->MaybeRejectWithNotAllowedError("Not an extension principal");
2761 outer
.forget(aPromise
);
2766 auto result
= addonPolicy
->GetURL(u
""_ns
);
2767 if (result
.isOk()) {
2768 scope
.Assign(NS_ConvertUTF16toUTF8(result
.unwrap()));
2770 outer
->MaybeRejectWithUnknownError("Unable to resolve addon scope URL");
2771 outer
.forget(aPromise
);
2776 addonPolicy
->GetBackgroundWorker(scriptURL
);
2778 if (scriptURL
.IsEmpty()) {
2779 outer
->MaybeRejectWithNotFoundError("Missing background worker script url");
2780 outer
.forget(aPromise
);
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
);
2794 Register(clientInfo
.ref(), scope
, NS_ConvertUTF16toUTF8(scriptURL
),
2795 dom::ServiceWorkerUpdateViaCache::Imports
);
2796 const RefPtr
<ServiceWorkerManager
> self(this);
2797 const nsCOMPtr
<nsIPrincipal
> principal(aPrincipal
);
2799 GetMainThreadSerialEventTarget(), __func__
,
2800 [self
, outer
, principal
,
2801 scope
](const ServiceWorkerRegistrationDescriptor
& regDesc
) {
2802 RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
2803 self
->GetRegistration(principal
, scope
);
2805 outer
->MaybeResolve(registration
);
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
);
2822 ServiceWorkerManager::GetRegistrationForAddonPrincipal(
2823 nsIPrincipal
* aPrincipal
, nsIServiceWorkerRegistrationInfo
** aInfo
) {
2824 MOZ_ASSERT(aPrincipal
);
2826 MOZ_ASSERT(aPrincipal
);
2827 auto* addonPolicy
= BasePrincipal::Cast(aPrincipal
)->AddonPolicy();
2829 return NS_ERROR_FAILURE
;
2833 auto result
= addonPolicy
->GetURL(u
""_ns
);
2834 if (result
.isOk()) {
2835 scope
.Assign(NS_ConvertUTF16toUTF8(result
.unwrap()));
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
);
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
;
2866 RefPtr
<Promise
> outer
= Promise::Create(global
, erv
);
2867 if (NS_WARN_IF(erv
.Failed())) {
2868 return erv
.StealNSResult();
2872 StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
2874 outer
->MaybeRejectWithNotAllowedError(
2875 "Disabled. extensions.backgroundServiceWorker.enabled is false");
2876 outer
.forget(aPromise
);
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
);
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
);
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
);
2909 ServiceWorkerPrivate
* workerPrivate
= info
->WorkerPrivate();
2911 workerPrivate
->WakeForExtensionAPIEvent(aAPINamespace
, aAPIEventName
);
2912 if (result
.isErr()) {
2913 outer
->MaybeReject(result
.propagateErr());
2914 outer
.forget(aPromise
);
2918 RefPtr
<ServiceWorkerPrivate::PromiseExtensionWorkerHasListener
> innerPromise
=
2922 GetMainThreadSerialEventTarget(), __func__
,
2923 [outer
](bool aSubscribedEvent
) { outer
->MaybeResolve(aSubscribedEvent
); },
2924 [outer
](nsresult aErrorResult
) { outer
->MaybeReject(aErrorResult
); });
2926 outer
.forget(aPromise
);
2931 ServiceWorkerManager::GetRegistrationByPrincipal(
2932 nsIPrincipal
* aPrincipal
, const nsAString
& aScope
,
2933 nsIServiceWorkerRegistrationInfo
** aInfo
) {
2934 MOZ_ASSERT(aPrincipal
);
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
);
2946 return NS_ERROR_FAILURE
;
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
) {
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
);
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
);
3019 ServiceWorkerManager::GetAllRegistrations(nsIArray
** aResult
) {
3020 MOZ_ASSERT(NS_IsMainThread());
3022 nsCOMPtr
<nsIMutableArray
> array(do_CreateInstance(NS_ARRAY_CONTRACTID
));
3024 return NS_ERROR_OUT_OF_MEMORY
;
3027 for (const auto& info
: mRegistrationInfos
.Values()) {
3028 for (ServiceWorkerRegistrationInfo
* reg
: info
->mInfos
.Values()) {
3031 array
->AppendElement(reg
);
3035 array
.forget(aResult
);
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()) {
3056 MOZ_ASSERT(reg
->Principal());
3058 bool matches
= pattern
.Matches(reg
->Principal()->OriginAttributesRef());
3063 ForceUnregister(data
.get(), reg
);
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
));
3084 aRegistrationData
->mUpdateTimers
.Lookup(aRegistration
->Scope())) {
3085 entry
.Data()->Cancel();
3089 // Since Unregister is async, it is ok to call it in an enumeration.
3090 Unregister(aRegistration
->Principal(), nullptr,
3091 NS_ConvertUTF8toUTF16(aRegistration
->Scope()));
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
);
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
);
3122 ServiceWorkerManager::Observe(nsISupports
* aSubject
, const char* aTopic
,
3123 const char16_t
* aData
) {
3124 if (strcmp(aTopic
, kFinishShutdownTopic
) == 0) {
3125 MaybeFinishShutdown();
3129 MOZ_CRASH("Received message we aren't supposed to be registered for!");
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
3143 if (NS_WARN_IF(!mActor
)) {
3144 return NS_ERROR_FAILURE
;
3147 PrincipalInfo principalInfo
;
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
))) {
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;
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());
3205 Notify(nsITimer
* aTimer
) override
{
3206 MOZ_ASSERT(NS_IsMainThread());
3208 RefPtr
<ServiceWorkerManager
> swm
= ServiceWorkerManager::GetInstance();
3210 // shutting down, do nothing
3214 swm
->UpdateTimerFired(mPrincipal
, mScope
);
3219 GetName(nsACString
& aName
) override
{
3220 aName
.AssignLiteral("UpdateTimerCallback");
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
) {
3239 nsAutoCString scopeKey
;
3240 nsresult rv
= PrincipalToScopeKey(aPrincipal
, scopeKey
);
3241 if (NS_WARN_IF(NS_FAILED(rv
))) {
3245 RegistrationDataPerPrincipal
* data
;
3246 if (!mRegistrationInfos
.Get(scopeKey
, &data
)) {
3250 data
->mUpdateTimers
.WithEntryHandle(
3251 aScope
, [&aPrincipal
, &aScope
](auto&& 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.
3260 nsCOMPtr
<nsITimerCallback
> callback
=
3261 new UpdateTimerCallback(aPrincipal
, aScope
);
3263 const uint32_t UPDATE_DELAY_MS
= 1000;
3265 nsCOMPtr
<nsITimer
> timer
;
3268 NS_NewTimerWithCallback(getter_AddRefs(timer
), callback
,
3269 UPDATE_DELAY_MS
, nsITimer::TYPE_ONE_SHOT
);
3271 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
) {
3289 // First cleanup the timer.
3290 nsAutoCString scopeKey
;
3291 nsresult rv
= PrincipalToScopeKey(aPrincipal
, scopeKey
);
3292 if (NS_WARN_IF(NS_FAILED(rv
))) {
3296 RegistrationDataPerPrincipal
* data
;
3297 if (!mRegistrationInfos
.Get(scopeKey
, &data
)) {
3301 if (auto entry
= data
->mUpdateTimers
.Lookup(aScope
)) {
3302 entry
.Data()->Cancel();
3306 RefPtr
<ServiceWorkerRegistrationInfo
> registration
;
3307 data
->mInfos
.Get(aScope
, getter_AddRefs(registration
));
3308 if (!registration
) {
3312 if (!registration
->CheckAndClearIfUpdateNeeded()) {
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());
3331 PrincipalInfo principalInfo
;
3332 nsresult rv
= PrincipalToPrincipalInfo(aPrincipal
, &principalInfo
);
3333 if (NS_WARN_IF(NS_FAILED(rv
))) {
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