Bug 1842773 - Part 18: Update TypedArray length, byteLength, and byteOffset accesses...
[gecko.git] / dom / serviceworkers / ServiceWorkerContainer.cpp
blob1a7b5bbed6efc4413af00e0c5b26273ef263ad44
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, &decision);
322 if (NS_FAILED(rv)) {
323 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
324 return nullptr;
326 if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
327 aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
328 return nullptr;
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);
335 if (aRv.Failed()) {
336 return nullptr;
339 nsCString cleanedScriptURL;
340 aRv = scriptURI->GetSpec(cleanedScriptURL);
341 if (aRv.Failed()) {
342 return nullptr;
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);
361 if (aRv.Failed()) {
362 return nullptr;
365 RefPtr<ServiceWorkerContainer> self = this;
367 if (!mActor) {
368 aRv.ThrowInvalidStateError("Can't register service worker");
369 return nullptr;
372 mActor->SendRegister(
373 clientInfo.ref().ToIPC(), nsCString(cleanedScopeURL),
374 nsCString(cleanedScriptURL), aOptions.mUpdateViaCache,
375 [self,
376 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
377 aResult) {
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));
385 return;
387 // success
388 const auto& ipcDesc =
389 aResult.get_IPCServiceWorkerRegistrationDescriptor();
390 ErrorResult rv;
391 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
392 if (rv.Failed()) {
393 outer->MaybeReject(std::move(rv));
394 return;
396 RefPtr<ServiceWorkerRegistration> reg =
397 global->GetOrCreateServiceWorkerRegistration(
398 ServiceWorkerRegistrationDescriptor(ipcDesc));
399 outer->MaybeResolve(reg);
401 [outer](ResponseRejectReason&& aReason) {
402 // IPC layer error
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;
413 return ref.forget();
416 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations(
417 ErrorResult& aRv) {
418 nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
419 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
420 "Service Workers"_ns, aDoc,
421 nsContentUtils::eDOM_PROPERTIES,
422 "ServiceWorkerGetRegistrationStorageError");
424 if (aRv.Failed()) {
425 return nullptr;
428 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
429 if (clientInfo.isNothing()) {
430 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
431 return nullptr;
434 RefPtr<Promise> outer =
435 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
436 if (aRv.Failed()) {
437 return nullptr;
440 RefPtr<ServiceWorkerContainer> self = this;
442 if (!mActor) {
443 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
444 return outer.forget();
447 mActor->SendGetRegistrations(
448 clientInfo.ref().ToIPC(),
449 [self, outer](
450 const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult&
451 aResult) {
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));
459 return;
461 // success
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));
470 ErrorResult rv;
471 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
472 if (rv.Failed()) {
473 outer->MaybeReject(std::move(rv));
474 return;
476 nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
477 for (auto& desc : list) {
478 RefPtr<ServiceWorkerRegistration> reg =
479 global->GetOrCreateServiceWorkerRegistration(desc);
480 if (reg) {
481 regList.AppendElement(std::move(reg));
484 outer->MaybeResolve(regList);
486 [outer](ResponseRejectReason&& aReason) {
487 // IPC layer error
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");
510 if (aRv.Failed()) {
511 return nullptr;
514 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
515 if (clientInfo.isNothing()) {
516 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
517 return nullptr;
520 nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
521 if (aRv.Failed()) {
522 return nullptr;
525 nsCOMPtr<nsIURI> uri;
526 aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI);
527 if (aRv.Failed()) {
528 return nullptr;
531 nsCString spec;
532 aRv = uri->GetSpec(spec);
533 if (aRv.Failed()) {
534 return nullptr;
537 RefPtr<Promise> outer =
538 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
539 if (aRv.Failed()) {
540 return nullptr;
543 RefPtr<ServiceWorkerContainer> self = this;
545 if (!mActor) {
546 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
547 return outer.forget();
550 mActor->SendGetRegistration(
551 clientInfo.ref().ToIPC(), spec,
552 [self,
553 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
554 aResult) {
555 if (aResult.type() ==
556 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult::
557 TCopyableErrorResult) {
558 CopyableErrorResult ipcRv(aResult.get_CopyableErrorResult());
559 ErrorResult rv(std::move(ipcRv));
560 if (!rv.Failed()) {
561 // ErrorResult rv;
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);
566 if (!rv.Failed()) {
567 outer->MaybeResolveWithUndefined();
568 return;
571 outer->MaybeReject(std::move(rv));
572 return;
574 // success
575 const auto& ipcDesc =
576 aResult.get_IPCServiceWorkerRegistrationDescriptor();
577 ErrorResult rv;
578 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
579 if (rv.Failed()) {
580 outer->MaybeReject(std::move(rv));
581 return;
583 RefPtr<ServiceWorkerRegistration> reg =
584 global->GetOrCreateServiceWorkerRegistration(
585 ServiceWorkerRegistrationDescriptor(ipcDesc));
586 outer->MaybeResolve(reg);
588 [self, outer](ResponseRejectReason&& aReason) {
589 // IPC layer error
590 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
592 return outer.forget();
595 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
596 if (mReadyPromise) {
597 return mReadyPromise;
600 nsIGlobalObject* global = GetGlobalIfValid(aRv);
601 if (aRv.Failed()) {
602 return nullptr;
604 MOZ_DIAGNOSTIC_ASSERT(global);
606 Maybe<ClientInfo> clientInfo(global->GetClientInfo());
607 if (clientInfo.isNothing()) {
608 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
609 return nullptr;
612 mReadyPromise =
613 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
614 if (aRv.Failed()) {
615 return nullptr;
618 RefPtr<ServiceWorkerContainer> self = this;
619 RefPtr<Promise> outer = mReadyPromise;
621 if (!mActor) {
622 mReadyPromise->MaybeReject(
623 CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
624 return mReadyPromise;
627 mActor->SendGetReady(
628 clientInfo.ref().ToIPC(),
629 [self,
630 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult&
631 aResult) {
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));
639 return;
641 // success
642 const auto& ipcDesc =
643 aResult.get_IPCServiceWorkerRegistrationDescriptor();
644 ErrorResult rv;
645 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
646 if (rv.Failed()) {
647 outer->MaybeReject(std::move(rv));
648 return;
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) {
663 // IPC layer error
664 outer->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR));
667 return mReadyPromise;
670 // Testing only.
671 void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
672 nsString& aScope,
673 ErrorResult& aRv) {
674 nsCOMPtr<nsIServiceWorkerManager> swm =
675 mozilla::components::ServiceWorkerManager::Service();
676 if (!swm) {
677 aRv.Throw(NS_ERROR_FAILURE);
678 return;
681 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
682 if (NS_WARN_IF(!window)) {
683 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
684 return;
687 nsCOMPtr<nsIPrincipal> principal;
688 nsresult rv = StoragePrincipalHelper::GetPrincipal(
689 window,
690 StaticPrefs::privacy_partition_serviceWorkers()
691 ? StoragePrincipalHelper::eForeignPartitionedPrincipal
692 : StoragePrincipalHelper::eRegularPrincipal,
693 getter_AddRefs(principal));
695 if (NS_WARN_IF(NS_FAILED(rv))) {
696 aRv.Throw(rv);
697 return;
700 aRv = swm->GetScopeForUrl(principal, aUrl, aScope);
703 nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid(
704 ErrorResult& aRv,
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);
713 return nullptr;
716 nsCOMPtr<Document> doc = window->GetExtantDoc();
717 if (NS_WARN_IF(!doc)) {
718 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
719 return nullptr;
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);
735 return nullptr;
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);
741 return nullptr;
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.
763 AutoJSAPI jsapi;
764 if (!jsapi.Init(globalObject)) {
765 return;
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) {
780 ErrorResult result;
781 bool deserializationFailed = false;
782 RootedDictionary<MessageEventInit> init(aCx);
783 auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result);
784 if (res.isErr()) {
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)) {
793 return;
797 RefPtr<MessageEvent> event = MessageEvent::Constructor(
798 this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init);
799 event->SetTrusted(true);
801 result = NS_OK;
802 DispatchEvent(*event, result);
803 if (result.Failed()) {
804 result.SuppressException();
809 namespace {
811 nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker,
812 nsString& aOrigin) {
813 using mozilla::ipc::PrincipalInfoToPrincipal;
815 nsresult rv;
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);
825 if (NS_FAILED(rv)) {
826 return rv;
829 CopyUTF8toUTF16(originUTF8, aOrigin);
830 return NS_OK;
833 } // namespace
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;
853 const nsresult rv =
854 FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
855 if (NS_FAILED(rv)) {
856 return Err(false);
859 JS::Rooted<JS::Value> messageData(aCx);
860 aMessage.mClonedData.Read(aCx, &messageData, aRv);
861 if (aRv.Failed()) {
862 return Err(true);
865 aInit.mData = messageData;
867 if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
868 xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
869 return Err(false);
872 return Ok();
875 void ServiceWorkerContainer::Shutdown() {
876 if (mShutdown) {
877 return;
879 mShutdown = true;
881 if (mActor) {
882 mActor->RevokeOwner(this);
883 mActor->MaybeStartTeardown();
884 mActor = nullptr;
888 } // namespace mozilla::dom