ServiceWorkerContainer.cpp (26053B)
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/. */ 6 7 #include "ServiceWorkerContainer.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/Components.h" 11 #include "mozilla/SchedulerGroup.h" 12 #include "mozilla/StaticPrefs_extensions.h" 13 #include "mozilla/StaticPrefs_privacy.h" 14 #include "mozilla/StoragePrincipalHelper.h" 15 #include "mozilla/dom/ClientIPCTypes.h" 16 #include "mozilla/dom/DOMMozPromiseRequestHolder.h" 17 #include "mozilla/dom/MessageEvent.h" 18 #include "mozilla/dom/MessageEventBinding.h" 19 #include "mozilla/dom/Navigator.h" 20 #include "mozilla/dom/Promise.h" 21 #include "mozilla/dom/RootedDictionary.h" 22 #include "mozilla/dom/ServiceWorker.h" 23 #include "mozilla/dom/ServiceWorkerContainerBinding.h" 24 #include "mozilla/dom/ServiceWorkerContainerChild.h" 25 #include "mozilla/dom/ServiceWorkerManager.h" 26 #include "mozilla/dom/ServiceWorkerRegistration.h" 27 #include "mozilla/dom/ServiceWorkerUtils.h" 28 #include "mozilla/dom/TrustedTypeUtils.h" 29 #include "mozilla/dom/TrustedTypesConstants.h" 30 #include "mozilla/dom/ipc/StructuredCloneData.h" 31 #include "mozilla/ipc/BackgroundChild.h" 32 #include "mozilla/ipc/PBackgroundChild.h" 33 #include "nsContentSecurityManager.h" 34 #include "nsContentUtils.h" 35 #include "nsCycleCollectionParticipant.h" 36 #include "nsGlobalWindowInner.h" 37 #include "nsIScriptError.h" 38 #include "nsIServiceWorkerManager.h" 39 #include "nsNetUtil.h" 40 #include "nsPIDOMWindow.h" 41 #include "nsServiceManagerUtils.h" 42 #include "nsThreadUtils.h" 43 44 // This is defined to something else on Windows 45 #ifdef DispatchMessage 46 # undef DispatchMessage 47 #endif 48 49 namespace mozilla::dom { 50 51 using mozilla::ipc::BackgroundChild; 52 using mozilla::ipc::PBackgroundChild; 53 using mozilla::ipc::ResponseRejectReason; 54 55 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer) 56 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 57 58 NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper) 59 NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper) 60 61 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper, 62 mControllerWorker, mReadyPromise) 63 64 // static 65 already_AddRefed<ServiceWorkerContainer> ServiceWorkerContainer::Create( 66 nsIGlobalObject* aGlobal) { 67 RefPtr<ServiceWorkerContainer> ref = new ServiceWorkerContainer(aGlobal); 68 return ref.forget(); 69 } 70 71 ServiceWorkerContainer::ServiceWorkerContainer(nsIGlobalObject* aGlobal) 72 : DOMEventTargetHelper(aGlobal), mShutdown(false) { 73 PBackgroundChild* parentActor = 74 BackgroundChild::GetOrCreateForCurrentThread(); 75 if (NS_WARN_IF(!parentActor)) { 76 Shutdown(); 77 return; 78 } 79 80 RefPtr<ServiceWorkerContainerChild> actor = 81 ServiceWorkerContainerChild::Create(); 82 if (NS_WARN_IF(!actor)) { 83 Shutdown(); 84 return; 85 } 86 87 PServiceWorkerContainerChild* sentActor = 88 parentActor->SendPServiceWorkerContainerConstructor(actor); 89 if (NS_WARN_IF(!sentActor)) { 90 Shutdown(); 91 return; 92 } 93 MOZ_DIAGNOSTIC_ASSERT(sentActor == actor); 94 95 mActor = std::move(actor); 96 mActor->SetOwner(this); 97 98 Maybe<ServiceWorkerDescriptor> controller = aGlobal->GetController(); 99 if (controller.isSome()) { 100 mControllerWorker = aGlobal->GetOrCreateServiceWorker(controller.ref()); 101 } 102 } 103 104 ServiceWorkerContainer::~ServiceWorkerContainer() { Shutdown(); } 105 106 void ServiceWorkerContainer::DisconnectFromOwner() { 107 mControllerWorker = nullptr; 108 mReadyPromise = nullptr; 109 DOMEventTargetHelper::DisconnectFromOwner(); 110 } 111 112 void ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) { 113 nsCOMPtr<nsIGlobalObject> go = GetParentObject(); 114 if (!go) { 115 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 116 return; 117 } 118 mControllerWorker = go->GetOrCreateServiceWorker(go->GetController().ref()); 119 aRv = DispatchTrustedEvent(u"controllerchange"_ns); 120 } 121 122 using mozilla::dom::ipc::StructuredCloneData; 123 124 // A ReceivedMessage represents a message sent via 125 // Client.postMessage(). It is used as used both for queuing of 126 // incoming messages and as an interface to DispatchMessage(). 127 struct MOZ_HEAP_CLASS ServiceWorkerContainer::ReceivedMessage { 128 explicit ReceivedMessage(const ClientPostMessageArgs& aArgs) 129 : mServiceWorker(aArgs.serviceWorker()) { 130 mClonedData.CopyFromClonedMessageData(aArgs.clonedData()); 131 } 132 133 ServiceWorkerDescriptor mServiceWorker; 134 StructuredCloneData mClonedData; 135 136 NS_INLINE_DECL_REFCOUNTING(ReceivedMessage) 137 138 private: 139 ~ReceivedMessage() = default; 140 }; 141 142 void ServiceWorkerContainer::ReceiveMessage( 143 const ClientPostMessageArgs& aArgs) { 144 RefPtr<ReceivedMessage> message = new ReceivedMessage(aArgs); 145 if (mMessagesStarted) { 146 EnqueueReceivedMessageDispatch(std::move(message)); 147 } else { 148 mPendingMessages.AppendElement(message.forget()); 149 } 150 } 151 152 void ServiceWorkerContainer::RevokeActor(ServiceWorkerContainerChild* aActor) { 153 MOZ_DIAGNOSTIC_ASSERT(mActor); 154 MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); 155 mActor->RevokeOwner(this); 156 mActor = nullptr; 157 158 mShutdown = true; 159 } 160 161 JSObject* ServiceWorkerContainer::WrapObject( 162 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 163 return ServiceWorkerContainer_Binding::Wrap(aCx, this, aGivenProto); 164 } 165 166 already_AddRefed<Promise> ServiceWorkerContainer::Register( 167 const TrustedScriptURLOrUSVString& aScriptURL, 168 const RegistrationOptions& aOptions, nsIPrincipal* aSubjectPrincipal, 169 ErrorResult& aRv) { 170 AUTO_PROFILER_MARKER_UNTYPED("SWC Register", DOM, {}); 171 172 // Note, we can't use GetGlobalIfValid() from the start here. If we 173 // hit a storage failure we want to log a message with the final 174 // scope string we put together below. 175 nsCOMPtr<nsIGlobalObject> global = GetParentObject(); 176 if (!global) { 177 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 178 return nullptr; 179 } 180 181 Maybe<ClientInfo> clientInfo = global->GetClientInfo(); 182 if (clientInfo.isNothing()) { 183 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 184 return nullptr; 185 } 186 187 nsCOMPtr<nsIURI> baseURI = global->GetBaseURI(); 188 if (!baseURI) { 189 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 190 return nullptr; 191 } 192 193 constexpr nsLiteralString sink = u"ServiceWorkerContainer register"_ns; 194 Maybe<nsAutoString> compliantStringHolder; 195 const nsAString* compliantString = 196 TrustedTypeUtils::GetTrustedTypesCompliantString( 197 aScriptURL, sink, kTrustedTypesOnlySinkGroup, *global, 198 aSubjectPrincipal, compliantStringHolder, aRv); 199 if (aRv.Failed()) { 200 return nullptr; 201 } 202 203 // Don't use NS_ConvertUTF16toUTF8 because that doesn't let us handle OOM. 204 nsAutoCString scriptURL; 205 if (!AppendUTF16toUTF8(*compliantString, scriptURL, fallible)) { 206 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 207 return nullptr; 208 } 209 210 nsCOMPtr<nsIURI> scriptURI; 211 nsresult rv = 212 NS_NewURI(getter_AddRefs(scriptURI), scriptURL, nullptr, baseURI); 213 if (NS_WARN_IF(NS_FAILED(rv))) { 214 aRv.ThrowTypeError<MSG_INVALID_URL>(scriptURL); 215 return nullptr; 216 } 217 218 // Never allow script URL with moz-extension scheme if support is fully 219 // disabled by the 'extensions.background_service_worker.enabled' pref. 220 if (scriptURI->SchemeIs("moz-extension") && 221 !StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) { 222 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 223 return nullptr; 224 } 225 226 // In ServiceWorkerContainer.register() the scope argument is parsed against 227 // different base URLs depending on whether it was passed or not. 228 nsCOMPtr<nsIURI> scopeURI; 229 230 // Step 4. If none passed, parse against script's URL 231 if (!aOptions.mScope.WasPassed()) { 232 constexpr auto defaultScope = "./"_ns; 233 rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope, nullptr, scriptURI); 234 if (NS_WARN_IF(NS_FAILED(rv))) { 235 nsAutoCString spec; 236 scriptURI->GetSpec(spec); 237 aRv.ThrowTypeError<MSG_INVALID_SCOPE>(defaultScope, spec); 238 return nullptr; 239 } 240 } else { 241 // Step 5. Parse against entry settings object's base URL. 242 rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr, 243 baseURI); 244 if (NS_WARN_IF(NS_FAILED(rv))) { 245 nsIURI* uri = baseURI ? baseURI : scriptURI; 246 nsAutoCString spec; 247 uri->GetSpec(spec); 248 aRv.ThrowTypeError<MSG_INVALID_SCOPE>( 249 NS_ConvertUTF16toUTF8(aOptions.mScope.Value()), spec); 250 return nullptr; 251 } 252 } 253 254 // Strip the any ref from both the script and scope URLs. 255 nsCOMPtr<nsIURI> cloneWithoutRef; 256 aRv = NS_GetURIWithoutRef(scriptURI, getter_AddRefs(cloneWithoutRef)); 257 if (aRv.Failed()) { 258 return nullptr; 259 } 260 scriptURI = std::move(cloneWithoutRef); 261 262 aRv = NS_GetURIWithoutRef(scopeURI, getter_AddRefs(cloneWithoutRef)); 263 if (aRv.Failed()) { 264 return nullptr; 265 } 266 scopeURI = std::move(cloneWithoutRef); 267 268 ServiceWorkerScopeAndScriptAreValid(clientInfo.ref(), scopeURI, scriptURI, 269 aRv, global); 270 if (aRv.Failed()) { 271 return nullptr; 272 } 273 274 // Get the string representation for both the script and scope since 275 // we sanitized them above. 276 nsCString cleanedScopeURL; 277 aRv = scopeURI->GetSpec(cleanedScopeURL); 278 if (aRv.Failed()) { 279 return nullptr; 280 } 281 282 nsCString cleanedScriptURL; 283 aRv = scriptURI->GetSpec(cleanedScriptURL); 284 if (aRv.Failed()) { 285 return nullptr; 286 } 287 288 // Verify that the global is valid and has permission to store 289 // data. We perform this late so that we can report the final 290 // scope URL in any error message. 291 (void)GetGlobalIfValid(aRv, [&](nsIGlobalObject* aGlobal) { 292 AutoTArray<nsString, 1> param; 293 CopyUTF8toUTF16(cleanedScopeURL, *param.AppendElement()); 294 aGlobal->ReportToConsole(nsIScriptError::errorFlag, "Service Workers"_ns, 295 nsContentUtils::eDOM_PROPERTIES, 296 "ServiceWorkerRegisterStorageError"_ns, param); 297 }); 298 299 // TODO: For bug 1836707 we will move this tracking to ServiceWorkerManager 300 // where it can establish the mapping between the job and our client info, 301 // which will also work on workers. For now we leave this notification for 302 // Windows only. 303 if (auto* window = global->GetAsInnerWindow()) { 304 window->NoteCalledRegisterForServiceWorkerScope(cleanedScopeURL); 305 } 306 307 RefPtr<Promise> outer = 308 Promise::Create(global, aRv, Promise::ePropagateUserInteraction); 309 if (aRv.Failed()) { 310 return nullptr; 311 } 312 313 RefPtr<ServiceWorkerContainer> self = this; 314 315 if (!mActor) { 316 aRv.ThrowInvalidStateError("Can't register service worker"); 317 return nullptr; 318 } 319 320 mActor->SendRegister( 321 clientInfo.ref().ToIPC(), nsCString(cleanedScopeURL), aOptions.mType, 322 nsCString(cleanedScriptURL), aOptions.mUpdateViaCache, 323 [self, 324 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult& 325 aResult) { 326 AUTO_PROFILER_MARKER_UNTYPED("SWC Register (inner)", DOM, {}); 327 328 if (aResult.type() == 329 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult:: 330 TCopyableErrorResult) { 331 // application layer error 332 CopyableErrorResult rv = aResult.get_CopyableErrorResult(); 333 MOZ_DIAGNOSTIC_ASSERT(rv.Failed()); 334 outer->MaybeReject(std::move(rv)); 335 return; 336 } 337 // success 338 const auto& ipcDesc = 339 aResult.get_IPCServiceWorkerRegistrationDescriptor(); 340 ErrorResult rv; 341 nsIGlobalObject* global = self->GetGlobalIfValid(rv); 342 if (rv.Failed()) { 343 outer->MaybeReject(std::move(rv)); 344 return; 345 } 346 RefPtr<ServiceWorkerRegistration> reg = 347 global->GetOrCreateServiceWorkerRegistration( 348 ServiceWorkerRegistrationDescriptor(ipcDesc)); 349 outer->MaybeResolve(reg); 350 }, 351 [outer](ResponseRejectReason&& aReason) { 352 // IPC layer error 353 CopyableErrorResult rv; 354 rv.ThrowInvalidStateError("Failed to register service worker"); 355 outer->MaybeReject(std::move(rv)); 356 }); 357 358 return outer.forget(); 359 } 360 361 already_AddRefed<ServiceWorker> ServiceWorkerContainer::GetController() { 362 RefPtr<ServiceWorker> ref = mControllerWorker; 363 return ref.forget(); 364 } 365 366 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistrations( 367 ErrorResult& aRv) { 368 nsIGlobalObject* global = GetGlobalIfValid(aRv, [](nsIGlobalObject* aGlobal) { 369 aGlobal->ReportToConsole(nsIScriptError::errorFlag, "Service Workers"_ns, 370 nsContentUtils::eDOM_PROPERTIES, 371 "ServiceWorkerGetRegistrationStorageError"_ns); 372 }); 373 if (aRv.Failed()) { 374 return nullptr; 375 } 376 377 Maybe<ClientInfo> clientInfo = global->GetClientInfo(); 378 if (clientInfo.isNothing()) { 379 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 380 return nullptr; 381 } 382 383 RefPtr<Promise> outer = 384 Promise::Create(global, aRv, Promise::ePropagateUserInteraction); 385 if (aRv.Failed()) { 386 return nullptr; 387 } 388 389 RefPtr<ServiceWorkerContainer> self = this; 390 391 if (!mActor) { 392 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 393 return outer.forget(); 394 } 395 396 mActor->SendGetRegistrations( 397 clientInfo.ref().ToIPC(), 398 [self, outer]( 399 const IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult& 400 aResult) { 401 if (aResult.type() == 402 IPCServiceWorkerRegistrationDescriptorListOrCopyableErrorResult:: 403 TCopyableErrorResult) { 404 // application layer error 405 const auto& rv = aResult.get_CopyableErrorResult(); 406 MOZ_DIAGNOSTIC_ASSERT(rv.Failed()); 407 outer->MaybeReject(CopyableErrorResult(rv)); 408 return; 409 } 410 // success 411 const auto& ipcList = 412 aResult.get_IPCServiceWorkerRegistrationDescriptorList(); 413 nsTArray<ServiceWorkerRegistrationDescriptor> list( 414 ipcList.values().Length()); 415 for (const auto& ipcDesc : ipcList.values()) { 416 list.AppendElement(ServiceWorkerRegistrationDescriptor(ipcDesc)); 417 } 418 419 ErrorResult rv; 420 nsIGlobalObject* global = self->GetGlobalIfValid(rv); 421 if (rv.Failed()) { 422 outer->MaybeReject(std::move(rv)); 423 return; 424 } 425 nsTArray<RefPtr<ServiceWorkerRegistration>> regList; 426 for (auto& desc : list) { 427 RefPtr<ServiceWorkerRegistration> reg = 428 global->GetOrCreateServiceWorkerRegistration(desc); 429 if (reg) { 430 regList.AppendElement(std::move(reg)); 431 } 432 } 433 outer->MaybeResolve(regList); 434 }, 435 [outer](ResponseRejectReason&& aReason) { 436 // IPC layer error 437 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 438 }); 439 440 return outer.forget(); 441 } 442 443 void ServiceWorkerContainer::StartMessages() { 444 while (!mPendingMessages.IsEmpty()) { 445 EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0)); 446 mPendingMessages.RemoveElementAt(0); 447 } 448 mMessagesStarted = true; 449 } 450 451 already_AddRefed<Promise> ServiceWorkerContainer::GetRegistration( 452 const nsAString& aURL, ErrorResult& aRv) { 453 nsIGlobalObject* global = GetGlobalIfValid(aRv, [](nsIGlobalObject* aGlobal) { 454 aGlobal->ReportToConsole(nsIScriptError::errorFlag, "Service Workers"_ns, 455 nsContentUtils::eDOM_PROPERTIES, 456 "ServiceWorkerGetRegistrationStorageError"_ns); 457 }); 458 if (aRv.Failed()) { 459 return nullptr; 460 } 461 462 Maybe<ClientInfo> clientInfo = global->GetClientInfo(); 463 if (clientInfo.isNothing()) { 464 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 465 return nullptr; 466 } 467 468 nsCOMPtr<nsIURI> baseURI = global->GetBaseURI(); 469 if (!baseURI) { 470 return nullptr; 471 } 472 473 nsCOMPtr<nsIURI> uri; 474 aRv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, baseURI); 475 if (aRv.Failed()) { 476 return nullptr; 477 } 478 479 nsCString spec; 480 aRv = uri->GetSpec(spec); 481 if (aRv.Failed()) { 482 return nullptr; 483 } 484 485 RefPtr<Promise> outer = 486 Promise::Create(global, aRv, Promise::ePropagateUserInteraction); 487 if (aRv.Failed()) { 488 return nullptr; 489 } 490 491 RefPtr<ServiceWorkerContainer> self = this; 492 493 if (!mActor) { 494 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 495 return outer.forget(); 496 } 497 498 mActor->SendGetRegistration( 499 clientInfo.ref().ToIPC(), spec, 500 [self, 501 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult& 502 aResult) { 503 if (aResult.type() == 504 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult:: 505 TCopyableErrorResult) { 506 CopyableErrorResult ipcRv(aResult.get_CopyableErrorResult()); 507 ErrorResult rv(std::move(ipcRv)); 508 if (!rv.Failed()) { 509 // ErrorResult rv; 510 // If rv is a failure then this is an application layer error. 511 // Note, though, we also reject with NS_OK to indicate that we just 512 // didn't find a registration. 513 (void)self->GetGlobalIfValid(rv); 514 if (!rv.Failed()) { 515 outer->MaybeResolveWithUndefined(); 516 return; 517 } 518 } 519 outer->MaybeReject(std::move(rv)); 520 return; 521 } 522 // success 523 const auto& ipcDesc = 524 aResult.get_IPCServiceWorkerRegistrationDescriptor(); 525 ErrorResult rv; 526 nsIGlobalObject* global = self->GetGlobalIfValid(rv); 527 if (rv.Failed()) { 528 outer->MaybeReject(std::move(rv)); 529 return; 530 } 531 RefPtr<ServiceWorkerRegistration> reg = 532 global->GetOrCreateServiceWorkerRegistration( 533 ServiceWorkerRegistrationDescriptor(ipcDesc)); 534 outer->MaybeResolve(reg); 535 }, 536 [self, outer](ResponseRejectReason&& aReason) { 537 // IPC layer error 538 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 539 }); 540 return outer.forget(); 541 } 542 543 Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) { 544 if (mReadyPromise) { 545 return mReadyPromise; 546 } 547 548 nsIGlobalObject* global = GetGlobalIfValid(aRv); 549 if (aRv.Failed()) { 550 return nullptr; 551 } 552 MOZ_DIAGNOSTIC_ASSERT(global); 553 554 Maybe<ClientInfo> clientInfo(global->GetClientInfo()); 555 if (clientInfo.isNothing()) { 556 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 557 return nullptr; 558 } 559 560 mReadyPromise = 561 Promise::Create(global, aRv, Promise::ePropagateUserInteraction); 562 if (aRv.Failed()) { 563 return nullptr; 564 } 565 566 RefPtr<ServiceWorkerContainer> self = this; 567 RefPtr<Promise> outer = mReadyPromise; 568 569 if (!mActor) { 570 mReadyPromise->MaybeReject( 571 CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 572 return mReadyPromise; 573 } 574 575 mActor->SendGetReady( 576 clientInfo.ref().ToIPC(), 577 [self, 578 outer](const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult& 579 aResult) { 580 if (aResult.type() == 581 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult:: 582 TCopyableErrorResult) { 583 // application layer error 584 CopyableErrorResult rv(aResult.get_CopyableErrorResult()); 585 MOZ_DIAGNOSTIC_ASSERT(rv.Failed()); 586 outer->MaybeReject(std::move(rv)); 587 return; 588 } 589 // success 590 const auto& ipcDesc = 591 aResult.get_IPCServiceWorkerRegistrationDescriptor(); 592 ErrorResult rv; 593 nsIGlobalObject* global = self->GetGlobalIfValid(rv); 594 if (rv.Failed()) { 595 outer->MaybeReject(std::move(rv)); 596 return; 597 } 598 RefPtr<ServiceWorkerRegistration> reg = 599 global->GetOrCreateServiceWorkerRegistration( 600 ServiceWorkerRegistrationDescriptor(ipcDesc)); 601 NS_ENSURE_TRUE_VOID(reg); 602 603 // Don't resolve the ready promise until the registration has 604 // reached the right version. This ensures that the active 605 // worker property is set correctly on the registration. 606 reg->WhenVersionReached(ipcDesc.version(), [outer, reg](bool aResult) { 607 outer->MaybeResolve(reg); 608 }); 609 }, 610 [outer](ResponseRejectReason&& aReason) { 611 // IPC layer error 612 outer->MaybeReject(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 613 }); 614 615 return mReadyPromise; 616 } 617 618 nsIGlobalObject* ServiceWorkerContainer::GetGlobalIfValid( 619 ErrorResult& aRv, 620 const std::function<void(nsIGlobalObject*)>&& aStorageFailureCB) const { 621 nsIGlobalObject* global = GetOwnerGlobal(); 622 if (NS_WARN_IF(!global)) { 623 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 624 return nullptr; 625 } 626 627 if (NS_FAILED(CheckCurrentGlobalCorrectness())) { 628 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 629 return nullptr; 630 } 631 632 // Don't allow a service worker to access service worker registrations 633 // from a global with storage disabled. If these globals can access 634 // the registration it increases the chance they can bypass the storage 635 // block via postMessage(), etc. 636 if (NS_WARN_IF(!ServiceWorkersStorageAllowedForGlobal(global))) { 637 if (aStorageFailureCB) { 638 aStorageFailureCB(global); 639 } 640 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 641 return nullptr; 642 } 643 644 // Don't allow service workers for system principals. 645 nsIPrincipal* principal = global->PrincipalOrNull(); 646 if (NS_WARN_IF(!principal || principal->IsSystemPrincipal())) { 647 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 648 return nullptr; 649 } 650 651 return global; 652 } 653 654 void ServiceWorkerContainer::EnqueueReceivedMessageDispatch( 655 RefPtr<ReceivedMessage> aMessage) { 656 NS_DispatchToCurrentThread(NewRunnableMethod<RefPtr<ReceivedMessage>>( 657 "ServiceWorkerContainer::DispatchMessage", this, 658 &ServiceWorkerContainer::DispatchMessage, std::move(aMessage))); 659 } 660 661 template <typename F> 662 void ServiceWorkerContainer::RunWithJSContext(F&& aCallable) { 663 nsCOMPtr<nsIGlobalObject> globalObject = GetOwnerGlobal(); 664 665 // If AutoJSAPI::Init() fails then either global is nullptr or not 666 // in a usable state. 667 AutoJSAPI jsapi; 668 if (!jsapi.Init(globalObject)) { 669 return; 670 } 671 672 aCallable(jsapi.cx(), globalObject); 673 } 674 675 void ServiceWorkerContainer::DispatchMessage(RefPtr<ReceivedMessage> aMessage) { 676 nsresult rv = CheckCurrentGlobalCorrectness(); 677 if (NS_FAILED(rv)) { 678 return; 679 } 680 681 // When dispatching a message, either DOMContentLoaded has already 682 // been fired, or someone called startMessages() or set onmessage. 683 // Either way, a global object is supposed to be present. If it's 684 // not, we'd fail to initialize the JS API and exit. 685 RunWithJSContext([this, message = std::move(aMessage)]( 686 JSContext* const aCx, nsIGlobalObject* const aGlobal) { 687 ErrorResult result; 688 bool deserializationFailed = false; 689 RootedDictionary<MessageEventInit> init(aCx); 690 auto res = FillInMessageEventInit(aCx, aGlobal, *message, init, result); 691 if (res.isErr()) { 692 deserializationFailed = res.unwrapErr(); 693 MOZ_ASSERT_IF(deserializationFailed, init.mData.isNull()); 694 MOZ_ASSERT_IF(deserializationFailed, init.mPorts.IsEmpty()); 695 MOZ_ASSERT_IF(deserializationFailed, !init.mOrigin.IsEmpty()); 696 MOZ_ASSERT_IF(deserializationFailed, !init.mSource.IsNull()); 697 result.SuppressException(); 698 699 if (!deserializationFailed && result.MaybeSetPendingException(aCx)) { 700 return; 701 } 702 } 703 704 RefPtr<MessageEvent> event = MessageEvent::Constructor( 705 this, deserializationFailed ? u"messageerror"_ns : u"message"_ns, init); 706 event->SetTrusted(true); 707 708 result = NS_OK; 709 DispatchEvent(*event, result); 710 if (result.Failed()) { 711 result.SuppressException(); 712 } 713 }); 714 } 715 716 namespace { 717 718 nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker, 719 nsString& aOrigin) { 720 using mozilla::ipc::PrincipalInfoToPrincipal; 721 722 nsresult rv; 723 724 auto principalOrErr = 725 PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo()); 726 if (NS_WARN_IF(principalOrErr.isErr())) { 727 return principalOrErr.unwrapErr(); 728 } 729 730 nsAutoCString originUTF8; 731 rv = principalOrErr.unwrap()->GetOriginNoSuffix(originUTF8); 732 if (NS_FAILED(rv)) { 733 return rv; 734 } 735 736 CopyUTF8toUTF16(originUTF8, aOrigin); 737 return NS_OK; 738 } 739 740 } // namespace 741 742 Result<Ok, bool> ServiceWorkerContainer::FillInMessageEventInit( 743 JSContext* const aCx, nsIGlobalObject* const aGlobal, 744 ReceivedMessage& aMessage, MessageEventInit& aInit, ErrorResult& aRv) { 745 // Determining the source and origin should preceed attempting deserialization 746 // because on a "messageerror" event (i.e. when deserialization fails), the 747 // dispatched message needs to contain such an origin and source, per spec: 748 // 749 // "If this throws an exception, catch it, fire an event named messageerror 750 // at destination, using MessageEvent, with the origin attribute initialized 751 // to origin and the source attribute initialized to source, and then abort 752 // these steps." - 6.4 of postMessage 753 // See: https://w3c.github.io/ServiceWorker/#service-worker-postmessage 754 const RefPtr<ServiceWorker> serviceWorkerInstance = 755 aGlobal->GetOrCreateServiceWorker(aMessage.mServiceWorker); 756 if (serviceWorkerInstance) { 757 aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance; 758 } 759 760 const nsresult rv = 761 FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin); 762 if (NS_FAILED(rv)) { 763 return Err(false); 764 } 765 766 JS::Rooted<JS::Value> messageData(aCx); 767 aMessage.mClonedData.Read(aCx, &messageData, aRv); 768 if (aRv.Failed()) { 769 return Err(true); 770 } 771 772 aInit.mData = messageData; 773 774 if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) { 775 xpc::Throw(aCx, NS_ERROR_OUT_OF_MEMORY); 776 return Err(false); 777 } 778 779 return Ok(); 780 } 781 782 void ServiceWorkerContainer::Shutdown() { 783 if (mShutdown) { 784 return; 785 } 786 mShutdown = true; 787 788 if (mActor) { 789 mActor->RevokeOwner(this); 790 mActor->MaybeStartTeardown(); 791 mActor = nullptr; 792 } 793 } 794 795 } // namespace mozilla::dom