ServiceWorkerRegistration.cpp (28470B)
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 "ServiceWorkerRegistration.h" 8 9 #include "ServiceWorkerRegistrationChild.h" 10 #include "mozilla/ScopeExit.h" 11 #include "mozilla/dom/CookieStoreManager.h" 12 #include "mozilla/dom/DOMMozPromiseRequestHolder.h" 13 #include "mozilla/dom/NavigationPreloadManager.h" 14 #include "mozilla/dom/NavigationPreloadManagerBinding.h" 15 #include "mozilla/dom/Notification.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/PushManager.h" 18 #include "mozilla/dom/ServiceWorker.h" 19 #include "mozilla/dom/ServiceWorkerUtils.h" 20 #include "mozilla/dom/WorkerPrivate.h" 21 #include "mozilla/ipc/BackgroundChild.h" 22 #include "mozilla/ipc/PBackgroundChild.h" 23 #include "mozilla/ipc/PBackgroundSharedTypes.h" 24 #include "nsCycleCollectionParticipant.h" 25 #include "nsPIDOMWindow.h" 26 27 using mozilla::ipc::ResponseRejectReason; 28 29 namespace mozilla::dom { 30 31 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration, 32 DOMEventTargetHelper, mInstallingWorker, 33 mWaitingWorker, mActiveWorker, 34 mNavigationPreloadManager, mPushManager, 35 mCookieStoreManager); 36 37 NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) 38 NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper) 39 40 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerRegistration) 41 NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorkerRegistration) 42 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 43 44 namespace { 45 const uint64_t kInvalidUpdateFoundId = 0; 46 } // anonymous namespace 47 48 ServiceWorkerRegistration::ServiceWorkerRegistration( 49 nsIGlobalObject* aGlobal, 50 const ServiceWorkerRegistrationDescriptor& aDescriptor) 51 : DOMEventTargetHelper(aGlobal), 52 mDescriptor(aDescriptor), 53 mShutdown(false), 54 mScheduledUpdateFoundId(kInvalidUpdateFoundId), 55 mDispatchedUpdateFoundId(kInvalidUpdateFoundId) { 56 ::mozilla::ipc::PBackgroundChild* parentActor = 57 ::mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); 58 if (NS_WARN_IF(!parentActor)) { 59 Shutdown(); 60 return; 61 } 62 63 auto actor = ServiceWorkerRegistrationChild::Create(); 64 if (NS_WARN_IF(!actor)) { 65 Shutdown(); 66 return; 67 } 68 69 Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo(); 70 if (clientInfo.isNothing()) { 71 Shutdown(); 72 return; 73 } 74 75 PServiceWorkerRegistrationChild* sentActor = 76 parentActor->SendPServiceWorkerRegistrationConstructor( 77 actor, aDescriptor.ToIPC(), clientInfo.ref().ToIPC()); 78 if (NS_WARN_IF(!sentActor)) { 79 Shutdown(); 80 return; 81 } 82 MOZ_DIAGNOSTIC_ASSERT(sentActor == actor); 83 84 mActor = std::move(actor); 85 mActor->SetOwner(this); 86 87 KeepAliveIfHasListenersFor(nsGkAtoms::onupdatefound); 88 } 89 90 ServiceWorkerRegistration::~ServiceWorkerRegistration() { Shutdown(); } 91 92 JSObject* ServiceWorkerRegistration::WrapObject( 93 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 94 return ServiceWorkerRegistration_Binding::Wrap(aCx, this, aGivenProto); 95 } 96 97 /* static */ 98 already_AddRefed<ServiceWorkerRegistration> 99 ServiceWorkerRegistration::CreateForMainThread( 100 nsPIDOMWindowInner* aWindow, 101 const ServiceWorkerRegistrationDescriptor& aDescriptor) { 102 MOZ_ASSERT(aWindow); 103 MOZ_ASSERT(NS_IsMainThread()); 104 105 RefPtr<ServiceWorkerRegistration> registration = 106 new ServiceWorkerRegistration(aWindow->AsGlobal(), aDescriptor); 107 // This is not called from within the constructor, as it may call content code 108 // which can cause the deletion of the registration, so we need to keep a 109 // strong reference while calling it. 110 registration->UpdateState(aDescriptor); 111 112 return registration.forget(); 113 } 114 115 /* static */ 116 already_AddRefed<ServiceWorkerRegistration> 117 ServiceWorkerRegistration::CreateForWorker( 118 WorkerPrivate* aWorkerPrivate, nsIGlobalObject* aGlobal, 119 const ServiceWorkerRegistrationDescriptor& aDescriptor) { 120 MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); 121 MOZ_DIAGNOSTIC_ASSERT(aGlobal); 122 aWorkerPrivate->AssertIsOnWorkerThread(); 123 124 RefPtr<ServiceWorkerRegistration> registration = 125 new ServiceWorkerRegistration(aGlobal, aDescriptor); 126 // This is not called from within the constructor, as it may call content code 127 // which can cause the deletion of the registration, so we need to keep a 128 // strong reference while calling it. 129 registration->UpdateState(aDescriptor); 130 131 return registration.forget(); 132 } 133 134 void ServiceWorkerRegistration::DisconnectFromOwner() { 135 DOMEventTargetHelper::DisconnectFromOwner(); 136 Shutdown(); 137 } 138 139 void ServiceWorkerRegistration::RegistrationCleared() { 140 // Its possible that the registration will fail to install and be 141 // immediately removed. In that case we may never receive the 142 // UpdateState() call if the actor was too slow to connect, etc. 143 // Ensure that we force all our known actors to redundant so that 144 // the appropriate statechange events are fired. If we got the 145 // UpdateState() already then this will be a no-op. 146 UpdateStateInternal(Maybe<ServiceWorkerDescriptor>(), 147 Maybe<ServiceWorkerDescriptor>(), 148 Maybe<ServiceWorkerDescriptor>()); 149 150 // Our underlying registration was removed from SWM, so we 151 // will never get an updatefound event again. We can let 152 // the object GC if content is not holding it alive. 153 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onupdatefound); 154 } 155 156 already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetInstalling() 157 const { 158 RefPtr<ServiceWorker> ref = mInstallingWorker; 159 return ref.forget(); 160 } 161 162 already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetWaiting() const { 163 RefPtr<ServiceWorker> ref = mWaitingWorker; 164 return ref.forget(); 165 } 166 167 already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetActive() const { 168 RefPtr<ServiceWorker> ref = mActiveWorker; 169 return ref.forget(); 170 } 171 172 already_AddRefed<NavigationPreloadManager> 173 ServiceWorkerRegistration::NavigationPreload() { 174 RefPtr<ServiceWorkerRegistration> reg = this; 175 if (!mNavigationPreloadManager) { 176 mNavigationPreloadManager = MakeRefPtr<NavigationPreloadManager>(reg); 177 } 178 RefPtr<NavigationPreloadManager> ref = mNavigationPreloadManager; 179 return ref.forget(); 180 } 181 182 CookieStoreManager* ServiceWorkerRegistration::GetCookies(ErrorResult& aRv) { 183 if (!mCookieStoreManager) { 184 nsIGlobalObject* globalObject = GetParentObject(); 185 if (!globalObject) { 186 aRv.ThrowInvalidStateError("No global"); 187 return nullptr; 188 } 189 190 mCookieStoreManager = 191 new CookieStoreManager(globalObject, mDescriptor.Scope()); 192 } 193 194 return mCookieStoreManager; 195 } 196 197 void ServiceWorkerRegistration::UpdateState( 198 const ServiceWorkerRegistrationDescriptor& aDescriptor) { 199 MOZ_DIAGNOSTIC_ASSERT(MatchesDescriptor(aDescriptor)); 200 201 mDescriptor = aDescriptor; 202 203 UpdateStateInternal(aDescriptor.GetInstalling(), aDescriptor.GetWaiting(), 204 aDescriptor.GetActive()); 205 206 nsTArray<UniquePtr<VersionCallback>> callbackList = 207 std::move(mVersionCallbackList); 208 for (auto& cb : callbackList) { 209 if (cb->mVersion > mDescriptor.Version()) { 210 mVersionCallbackList.AppendElement(std::move(cb)); 211 continue; 212 } 213 214 cb->mFunc(cb->mVersion == mDescriptor.Version()); 215 } 216 } 217 218 bool ServiceWorkerRegistration::MatchesDescriptor( 219 const ServiceWorkerRegistrationDescriptor& aDescriptor) const { 220 return aDescriptor.Id() == mDescriptor.Id() && 221 aDescriptor.PrincipalInfo() == mDescriptor.PrincipalInfo() && 222 aDescriptor.Scope() == mDescriptor.Scope(); 223 } 224 225 void ServiceWorkerRegistration::GetScope(nsAString& aScope) const { 226 CopyUTF8toUTF16(mDescriptor.Scope(), aScope); 227 } 228 229 ServiceWorkerUpdateViaCache ServiceWorkerRegistration::GetUpdateViaCache( 230 ErrorResult& aRv) const { 231 return mDescriptor.UpdateViaCache(); 232 } 233 234 already_AddRefed<Promise> ServiceWorkerRegistration::Update(ErrorResult& aRv) { 235 AUTO_PROFILER_MARKER_UNTYPED("ServiceWorkerRegistration::Update", DOM, {}); 236 237 nsIGlobalObject* global = GetParentObject(); 238 if (!global) { 239 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 240 return nullptr; 241 } 242 243 RefPtr<Promise> outer = Promise::Create(global, aRv); 244 if (NS_WARN_IF(aRv.Failed())) { 245 return nullptr; 246 } 247 248 // `ServiceWorker` objects are not exposed on worker threads yet, so calling 249 // `ServiceWorkerRegistration::Get{Installing,Waiting,Active}` won't work. 250 const Maybe<ServiceWorkerDescriptor> newestWorkerDescriptor = 251 mDescriptor.Newest(); 252 253 // "If newestWorker is null, return a promise rejected with an 254 // "InvalidStateError" DOMException and abort these steps." 255 if (newestWorkerDescriptor.isNothing()) { 256 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 257 return outer.forget(); 258 } 259 260 // "If the context object’s relevant settings object’s global object 261 // globalObject is a ServiceWorkerGlobalScope object, and globalObject’s 262 // associated service worker's state is "installing", return a promise 263 // rejected with an "InvalidStateError" DOMException and abort these steps." 264 if (!NS_IsMainThread()) { 265 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 266 MOZ_ASSERT(workerPrivate); 267 268 if (workerPrivate->IsServiceWorker() && 269 (workerPrivate->GetServiceWorkerDescriptor().State() == 270 ServiceWorkerState::Installing)) { 271 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 272 return outer.forget(); 273 } 274 } 275 276 // Keep the SWR and thereby its actor live throughout the IPC call (unless 277 // the global is torn down and DisconnectFromOwner is called which will cause 278 // us to call Shutdown() which will shutdown the actor and reject the IPC 279 // calls). 280 RefPtr<ServiceWorkerRegistration> self = this; 281 282 if (!mActor) { 283 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 284 return outer.forget(); 285 } 286 287 mActor->SendUpdate( 288 newestWorkerDescriptor.ref().ScriptURL(), 289 [outer, self = std::move(self)]( 290 const IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult& 291 aResult) { 292 AUTO_PROFILER_MARKER_UNTYPED( 293 "ServiceWorkerRegistration::Update (inner)", DOM, {}); 294 295 if (aResult.type() == 296 IPCServiceWorkerRegistrationDescriptorOrCopyableErrorResult:: 297 TCopyableErrorResult) { 298 // application layer error 299 const auto& rv = aResult.get_CopyableErrorResult(); 300 MOZ_DIAGNOSTIC_ASSERT(rv.Failed()); 301 outer->MaybeReject(CopyableErrorResult(rv)); 302 return; 303 } 304 // success 305 const auto& ipcDesc = 306 aResult.get_IPCServiceWorkerRegistrationDescriptor(); 307 nsIGlobalObject* global = self->GetParentObject(); 308 // Given that we destroy the actor on DisconnectFromOwner, it should be 309 // impossible for global to be null here since we should only process 310 // the reject case below in that case. (And in the event there is an 311 // in-flight IPC message, it will be discarded.) This assertion will 312 // help validate this without inconveniencing users. 313 MOZ_ASSERT_DEBUG_OR_FUZZING(global); 314 if (!global) { 315 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 316 return; 317 } 318 // TODO: Given that we are keeping this registration alive through the 319 // call, it's not clear how `ref` could be anything but this instance. 320 // Consider just returning `self` after doing the code archaeology to 321 // ensure there isn't some still-valid reason. 322 RefPtr<ServiceWorkerRegistration> ref = 323 global->GetOrCreateServiceWorkerRegistration( 324 ServiceWorkerRegistrationDescriptor(ipcDesc)); 325 if (!ref) { 326 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 327 return; 328 } 329 outer->MaybeResolve(ref); 330 }, 331 [outer](ResponseRejectReason&& aReason) { 332 // IPC layer error 333 outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); 334 }); 335 336 return outer.forget(); 337 } 338 339 already_AddRefed<Promise> ServiceWorkerRegistration::Unregister( 340 ErrorResult& aRv) { 341 nsIGlobalObject* global = GetParentObject(); 342 if (NS_WARN_IF(!global)) { 343 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 344 return nullptr; 345 } 346 347 RefPtr<Promise> outer = Promise::Create(global, aRv); 348 if (NS_WARN_IF(aRv.Failed())) { 349 return nullptr; 350 } 351 352 if (!mActor) { 353 outer->MaybeResolve(false); 354 return outer.forget(); 355 } 356 357 // Keep the SWR and thereby its actor live throughout the IPC call (unless 358 // the global is torn down and DisconnectFromOwner is called which will cause 359 // us to call Shutdown() which will shutdown the actor and reject the IPC 360 // calls). 361 RefPtr<ServiceWorkerRegistration> self = this; 362 mActor->SendUnregister( 363 [self = std::move(self), 364 outer](std::tuple<bool, CopyableErrorResult>&& aResult) { 365 if (std::get<1>(aResult).Failed()) { 366 // application layer error 367 // register() should be resilient and resolve false instead of 368 // rejecting in most cases. 369 std::get<1>(aResult).SuppressException(); 370 outer->MaybeResolve(false); 371 return; 372 } 373 // success 374 outer->MaybeResolve(std::get<0>(aResult)); 375 }, 376 [outer](ResponseRejectReason&& aReason) { 377 // IPC layer error 378 outer->MaybeResolve(false); 379 }); 380 381 return outer.forget(); 382 } 383 384 already_AddRefed<PushManager> ServiceWorkerRegistration::GetPushManager( 385 JSContext* aCx, ErrorResult& aRv) { 386 if (!mPushManager) { 387 nsCOMPtr<nsIGlobalObject> globalObject = GetParentObject(); 388 389 if (!globalObject) { 390 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 391 return nullptr; 392 } 393 394 GlobalObject global(aCx, globalObject->GetGlobalJSObject()); 395 mPushManager = PushManager::Constructor( 396 global, NS_ConvertUTF8toUTF16(mDescriptor.Scope()), aRv); 397 if (aRv.Failed()) { 398 return nullptr; 399 } 400 } 401 402 RefPtr<PushManager> ret = mPushManager; 403 return ret.forget(); 404 } 405 406 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification 407 already_AddRefed<Promise> ServiceWorkerRegistration::ShowNotification( 408 JSContext* aCx, const nsAString& aTitle, 409 const NotificationOptions& aOptions, ErrorResult& aRv) { 410 // Step 1: Let global be this’s relevant global object. 411 nsIGlobalObject* global = GetParentObject(); 412 if (!global) { 413 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 414 return nullptr; 415 } 416 417 // Step 3: If this’s active worker is null, then reject promise with a 418 // TypeError and return promise. 419 // 420 // Until we ship ServiceWorker objects on worker threads the active 421 // worker will always be nullptr. So limit this check to main 422 // thread for now. 423 if (mDescriptor.GetActive().isNothing() && NS_IsMainThread()) { 424 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(mDescriptor.Scope()); 425 return nullptr; 426 } 427 428 NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope()); 429 430 // Step 2, 5, 6 431 RefPtr<Promise> p = Notification::ShowPersistentNotification( 432 aCx, global, scope, aTitle, aOptions, mDescriptor, aRv); 433 if (NS_WARN_IF(aRv.Failed())) { 434 return nullptr; 435 } 436 437 // Step 7: Return promise. 438 return p.forget(); 439 } 440 441 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-getnotifications 442 already_AddRefed<Promise> ServiceWorkerRegistration::GetNotifications( 443 const GetNotificationOptions& aOptions, ErrorResult& aRv) { 444 // Step 1: Let global be this’s relevant global object. 445 // Step 2: Let realm be this’s relevant Realm. 446 nsCOMPtr<nsIGlobalObject> global = GetParentObject(); 447 if (!global) { 448 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 449 return nullptr; 450 } 451 452 // Step 3: Let origin be this’s relevant settings object’s origin. 453 // (Done in ServiceWorkerRegistrationProxy::GetNotifications) 454 455 // Step 4: Let promise be a new promise in realm. 456 RefPtr<Promise> promise = Promise::CreateInfallible(global); 457 458 // Step 5: Run these steps in parallel: 459 // Step 5.1: Let tag be filter["tag"]. 460 // Step 5.2: Let notifications be a list of all notifications in the list of 461 // notifications whose origin is same origin with origin, whose service worker 462 // registration is this, and whose tag, if tag is not the empty string, is 463 // tag. 464 465 if (!mActor) { 466 // While it's not clear from the current spec, it's fair to say that 467 // unregistered registrations cannot have a match in the step 5.2. See also 468 // bug 1881812. 469 // One could also say we should throw here, but no browsers throw. 470 promise->MaybeResolve(nsTArray<RefPtr<Notification>>()); 471 return promise.forget(); 472 } 473 474 // Keep the SWR and thereby its actor live throughout the IPC call (unless 475 // the global is torn down and DisconnectFromOwner is called which will cause 476 // us to call Shutdown() which will shutdown the actor and reject the IPC 477 // calls). 478 RefPtr<ServiceWorkerRegistration> self = this; 479 480 // Step 5.3: Queue a global task on the DOM manipulation task source 481 // given global to run these steps: 482 mActor->SendGetNotifications(aOptions.mTag) 483 ->Then(GetCurrentSerialEventTarget(), __func__, 484 [self = std::move(self), promise, 485 scope = NS_ConvertUTF8toUTF16(mDescriptor.Scope())]( 486 const PServiceWorkerRegistrationChild:: 487 GetNotificationsPromise::ResolveOrRejectValue&& aValue) { 488 if (aValue.IsReject()) { 489 // An unregistered registration 490 promise->MaybeResolve(nsTArray<RefPtr<Notification>>()); 491 return; 492 } 493 494 if (aValue.ResolveValue().type() == 495 IPCNotificationsOrError::Tnsresult) { 496 // An active registration but had some internal error 497 promise->MaybeRejectWithInvalidStateError( 498 "Could not retrieve notifications"_ns); 499 return; 500 } 501 502 const nsTArray<IPCNotification>& notifications = 503 aValue.ResolveValue().get_ArrayOfIPCNotification(); 504 505 // Step 5.3.1: Let objects be a list. 506 nsTArray<RefPtr<Notification>> objects(notifications.Length()); 507 508 // Step 5.3.2: For each notification in notifications, in 509 // creation order, create a new Notification object with realm 510 // representing notification, and append it to objects. 511 for (const IPCNotification& ipcNotification : notifications) { 512 auto result = Notification::ConstructFromIPC( 513 promise->GetParentObject(), ipcNotification, scope); 514 if (result.isErr()) { 515 continue; 516 } 517 RefPtr<Notification> n = result.unwrap(); 518 objects.AppendElement(n.forget()); 519 } 520 521 // Step 5.3.3: Resolve promise with objects. 522 promise->MaybeResolve(std::move(objects)); 523 }); 524 525 // Step 6: Return promise. 526 return promise.forget(); 527 } 528 529 void ServiceWorkerRegistration::SetNavigationPreloadEnabled( 530 bool aEnabled, ServiceWorkerBoolCallback&& aSuccessCB, 531 ServiceWorkerFailureCallback&& aFailureCB) { 532 if (!mActor) { 533 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 534 return; 535 } 536 537 // Keep the SWR and thereby its actor live throughout the IPC call (unless 538 // the global is torn down and DisconnectFromOwner is called which will cause 539 // us to call Shutdown() which will shutdown the actor and reject the IPC 540 // calls). 541 RefPtr<ServiceWorkerRegistration> self = this; 542 543 mActor->SendSetNavigationPreloadEnabled( 544 aEnabled, 545 [self = std::move(self), successCB = std::move(aSuccessCB), 546 aFailureCB](bool aResult) { 547 if (!aResult) { 548 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 549 return; 550 } 551 successCB(aResult); 552 }, 553 [aFailureCB](ResponseRejectReason&& aReason) { 554 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 555 }); 556 } 557 558 void ServiceWorkerRegistration::SetNavigationPreloadHeader( 559 const nsCString& aHeader, ServiceWorkerBoolCallback&& aSuccessCB, 560 ServiceWorkerFailureCallback&& aFailureCB) { 561 if (!mActor) { 562 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 563 return; 564 } 565 566 // Keep the SWR and thereby its actor live throughout the IPC call (unless 567 // the global is torn down and DisconnectFromOwner is called which will cause 568 // us to call Shutdown() which will shutdown the actor and reject the IPC 569 // calls). 570 RefPtr<ServiceWorkerRegistration> self = this; 571 572 mActor->SendSetNavigationPreloadHeader( 573 aHeader, 574 [self = std::move(self), successCB = std::move(aSuccessCB), 575 aFailureCB](bool aResult) { 576 if (!aResult) { 577 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 578 return; 579 } 580 successCB(aResult); 581 }, 582 [aFailureCB](ResponseRejectReason&& aReason) { 583 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 584 }); 585 } 586 587 void ServiceWorkerRegistration::GetNavigationPreloadState( 588 NavigationPreloadGetStateCallback&& aSuccessCB, 589 ServiceWorkerFailureCallback&& aFailureCB) { 590 if (!mActor) { 591 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 592 return; 593 } 594 595 // Keep the SWR and thereby its actor live throughout the IPC call (unless 596 // the global is torn down and DisconnectFromOwner is called which will cause 597 // us to call Shutdown() which will shutdown the actor and reject the IPC 598 // calls). 599 RefPtr<ServiceWorkerRegistration> self = this; 600 601 mActor->SendGetNavigationPreloadState( 602 [self = std::move(self), successCB = std::move(aSuccessCB), 603 aFailureCB](Maybe<IPCNavigationPreloadState>&& aState) { 604 if (NS_WARN_IF(!aState)) { 605 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 606 return; 607 } 608 609 NavigationPreloadState state; 610 state.mEnabled = aState.ref().enabled(); 611 state.mHeaderValue.Construct(std::move(aState.ref().headerValue())); 612 successCB(std::move(state)); 613 }, 614 [aFailureCB](ResponseRejectReason&& aReason) { 615 aFailureCB(CopyableErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); 616 }); 617 } 618 619 const ServiceWorkerRegistrationDescriptor& 620 ServiceWorkerRegistration::Descriptor() const { 621 return mDescriptor; 622 } 623 624 void ServiceWorkerRegistration::WhenVersionReached( 625 uint64_t aVersion, ServiceWorkerBoolCallback&& aCallback) { 626 if (aVersion <= mDescriptor.Version()) { 627 aCallback(aVersion == mDescriptor.Version()); 628 return; 629 } 630 631 mVersionCallbackList.AppendElement( 632 MakeUnique<VersionCallback>(aVersion, std::move(aCallback))); 633 } 634 635 void ServiceWorkerRegistration::MaybeScheduleUpdateFound( 636 const Maybe<ServiceWorkerDescriptor>& aInstallingDescriptor) { 637 // This function sets mScheduledUpdateFoundId to note when we were told about 638 // a new installing worker. We rely on a call to MaybeDispatchUpdateFound via 639 // ServiceWorkerRegistrationChild::RecvFireUpdateFound to trigger the properly 640 // timed notification... 641 uint64_t newId = aInstallingDescriptor.isSome() 642 ? aInstallingDescriptor.ref().Id() 643 : kInvalidUpdateFoundId; 644 645 // ...but the whole reason this logic exists is because SWRegistrations are 646 // bootstrapped off of inherently stale descriptor snapshots and receive 647 // catch-up updates once the actor is created and registered in the parent. 648 // To handle the catch-up case where we need to generate a synthetic 649 // updatefound that would otherwise be lost, we immediately flush here. 650 if (mScheduledUpdateFoundId != kInvalidUpdateFoundId) { 651 if (mScheduledUpdateFoundId == newId) { 652 return; 653 } 654 MaybeDispatchUpdateFound(); 655 MOZ_DIAGNOSTIC_ASSERT(mScheduledUpdateFoundId == kInvalidUpdateFoundId); 656 } 657 658 bool updateFound = 659 newId != kInvalidUpdateFoundId && mDispatchedUpdateFoundId != newId; 660 661 if (!updateFound) { 662 return; 663 } 664 665 mScheduledUpdateFoundId = newId; 666 } 667 668 void ServiceWorkerRegistration::MaybeDispatchUpdateFound() { 669 uint64_t scheduledId = mScheduledUpdateFoundId; 670 mScheduledUpdateFoundId = kInvalidUpdateFoundId; 671 672 if (scheduledId == kInvalidUpdateFoundId || 673 scheduledId == mDispatchedUpdateFoundId) { 674 return; 675 } 676 677 mDispatchedUpdateFoundId = scheduledId; 678 DispatchTrustedEvent(u"updatefound"_ns); 679 } 680 681 void ServiceWorkerRegistration::UpdateStateInternal( 682 const Maybe<ServiceWorkerDescriptor>& aInstalling, 683 const Maybe<ServiceWorkerDescriptor>& aWaiting, 684 const Maybe<ServiceWorkerDescriptor>& aActive) { 685 // Do this immediately as it may flush an already pending updatefound 686 // event. In that case we want to fire the pending event before 687 // modifying any of the registration properties. 688 MaybeScheduleUpdateFound(aInstalling); 689 690 // Move the currently exposed workers into a separate list 691 // of "old" workers. We will then potentially add them 692 // back to the registration properties below based on the 693 // given descriptor. Any that are not restored will need 694 // to be moved to the redundant state. 695 AutoTArray<RefPtr<ServiceWorker>, 3> oldWorkerList({ 696 std::move(mInstallingWorker), 697 std::move(mWaitingWorker), 698 std::move(mActiveWorker), 699 }); 700 701 // Its important that all state changes are actually applied before 702 // dispatching any statechange events. Each ServiceWorker object 703 // should be in the correct state and the ServiceWorkerRegistration 704 // properties need to be set correctly as well. To accomplish this 705 // we use a ScopeExit to dispatch any statechange events. 706 auto scopeExit = MakeScopeExit([&] { 707 // Check to see if any of the "old" workers was completely discarded. 708 // Set these workers to the redundant state. 709 for (auto& oldWorker : oldWorkerList) { 710 if (!oldWorker || oldWorker == mInstallingWorker || 711 oldWorker == mWaitingWorker || oldWorker == mActiveWorker) { 712 continue; 713 } 714 715 oldWorker->SetState(ServiceWorkerState::Redundant); 716 } 717 718 // Check each worker to see if it needs a statechange event dispatched. 719 if (mInstallingWorker) { 720 mInstallingWorker->MaybeDispatchStateChangeEvent(); 721 } 722 if (mWaitingWorker) { 723 mWaitingWorker->MaybeDispatchStateChangeEvent(); 724 } 725 if (mActiveWorker) { 726 mActiveWorker->MaybeDispatchStateChangeEvent(); 727 } 728 729 // We also check the "old" workers to see if they need a statechange 730 // event as well. Note, these may overlap with the known worker properties 731 // above, but MaybeDispatchStateChangeEvent() will ignore duplicated calls. 732 for (auto& oldWorker : oldWorkerList) { 733 if (!oldWorker) { 734 continue; 735 } 736 737 oldWorker->MaybeDispatchStateChangeEvent(); 738 } 739 }); 740 741 // Clear all workers if the registration has been detached from the global. 742 nsCOMPtr<nsIGlobalObject> global = GetParentObject(); 743 if (!global) { 744 return; 745 } 746 747 if (aActive.isSome()) { 748 if ((mActiveWorker = global->GetOrCreateServiceWorker(aActive.ref()))) { 749 mActiveWorker->SetState(aActive.ref().State()); 750 } 751 } else { 752 mActiveWorker = nullptr; 753 } 754 755 if (aWaiting.isSome()) { 756 if ((mWaitingWorker = global->GetOrCreateServiceWorker(aWaiting.ref()))) { 757 mWaitingWorker->SetState(aWaiting.ref().State()); 758 } 759 } else { 760 mWaitingWorker = nullptr; 761 } 762 763 if (aInstalling.isSome()) { 764 if ((mInstallingWorker = 765 global->GetOrCreateServiceWorker(aInstalling.ref()))) { 766 mInstallingWorker->SetState(aInstalling.ref().State()); 767 } 768 } else { 769 mInstallingWorker = nullptr; 770 } 771 } 772 773 void ServiceWorkerRegistration::RevokeActor( 774 ServiceWorkerRegistrationChild* aActor) { 775 MOZ_DIAGNOSTIC_ASSERT(mActor); 776 MOZ_DIAGNOSTIC_ASSERT(mActor == aActor); 777 mActor->RevokeOwner(this); 778 mActor = nullptr; 779 780 mShutdown = true; 781 782 RegistrationCleared(); 783 } 784 785 void ServiceWorkerRegistration::Shutdown() { 786 if (mShutdown) { 787 return; 788 } 789 mShutdown = true; 790 791 if (mActor) { 792 mActor->RevokeOwner(this); 793 mActor->Shutdown(); 794 mActor = nullptr; 795 } 796 } 797 798 } // namespace mozilla::dom