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 "ServiceWorkerContainer.h"
9 #include "nsContentPolicyUtils.h"
10 #include "nsContentSecurityManager.h"
11 #include "nsContentUtils.h"
12 #include "mozilla/dom/Document.h"
13 #include "nsIServiceWorkerManager.h"
14 #include "nsIScriptError.h"
15 #include "nsThreadUtils.h"
16 #include "nsNetUtil.h"
17 #include "nsPIDOMWindow.h"
18 #include "mozilla/Components.h"
19 #include "mozilla/StaticPrefs_dom.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsServiceManagerUtils.h"
23 #include "mozilla/LoadInfo.h"
24 #include "mozilla/SchedulerGroup.h"
25 #include "mozilla/StaticPrefs_extensions.h"
26 #include "mozilla/StaticPrefs_privacy.h"
27 #include "mozilla/StorageAccess.h"
28 #include "mozilla/StoragePrincipalHelper.h"
29 #include "mozilla/dom/ClientIPCTypes.h"
30 #include "mozilla/dom/DOMMozPromiseRequestHolder.h"
31 #include "mozilla/dom/MessageEvent.h"
32 #include "mozilla/dom/MessageEventBinding.h"
33 #include "mozilla/dom/Navigator.h"
34 #include "mozilla/dom/Promise.h"
35 #include "mozilla/dom/RootedDictionary.h"
36 #include "mozilla/dom/ServiceWorker.h"
37 #include "mozilla/dom/ServiceWorkerContainerBinding.h"
38 #include "mozilla/dom/ServiceWorkerContainerChild.h"
39 #include "mozilla/dom/ServiceWorkerManager.h"
40 #include "mozilla/dom/ipc/StructuredCloneData.h"
41 #include "mozilla/ipc/BackgroundChild.h"
42 #include "mozilla/ipc/PBackgroundChild.h"
44 #include "ServiceWorker.h"
45 #include "ServiceWorkerRegistration.h"
46 #include "ServiceWorkerUtils.h"
48 // This is defined to something else on Windows
49 #ifdef DispatchMessage
50 # undef DispatchMessage
53 namespace mozilla::dom
{
55 using mozilla::ipc::BackgroundChild
;
56 using mozilla::ipc::PBackgroundChild
;
57 using mozilla::ipc::ResponseRejectReason
;
59 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer
)
60 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
62 NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
)
63 NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
)
65 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
,
66 mControllerWorker
, mReadyPromise
)
69 already_AddRefed
<ServiceWorkerContainer
> ServiceWorkerContainer::Create(
70 nsIGlobalObject
* aGlobal
) {
71 RefPtr
<ServiceWorkerContainer
> ref
= new ServiceWorkerContainer(aGlobal
);
75 ServiceWorkerContainer::ServiceWorkerContainer(nsIGlobalObject
* aGlobal
)
76 : DOMEventTargetHelper(aGlobal
), mShutdown(false) {
77 PBackgroundChild
* parentActor
=
78 BackgroundChild::GetOrCreateForCurrentThread();
79 if (NS_WARN_IF(!parentActor
)) {
84 RefPtr
<ServiceWorkerContainerChild
> actor
=
85 ServiceWorkerContainerChild::Create();
86 if (NS_WARN_IF(!actor
)) {
91 PServiceWorkerContainerChild
* sentActor
=
92 parentActor
->SendPServiceWorkerContainerConstructor(actor
);
93 if (NS_WARN_IF(!sentActor
)) {
97 MOZ_DIAGNOSTIC_ASSERT(sentActor
== actor
);
99 mActor
= std::move(actor
);
100 mActor
->SetOwner(this);
102 Maybe
<ServiceWorkerDescriptor
> controller
= aGlobal
->GetController();
103 if (controller
.isSome()) {
104 mControllerWorker
= aGlobal
->GetOrCreateServiceWorker(controller
.ref());
108 ServiceWorkerContainer::~ServiceWorkerContainer() { Shutdown(); }
110 void ServiceWorkerContainer::DisconnectFromOwner() {
111 mControllerWorker
= nullptr;
112 mReadyPromise
= nullptr;
113 DOMEventTargetHelper::DisconnectFromOwner();
116 void ServiceWorkerContainer::ControllerChanged(ErrorResult
& aRv
) {
117 nsCOMPtr
<nsIGlobalObject
> go
= GetParentObject();
119 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
122 mControllerWorker
= go
->GetOrCreateServiceWorker(go
->GetController().ref());
123 aRv
= DispatchTrustedEvent(u
"controllerchange"_ns
);
126 using mozilla::dom::ipc::StructuredCloneData
;
128 // A ReceivedMessage represents a message sent via
129 // Client.postMessage(). It is used as used both for queuing of
130 // incoming messages and as an interface to DispatchMessage().
131 struct MOZ_HEAP_CLASS
ServiceWorkerContainer::ReceivedMessage
{
132 explicit ReceivedMessage(const ClientPostMessageArgs
& aArgs
)
133 : mServiceWorker(aArgs
.serviceWorker()) {
134 mClonedData
.CopyFromClonedMessageData(aArgs
.clonedData());
137 ServiceWorkerDescriptor mServiceWorker
;
138 StructuredCloneData mClonedData
;
140 NS_INLINE_DECL_REFCOUNTING(ReceivedMessage
)
143 ~ReceivedMessage() = default;
146 void ServiceWorkerContainer::ReceiveMessage(
147 const ClientPostMessageArgs
& aArgs
) {
148 RefPtr
<ReceivedMessage
> message
= new ReceivedMessage(aArgs
);
149 if (mMessagesStarted
) {
150 EnqueueReceivedMessageDispatch(std::move(message
));
152 mPendingMessages
.AppendElement(message
.forget());
156 void ServiceWorkerContainer::RevokeActor(ServiceWorkerContainerChild
* aActor
) {
157 MOZ_DIAGNOSTIC_ASSERT(mActor
);
158 MOZ_DIAGNOSTIC_ASSERT(mActor
== aActor
);
159 mActor
->RevokeOwner(this);
165 JSObject
* ServiceWorkerContainer::WrapObject(
166 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
167 return ServiceWorkerContainer_Binding::Wrap(aCx
, this, aGivenProto
);
172 already_AddRefed
<nsIURI
> GetBaseURIFromGlobal(nsIGlobalObject
* aGlobal
,
174 // It would be nice not to require a window here, but right
175 // now we don't have a great way to get the base URL just
176 // from the nsIGlobalObject.
177 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aGlobal
);
179 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
183 Document
* doc
= window
->GetExtantDoc();
185 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
189 nsCOMPtr
<nsIURI
> baseURI
= doc
->GetDocBaseURI();
191 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
195 return baseURI
.forget();
198 } // anonymous namespace
200 already_AddRefed
<Promise
> ServiceWorkerContainer::Register(
201 const nsAString
& aScriptURL
, const RegistrationOptions
& aOptions
,
202 const CallerType aCallerType
, ErrorResult
& aRv
) {
203 // Note, we can't use GetGlobalIfValid() from the start here. If we
204 // hit a storage failure we want to log a message with the final
205 // scope string we put together below.
206 nsIGlobalObject
* global
= GetParentObject();
208 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
212 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
213 if (clientInfo
.isNothing()) {
214 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
218 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURIFromGlobal(global
, aRv
);
223 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
224 nsAutoCString scriptURL
;
225 if (!AppendUTF16toUTF8(aScriptURL
, scriptURL
, fallible
)) {
226 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
230 nsCOMPtr
<nsIURI
> scriptURI
;
232 NS_NewURI(getter_AddRefs(scriptURI
), scriptURL
, nullptr, baseURI
);
233 if (NS_WARN_IF(NS_FAILED(rv
))) {
234 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(scriptURL
);
238 // Never allow script URL with moz-extension scheme if support is fully
239 // disabled by the 'extensions.background_service_worker.enabled' pref.
240 if (scriptURI
->SchemeIs("moz-extension") &&
241 !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
242 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
246 // In ServiceWorkerContainer.register() the scope argument is parsed against
247 // different base URLs depending on whether it was passed or not.
248 nsCOMPtr
<nsIURI
> scopeURI
;
250 // Step 4. If none passed, parse against script's URL
251 if (!aOptions
.mScope
.WasPassed()) {
252 constexpr auto defaultScope
= "./"_ns
;
253 rv
= NS_NewURI(getter_AddRefs(scopeURI
), defaultScope
, nullptr, scriptURI
);
254 if (NS_WARN_IF(NS_FAILED(rv
))) {
256 scriptURI
->GetSpec(spec
);
257 aRv
.ThrowTypeError
<MSG_INVALID_SCOPE
>(defaultScope
, spec
);
261 // Step 5. Parse against entry settings object's base URL.
262 rv
= NS_NewURI(getter_AddRefs(scopeURI
), aOptions
.mScope
.Value(), nullptr,
264 if (NS_WARN_IF(NS_FAILED(rv
))) {
265 nsIURI
* uri
= baseURI
? baseURI
: scriptURI
;
268 aRv
.ThrowTypeError
<MSG_INVALID_SCOPE
>(
269 NS_ConvertUTF16toUTF8(aOptions
.mScope
.Value()), spec
);
274 // Strip the any ref from both the script and scope URLs.
275 nsCOMPtr
<nsIURI
> cloneWithoutRef
;
276 aRv
= NS_GetURIWithoutRef(scriptURI
, getter_AddRefs(cloneWithoutRef
));
280 scriptURI
= std::move(cloneWithoutRef
);
282 aRv
= NS_GetURIWithoutRef(scopeURI
, getter_AddRefs(cloneWithoutRef
));
286 scopeURI
= std::move(cloneWithoutRef
);
288 ServiceWorkerScopeAndScriptAreValid(clientInfo
.ref(), scopeURI
, scriptURI
,
294 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
296 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
300 Document
* doc
= window
->GetExtantDoc();
302 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
306 // The next section of code executes an NS_CheckContentLoadPolicy()
307 // check. This is necessary to enforce the CSP of the calling client.
308 // Currently this requires an Document. Once bug 965637 lands we
309 // should try to move this into ServiceWorkerScopeAndScriptAreValid()
310 // using the ClientInfo instead of doing a window-specific check here.
311 // See bug 1455077 for further investigation.
312 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new mozilla::net::LoadInfo(
313 doc
->NodePrincipal(), // loading principal
314 doc
->NodePrincipal(), // triggering principal
316 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
,
317 nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
);
319 // Check content policy.
320 int16_t decision
= nsIContentPolicy::ACCEPT
;
321 rv
= NS_CheckContentLoadPolicy(scriptURI
, secCheckLoadInfo
,
322 "application/javascript"_ns
, &decision
);
324 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
327 if (NS_WARN_IF(decision
!= nsIContentPolicy::ACCEPT
)) {
328 aRv
.Throw(NS_ERROR_CONTENT_BLOCKED
);
332 // Get the string representation for both the script and scope since
333 // we sanitized them above.
334 nsCString cleanedScopeURL
;
335 aRv
= scopeURI
->GetSpec(cleanedScopeURL
);
340 nsCString cleanedScriptURL
;
341 aRv
= scriptURI
->GetSpec(cleanedScriptURL
);
346 // Verify that the global is valid and has permission to store
347 // data. We perform this late so that we can report the final
348 // scope URL in any error message.
349 Unused
<< GetGlobalIfValid(aRv
, [&](Document
* aDoc
) {
350 AutoTArray
<nsString
, 1> param
;
351 CopyUTF8toUTF16(cleanedScopeURL
, *param
.AppendElement());
352 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
353 "Service Workers"_ns
, aDoc
,
354 nsContentUtils::eDOM_PROPERTIES
,
355 "ServiceWorkerRegisterStorageError", param
);
358 window
->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL
);
360 RefPtr
<Promise
> outer
=
361 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
366 RefPtr
<ServiceWorkerContainer
> self
= this;
369 aRv
.ThrowInvalidStateError("Can't register service worker");
373 mActor
->SendRegister(
374 clientInfo
.ref().ToIPC(), nsCString(cleanedScopeURL
),
375 nsCString(cleanedScriptURL
), aOptions
.mUpdateViaCache
,
377 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
379 if (aResult
.type() ==
380 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
381 TCopyableErrorResult
) {
382 // application layer error
383 CopyableErrorResult rv
= aResult
.get_CopyableErrorResult();
384 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
385 outer
->MaybeReject(std::move(rv
));
389 const auto& ipcDesc
=
390 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
392 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
394 outer
->MaybeReject(std::move(rv
));
397 RefPtr
<ServiceWorkerRegistration
> reg
=
398 global
->GetOrCreateServiceWorkerRegistration(
399 ServiceWorkerRegistrationDescriptor(ipcDesc
));
400 outer
->MaybeResolve(reg
);
402 [outer
](ResponseRejectReason
&& aReason
) {
404 CopyableErrorResult rv
;
405 rv
.ThrowInvalidStateError("Failed to register service worker");
406 outer
->MaybeReject(std::move(rv
));
409 return outer
.forget();
412 already_AddRefed
<ServiceWorker
> ServiceWorkerContainer::GetController() {
413 RefPtr
<ServiceWorker
> ref
= mControllerWorker
;
417 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistrations(
419 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
420 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
421 "Service Workers"_ns
, aDoc
,
422 nsContentUtils::eDOM_PROPERTIES
,
423 "ServiceWorkerGetRegistrationStorageError");
429 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
430 if (clientInfo
.isNothing()) {
431 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
435 RefPtr
<Promise
> outer
=
436 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
441 RefPtr
<ServiceWorkerContainer
> self
= this;
444 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
445 return outer
.forget();
448 mActor
->SendGetRegistrations(
449 clientInfo
.ref().ToIPC(),
451 const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult
&
453 if (aResult
.type() ==
454 IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult::
455 TCopyableErrorResult
) {
456 // application layer error
457 const auto& rv
= aResult
.get_CopyableErrorResult();
458 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
459 outer
->MaybeReject(CopyableErrorResult(rv
));
463 const auto& ipcList
=
464 aResult
.get_IPCServiceWorkerRegistrationDescriptorList();
465 nsTArray
<ServiceWorkerRegistrationDescriptor
> list(
466 ipcList
.values().Length());
467 for (const auto& ipcDesc
: ipcList
.values()) {
468 list
.AppendElement(ServiceWorkerRegistrationDescriptor(ipcDesc
));
472 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
474 outer
->MaybeReject(std::move(rv
));
477 nsTArray
<RefPtr
<ServiceWorkerRegistration
>> regList
;
478 for (auto& desc
: list
) {
479 RefPtr
<ServiceWorkerRegistration
> reg
=
480 global
->GetOrCreateServiceWorkerRegistration(desc
);
482 regList
.AppendElement(std::move(reg
));
485 outer
->MaybeResolve(regList
);
487 [outer
](ResponseRejectReason
&& aReason
) {
489 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
492 return outer
.forget();
495 void ServiceWorkerContainer::StartMessages() {
496 while (!mPendingMessages
.IsEmpty()) {
497 EnqueueReceivedMessageDispatch(mPendingMessages
.ElementAt(0));
498 mPendingMessages
.RemoveElementAt(0);
500 mMessagesStarted
= true;
503 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistration(
504 const nsAString
& aURL
, ErrorResult
& aRv
) {
505 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
506 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
507 "Service Workers"_ns
, aDoc
,
508 nsContentUtils::eDOM_PROPERTIES
,
509 "ServiceWorkerGetRegistrationStorageError");
515 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
516 if (clientInfo
.isNothing()) {
517 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
521 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURIFromGlobal(global
, aRv
);
526 nsCOMPtr
<nsIURI
> uri
;
527 aRv
= NS_NewURI(getter_AddRefs(uri
), aURL
, nullptr, baseURI
);
533 aRv
= uri
->GetSpec(spec
);
538 RefPtr
<Promise
> outer
=
539 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
544 RefPtr
<ServiceWorkerContainer
> self
= this;
547 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
548 return outer
.forget();
551 mActor
->SendGetRegistration(
552 clientInfo
.ref().ToIPC(), spec
,
554 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
556 if (aResult
.type() ==
557 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
558 TCopyableErrorResult
) {
559 CopyableErrorResult
ipcRv(aResult
.get_CopyableErrorResult());
560 ErrorResult
rv(std::move(ipcRv
));
563 // If rv is a failure then this is an application layer error.
564 // Note, though, we also reject with NS_OK to indicate that we just
565 // didn't find a registration.
566 Unused
<< self
->GetGlobalIfValid(rv
);
568 outer
->MaybeResolveWithUndefined();
572 outer
->MaybeReject(std::move(rv
));
576 const auto& ipcDesc
=
577 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
579 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
581 outer
->MaybeReject(std::move(rv
));
584 RefPtr
<ServiceWorkerRegistration
> reg
=
585 global
->GetOrCreateServiceWorkerRegistration(
586 ServiceWorkerRegistrationDescriptor(ipcDesc
));
587 outer
->MaybeResolve(reg
);
589 [self
, outer
](ResponseRejectReason
&& aReason
) {
591 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
593 return outer
.forget();
596 Promise
* ServiceWorkerContainer::GetReady(ErrorResult
& aRv
) {
598 return mReadyPromise
;
601 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
);
605 MOZ_DIAGNOSTIC_ASSERT(global
);
607 Maybe
<ClientInfo
> clientInfo(global
->GetClientInfo());
608 if (clientInfo
.isNothing()) {
609 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
614 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
619 RefPtr
<ServiceWorkerContainer
> self
= this;
620 RefPtr
<Promise
> outer
= mReadyPromise
;
623 mReadyPromise
->MaybeReject(
624 CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR
));
625 return mReadyPromise
;
628 mActor
->SendGetReady(
629 clientInfo
.ref().ToIPC(),
631 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
633 if (aResult
.type() ==
634 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
635 TCopyableErrorResult
) {
636 // application layer error
637 CopyableErrorResult
rv(aResult
.get_CopyableErrorResult());
638 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
639 outer
->MaybeReject(std::move(rv
));
643 const auto& ipcDesc
=
644 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
646 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
648 outer
->MaybeReject(std::move(rv
));
651 RefPtr
<ServiceWorkerRegistration
> reg
=
652 global
->GetOrCreateServiceWorkerRegistration(
653 ServiceWorkerRegistrationDescriptor(ipcDesc
));
654 NS_ENSURE_TRUE_VOID(reg
);
656 // Don't resolve the ready promise until the registration has
657 // reached the right version. This ensures that the active
658 // worker property is set correctly on the registration.
659 reg
->WhenVersionReached(ipcDesc
.version(), [outer
, reg
](bool aResult
) {
660 outer
->MaybeResolve(reg
);
663 [outer
](ResponseRejectReason
&& aReason
) {
665 outer
->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR
));
668 return mReadyPromise
;
672 void ServiceWorkerContainer::GetScopeForUrl(const nsAString
& aUrl
,
675 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
676 mozilla::components::ServiceWorkerManager::Service();
678 aRv
.Throw(NS_ERROR_FAILURE
);
682 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
683 if (NS_WARN_IF(!window
)) {
684 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
688 nsCOMPtr
<nsIPrincipal
> principal
;
689 nsresult rv
= StoragePrincipalHelper::GetPrincipal(
691 StaticPrefs::privacy_partition_serviceWorkers()
692 ? StoragePrincipalHelper::eForeignPartitionedPrincipal
693 : StoragePrincipalHelper::eRegularPrincipal
,
694 getter_AddRefs(principal
));
696 if (NS_WARN_IF(NS_FAILED(rv
))) {
701 aRv
= swm
->GetScopeForUrl(principal
, aUrl
, aScope
);
704 nsIGlobalObject
* ServiceWorkerContainer::GetGlobalIfValid(
706 const std::function
<void(Document
*)>&& aStorageFailureCB
) const {
707 // For now we require a window since ServiceWorkerContainer is
708 // not exposed on worker globals yet. The main thing we need
709 // to fix here to support that is the storage access check via
710 // the nsIGlobalObject.
711 nsPIDOMWindowInner
* window
= GetOwner();
712 if (NS_WARN_IF(!window
)) {
713 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
717 nsCOMPtr
<Document
> doc
= window
->GetExtantDoc();
718 if (NS_WARN_IF(!doc
)) {
719 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
723 // Don't allow a service worker to access service worker registrations
724 // from a window with storage disabled. If these windows can access
725 // the registration it increases the chance they can bypass the storage
726 // block via postMessage(), etc.
727 auto storageAllowed
= StorageAllowedForWindow(window
);
728 if (NS_WARN_IF(storageAllowed
!= StorageAccess::eAllow
&&
729 (!StaticPrefs::privacy_partition_serviceWorkers() ||
730 !StoragePartitioningEnabled(storageAllowed
,
731 doc
->CookieJarSettings())))) {
732 if (aStorageFailureCB
) {
733 aStorageFailureCB(doc
);
735 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
739 // Don't allow service workers when the document is chrome.
740 if (NS_WARN_IF(doc
->NodePrincipal()->IsSystemPrincipal())) {
741 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
745 return window
->AsGlobal();
748 void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
749 RefPtr
<ReceivedMessage
> aMessage
) {
750 if (nsPIDOMWindowInner
* const window
= GetOwner()) {
751 if (auto* const target
= window
->EventTargetFor(TaskCategory::Other
)) {
752 target
->Dispatch(NewRunnableMethod
<RefPtr
<ReceivedMessage
>>(
753 "ServiceWorkerContainer::DispatchMessage", this,
754 &ServiceWorkerContainer::DispatchMessage
, std::move(aMessage
)));
759 template <typename F
>
760 void ServiceWorkerContainer::RunWithJSContext(F
&& aCallable
) {
761 nsCOMPtr
<nsIGlobalObject
> globalObject
;
762 if (nsPIDOMWindowInner
* const window
= GetOwner()) {
763 globalObject
= do_QueryInterface(window
);
766 // If AutoJSAPI::Init() fails then either global is nullptr or not
767 // in a usable state.
769 if (!jsapi
.Init(globalObject
)) {
773 aCallable(jsapi
.cx(), globalObject
);
776 void ServiceWorkerContainer::DispatchMessage(RefPtr
<ReceivedMessage
> aMessage
) {
777 MOZ_ASSERT(NS_IsMainThread());
779 // When dispatching a message, either DOMContentLoaded has already
780 // been fired, or someone called startMessages() or set onmessage.
781 // Either way, a global object is supposed to be present. If it's
782 // not, we'd fail to initialize the JS API and exit.
783 RunWithJSContext([this, message
= std::move(aMessage
)](
784 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
) {
786 bool deserializationFailed
= false;
787 RootedDictionary
<MessageEventInit
> init(aCx
);
788 auto res
= FillInMessageEventInit(aCx
, aGlobal
, *message
, init
, result
);
790 deserializationFailed
= res
.unwrapErr();
791 MOZ_ASSERT_IF(deserializationFailed
, init
.mData
.isNull());
792 MOZ_ASSERT_IF(deserializationFailed
, init
.mPorts
.IsEmpty());
793 MOZ_ASSERT_IF(deserializationFailed
, !init
.mOrigin
.IsEmpty());
794 MOZ_ASSERT_IF(deserializationFailed
, !init
.mSource
.IsNull());
795 result
.SuppressException();
797 if (!deserializationFailed
&& result
.MaybeSetPendingException(aCx
)) {
802 RefPtr
<MessageEvent
> event
= MessageEvent::Constructor(
803 this, deserializationFailed
? u
"messageerror"_ns
: u
"message"_ns
, init
);
804 event
->SetTrusted(true);
807 DispatchEvent(*event
, result
);
808 if (result
.Failed()) {
809 result
.SuppressException();
816 nsresult
FillInOriginNoSuffix(const ServiceWorkerDescriptor
& aServiceWorker
,
818 using mozilla::ipc::PrincipalInfoToPrincipal
;
822 auto principalOrErr
=
823 PrincipalInfoToPrincipal(aServiceWorker
.PrincipalInfo());
824 if (NS_WARN_IF(principalOrErr
.isErr())) {
825 return principalOrErr
.unwrapErr();
828 nsAutoCString originUTF8
;
829 rv
= principalOrErr
.unwrap()->GetOriginNoSuffix(originUTF8
);
834 CopyUTF8toUTF16(originUTF8
, aOrigin
);
840 Result
<Ok
, bool> ServiceWorkerContainer::FillInMessageEventInit(
841 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
,
842 ReceivedMessage
& aMessage
, MessageEventInit
& aInit
, ErrorResult
& aRv
) {
843 // Determining the source and origin should preceed attempting deserialization
844 // because on a "messageerror" event (i.e. when deserialization fails), the
845 // dispatched message needs to contain such an origin and source, per spec:
847 // "If this throws an exception, catch it, fire an event named messageerror
848 // at destination, using MessageEvent, with the origin attribute initialized
849 // to origin and the source attribute initialized to source, and then abort
850 // these steps." - 6.4 of postMessage
851 // See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage
852 const RefPtr
<ServiceWorker
> serviceWorkerInstance
=
853 aGlobal
->GetOrCreateServiceWorker(aMessage
.mServiceWorker
);
854 if (serviceWorkerInstance
) {
855 aInit
.mSource
.SetValue().SetAsServiceWorker() = serviceWorkerInstance
;
859 FillInOriginNoSuffix(aMessage
.mServiceWorker
, aInit
.mOrigin
);
864 JS::Rooted
<JS::Value
> messageData(aCx
);
865 aMessage
.mClonedData
.Read(aCx
, &messageData
, aRv
);
870 aInit
.mData
= messageData
;
872 if (!aMessage
.mClonedData
.TakeTransferredPortsAsSequence(aInit
.mPorts
)) {
873 xpc::Throw(aCx
, NS_ERROR_OUT_OF_MEMORY
);
880 void ServiceWorkerContainer::Shutdown() {
887 mActor
->RevokeOwner(this);
888 mActor
->MaybeStartTeardown();
893 } // namespace mozilla::dom