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/Services.h"
19 #include "mozilla/StaticPrefs_dom.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsServiceManagerUtils.h"
23 #include "mozilla/BasePrincipal.h"
24 #include "mozilla/LoadInfo.h"
25 #include "mozilla/SchedulerGroup.h"
26 #include "mozilla/StaticPrefs_extensions.h"
27 #include "mozilla/StorageAccess.h"
28 #include "mozilla/dom/ClientIPCTypes.h"
29 #include "mozilla/dom/DOMMozPromiseRequestHolder.h"
30 #include "mozilla/dom/MessageEvent.h"
31 #include "mozilla/dom/MessageEventBinding.h"
32 #include "mozilla/dom/Navigator.h"
33 #include "mozilla/dom/Promise.h"
34 #include "mozilla/dom/ServiceWorker.h"
35 #include "mozilla/dom/ServiceWorkerContainerBinding.h"
36 #include "mozilla/dom/ServiceWorkerManager.h"
37 #include "mozilla/dom/ipc/StructuredCloneData.h"
39 #include "RemoteServiceWorkerContainerImpl.h"
40 #include "ServiceWorker.h"
41 #include "ServiceWorkerContainerImpl.h"
42 #include "ServiceWorkerRegistration.h"
43 #include "ServiceWorkerUtils.h"
45 // This is defined to something else on Windows
46 #ifdef DispatchMessage
47 # undef DispatchMessage
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer
)
54 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
56 NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
)
57 NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
)
59 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer
, DOMEventTargetHelper
,
60 mControllerWorker
, mReadyPromise
)
64 bool IsInPrivateBrowsing(JSContext
* const aCx
) {
65 if (const nsCOMPtr
<nsIGlobalObject
> global
= xpc::CurrentNativeGlobal(aCx
)) {
66 if (const nsCOMPtr
<nsIPrincipal
> principal
= global
->PrincipalOrNull()) {
67 return principal
->GetPrivateBrowsingId() > 0;
73 bool IsServiceWorkersTestingEnabledInWindow(JSObject
* const aGlobal
) {
74 if (const nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
75 Navigator::GetWindowFromGlobal(aGlobal
)) {
76 if (const nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
=
77 innerWindow
->GetOuterWindow()) {
78 return outerWindow
->GetServiceWorkersTestingEnabled();
87 bool ServiceWorkerContainer::IsEnabled(JSContext
* aCx
, JSObject
* aGlobal
) {
88 MOZ_ASSERT(NS_IsMainThread());
90 JS::Rooted
<JSObject
*> global(aCx
, aGlobal
);
92 if (!StaticPrefs::dom_serviceWorkers_enabled()) {
96 if (IsInPrivateBrowsing(aCx
)) {
100 if (IsSecureContextOrObjectIsFromSecureContext(aCx
, global
)) {
104 const bool isTestingEnabledInWindow
=
105 IsServiceWorkersTestingEnabledInWindow(global
);
106 const bool isTestingEnabledByPref
=
107 StaticPrefs::dom_serviceWorkers_testing_enabled();
108 const bool isTestingEnabled
=
109 isTestingEnabledByPref
|| isTestingEnabledInWindow
;
111 return isTestingEnabled
;
115 already_AddRefed
<ServiceWorkerContainer
> ServiceWorkerContainer::Create(
116 nsIGlobalObject
* aGlobal
) {
118 if (ServiceWorkerParentInterceptEnabled()) {
119 inner
= new RemoteServiceWorkerContainerImpl();
121 inner
= new ServiceWorkerContainerImpl();
123 NS_ENSURE_TRUE(inner
, nullptr);
125 RefPtr
<ServiceWorkerContainer
> ref
=
126 new ServiceWorkerContainer(aGlobal
, inner
.forget());
130 ServiceWorkerContainer::ServiceWorkerContainer(
131 nsIGlobalObject
* aGlobal
,
132 already_AddRefed
<ServiceWorkerContainer::Inner
> aInner
)
133 : DOMEventTargetHelper(aGlobal
), mInner(aInner
) {
134 mInner
->AddContainer(this);
135 Maybe
<ServiceWorkerDescriptor
> controller
= aGlobal
->GetController();
136 if (controller
.isSome()) {
137 mControllerWorker
= aGlobal
->GetOrCreateServiceWorker(controller
.ref());
141 ServiceWorkerContainer::~ServiceWorkerContainer() {
142 mInner
->RemoveContainer(this);
145 void ServiceWorkerContainer::DisconnectFromOwner() {
146 mControllerWorker
= nullptr;
147 mReadyPromise
= nullptr;
148 DOMEventTargetHelper::DisconnectFromOwner();
151 void ServiceWorkerContainer::ControllerChanged(ErrorResult
& aRv
) {
152 nsCOMPtr
<nsIGlobalObject
> go
= GetParentObject();
154 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
157 mControllerWorker
= go
->GetOrCreateServiceWorker(go
->GetController().ref());
158 aRv
= DispatchTrustedEvent(u
"controllerchange"_ns
);
161 using mozilla::dom::ipc::StructuredCloneData
;
163 // A ReceivedMessage represents a message sent via
164 // Client.postMessage(). It is used as used both for queuing of
165 // incoming messages and as an interface to DispatchMessage().
166 struct MOZ_HEAP_CLASS
ServiceWorkerContainer::ReceivedMessage
{
167 explicit ReceivedMessage(const ClientPostMessageArgs
& aArgs
)
168 : mServiceWorker(aArgs
.serviceWorker()) {
169 mClonedData
.CopyFromClonedMessageDataForBackgroundChild(aArgs
.clonedData());
172 ServiceWorkerDescriptor mServiceWorker
;
173 StructuredCloneData mClonedData
;
175 NS_INLINE_DECL_REFCOUNTING(ReceivedMessage
)
178 ~ReceivedMessage() = default;
181 void ServiceWorkerContainer::ReceiveMessage(
182 const ClientPostMessageArgs
& aArgs
) {
183 RefPtr
<ReceivedMessage
> message
= new ReceivedMessage(aArgs
);
184 if (mMessagesStarted
) {
185 EnqueueReceivedMessageDispatch(std::move(message
));
187 mPendingMessages
.AppendElement(message
.forget());
191 JSObject
* ServiceWorkerContainer::WrapObject(
192 JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
) {
193 return ServiceWorkerContainer_Binding::Wrap(aCx
, this, aGivenProto
);
198 already_AddRefed
<nsIURI
> GetBaseURIFromGlobal(nsIGlobalObject
* aGlobal
,
200 // It would be nice not to require a window here, but right
201 // now we don't have a great way to get the base URL just
202 // from the nsIGlobalObject.
203 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aGlobal
);
205 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
209 Document
* doc
= window
->GetExtantDoc();
211 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
215 nsCOMPtr
<nsIURI
> baseURI
= doc
->GetDocBaseURI();
217 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
221 return baseURI
.forget();
224 } // anonymous namespace
226 already_AddRefed
<Promise
> ServiceWorkerContainer::Register(
227 const nsAString
& aScriptURL
, const RegistrationOptions
& aOptions
,
228 const CallerType aCallerType
, ErrorResult
& aRv
) {
229 // Note, we can't use GetGlobalIfValid() from the start here. If we
230 // hit a storage failure we want to log a message with the final
231 // scope string we put together below.
232 nsIGlobalObject
* global
= GetParentObject();
234 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
238 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
239 if (clientInfo
.isNothing()) {
240 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
244 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURIFromGlobal(global
, aRv
);
249 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM.
250 nsAutoCString scriptURL
;
251 if (!AppendUTF16toUTF8(aScriptURL
, scriptURL
, fallible
)) {
252 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
256 nsCOMPtr
<nsIURI
> scriptURI
;
258 NS_NewURI(getter_AddRefs(scriptURI
), scriptURL
, nullptr, baseURI
);
259 if (NS_WARN_IF(NS_FAILED(rv
))) {
260 aRv
.ThrowTypeError
<MSG_INVALID_URL
>(scriptURL
);
264 // Never allow script URL with moz-extension scheme if support is fully
265 // disabled by the 'extensions.background_service_worker.enabled' pref.
266 if (scriptURI
->SchemeIs("moz-extension") &&
267 !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
268 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
272 // Allow a webextension principal to register a service worker script with
273 // a moz-extension url only if 'extensions.service_worker_register.allowed'
275 if (scriptURI
->SchemeIs("moz-extension") &&
276 aCallerType
== CallerType::NonSystem
&&
277 (!baseURI
->SchemeIs("moz-extension") ||
278 !StaticPrefs::extensions_serviceWorkerRegister_allowed())) {
279 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
283 // In ServiceWorkerContainer.register() the scope argument is parsed against
284 // different base URLs depending on whether it was passed or not.
285 nsCOMPtr
<nsIURI
> scopeURI
;
287 // Step 4. If none passed, parse against script's URL
288 if (!aOptions
.mScope
.WasPassed()) {
289 constexpr auto defaultScope
= "./"_ns
;
290 rv
= NS_NewURI(getter_AddRefs(scopeURI
), defaultScope
, nullptr, scriptURI
);
291 if (NS_WARN_IF(NS_FAILED(rv
))) {
293 scriptURI
->GetSpec(spec
);
294 aRv
.ThrowTypeError
<MSG_INVALID_SCOPE
>(defaultScope
, spec
);
298 // Step 5. Parse against entry settings object's base URL.
299 rv
= NS_NewURI(getter_AddRefs(scopeURI
), aOptions
.mScope
.Value(), nullptr,
301 if (NS_WARN_IF(NS_FAILED(rv
))) {
302 nsIURI
* uri
= baseURI
? baseURI
: scriptURI
;
305 aRv
.ThrowTypeError
<MSG_INVALID_SCOPE
>(
306 NS_ConvertUTF16toUTF8(aOptions
.mScope
.Value()), spec
);
311 // Strip the any ref from both the script and scope URLs.
312 nsCOMPtr
<nsIURI
> cloneWithoutRef
;
313 aRv
= NS_GetURIWithoutRef(scriptURI
, getter_AddRefs(cloneWithoutRef
));
317 scriptURI
= std::move(cloneWithoutRef
);
319 aRv
= NS_GetURIWithoutRef(scopeURI
, getter_AddRefs(cloneWithoutRef
));
323 scopeURI
= std::move(cloneWithoutRef
);
325 ServiceWorkerScopeAndScriptAreValid(clientInfo
.ref(), scopeURI
, scriptURI
,
331 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
333 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
337 Document
* doc
= window
->GetExtantDoc();
339 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
343 // The next section of code executes an NS_CheckContentLoadPolicy()
344 // check. This is necessary to enforce the CSP of the calling client.
345 // Currently this requires an Document. Once bug 965637 lands we
346 // should try to move this into ServiceWorkerScopeAndScriptAreValid()
347 // using the ClientInfo instead of doing a window-specific check here.
348 // See bug 1455077 for further investigation.
349 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new mozilla::net::LoadInfo(
350 doc
->NodePrincipal(), // loading principal
351 doc
->NodePrincipal(), // triggering principal
353 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
,
354 nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
);
356 // Check content policy.
357 int16_t decision
= nsIContentPolicy::ACCEPT
;
358 rv
= NS_CheckContentLoadPolicy(scriptURI
, secCheckLoadInfo
,
359 "application/javascript"_ns
, &decision
);
361 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
364 if (NS_WARN_IF(decision
!= nsIContentPolicy::ACCEPT
)) {
365 aRv
.Throw(NS_ERROR_CONTENT_BLOCKED
);
369 // Get the string representation for both the script and scope since
370 // we sanitized them above.
371 nsCString cleanedScopeURL
;
372 aRv
= scopeURI
->GetSpec(cleanedScopeURL
);
377 nsCString cleanedScriptURL
;
378 aRv
= scriptURI
->GetSpec(cleanedScriptURL
);
383 // Verify that the global is valid and has permission to store
384 // data. We perform this late so that we can report the final
385 // scope URL in any error message.
386 Unused
<< GetGlobalIfValid(aRv
, [&](Document
* aDoc
) {
387 AutoTArray
<nsString
, 1> param
;
388 CopyUTF8toUTF16(cleanedScopeURL
, *param
.AppendElement());
389 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
390 "Service Workers"_ns
, aDoc
,
391 nsContentUtils::eDOM_PROPERTIES
,
392 "ServiceWorkerRegisterStorageError", param
);
395 window
->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL
);
397 RefPtr
<Promise
> outer
=
398 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
403 RefPtr
<ServiceWorkerContainer
> self
= this;
406 clientInfo
.ref(), cleanedScopeURL
, cleanedScriptURL
,
407 aOptions
.mUpdateViaCache
,
408 [self
, outer
](const ServiceWorkerRegistrationDescriptor
& aDesc
) {
410 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
412 outer
->MaybeReject(std::move(rv
));
415 RefPtr
<ServiceWorkerRegistration
> reg
=
416 global
->GetOrCreateServiceWorkerRegistration(aDesc
);
417 outer
->MaybeResolve(reg
);
419 [outer
](ErrorResult
&& aRv
) { outer
->MaybeReject(std::move(aRv
)); });
421 return outer
.forget();
424 already_AddRefed
<ServiceWorker
> ServiceWorkerContainer::GetController() {
425 RefPtr
<ServiceWorker
> ref
= mControllerWorker
;
429 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistrations(
431 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
432 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
433 "Service Workers"_ns
, aDoc
,
434 nsContentUtils::eDOM_PROPERTIES
,
435 "ServiceWorkerGetRegistrationStorageError");
441 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
442 if (clientInfo
.isNothing()) {
443 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
447 RefPtr
<Promise
> outer
=
448 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
453 RefPtr
<ServiceWorkerContainer
> self
= this;
455 mInner
->GetRegistrations(
458 outer
](const nsTArray
<ServiceWorkerRegistrationDescriptor
>& aDescList
) {
460 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
462 outer
->MaybeReject(std::move(rv
));
465 nsTArray
<RefPtr
<ServiceWorkerRegistration
>> regList
;
466 for (auto& desc
: aDescList
) {
467 RefPtr
<ServiceWorkerRegistration
> reg
=
468 global
->GetOrCreateServiceWorkerRegistration(desc
);
470 regList
.AppendElement(std::move(reg
));
473 outer
->MaybeResolve(regList
);
475 [self
, outer
](ErrorResult
&& aRv
) { outer
->MaybeReject(std::move(aRv
)); });
477 return outer
.forget();
480 void ServiceWorkerContainer::StartMessages() {
481 while (!mPendingMessages
.IsEmpty()) {
482 EnqueueReceivedMessageDispatch(mPendingMessages
.ElementAt(0));
483 mPendingMessages
.RemoveElementAt(0);
485 mMessagesStarted
= true;
488 already_AddRefed
<Promise
> ServiceWorkerContainer::GetRegistration(
489 const nsAString
& aURL
, ErrorResult
& aRv
) {
490 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
, [](Document
* aDoc
) {
491 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
,
492 "Service Workers"_ns
, aDoc
,
493 nsContentUtils::eDOM_PROPERTIES
,
494 "ServiceWorkerGetRegistrationStorageError");
500 Maybe
<ClientInfo
> clientInfo
= global
->GetClientInfo();
501 if (clientInfo
.isNothing()) {
502 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
506 nsCOMPtr
<nsIURI
> baseURI
= GetBaseURIFromGlobal(global
, aRv
);
511 nsCOMPtr
<nsIURI
> uri
;
512 aRv
= NS_NewURI(getter_AddRefs(uri
), aURL
, nullptr, baseURI
);
518 aRv
= uri
->GetSpec(spec
);
523 RefPtr
<Promise
> outer
=
524 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
529 RefPtr
<ServiceWorkerContainer
> self
= this;
531 mInner
->GetRegistration(
532 clientInfo
.ref(), spec
,
533 [self
, outer
](const ServiceWorkerRegistrationDescriptor
& aDescriptor
) {
535 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
537 outer
->MaybeReject(std::move(rv
));
540 RefPtr
<ServiceWorkerRegistration
> reg
=
541 global
->GetOrCreateServiceWorkerRegistration(aDescriptor
);
542 outer
->MaybeResolve(reg
);
544 [self
, outer
](ErrorResult
&& aRv
) {
546 Unused
<< self
->GetGlobalIfValid(aRv
);
548 outer
->MaybeResolveWithUndefined();
552 outer
->MaybeReject(std::move(aRv
));
555 return outer
.forget();
558 Promise
* ServiceWorkerContainer::GetReady(ErrorResult
& aRv
) {
560 return mReadyPromise
;
563 nsIGlobalObject
* global
= GetGlobalIfValid(aRv
);
567 MOZ_DIAGNOSTIC_ASSERT(global
);
569 Maybe
<ClientInfo
> clientInfo(global
->GetClientInfo());
570 if (clientInfo
.isNothing()) {
571 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
576 Promise::Create(global
, aRv
, Promise::ePropagateUserInteraction
);
581 RefPtr
<ServiceWorkerContainer
> self
= this;
582 RefPtr
<Promise
> outer
= mReadyPromise
;
586 [self
, outer
](const ServiceWorkerRegistrationDescriptor
& aDescriptor
) {
588 nsIGlobalObject
* global
= self
->GetGlobalIfValid(rv
);
590 outer
->MaybeReject(std::move(rv
));
593 RefPtr
<ServiceWorkerRegistration
> reg
=
594 global
->GetOrCreateServiceWorkerRegistration(aDescriptor
);
595 NS_ENSURE_TRUE_VOID(reg
);
597 // Don't resolve the ready promise until the registration has
598 // reached the right version. This ensures that the active
599 // worker property is set correctly on the registration.
600 reg
->WhenVersionReached(
601 aDescriptor
.Version(),
602 [outer
, reg
](bool aResult
) { outer
->MaybeResolve(reg
); });
604 [self
, outer
](ErrorResult
&& aRv
) { outer
->MaybeReject(std::move(aRv
)); });
606 return mReadyPromise
;
610 void ServiceWorkerContainer::GetScopeForUrl(const nsAString
& aUrl
,
613 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
614 mozilla::services::GetServiceWorkerManager();
616 aRv
.Throw(NS_ERROR_FAILURE
);
620 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
621 if (NS_WARN_IF(!window
)) {
622 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
626 nsCOMPtr
<Document
> doc
= window
->GetExtantDoc();
627 if (NS_WARN_IF(!doc
)) {
628 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
632 aRv
= swm
->GetScopeForUrl(doc
->NodePrincipal(), aUrl
, aScope
);
635 nsIGlobalObject
* ServiceWorkerContainer::GetGlobalIfValid(
637 const std::function
<void(Document
*)>&& aStorageFailureCB
) const {
638 // For now we require a window since ServiceWorkerContainer is
639 // not exposed on worker globals yet. The main thing we need
640 // to fix here to support that is the storage access check via
641 // the nsIGlobalObject.
642 nsPIDOMWindowInner
* window
= GetOwner();
643 if (NS_WARN_IF(!window
)) {
644 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
648 nsCOMPtr
<Document
> doc
= window
->GetExtantDoc();
649 if (NS_WARN_IF(!doc
)) {
650 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
654 // Don't allow a service worker to access service worker registrations
655 // from a window with storage disabled. If these windows can access
656 // the registration it increases the chance they can bypass the storage
657 // block via postMessage(), etc.
658 auto storageAllowed
= StorageAllowedForWindow(window
);
659 if (NS_WARN_IF(storageAllowed
!= StorageAccess::eAllow
)) {
660 if (aStorageFailureCB
) {
661 aStorageFailureCB(doc
);
663 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
667 // Don't allow service workers when the document is chrome.
668 if (NS_WARN_IF(doc
->NodePrincipal()->IsSystemPrincipal())) {
669 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
673 return window
->AsGlobal();
676 void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(
677 RefPtr
<ReceivedMessage
> aMessage
) {
678 if (nsPIDOMWindowInner
* const window
= GetOwner()) {
679 if (auto* const target
= window
->EventTargetFor(TaskCategory::Other
)) {
680 target
->Dispatch(NewRunnableMethod
<RefPtr
<ReceivedMessage
>>(
681 "ServiceWorkerContainer::DispatchMessage", this,
682 &ServiceWorkerContainer::DispatchMessage
, std::move(aMessage
)));
687 template <typename F
>
688 void ServiceWorkerContainer::RunWithJSContext(F
&& aCallable
) {
689 nsCOMPtr
<nsIGlobalObject
> globalObject
;
690 if (nsPIDOMWindowInner
* const window
= GetOwner()) {
691 globalObject
= do_QueryInterface(window
);
694 // If AutoJSAPI::Init() fails then either global is nullptr or not
695 // in a usable state.
697 if (!jsapi
.Init(globalObject
)) {
701 aCallable(jsapi
.cx(), globalObject
);
704 void ServiceWorkerContainer::DispatchMessage(RefPtr
<ReceivedMessage
> aMessage
) {
705 MOZ_ASSERT(NS_IsMainThread());
707 // When dispatching a message, either DOMContentLoaded has already
708 // been fired, or someone called startMessages() or set onmessage.
709 // Either way, a global object is supposed to be present. If it's
710 // not, we'd fail to initialize the JS API and exit.
711 RunWithJSContext([this, message
= std::move(aMessage
)](
712 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
) {
714 bool deserializationFailed
= false;
715 RootedDictionary
<MessageEventInit
> init(aCx
);
716 auto res
= FillInMessageEventInit(aCx
, aGlobal
, *message
, init
, result
);
718 deserializationFailed
= res
.unwrapErr();
719 MOZ_ASSERT_IF(deserializationFailed
, init
.mData
.isNull());
720 MOZ_ASSERT_IF(deserializationFailed
, init
.mPorts
.IsEmpty());
721 MOZ_ASSERT_IF(deserializationFailed
, !init
.mOrigin
.IsEmpty());
722 MOZ_ASSERT_IF(deserializationFailed
, !init
.mSource
.IsNull());
723 result
.SuppressException();
725 if (!deserializationFailed
&& result
.MaybeSetPendingException(aCx
)) {
730 RefPtr
<MessageEvent
> event
= MessageEvent::Constructor(
731 this, deserializationFailed
? u
"messageerror"_ns
: u
"message"_ns
, init
);
732 event
->SetTrusted(true);
735 DispatchEvent(*event
, result
);
736 if (result
.Failed()) {
737 result
.SuppressException();
744 nsresult
FillInOriginNoSuffix(const ServiceWorkerDescriptor
& aServiceWorker
,
746 using mozilla::ipc::PrincipalInfoToPrincipal
;
750 auto principalOrErr
=
751 PrincipalInfoToPrincipal(aServiceWorker
.PrincipalInfo());
752 if (NS_WARN_IF(principalOrErr
.isErr())) {
753 return principalOrErr
.unwrapErr();
756 nsAutoCString originUTF8
;
757 rv
= principalOrErr
.unwrap()->GetOriginNoSuffix(originUTF8
);
762 CopyUTF8toUTF16(originUTF8
, aOrigin
);
766 already_AddRefed
<ServiceWorker
> GetOrCreateServiceWorkerWithoutWarnings(
767 nsIGlobalObject
* const aGlobal
,
768 const ServiceWorkerDescriptor
& aDescriptor
) {
769 // In child-intercept mode we have to verify that the registration
770 // exists in the current process. This exact check is also performed
771 // (indirectly) in nsIGlobalObject::GetOrCreateServiceWorker, but it
772 // also emits a warning when the registration is not present. To
773 // to avoid having too many warnings, we do a precheck here.
774 if (!ServiceWorkerParentInterceptEnabled()) {
775 const RefPtr
<ServiceWorkerManager
> serviceWorkerManager
=
776 ServiceWorkerManager::GetInstance();
777 if (!serviceWorkerManager
) {
781 const RefPtr
<ServiceWorkerRegistrationInfo
> registration
=
782 serviceWorkerManager
->GetRegistration(aDescriptor
.PrincipalInfo(),
783 aDescriptor
.Scope());
789 return aGlobal
->GetOrCreateServiceWorker(aDescriptor
).forget();
794 Result
<Ok
, bool> ServiceWorkerContainer::FillInMessageEventInit(
795 JSContext
* const aCx
, nsIGlobalObject
* const aGlobal
,
796 ReceivedMessage
& aMessage
, MessageEventInit
& aInit
, ErrorResult
& aRv
) {
797 // Determining the source and origin should preceed attempting deserialization
798 // because on a "messageerror" event (i.e. when deserialization fails), the
799 // dispatched message needs to contain such an origin and source, per spec:
801 // "If this throws an exception, catch it, fire an event named messageerror
802 // at destination, using MessageEvent, with the origin attribute initialized
803 // to origin and the source attribute initialized to source, and then abort
804 // these steps." - 6.4 of postMessage
805 // See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage
806 const RefPtr
<ServiceWorker
> serviceWorkerInstance
=
807 GetOrCreateServiceWorkerWithoutWarnings(aGlobal
, aMessage
.mServiceWorker
);
808 if (serviceWorkerInstance
) {
809 aInit
.mSource
.SetValue().SetAsServiceWorker() = serviceWorkerInstance
;
813 FillInOriginNoSuffix(aMessage
.mServiceWorker
, aInit
.mOrigin
);
818 JS::Rooted
<JS::Value
> messageData(aCx
);
819 aMessage
.mClonedData
.Read(aCx
, &messageData
, aRv
);
824 aInit
.mData
= messageData
;
826 if (!aMessage
.mClonedData
.TakeTransferredPortsAsSequence(aInit
.mPorts
)) {
827 xpc::Throw(aCx
, NS_ERROR_OUT_OF_MEMORY
);
835 } // namespace mozilla