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
, &decision
);
323 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
326 if (NS_WARN_IF(decision
!= nsIContentPolicy::ACCEPT
)) {
327 aRv
.Throw(NS_ERROR_CONTENT_BLOCKED
);
331 // Get the string representation for both the script and scope since
332 // we sanitized them above.
333 nsCString cleanedScopeURL
;
334 aRv
= scopeURI
->GetSpec(cleanedScopeURL
);
339 nsCString cleanedScriptURL
;
340 aRv
= scriptURI
->GetSpec(cleanedScriptURL
);
345 // Verify that the global is valid and has permission to store
346 // data. We perform this late so that we can report the final
347 // scope URL in any error message.
348 Unused
<< GetGlobalIfValid(aRv
, [&](Document
* aDoc
) {
349 AutoTArray
<nsString
, 1> param
;
350 CopyUTF8toUTF16(cleanedScopeURL
, *param
.AppendElement());
351 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
352 "Service Workers"_ns
, aDoc
,
353 nsContentUtils::eDOM_PROPERTIES
,
354 "ServiceWorkerRegisterStorageError", param
);
357 window
->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL
);
359 RefPtr
<Promise
> outer
=
360 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
365 RefPtr
<ServiceWorkerContainer
> self
= this;
368 aRv
.ThrowInvalidStateError("Can't register service worker");
372 mActor
->SendRegister(
373 clientInfo
.ref().ToIPC(), nsCString(cleanedScopeURL
),
374 nsCString(cleanedScriptURL
), aOptions
.mUpdateViaCache
,
376 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
378 if (aResult
.type() ==
379 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
380 TCopyableErrorResult
) {
381 // application layer error
382 CopyableErrorResult rv
= aResult
.get_CopyableErrorResult();
383 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
384 outer
->MaybeReject(std::move(rv
));
388 const auto& ipcDesc
=
389 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
391 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
393 outer
->MaybeReject(std::move(rv
));
396 RefPtr
<ServiceWorkerRegistration
> reg
=
397 global
->GetOrCreateServiceWorkerRegistration(
398 ServiceWorkerRegistrationDescriptor(ipcDesc
));
399 outer
->MaybeResolve(reg
);
401 [outer
](ResponseRejectReason
&& aReason
) {
403 CopyableErrorResult rv
;
404 rv
.ThrowInvalidStateError("Failed to register service worker");
405 outer
->MaybeReject(std::move(rv
));
408 return outer
.forget();
411 already_AddRefed
<ServiceWorker
> ServiceWorkerContainer::GetController() {
412 RefPtr
<ServiceWorker
> ref
= mControllerWorker
;
416 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistrations(
418 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
419 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
420 "Service Workers"_ns
, aDoc
,
421 nsContentUtils::eDOM_PROPERTIES
,
422 "ServiceWorkerGetRegistrationStorageError");
428 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
429 if (clientInfo
.isNothing()) {
430 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
434 RefPtr
<Promise
> outer
=
435 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
440 RefPtr
<ServiceWorkerContainer
> self
= this;
443 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
444 return outer
.forget();
447 mActor
->SendGetRegistrations(
448 clientInfo
.ref().ToIPC(),
450 const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult
&
452 if (aResult
.type() ==
453 IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult::
454 TCopyableErrorResult
) {
455 // application layer error
456 const auto& rv
= aResult
.get_CopyableErrorResult();
457 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
458 outer
->MaybeReject(CopyableErrorResult(rv
));
462 const auto& ipcList
=
463 aResult
.get_IPCServiceWorkerRegistrationDescriptorList();
464 nsTArray
<ServiceWorkerRegistrationDescriptor
> list(
465 ipcList
.values().Length());
466 for (const auto& ipcDesc
: ipcList
.values()) {
467 list
.AppendElement(ServiceWorkerRegistrationDescriptor(ipcDesc
));
471 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
473 outer
->MaybeReject(std::move(rv
));
476 nsTArray
<RefPtr
<ServiceWorkerRegistration
>> regList
;
477 for (auto& desc
: list
) {
478 RefPtr
<ServiceWorkerRegistration
> reg
=
479 global
->GetOrCreateServiceWorkerRegistration(desc
);
481 regList
.AppendElement(std::move(reg
));
484 outer
->MaybeResolve(regList
);
486 [outer
](ResponseRejectReason
&& aReason
) {
488 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
491 return outer
.forget();
494 void ServiceWorkerContainer::StartMessages() {
495 while (!mPendingMessages
.IsEmpty()) {
496 EnqueueReceivedMessageDispatch(mPendingMessages
.ElementAt(0));
497 mPendingMessages
.RemoveElementAt(0);
499 mMessagesStarted
= true;
502 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistration(
503 const nsAString
& aURL
, ErrorResult
& aRv
) {
504 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
505 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
506 "Service Workers"_ns
, aDoc
,
507 nsContentUtils::eDOM_PROPERTIES
,
508 "ServiceWorkerGetRegistrationStorageError");
514 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
515 if (clientInfo
.isNothing()) {
516 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
520 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURIFromGlobal(global
, aRv
);
525 nsCOMPtr
<nsIURI
> uri
;
526 aRv
= NS_NewURI(getter_AddRefs(uri
), aURL
, nullptr, baseURI
);
532 aRv
= uri
->GetSpec(spec
);
537 RefPtr
<Promise
> outer
=
538 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
543 RefPtr
<ServiceWorkerContainer
> self
= this;
546 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
547 return outer
.forget();
550 mActor
->SendGetRegistration(
551 clientInfo
.ref().ToIPC(), spec
,
553 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
555 if (aResult
.type() ==
556 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
557 TCopyableErrorResult
) {
558 CopyableErrorResult
ipcRv(aResult
.get_CopyableErrorResult());
559 ErrorResult
rv(std::move(ipcRv
));
562 // If rv is a failure then this is an application layer error.
563 // Note, though, we also reject with NS_OK to indicate that we just
564 // didn't find a registration.
565 Unused
<< self
->GetGlobalIfValid(rv
);
567 outer
->MaybeResolveWithUndefined();
571 outer
->MaybeReject(std::move(rv
));
575 const auto& ipcDesc
=
576 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
578 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
580 outer
->MaybeReject(std::move(rv
));
583 RefPtr
<ServiceWorkerRegistration
> reg
=
584 global
->GetOrCreateServiceWorkerRegistration(
585 ServiceWorkerRegistrationDescriptor(ipcDesc
));
586 outer
->MaybeResolve(reg
);
588 [self
, outer
](ResponseRejectReason
&& aReason
) {
590 outer
->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR
);
592 return outer
.forget();
595 Promise
* ServiceWorkerContainer::GetReady(ErrorResult
& aRv
) {
597 return mReadyPromise
;
600 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
);
604 MOZ_DIAGNOSTIC_ASSERT(global
);
606 Maybe
<ClientInfo
> clientInfo(global
->GetClientInfo());
607 if (clientInfo
.isNothing()) {
608 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
613 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
618 RefPtr
<ServiceWorkerContainer
> self
= this;
619 RefPtr
<Promise
> outer
= mReadyPromise
;
622 mReadyPromise
->MaybeReject(
623 CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR
));
624 return mReadyPromise
;
627 mActor
->SendGetReady(
628 clientInfo
.ref().ToIPC(),
630 outer
](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult
&
632 if (aResult
.type() ==
633 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
634 TCopyableErrorResult
) {
635 // application layer error
636 CopyableErrorResult
rv(aResult
.get_CopyableErrorResult());
637 MOZ_DIAGNOSTIC_ASSERT(rv
.Failed());
638 outer
->MaybeReject(std::move(rv
));
642 const auto& ipcDesc
=
643 aResult
.get_IPCServiceWorkerRegistrationDescriptor();
645 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
647 outer
->MaybeReject(std::move(rv
));
650 RefPtr
<ServiceWorkerRegistration
> reg
=
651 global
->GetOrCreateServiceWorkerRegistration(
652 ServiceWorkerRegistrationDescriptor(ipcDesc
));
653 NS_ENSURE_TRUE_VOID(reg
);
655 // Don't resolve the ready promise until the registration has
656 // reached the right version. This ensures that the active
657 // worker property is set correctly on the registration.
658 reg
->WhenVersionReached(ipcDesc
.version(), [outer
, reg
](bool aResult
) {
659 outer
->MaybeResolve(reg
);
662 [outer
](ResponseRejectReason
&& aReason
) {
664 outer
->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR
));
667 return mReadyPromise
;
671 void ServiceWorkerContainer::GetScopeForUrl(const nsAString
& aUrl
,
674 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
675 mozilla::components::ServiceWorkerManager::Service();
677 aRv
.Throw(NS_ERROR_FAILURE
);
681 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
682 if (NS_WARN_IF(!window
)) {
683 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
687 nsCOMPtr
<nsIPrincipal
> principal
;
688 nsresult rv
= StoragePrincipalHelper::GetPrincipal(
690 StaticPrefs::privacy_partition_serviceWorkers()
691 ? StoragePrincipalHelper::eForeignPartitionedPrincipal
692 : StoragePrincipalHelper::eRegularPrincipal
,
693 getter_AddRefs(principal
));
695 if (NS_WARN_IF(NS_FAILED(rv
))) {
700 aRv
= swm
->GetScopeForUrl(principal
, aUrl
, aScope
);
703 nsIGlobalObject
* ServiceWorkerContainer::GetGlobalIfValid(
705 const std::function
<void(Document
*)>&& aStorageFailureCB
) const {
706 // For now we require a window since ServiceWorkerContainer is
707 // not exposed on worker globals yet. The main thing we need
708 // to fix here to support that is the storage access check via
709 // the nsIGlobalObject.
710 nsPIDOMWindowInner
* window
= GetOwner();
711 if (NS_WARN_IF(!window
)) {
712 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
716 nsCOMPtr
<Document
> doc
= window
->GetExtantDoc();
717 if (NS_WARN_IF(!doc
)) {
718 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
722 // Don't allow a service worker to access service worker registrations
723 // from a window with storage disabled. If these windows can access
724 // the registration it increases the chance they can bypass the storage
725 // block via postMessage(), etc.
726 auto storageAllowed
= StorageAllowedForWindow(window
);
727 if (NS_WARN_IF(storageAllowed
!= StorageAccess::eAllow
&&
728 (!StaticPrefs::privacy_partition_serviceWorkers() ||
729 !StoragePartitioningEnabled(storageAllowed
,
730 doc
->CookieJarSettings())))) {
731 if (aStorageFailureCB
) {
732 aStorageFailureCB(doc
);
734 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
738 // Don't allow service workers when the document is chrome.
739 if (NS_WARN_IF(doc
->NodePrincipal()->IsSystemPrincipal())) {
740 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
744 return window
->AsGlobal();
747 void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
748 RefPtr
<ReceivedMessage
> aMessage
) {
749 NS_DispatchToMainThread(NewRunnableMethod
<RefPtr
<ReceivedMessage
>>(
750 "ServiceWorkerContainer::DispatchMessage", this,
751 &ServiceWorkerContainer::DispatchMessage
, std::move(aMessage
)));
754 template <typename F
>
755 void ServiceWorkerContainer::RunWithJSContext(F
&& aCallable
) {
756 nsCOMPtr
<nsIGlobalObject
> globalObject
;
757 if (nsPIDOMWindowInner
* const window
= GetOwner()) {
758 globalObject
= do_QueryInterface(window
);
761 // If AutoJSAPI::Init() fails then either global is nullptr or not
762 // in a usable state.
764 if (!jsapi
.Init(globalObject
)) {
768 aCallable(jsapi
.cx(), globalObject
);
771 void ServiceWorkerContainer::DispatchMessage(RefPtr
<ReceivedMessage
> aMessage
) {
772 MOZ_ASSERT(NS_IsMainThread());
774 // When dispatching a message, either DOMContentLoaded has already
775 // been fired, or someone called startMessages() or set onmessage.
776 // Either way, a global object is supposed to be present. If it's
777 // not, we'd fail to initialize the JS API and exit.
778 RunWithJSContext([this, message
= std::move(aMessage
)](
779 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
) {
781 bool deserializationFailed
= false;
782 RootedDictionary
<MessageEventInit
> init(aCx
);
783 auto res
= FillInMessageEventInit(aCx
, aGlobal
, *message
, init
, result
);
785 deserializationFailed
= res
.unwrapErr();
786 MOZ_ASSERT_IF(deserializationFailed
, init
.mData
.isNull());
787 MOZ_ASSERT_IF(deserializationFailed
, init
.mPorts
.IsEmpty());
788 MOZ_ASSERT_IF(deserializationFailed
, !init
.mOrigin
.IsEmpty());
789 MOZ_ASSERT_IF(deserializationFailed
, !init
.mSource
.IsNull());
790 result
.SuppressException();
792 if (!deserializationFailed
&& result
.MaybeSetPendingException(aCx
)) {
797 RefPtr
<MessageEvent
> event
= MessageEvent::Constructor(
798 this, deserializationFailed
? u
"messageerror"_ns
: u
"message"_ns
, init
);
799 event
->SetTrusted(true);
802 DispatchEvent(*event
, result
);
803 if (result
.Failed()) {
804 result
.SuppressException();
811 nsresult
FillInOriginNoSuffix(const ServiceWorkerDescriptor
& aServiceWorker
,
813 using mozilla::ipc::PrincipalInfoToPrincipal
;
817 auto principalOrErr
=
818 PrincipalInfoToPrincipal(aServiceWorker
.PrincipalInfo());
819 if (NS_WARN_IF(principalOrErr
.isErr())) {
820 return principalOrErr
.unwrapErr();
823 nsAutoCString originUTF8
;
824 rv
= principalOrErr
.unwrap()->GetOriginNoSuffix(originUTF8
);
829 CopyUTF8toUTF16(originUTF8
, aOrigin
);
835 Result
<Ok
, bool> ServiceWorkerContainer::FillInMessageEventInit(
836 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
,
837 ReceivedMessage
& aMessage
, MessageEventInit
& aInit
, ErrorResult
& aRv
) {
838 // Determining the source and origin should preceed attempting deserialization
839 // because on a "messageerror" event (i.e. when deserialization fails), the
840 // dispatched message needs to contain such an origin and source, per spec:
842 // "If this throws an exception, catch it, fire an event named messageerror
843 // at destination, using MessageEvent, with the origin attribute initialized
844 // to origin and the source attribute initialized to source, and then abort
845 // these steps." - 6.4 of postMessage
846 // See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage
847 const RefPtr
<ServiceWorker
> serviceWorkerInstance
=
848 aGlobal
->GetOrCreateServiceWorker(aMessage
.mServiceWorker
);
849 if (serviceWorkerInstance
) {
850 aInit
.mSource
.SetValue().SetAsServiceWorker() = serviceWorkerInstance
;
854 FillInOriginNoSuffix(aMessage
.mServiceWorker
, aInit
.mOrigin
);
859 JS::Rooted
<JS::Value
> messageData(aCx
);
860 aMessage
.mClonedData
.Read(aCx
, &messageData
, aRv
);
865 aInit
.mData
= messageData
;
867 if (!aMessage
.mClonedData
.TakeTransferredPortsAsSequence(aInit
.mPorts
)) {
868 xpc::Throw(aCx
, NS_ERROR_OUT_OF_MEMORY
);
875 void ServiceWorkerContainer::Shutdown() {
882 mActor
->RevokeOwner(this);
883 mActor
->MaybeStartTeardown();
888 } // namespace mozilla::dom