Bug 1663089 [wpt PR 25399] - idle-detection: Implement requestPermission() method...
[gecko.git] / dom / serviceworkers / ServiceWorkerContainer.cpp
blob7816b18243e2c0041e3e271ebedd246fcad611c3
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
48 #endif
50 namespace mozilla {
51 namespace dom {
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)
62 namespace {
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;
70 return false;
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();
81 return false;
84 } // namespace
86 /* static */
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()) {
93 return false;
96 if (IsInPrivateBrowsing(aCx)) {
97 return false;
100 if (IsSecureContextOrObjectIsFromSecureContext(aCx, global)) {
101 return true;
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;
114 // static
115 already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create(
116 nsIGlobalObject* aGlobal) {
117 RefPtr<Inner> inner;
118 if (ServiceWorkerParentInterceptEnabled()) {
119 inner = new RemoteServiceWorkerContainerImpl();
120 } else {
121 inner = new ServiceWorkerContainerImpl();
123 NS_ENSURE_TRUE(inner, nullptr);
125 RefPtr<ServiceWorkerContainer> ref =
126 new ServiceWorkerContainer(aGlobal, inner.forget());
127 return ref.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();
153 if (!go) {
154 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
155 return;
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)
177 private:
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));
186 } else {
187 mPendingMessages.AppendElement(message.forget());
191 JSObject* ServiceWorkerContainer::WrapObject(
192 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
193 return ServiceWorkerContainer_Binding::Wrap(aCx, this, aGivenProto);
196 namespace {
198 already_AddRefed<nsIURI> GetBaseURIFromGlobal(nsIGlobalObject* aGlobal,
199 ErrorResult& aRv) {
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);
204 if (!window) {
205 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
206 return nullptr;
209 Document* doc = window->GetExtantDoc();
210 if (!doc) {
211 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
212 return nullptr;
215 nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
216 if (!baseURI) {
217 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
218 return nullptr;
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();
233 if (!global) {
234 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
235 return nullptr;
238 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
239 if (clientInfo.isNothing()) {
240 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
241 return nullptr;
244 nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
245 if (aRv.Failed()) {
246 return nullptr;
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);
253 return nullptr;
256 nsCOMPtr<nsIURI> scriptURI;
257 nsresult rv =
258 NS_NewURI(getter_AddRefs(scriptURI), scriptURL, nullptr, baseURI);
259 if (NS_WARN_IF(NS_FAILED(rv))) {
260 aRv.ThrowTypeError<MSG_INVALID_URL>(scriptURL);
261 return nullptr;
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);
269 return nullptr;
272 // Allow a webextension principal to register a service worker script with
273 // a moz-extension url only if 'extensions.service_worker_register.allowed'
274 // is true.
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);
280 return nullptr;
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))) {
292 nsAutoCString spec;
293 scriptURI->GetSpec(spec);
294 aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, spec);
295 return nullptr;
297 } else {
298 // Step 5. Parse against entry settings object's base URL.
299 rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr,
300 baseURI);
301 if (NS_WARN_IF(NS_FAILED(rv))) {
302 nsIURI* uri = baseURI ? baseURI : scriptURI;
303 nsAutoCString spec;
304 uri->GetSpec(spec);
305 aRv.ThrowTypeError<MSG_INVALID_SCOPE>(
306 NS_ConvertUTF16toUTF8(aOptions.mScope.Value()), spec);
307 return nullptr;
311 // Strip the any ref from both the script and scope URLs.
312 nsCOMPtr<nsIURI> cloneWithoutRef;
313 aRv = NS_GetURIWithoutRef(scriptURI, getter_AddRefs(cloneWithoutRef));
314 if (aRv.Failed()) {
315 return nullptr;
317 scriptURI = std::move(cloneWithoutRef);
319 aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef));
320 if (aRv.Failed()) {
321 return nullptr;
323 scopeURI = std::move(cloneWithoutRef);
325 ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI, scriptURI,
326 aRv);
327 if (aRv.Failed()) {
328 return nullptr;
331 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
332 if (!window) {
333 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
334 return nullptr;
337 Document* doc = window->GetExtantDoc();
338 if (!doc) {
339 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
340 return nullptr;
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
352 doc, // loading node
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);
360 if (NS_FAILED(rv)) {
361 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
362 return nullptr;
364 if (NS_WARN_IF(decision != nsIContentPolicy::ACCEPT)) {
365 aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
366 return nullptr;
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);
373 if (aRv.Failed()) {
374 return nullptr;
377 nsCString cleanedScriptURL;
378 aRv = scriptURI->GetSpec(cleanedScriptURL);
379 if (aRv.Failed()) {
380 return nullptr;
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);
399 if (aRv.Failed()) {
400 return nullptr;
403 RefPtr<ServiceWorkerContainer> self = this;
405 mInner->Register(
406 clientInfo.ref(), cleanedScopeURL, cleanedScriptURL,
407 aOptions.mUpdateViaCache,
408 [self, outer](const ServiceWorkerRegistrationDescriptor& aDesc) {
409 ErrorResult rv;
410 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
411 if (rv.Failed()) {
412 outer->MaybeReject(std::move(rv));
413 return;
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;
426 return ref.forget();
429 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations(
430 ErrorResult& aRv) {
431 nsIGlobalObject* global = GetGlobalIfValid(aRv, [](Document* aDoc) {
432 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
433 "Service Workers"_ns, aDoc,
434 nsContentUtils::eDOM_PROPERTIES,
435 "ServiceWorkerGetRegistrationStorageError");
437 if (aRv.Failed()) {
438 return nullptr;
441 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
442 if (clientInfo.isNothing()) {
443 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
444 return nullptr;
447 RefPtr<Promise> outer =
448 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
449 if (aRv.Failed()) {
450 return nullptr;
453 RefPtr<ServiceWorkerContainer> self = this;
455 mInner->GetRegistrations(
456 clientInfo.ref(),
457 [self,
458 outer](const nsTArray<ServiceWorkerRegistrationDescriptor>& aDescList) {
459 ErrorResult rv;
460 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
461 if (rv.Failed()) {
462 outer->MaybeReject(std::move(rv));
463 return;
465 nsTArray<RefPtr<ServiceWorkerRegistration>> regList;
466 for (auto& desc : aDescList) {
467 RefPtr<ServiceWorkerRegistration> reg =
468 global->GetOrCreateServiceWorkerRegistration(desc);
469 if (reg) {
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");
496 if (aRv.Failed()) {
497 return nullptr;
500 Maybe<ClientInfo> clientInfo = global->GetClientInfo();
501 if (clientInfo.isNothing()) {
502 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
503 return nullptr;
506 nsCOMPtr<nsIURI> baseURI = GetBaseURIFromGlobal(global, aRv);
507 if (aRv.Failed()) {
508 return nullptr;
511 nsCOMPtr<nsIURI> uri;
512 aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI);
513 if (aRv.Failed()) {
514 return nullptr;
517 nsCString spec;
518 aRv = uri->GetSpec(spec);
519 if (aRv.Failed()) {
520 return nullptr;
523 RefPtr<Promise> outer =
524 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
525 if (aRv.Failed()) {
526 return nullptr;
529 RefPtr<ServiceWorkerContainer> self = this;
531 mInner->GetRegistration(
532 clientInfo.ref(), spec,
533 [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
534 ErrorResult rv;
535 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
536 if (rv.Failed()) {
537 outer->MaybeReject(std::move(rv));
538 return;
540 RefPtr<ServiceWorkerRegistration> reg =
541 global->GetOrCreateServiceWorkerRegistration(aDescriptor);
542 outer->MaybeResolve(reg);
544 [self, outer](ErrorResult&& aRv) {
545 if (!aRv.Failed()) {
546 Unused << self->GetGlobalIfValid(aRv);
547 if (!aRv.Failed()) {
548 outer->MaybeResolveWithUndefined();
549 return;
552 outer->MaybeReject(std::move(aRv));
555 return outer.forget();
558 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) {
559 if (mReadyPromise) {
560 return mReadyPromise;
563 nsIGlobalObject* global = GetGlobalIfValid(aRv);
564 if (aRv.Failed()) {
565 return nullptr;
567 MOZ_DIAGNOSTIC_ASSERT(global);
569 Maybe<ClientInfo> clientInfo(global->GetClientInfo());
570 if (clientInfo.isNothing()) {
571 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
572 return nullptr;
575 mReadyPromise =
576 Promise::Create(global, aRv, Promise::ePropagateUserInteraction);
577 if (aRv.Failed()) {
578 return nullptr;
581 RefPtr<ServiceWorkerContainer> self = this;
582 RefPtr<Promise> outer = mReadyPromise;
584 mInner->GetReady(
585 clientInfo.ref(),
586 [self, outer](const ServiceWorkerRegistrationDescriptor& aDescriptor) {
587 ErrorResult rv;
588 nsIGlobalObject* global = self->GetGlobalIfValid(rv);
589 if (rv.Failed()) {
590 outer->MaybeReject(std::move(rv));
591 return;
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;
609 // Testing only.
610 void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
611 nsString& aScope,
612 ErrorResult& aRv) {
613 nsCOMPtr<nsIServiceWorkerManager> swm =
614 mozilla::services::GetServiceWorkerManager();
615 if (!swm) {
616 aRv.Throw(NS_ERROR_FAILURE);
617 return;
620 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
621 if (NS_WARN_IF(!window)) {
622 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
623 return;
626 nsCOMPtr<Document> doc = window->GetExtantDoc();
627 if (NS_WARN_IF(!doc)) {
628 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
629 return;
632 aRv = swm->GetScopeForUrl(doc->NodePrincipal(), aUrl, aScope);
635 nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid(
636 ErrorResult& aRv,
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);
645 return nullptr;
648 nsCOMPtr<Document> doc = window->GetExtantDoc();
649 if (NS_WARN_IF(!doc)) {
650 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
651 return nullptr;
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);
664 return nullptr;
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);
670 return nullptr;
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.
696 AutoJSAPI jsapi;
697 if (!jsapi.Init(globalObject)) {
698 return;
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) {
713 ErrorResult result;
714 bool deserializationFailed = false;
715 RootedDictionary<MessageEventInit> init(aCx);
716 auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result);
717 if (res.isErr()) {
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)) {
726 return;
730 RefPtr<MessageEvent> event = MessageEvent::Constructor(
731 this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init);
732 event->SetTrusted(true);
734 result = NS_OK;
735 DispatchEvent(*event, result);
736 if (result.Failed()) {
737 result.SuppressException();
742 namespace {
744 nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker,
745 nsString& aOrigin) {
746 using mozilla::ipc::PrincipalInfoToPrincipal;
748 nsresult rv;
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);
758 if (NS_FAILED(rv)) {
759 return rv;
762 CopyUTF8toUTF16(originUTF8, aOrigin);
763 return NS_OK;
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) {
778 return nullptr;
781 const RefPtr<ServiceWorkerRegistrationInfo> registration =
782 serviceWorkerManager->GetRegistration(aDescriptor.PrincipalInfo(),
783 aDescriptor.Scope());
784 if (!registration) {
785 return nullptr;
789 return aGlobal->GetOrCreateServiceWorker(aDescriptor).forget();
792 } // namespace
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;
812 const nsresult rv =
813 FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin);
814 if (NS_FAILED(rv)) {
815 return Err(false);
818 JS::Rooted<JS::Value> messageData(aCx);
819 aMessage.mClonedData.Read(aCx, &messageData, aRv);
820 if (aRv.Failed()) {
821 return Err(true);
824 aInit.mData = messageData;
826 if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) {
827 xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
828 return Err(false);
831 return Ok();
834 } // namespace dom
835 } // namespace mozilla