WebAuthnTransactionParent.cpp (21874B)
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 "mozilla/dom/WebAuthnTransactionParent.h" 8 9 #include "WebAuthnArgs.h" 10 #include "WebAuthnUtil.h" 11 #include "mozilla/Base64.h" 12 #include "mozilla/JSONStringWriteFuncs.h" 13 #include "mozilla/JSONWriter.h" 14 #include "mozilla/StaticPrefs_security.h" 15 #include "mozilla/dom/PWindowGlobalParent.h" 16 #include "mozilla/dom/WindowGlobalParent.h" 17 #include "nsThreadUtils.h" 18 19 #ifdef MOZ_WIDGET_ANDROID 20 # include "mozilla/java/WebAuthnTokenManagerWrappers.h" 21 #endif 22 23 namespace mozilla::dom { 24 25 nsresult AssembleClientData(WindowGlobalParent* aManager, 26 const nsACString& aType, 27 const nsTArray<uint8_t>& aChallenge, 28 /* out */ nsACString& aJsonOut) { 29 nsAutoCString challengeBase64; 30 nsresult rv = 31 Base64URLEncode(aChallenge.Length(), aChallenge.Elements(), 32 Base64URLEncodePaddingPolicy::Omit, challengeBase64); 33 if (NS_FAILED(rv)) { 34 return NS_ERROR_FAILURE; 35 } 36 37 nsIPrincipal* principal = aManager->DocumentPrincipal(); 38 nsIPrincipal* topPrincipal = 39 aManager->TopWindowContext()->DocumentPrincipal(); 40 41 nsCString origin; 42 rv = principal->GetWebExposedOriginSerialization(origin); 43 if (NS_FAILED(rv)) { 44 return NS_ERROR_FAILURE; 45 } 46 47 bool crossOrigin = !principal->Equals(topPrincipal); 48 49 // Serialize the collected client data using the algorithm from 50 // https://www.w3.org/TR/webauthn-3/#clientdatajson-serialization. 51 // Please update the definition of CollectedClientData in 52 // dom/webidl/WebAuthentication.webidl when changes are made here. 53 JSONStringRefWriteFunc f(aJsonOut); 54 JSONWriter w(f, JSONWriter::CollectionStyle::SingleLineStyle); 55 w.Start(); 56 // Steps 2 and 3 57 w.StringProperty("type", aType); 58 // Steps 4 and 5 59 w.StringProperty("challenge", challengeBase64); 60 // Steps 6 and 7 61 w.StringProperty("origin", origin); 62 // Steps 8 - 10 63 w.BoolProperty("crossOrigin", crossOrigin); 64 // Step 11. The description of the algorithm says "If topOrigin is present", 65 // but the definition of topOrigin says that topOrigin "is set only if [...] 66 // crossOrigin is true." so we use the latter condition instead. 67 if (crossOrigin) { 68 nsCString topOrigin; 69 rv = topPrincipal->GetWebExposedOriginSerialization(topOrigin); 70 if (NS_FAILED(rv)) { 71 return NS_ERROR_FAILURE; 72 } 73 w.StringProperty("topOrigin", topOrigin); 74 } 75 w.End(); 76 77 return NS_OK; 78 } 79 80 bool GetAssertionRequestIncludesLargeBlobRead( 81 const WebAuthnGetAssertionInfo& aInfo) { 82 for (const WebAuthnExtension& ext : aInfo.Extensions()) { 83 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionLargeBlob) { 84 if (ext.get_WebAuthnExtensionLargeBlob().flag().isSome()) { 85 return ext.get_WebAuthnExtensionLargeBlob().flag().ref(); 86 } 87 } 88 } 89 return false; 90 } 91 92 bool MakeCredentialRequestIncludesPrfExtension( 93 const WebAuthnMakeCredentialInfo& aInfo) { 94 for (const WebAuthnExtension& ext : aInfo.Extensions()) { 95 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionPrf) { 96 return true; 97 } 98 } 99 return false; 100 } 101 102 void WebAuthnTransactionParent::CompleteTransaction() { 103 if (mTransactionId.isSome()) { 104 if (mRegisterPromiseRequest.Exists()) { 105 mRegisterPromiseRequest.Complete(); 106 } 107 if (mSignPromiseRequest.Exists()) { 108 mSignPromiseRequest.Complete(); 109 } 110 if (mWebAuthnService) { 111 // We have to do this to work around Bug 1864526. 112 mWebAuthnService->Cancel(mTransactionId.ref()); 113 } 114 mTransactionId.reset(); 115 } 116 } 117 118 void WebAuthnTransactionParent::DisconnectTransaction() { 119 mTransactionId.reset(); 120 mRegisterPromiseRequest.DisconnectIfExists(); 121 mSignPromiseRequest.DisconnectIfExists(); 122 if (mWebAuthnService) { 123 mWebAuthnService->Reset(); 124 } 125 } 126 127 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestRegister( 128 const WebAuthnMakeCredentialInfo& aTransactionInfo, 129 RequestRegisterResolver&& aResolver) { 130 MOZ_ASSERT(NS_IsMainThread()); 131 132 if (!mWebAuthnService) { 133 mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1"); 134 if (!mWebAuthnService) { 135 aResolver(NS_ERROR_NOT_AVAILABLE); 136 return IPC_OK(); 137 } 138 } 139 140 // If there's an ongoing transaction, abort it. 141 if (mTransactionId.isSome()) { 142 DisconnectTransaction(); 143 } 144 uint64_t aTransactionId = NextId(); 145 mTransactionId = Some(aTransactionId); 146 147 WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager()); 148 149 if (!IsWebAuthnAllowedInContext(manager)) { 150 aResolver(NS_ERROR_DOM_SECURITY_ERR); 151 return IPC_OK(); 152 } 153 154 nsIPrincipal* principal = manager->DocumentPrincipal(); 155 if (!IsValidRpId(principal, aTransactionInfo.RpId())) { 156 aResolver(NS_ERROR_DOM_SECURITY_ERR); 157 return IPC_OK(); 158 } 159 160 nsCString origin; 161 nsresult rv = principal->GetWebExposedOriginSerialization(origin); 162 if (NS_FAILED(rv)) { 163 aResolver(NS_ERROR_FAILURE); 164 return IPC_OK(); 165 } 166 167 nsCString clientDataJSON; 168 rv = AssembleClientData(manager, "webauthn.create"_ns, 169 aTransactionInfo.Challenge(), clientDataJSON); 170 if (NS_FAILED(rv)) { 171 aResolver(NS_ERROR_FAILURE); 172 return IPC_OK(); 173 } 174 175 bool requestIncludesPrfExtension = 176 MakeCredentialRequestIncludesPrfExtension(aTransactionInfo); 177 178 RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder = 179 new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget()); 180 181 RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure(); 182 promise 183 ->Then( 184 GetCurrentSerialEventTarget(), __func__, 185 [self = RefPtr{this}, inputClientData = clientDataJSON, 186 requestIncludesPrfExtension, resolver = std::move(aResolver)]( 187 const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) { 188 self->CompleteTransaction(); 189 190 if (aValue.IsReject()) { 191 resolver(aValue.RejectValue()); 192 return; 193 } 194 195 auto rejectWithNotAllowed = MakeScopeExit( 196 [&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); }); 197 198 RefPtr<nsIWebAuthnRegisterResult> registerResult = 199 aValue.ResolveValue(); 200 201 nsCString clientData; 202 nsresult rv = registerResult->GetClientDataJSON(clientData); 203 if (rv == NS_ERROR_NOT_AVAILABLE) { 204 clientData = inputClientData; 205 } else if (NS_FAILED(rv)) { 206 return; 207 } 208 209 nsTArray<uint8_t> attObj; 210 rv = registerResult->GetAttestationObject(attObj); 211 if (NS_WARN_IF(NS_FAILED(rv))) { 212 return; 213 } 214 215 nsTArray<uint8_t> credentialId; 216 rv = registerResult->GetCredentialId(credentialId); 217 if (NS_WARN_IF(NS_FAILED(rv))) { 218 return; 219 } 220 221 nsTArray<nsString> transports; 222 rv = registerResult->GetTransports(transports); 223 if (NS_WARN_IF(NS_FAILED(rv))) { 224 return; 225 } 226 227 Maybe<nsString> authenticatorAttachment; 228 nsString maybeAuthenticatorAttachment; 229 rv = registerResult->GetAuthenticatorAttachment( 230 maybeAuthenticatorAttachment); 231 if (rv != NS_ERROR_NOT_AVAILABLE) { 232 if (NS_WARN_IF(NS_FAILED(rv))) { 233 return; 234 } 235 authenticatorAttachment = Some(maybeAuthenticatorAttachment); 236 } 237 238 nsTArray<WebAuthnExtensionResult> extensions; 239 bool credPropsRk; 240 rv = registerResult->GetCredPropsRk(&credPropsRk); 241 if (rv != NS_ERROR_NOT_AVAILABLE) { 242 if (NS_WARN_IF(NS_FAILED(rv))) { 243 return; 244 } 245 extensions.AppendElement( 246 WebAuthnExtensionResultCredProps(credPropsRk)); 247 } 248 249 bool hmacCreateSecret; 250 rv = registerResult->GetHmacCreateSecret(&hmacCreateSecret); 251 if (rv != NS_ERROR_NOT_AVAILABLE) { 252 if (NS_WARN_IF(NS_FAILED(rv))) { 253 return; 254 } 255 extensions.AppendElement( 256 WebAuthnExtensionResultHmacSecret(hmacCreateSecret)); 257 } 258 259 bool largeBlobSupported; 260 rv = registerResult->GetLargeBlobSupported(&largeBlobSupported); 261 if (rv != NS_ERROR_NOT_AVAILABLE) { 262 if (NS_FAILED(rv)) { 263 return; 264 } 265 nsTArray<uint8_t> blob; // unused 266 extensions.AppendElement(WebAuthnExtensionResultLargeBlob( 267 largeBlobSupported, blob, false)); 268 } 269 270 if (requestIncludesPrfExtension) { 271 Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing(); 272 273 bool prfEnabled = false; 274 rv = registerResult->GetPrfEnabled(&prfEnabled); 275 if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) { 276 return; 277 } 278 279 nsTArray<uint8_t> prfResultsFirst; 280 rv = registerResult->GetPrfResultsFirst(prfResultsFirst); 281 if (rv != NS_ERROR_NOT_AVAILABLE) { 282 if (NS_WARN_IF(NS_FAILED(rv))) { 283 return; 284 } 285 286 bool prfResultsSecondMaybe = false; 287 nsTArray<uint8_t> prfResultsSecond; 288 rv = registerResult->GetPrfResultsSecond(prfResultsSecond); 289 if (rv != NS_ERROR_NOT_AVAILABLE) { 290 if (NS_WARN_IF(NS_FAILED(rv))) { 291 return; 292 } 293 prfResultsSecondMaybe = true; 294 } 295 296 prfResults = Some(WebAuthnExtensionPrfValues( 297 prfResultsFirst, prfResultsSecondMaybe, prfResultsSecond)); 298 } 299 300 extensions.AppendElement( 301 WebAuthnExtensionResultPrf(Some(prfEnabled), prfResults)); 302 } 303 304 WebAuthnMakeCredentialResult result( 305 clientData, attObj, credentialId, transports, extensions, 306 authenticatorAttachment); 307 308 rejectWithNotAllowed.release(); 309 resolver(result); 310 }) 311 ->Track(mRegisterPromiseRequest); 312 313 uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id(); 314 bool privateBrowsing = principal->GetIsInPrivateBrowsing(); 315 auto args = MakeRefPtr<WebAuthnRegisterArgs>( 316 origin, clientDataJSON, privateBrowsing, aTransactionInfo); 317 rv = mWebAuthnService->MakeCredential(aTransactionId, browsingContextId, args, 318 promiseHolder); 319 if (NS_FAILED(rv)) { 320 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR); 321 } 322 323 return IPC_OK(); 324 } 325 326 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestSign( 327 const WebAuthnGetAssertionInfo& aTransactionInfo, 328 RequestSignResolver&& aResolver) { 329 MOZ_ASSERT(NS_IsMainThread()); 330 331 if (!mWebAuthnService) { 332 mWebAuthnService = do_GetService("@mozilla.org/webauthn/service;1"); 333 if (!mWebAuthnService) { 334 aResolver(NS_ERROR_NOT_AVAILABLE); 335 return IPC_OK(); 336 } 337 } 338 339 if (mTransactionId.isSome()) { 340 DisconnectTransaction(); 341 } 342 uint64_t transactionId = NextId(); 343 mTransactionId = Some(transactionId); 344 345 WindowGlobalParent* manager = static_cast<WindowGlobalParent*>(Manager()); 346 347 if (!IsWebAuthnAllowedInContext(manager)) { 348 aResolver(NS_ERROR_DOM_SECURITY_ERR); 349 return IPC_OK(); 350 } 351 352 nsIPrincipal* principal = manager->DocumentPrincipal(); 353 if (!IsValidRpId(principal, aTransactionInfo.RpId())) { 354 aResolver(NS_ERROR_DOM_SECURITY_ERR); 355 return IPC_OK(); 356 } 357 358 if (aTransactionInfo.AppId().isSome() && 359 !IsValidAppId(principal, aTransactionInfo.AppId().ref())) { 360 aResolver(NS_ERROR_DOM_SECURITY_ERR); 361 return IPC_OK(); 362 } 363 364 nsCString origin; 365 nsresult rv = principal->GetWebExposedOriginSerialization(origin); 366 if (NS_FAILED(rv)) { 367 aResolver(NS_ERROR_FAILURE); 368 return IPC_OK(); 369 } 370 371 nsCString clientDataJSON; 372 rv = AssembleClientData(manager, "webauthn.get"_ns, 373 aTransactionInfo.Challenge(), clientDataJSON); 374 if (NS_FAILED(rv)) { 375 aResolver(NS_ERROR_FAILURE); 376 return IPC_OK(); 377 } 378 379 bool requestIncludesAppId = aTransactionInfo.AppId().isSome(); 380 381 bool requestIncludesLargeBlobRead = 382 GetAssertionRequestIncludesLargeBlobRead(aTransactionInfo); 383 384 RefPtr<WebAuthnSignPromiseHolder> promiseHolder = 385 new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget()); 386 387 RefPtr<WebAuthnSignPromise> promise = promiseHolder->Ensure(); 388 promise 389 ->Then( 390 GetCurrentSerialEventTarget(), __func__, 391 [self = RefPtr{this}, inputClientData = clientDataJSON, 392 requestIncludesAppId, requestIncludesLargeBlobRead, 393 resolver = std::move(aResolver)]( 394 const WebAuthnSignPromise::ResolveOrRejectValue& aValue) { 395 self->CompleteTransaction(); 396 397 if (aValue.IsReject()) { 398 resolver(aValue.RejectValue()); 399 return; 400 } 401 402 auto rejectWithNotAllowed = MakeScopeExit( 403 [&]() { resolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); }); 404 405 RefPtr<nsIWebAuthnSignResult> signResult = aValue.ResolveValue(); 406 407 nsCString clientData; 408 nsresult rv = signResult->GetClientDataJSON(clientData); 409 if (rv == NS_ERROR_NOT_AVAILABLE) { 410 clientData = inputClientData; 411 } else if (NS_FAILED(rv)) { 412 return; 413 } 414 415 nsTArray<uint8_t> credentialId; 416 rv = signResult->GetCredentialId(credentialId); 417 if (NS_WARN_IF(NS_FAILED(rv))) { 418 return; 419 } 420 421 nsTArray<uint8_t> signature; 422 rv = signResult->GetSignature(signature); 423 if (NS_WARN_IF(NS_FAILED(rv))) { 424 return; 425 } 426 427 nsTArray<uint8_t> authenticatorData; 428 rv = signResult->GetAuthenticatorData(authenticatorData); 429 if (NS_WARN_IF(NS_FAILED(rv))) { 430 return; 431 } 432 433 nsTArray<uint8_t> userHandle; 434 (void)signResult->GetUserHandle(userHandle); // optional 435 436 Maybe<nsString> authenticatorAttachment; 437 nsString maybeAuthenticatorAttachment; 438 rv = signResult->GetAuthenticatorAttachment( 439 maybeAuthenticatorAttachment); 440 if (rv != NS_ERROR_NOT_AVAILABLE) { 441 if (NS_WARN_IF(NS_FAILED(rv))) { 442 return; 443 } 444 authenticatorAttachment = Some(maybeAuthenticatorAttachment); 445 } 446 447 nsTArray<WebAuthnExtensionResult> extensions; 448 if (requestIncludesAppId) { 449 bool usedAppId = false; 450 rv = signResult->GetUsedAppId(&usedAppId); 451 if (rv != NS_ERROR_NOT_AVAILABLE && NS_FAILED(rv)) { 452 return; 453 } 454 extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId)); 455 } 456 457 nsTArray<uint8_t> largeBlobValue; 458 rv = signResult->GetLargeBlobValue(largeBlobValue); 459 if (rv != NS_ERROR_NOT_AVAILABLE) { 460 if (NS_FAILED(rv)) { 461 return; 462 } 463 extensions.AppendElement(WebAuthnExtensionResultLargeBlob( 464 true, largeBlobValue, false)); 465 } else if (requestIncludesLargeBlobRead) { 466 // Signal a read error by setting both flags. 467 extensions.AppendElement( 468 WebAuthnExtensionResultLargeBlob(true, largeBlobValue, true)); 469 } else { 470 // Read and write operations are mutually exclusive, so we only 471 // check for a write result if the read result is not available. 472 bool largeBlobWritten; 473 rv = signResult->GetLargeBlobWritten(&largeBlobWritten); 474 if (rv != NS_ERROR_NOT_AVAILABLE) { 475 if (NS_FAILED(rv)) { 476 return; 477 } 478 extensions.AppendElement(WebAuthnExtensionResultLargeBlob( 479 false, largeBlobValue, largeBlobWritten)); 480 } 481 } 482 483 { 484 Maybe<WebAuthnExtensionPrfValues> prfResults; 485 bool prfMaybe = false; 486 rv = signResult->GetPrfMaybe(&prfMaybe); 487 if (rv == NS_OK && prfMaybe) { 488 nsTArray<uint8_t> prfResultsFirst; 489 rv = signResult->GetPrfResultsFirst(prfResultsFirst); 490 if (rv != NS_ERROR_NOT_AVAILABLE) { 491 if (NS_WARN_IF(NS_FAILED(rv))) { 492 return; 493 } 494 495 bool prfResultsSecondMaybe = false; 496 nsTArray<uint8_t> prfResultsSecond; 497 rv = signResult->GetPrfResultsSecond(prfResultsSecond); 498 if (rv != NS_ERROR_NOT_AVAILABLE) { 499 if (NS_WARN_IF(NS_FAILED(rv))) { 500 return; 501 } 502 prfResultsSecondMaybe = true; 503 } 504 505 prfResults = Some(WebAuthnExtensionPrfValues( 506 prfResultsFirst, prfResultsSecondMaybe, 507 prfResultsSecond)); 508 } else { 509 prfResults = Nothing(); 510 } 511 512 extensions.AppendElement( 513 WebAuthnExtensionResultPrf(Nothing(), prfResults)); 514 } 515 } 516 517 WebAuthnGetAssertionResult result( 518 clientData, credentialId, signature, authenticatorData, 519 extensions, userHandle, authenticatorAttachment); 520 521 rejectWithNotAllowed.release(); 522 resolver(result); 523 }) 524 ->Track(mSignPromiseRequest); 525 526 uint64_t browsingContextId = manager->GetBrowsingContext()->Top()->Id(); 527 bool privateBrowsing = principal->GetIsInPrivateBrowsing(); 528 auto args = MakeRefPtr<WebAuthnSignArgs>(origin, clientDataJSON, 529 privateBrowsing, aTransactionInfo); 530 rv = mWebAuthnService->GetAssertion(transactionId, browsingContextId, args, 531 promiseHolder); 532 if (NS_FAILED(rv)) { 533 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR); 534 } 535 536 return IPC_OK(); 537 } 538 539 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel() { 540 MOZ_ASSERT(NS_IsMainThread()); 541 542 if (mTransactionId.isNothing()) { 543 return IPC_OK(); 544 } 545 546 DisconnectTransaction(); 547 return IPC_OK(); 548 } 549 550 mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA( 551 RequestIsUVPAAResolver&& aResolver) { 552 MOZ_ASSERT(NS_IsMainThread()); 553 554 #ifdef MOZ_WIDGET_ANDROID 555 // Try the nsIWebAuthnService. If we're configured for tests we 556 // will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED. 557 nsCOMPtr<nsIWebAuthnService> service( 558 do_GetService("@mozilla.org/webauthn/service;1")); 559 bool available; 560 nsresult rv = service->GetIsUVPAA(&available); 561 if (NS_SUCCEEDED(rv)) { 562 aResolver(available); 563 return IPC_OK(); 564 } 565 566 // Don't consult the platform API if resident key support is disabled. 567 if (!StaticPrefs:: 568 security_webauthn_webauthn_enable_android_fido2_residentkey()) { 569 aResolver(false); 570 return IPC_OK(); 571 } 572 573 // The GeckoView implementation of 574 // isUserVerifiyingPlatformAuthenticatorAvailable dispatches the work to a 575 // background thread and returns a MozPromise which we can ->Then to call 576 // aResolver on the current thread. 577 nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget(); 578 auto result = java::WebAuthnTokenManager:: 579 WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable(); 580 auto geckoResult = java::GeckoResult::LocalRef(std::move(result)); 581 MozPromise<bool, bool, false>::FromGeckoResult(geckoResult) 582 ->Then(target, __func__, 583 [resolver = std::move(aResolver)]( 584 const MozPromise<bool, bool, false>::ResolveOrRejectValue& 585 aValue) { 586 if (aValue.IsResolve()) { 587 resolver(aValue.ResolveValue()); 588 } else { 589 resolver(false); 590 } 591 }); 592 return IPC_OK(); 593 594 #else 595 596 nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget(); 597 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction( 598 __func__, [target, resolver = std::move(aResolver)]() { 599 bool available; 600 nsCOMPtr<nsIWebAuthnService> service( 601 do_GetService("@mozilla.org/webauthn/service;1")); 602 nsresult rv = service->GetIsUVPAA(&available); 603 if (NS_FAILED(rv)) { 604 available = false; 605 } 606 BoolPromise::CreateAndResolve(available, __func__) 607 ->Then(target, __func__, 608 [resolver](const BoolPromise::ResolveOrRejectValue& value) { 609 if (value.IsResolve()) { 610 resolver(value.ResolveValue()); 611 } else { 612 resolver(false); 613 } 614 }); 615 })); 616 NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); 617 return IPC_OK(); 618 #endif 619 } 620 621 void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy) { 622 // Called either by Send__delete__() in RecvDestroyMe() above, or when 623 // the channel disconnects. Ensure the token manager forgets about us. 624 MOZ_ASSERT(NS_IsMainThread()); 625 626 if (mTransactionId.isSome()) { 627 DisconnectTransaction(); 628 } 629 } 630 631 } // namespace mozilla::dom