Bug 1853814 [wpt PR 42017] - LoAF: Expose script URL for promise resolvers, a=testonly
[gecko.git] / dom / serviceworkers / ServiceWorkerContainer.cpp
blob4e33b9fc753c813c01dd4792c06b2273f01dc7aa
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
51 #endif
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)
68 // static
69 already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create(
70 nsIGlobalObject* aGlobal) {
71 RefPtr<ServiceWorkerContainer> ref = new ServiceWorkerContainer(aGlobal);
72 return ref.forget();
75 ServiceWorkerContainer::ServiceWorkerContainer(nsIGlobalObject* aGlobal)
76 : DOMEventTargetHelper(aGlobal), mShutdown(false) {
77 PBackgroundChild* parentActor =
78 BackgroundChild::GetOrCreateForCurrentThread();
79 if (NS_WARN_IF(!parentActor)) {
80 Shutdown();
81 return;
84 RefPtr<ServiceWorkerContainerChild> actor =
85 ServiceWorkerContainerChild::Create();
86 if (NS_WARN_IF(!actor)) {
87 Shutdown();
88 return;
91 PServiceWorkerContainerChild* sentActor =
92 parentActor->SendPServiceWorkerContainerConstructor(actor);
93 if (NS_WARN_IF(!sentActor)) {
94 Shutdown();
95 return;
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();
118 if (!go) {
119 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
120 return;
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)
142 private:
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));
151 } else {
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);
160 mActor = nullptr;
162 mShutdown = true;
165 JSObject* ServiceWorkerContainer::WrapObject(
166 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
167 return ServiceWorkerContainer_Binding::Wrap(aCx, this, aGivenProto);
170 namespace {
172 already_AddRefed<nsIURI> GetBaseURIFromGlobal(nsIGlobalObject* aGlobal,
173 ErrorResult& aRv) {
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);
178 if (!window) {
179 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
180 return nullptr;
183 Document* doc = window->GetExtantDoc();
184 if (!doc) {
185 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
186 return nullptr;
189 nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
190 if (!baseURI) {
191 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
192 return nullptr;
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();
207 if (!global) {
208 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
209 return nullptr;
212 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
213 if (clientInfo.isNothing()) {
214 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
215 return nullptr;
218 nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
219 if (aRv.Failed()) {
220 return nullptr;
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);
227 return nullptr;
230 nsCOMPtr<nsIURI> scriptURI;
231 nsresult rv =
232 NS_NewURI(getter_AddRefs(scriptURI), scriptURL, nullptr, baseURI);
233 if (NS_WARN_IF(NS_FAILED(rv))) {
234 aRv.ThrowTypeError<MSG_INVALID_URL>(scriptURL);
235 return nullptr;
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);
243 return nullptr;
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))) {
255 nsAutoCString spec;
256 scriptURI->GetSpec(spec);
257 aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, spec);
258 return nullptr;
260 } else {
261 // Step 5. Parse against entry settings object's base URL.
262 rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr,
263 baseURI);
264 if (NS_WARN_IF(NS_FAILED(rv))) {
265 nsIURI* uri = baseURI ? baseURI : scriptURI;
266 nsAutoCString spec;
267 uri->GetSpec(spec);
268 aRv.ThrowTypeError<MSG_INVALID_SCOPE>(
269 NS_ConvertUTF16toUTF8(aOptions.mScope.Value()), spec);
270 return nullptr;
274 // Strip the any ref from both the script and scope URLs.
275 nsCOMPtr<nsIURI> cloneWithoutRef;
276 aRv = NS_GetURIWithoutRef(scriptURI, getter_AddRefs(cloneWithoutRef));
277 if (aRv.Failed()) {
278 return nullptr;
280 scriptURI = std::move(cloneWithoutRef);
282 aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef));
283 if (aRv.Failed()) {
284 return nullptr;
286 scopeURI = std::move(cloneWithoutRef);
288 ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI, scriptURI,
289 aRv);
290 if (aRv.Failed()) {
291 return nullptr;
294 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
295 if (!window) {
296 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
297 return nullptr;
300 Document* doc = window->GetExtantDoc();
301 if (!doc) {
302 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
303 return nullptr;
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
315 doc, // loading node
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);
323 if (NS_FAILED(rv)) {
324 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
325 return nullptr;
327 if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
328 aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
329 return nullptr;
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);
336 if (aRv.Failed()) {
337 return nullptr;
340 nsCString cleanedScriptURL;
341 aRv = scriptURI->GetSpec(cleanedScriptURL);
342 if (aRv.Failed()) {
343 return nullptr;
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);
362 if (aRv.Failed()) {
363 return nullptr;
366 RefPtr<ServiceWorkerContainer> self = this;
368 if (!mActor) {
369 aRv.ThrowInvalidStateError("Can't register service worker");
370 return nullptr;
373 mActor->SendRegister(
374 clientInfo.ref().ToIPC(), nsCString(cleanedScopeURL),
375 nsCString(cleanedScriptURL), aOptions.mUpdateViaCache,
376 [self,
377 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
378 aResult) {
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));
386 return;
388 // success
389 const auto& ipcDesc =
390 aResult.get_IPCServiceWorkerRegistrationDescriptor();
391 ErrorResult rv;
392 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
393 if (rv.Failed()) {
394 outer->MaybeReject(std::move(rv));
395 return;
397 RefPtr<ServiceWorkerRegistration> reg =
398 global->GetOrCreateServiceWorkerRegistration(
399 ServiceWorkerRegistrationDescriptor(ipcDesc));
400 outer->MaybeResolve(reg);
402 [outer](ResponseRejectReason&& aReason) {
403 // IPC layer error
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;
414 return ref.forget();
417 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations(
418 ErrorResult& aRv) {
419 nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
420 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
421 "Service Workers"_ns, aDoc,
422 nsContentUtils::eDOM_PROPERTIES,
423 "ServiceWorkerGetRegistrationStorageError");
425 if (aRv.Failed()) {
426 return nullptr;
429 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
430 if (clientInfo.isNothing()) {
431 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
432 return nullptr;
435 RefPtr<Promise> outer =
436 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
437 if (aRv.Failed()) {
438 return nullptr;
441 RefPtr<ServiceWorkerContainer> self = this;
443 if (!mActor) {
444 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
445 return outer.forget();
448 mActor->SendGetRegistrations(
449 clientInfo.ref().ToIPC(),
450 [self, outer](
451 const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult&
452 aResult) {
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));
460 return;
462 // success
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));
471 ErrorResult rv;
472 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
473 if (rv.Failed()) {
474 outer->MaybeReject(std::move(rv));
475 return;
477 nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
478 for (auto& desc : list) {
479 RefPtr<ServiceWorkerRegistration> reg =
480 global->GetOrCreateServiceWorkerRegistration(desc);
481 if (reg) {
482 regList.AppendElement(std::move(reg));
485 outer->MaybeResolve(regList);
487 [outer](ResponseRejectReason&& aReason) {
488 // IPC layer error
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");
511 if (aRv.Failed()) {
512 return nullptr;
515 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
516 if (clientInfo.isNothing()) {
517 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
518 return nullptr;
521 nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
522 if (aRv.Failed()) {
523 return nullptr;
526 nsCOMPtr<nsIURI> uri;
527 aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI);
528 if (aRv.Failed()) {
529 return nullptr;
532 nsCString spec;
533 aRv = uri->GetSpec(spec);
534 if (aRv.Failed()) {
535 return nullptr;
538 RefPtr<Promise> outer =
539 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
540 if (aRv.Failed()) {
541 return nullptr;
544 RefPtr<ServiceWorkerContainer> self = this;
546 if (!mActor) {
547 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
548 return outer.forget();
551 mActor->SendGetRegistration(
552 clientInfo.ref().ToIPC(), spec,
553 [self,
554 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
555 aResult) {
556 if (aResult.type() ==
557 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
558 TCopyableErrorResult) {
559 CopyableErrorResult ipcRv(aResult.get_CopyableErrorResult());
560 ErrorResult rv(std::move(ipcRv));
561 if (!rv.Failed()) {
562 // ErrorResult rv;
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);
567 if (!rv.Failed()) {
568 outer->MaybeResolveWithUndefined();
569 return;
572 outer->MaybeReject(std::move(rv));
573 return;
575 // success
576 const auto& ipcDesc =
577 aResult.get_IPCServiceWorkerRegistrationDescriptor();
578 ErrorResult rv;
579 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
580 if (rv.Failed()) {
581 outer->MaybeReject(std::move(rv));
582 return;
584 RefPtr<ServiceWorkerRegistration> reg =
585 global->GetOrCreateServiceWorkerRegistration(
586 ServiceWorkerRegistrationDescriptor(ipcDesc));
587 outer->MaybeResolve(reg);
589 [self, outer](ResponseRejectReason&& aReason) {
590 // IPC layer error
591 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
593 return outer.forget();
596 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
597 if (mReadyPromise) {
598 return mReadyPromise;
601 nsIGlobalObject* global = GetGlobalIfValid(aRv);
602 if (aRv.Failed()) {
603 return nullptr;
605 MOZ_DIAGNOSTIC_ASSERT(global);
607 Maybe<ClientInfo> clientInfo(global->GetClientInfo());
608 if (clientInfo.isNothing()) {
609 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
610 return nullptr;
613 mReadyPromise =
614 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
615 if (aRv.Failed()) {
616 return nullptr;
619 RefPtr<ServiceWorkerContainer> self = this;
620 RefPtr<Promise> outer = mReadyPromise;
622 if (!mActor) {
623 mReadyPromise->MaybeReject(
624 CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
625 return mReadyPromise;
628 mActor->SendGetReady(
629 clientInfo.ref().ToIPC(),
630 [self,
631 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
632 aResult) {
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));
640 return;
642 // success
643 const auto& ipcDesc =
644 aResult.get_IPCServiceWorkerRegistrationDescriptor();
645 ErrorResult rv;
646 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
647 if (rv.Failed()) {
648 outer->MaybeReject(std::move(rv));
649 return;
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) {
664 // IPC layer error
665 outer->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
668 return mReadyPromise;
671 // Testing only.
672 void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
673 nsString& aScope,
674 ErrorResult& aRv) {
675 nsCOMPtr<nsIServiceWorkerManager> swm =
676 mozilla::components::ServiceWorkerManager::Service();
677 if (!swm) {
678 aRv.Throw(NS_ERROR_FAILURE);
679 return;
682 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
683 if (NS_WARN_IF(!window)) {
684 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
685 return;
688 nsCOMPtr<nsIPrincipal> principal;
689 nsresult rv = StoragePrincipalHelper::GetPrincipal(
690 window,
691 StaticPrefs::privacy_partition_serviceWorkers()
692 ? StoragePrincipalHelper::eForeignPartitionedPrincipal
693 : StoragePrincipalHelper::eRegularPrincipal,
694 getter_AddRefs(principal));
696 if (NS_WARN_IF(NS_FAILED(rv))) {
697 aRv.Throw(rv);
698 return;
701 aRv = swm->GetScopeForUrl(principal, aUrl, aScope);
704 nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid(
705 ErrorResult& aRv,
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);
714 return nullptr;
717 nsCOMPtr<Document> doc = window->GetExtantDoc();
718 if (NS_WARN_IF(!doc)) {
719 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
720 return nullptr;
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);
736 return nullptr;
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);
742 return nullptr;
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.
768 AutoJSAPI jsapi;
769 if (!jsapi.Init(globalObject)) {
770 return;
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) {
785 ErrorResult result;
786 bool deserializationFailed = false;
787 RootedDictionary<MessageEventInit> init(aCx);
788 auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result);
789 if (res.isErr()) {
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)) {
798 return;
802 RefPtr<MessageEvent> event = MessageEvent::Constructor(
803 this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init);
804 event->SetTrusted(true);
806 result = NS_OK;
807 DispatchEvent(*event, result);
808 if (result.Failed()) {
809 result.SuppressException();
814 namespace {
816 nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker,
817 nsString& aOrigin) {
818 using mozilla::ipc::PrincipalInfoToPrincipal;
820 nsresult rv;
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);
830 if (NS_FAILED(rv)) {
831 return rv;
834 CopyUTF8toUTF16(originUTF8, aOrigin);
835 return NS_OK;
838 } // namespace
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;
858 const nsresult rv =
859 FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
860 if (NS_FAILED(rv)) {
861 return Err(false);
864 JS::Rooted<JS::Value> messageData(aCx);
865 aMessage.mClonedData.Read(aCx, &messageData, aRv);
866 if (aRv.Failed()) {
867 return Err(true);
870 aInit.mData = messageData;
872 if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
873 xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
874 return Err(false);
877 return Ok();
880 void ServiceWorkerContainer::Shutdown() {
881 if (mShutdown) {
882 return;
884 mShutdown = true;
886 if (mActor) {
887 mActor->RevokeOwner(this);
888 mActor->MaybeStartTeardown();
889 mActor = nullptr;
893 } // namespace mozilla::dom