File mozilla-bmo2008744.patch of Package MozillaFirefox

Index: firefox-147.0.3/dom/credentialmanagement/CredentialsContainer.cpp
===================================================================
--- firefox-147.0.3.orig/dom/credentialmanagement/CredentialsContainer.cpp
+++ firefox-147.0.3/dom/credentialmanagement/CredentialsContainer.cpp
@@ -197,7 +197,8 @@ already_AddRefed<Promise> CredentialsCon
     }
 
     EnsureWebAuthnHandler();
-    return mWebAuthnHandler->GetAssertion(aOptions.mPublicKey.Value(),
+    auto *aCx = nsContentUtils::GetCurrentJSContext();
+    return mWebAuthnHandler->GetAssertion(aCx, aOptions.mPublicKey.Value(),
                                           conditionallyMediated,
                                           aOptions.mSignal, aRv);
   }
@@ -266,7 +267,8 @@ already_AddRefed<Promise> CredentialsCon
     }
 
     EnsureWebAuthnHandler();
-    return mWebAuthnHandler->MakeCredential(aOptions.mPublicKey.Value(),
+    auto *aCx = nsContentUtils::GetCurrentJSContext();
+    return mWebAuthnHandler->MakeCredential(aCx, aOptions.mPublicKey.Value(),
                                             aOptions.mSignal, aRv);
   }
 
Index: firefox-147.0.3/dom/webauthn/AndroidWebAuthnService.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/AndroidWebAuthnService.cpp
+++ firefox-147.0.3/dom/webauthn/AndroidWebAuthnService.cpp
@@ -197,9 +197,12 @@ AndroidWebAuthnService::MakeCredential(u
                                            : java::sdk::Boolean::FALSE());
         GECKOBUNDLE_FINISH(extensionsBundle);
 
+        nsString json;
+        (void)aArgs->GetJson(json);
+
         auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
             credentialBundle, uid, challenge, idList, transportList,
-            authSelBundle, extensionsBundle, algs, hash);
+            authSelBundle, extensionsBundle, algs, hash, json);
 
         auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
 
@@ -313,9 +316,12 @@ AndroidWebAuthnService::GetAssertion(uin
 
         GECKOBUNDLE_FINISH(extensionsBundle);
 
+        nsString json;
+        (void)aArgs->GetJson(json);
+
         auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
             challenge, idList, transportList, assertionBundle, extensionsBundle,
-            hash);
+            hash, json);
         auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
         MozPromise<RefPtr<WebAuthnSignResult>, AndroidWebAuthnError,
                    true>::FromGeckoResult(geckoResult)
Index: firefox-147.0.3/dom/webauthn/PWebAuthnTransaction.ipdl
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/PWebAuthnTransaction.ipdl
+++ firefox-147.0.3/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -144,6 +144,7 @@ struct WebAuthnMakeCredentialInfo {
   WebAuthnAuthenticatorSelection AuthenticatorSelection;
   nsString attestationConveyancePreference;
   nsString[] Hints;
+  nsString Json;
 };
 
 struct WebAuthnMakeCredentialResult {
@@ -170,6 +171,7 @@ struct WebAuthnGetAssertionInfo {
   nsString userVerificationRequirement;
   bool ConditionallyMediated;
   nsString[] Hints;
+  nsString Json;
 };
 
 struct WebAuthnGetAssertionResult {
Index: firefox-147.0.3/dom/webauthn/WebAuthnArgs.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnArgs.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnArgs.cpp
@@ -252,6 +252,12 @@ WebAuthnRegisterArgs::GetHints(nsTArray<
   return NS_OK;
 }
 
+NS_IMETHODIMP
+WebAuthnRegisterArgs::GetJson(nsAString& aJSON) {
+  aJSON = mInfo.Json();
+  return NS_OK;
+}
+
 NS_IMPL_ISUPPORTS(WebAuthnSignArgs, nsIWebAuthnSignArgs)
 
 NS_IMETHODIMP
@@ -497,4 +503,10 @@ WebAuthnSignArgs::GetHints(nsTArray<nsSt
   return NS_OK;
 }
 
+NS_IMETHODIMP
+WebAuthnSignArgs::GetJson(nsAString& aJSON) {
+  aJSON = mInfo.Json();
+  return NS_OK;
+}
+
 }  // namespace mozilla::dom
Index: firefox-147.0.3/dom/webauthn/WebAuthnHandler.h
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnHandler.h
+++ firefox-147.0.3/dom/webauthn/WebAuthnHandler.h
@@ -84,10 +84,12 @@ class WebAuthnHandler final : public Abo
   }
 
   already_AddRefed<Promise> MakeCredential(
+      JSContext *aCx,
       const PublicKeyCredentialCreationOptions& aOptions,
       const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
 
   already_AddRefed<Promise> GetAssertion(
+      JSContext *aCx,
       const PublicKeyCredentialRequestOptions& aOptions,
       const bool aConditionallyMediated,
       const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError);
Index: firefox-147.0.3/dom/webauthn/WebAuthnHandler.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnHandler.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnHandler.cpp
@@ -17,10 +17,12 @@
 #include "mozilla/dom/AuthenticatorAttestationResponse.h"
 #include "mozilla/dom/PWebAuthnTransaction.h"
 #include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
 #include "mozilla/dom/WebAuthnTransactionChild.h"
 #include "mozilla/dom/WebAuthnUtil.h"
 #include "mozilla/dom/WindowGlobalChild.h"
 #include "mozilla/glean/DomWebauthnMetrics.h"
+#include "nsContentUtils.h"
 #include "nsHTMLDocument.h"
 #include "nsIURIMutator.h"
 #include "nsThreadUtils.h"
@@ -116,6 +118,7 @@ void WebAuthnHandler::ActorDestroyed() {
 }
 
 already_AddRefed<Promise> WebAuthnHandler::MakeCredential(
+    JSContext *aCx,
     const PublicKeyCredentialCreationOptions& aOptions,
     const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
   MOZ_ASSERT(NS_IsMainThread());
@@ -173,13 +176,7 @@ already_AddRefed<Promise> WebAuthnHandle
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
-
-  uint32_t adjustedTimeout = 30000;
-  if (aOptions.mTimeout.WasPassed()) {
-    adjustedTimeout = aOptions.mTimeout.Value();
-    adjustedTimeout = std::max(15000u, adjustedTimeout);
-    adjustedTimeout = std::min(120000u, adjustedTimeout);
-  }
+  uint32_t adjustedTimeout = WebAuthnTimeout(aOptions.mTimeout);
 
   // <https://w3c.github.io/webauthn/#sctn-appid-extension>
   if (aOptions.mExtensions.mAppid.WasPassed()) {
@@ -375,21 +372,23 @@ already_AddRefed<Promise> WebAuthnHandle
 
   // Abort the request if aborted flag is already set.
   if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(global)) {
-      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-      return promise.forget();
-    }
-    JSContext* cx = jsapi.cx();
-    JS::Rooted<JS::Value> reason(cx);
-    aSignal.Value().GetReason(cx, &reason);
+    JS::Rooted<JS::Value> reason(aCx);
+    aSignal.Value().GetReason(aCx, &reason);
     promise->MaybeReject(reason);
     return promise.forget();
   }
 
-  WebAuthnMakeCredentialInfo info(rpId, challenge, adjustedTimeout, excludeList,
-                                  rpInfo, userInfo, coseAlgos, extensions,
-                                  authSelection, attestation, aOptions.mHints);
+  nsString json;
+  nsresult rv = SerializeWebAuthnCreationOptions(
+      aCx, NS_ConvertUTF8toUTF16(rpId), aOptions, json);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+    return promise.forget();
+  }
+
+  WebAuthnMakeCredentialInfo info(
+      rpId, challenge, adjustedTimeout, excludeList, rpInfo, userInfo,
+      coseAlgos, extensions, authSelection, attestation, aOptions.mHints, json);
 
   // Set up the transaction state. Fallible operations should not be performed
   // below this line, as we must not leave the transaction state partially
@@ -430,6 +429,7 @@ already_AddRefed<Promise> WebAuthnHandle
 const size_t MAX_ALLOWED_CREDENTIALS = 20;
 
 already_AddRefed<Promise> WebAuthnHandler::GetAssertion(
+    JSContext *aCx,
     const PublicKeyCredentialRequestOptions& aOptions,
     const bool aConditionallyMediated,
     const Optional<OwningNonNull<AbortSignal>>& aSignal, ErrorResult& aError) {
@@ -478,13 +478,7 @@ already_AddRefed<Promise> WebAuthnHandle
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
-
-  uint32_t adjustedTimeout = 30000;
-  if (aOptions.mTimeout.WasPassed()) {
-    adjustedTimeout = aOptions.mTimeout.Value();
-    adjustedTimeout = std::max(15000u, adjustedTimeout);
-    adjustedTimeout = std::min(120000u, adjustedTimeout);
-  }
+  uint32_t adjustedTimeout = WebAuthnTimeout(aOptions.mTimeout);
 
   // Abort the request if the allowCredentials set is too large
   if (aOptions.mAllowCredentials.Length() > MAX_ALLOWED_CREDENTIALS) {
@@ -645,21 +639,23 @@ already_AddRefed<Promise> WebAuthnHandle
 
   // Abort the request if aborted flag is already set.
   if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(global)) {
-      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-      return promise.forget();
-    }
-    JSContext* cx = jsapi.cx();
-    JS::Rooted<JS::Value> reason(cx);
-    aSignal.Value().GetReason(cx, &reason);
+    JS::Rooted<JS::Value> reason(aCx);
+    aSignal.Value().GetReason(aCx, &reason);
     promise->MaybeReject(reason);
     return promise.forget();
   }
 
-  WebAuthnGetAssertionInfo info(
-      rpId, maybeAppId, challenge, adjustedTimeout, allowList, extensions,
-      aOptions.mUserVerification, aConditionallyMediated, aOptions.mHints);
+  nsString json;
+  nsresult rv = SerializeWebAuthnRequestOptions(
+      aCx, NS_ConvertUTF8toUTF16(rpId), aOptions, json);
+  if (NS_FAILED(rv)) {
+    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
+    return promise.forget();
+  }
+  WebAuthnGetAssertionInfo info(rpId, maybeAppId, challenge, adjustedTimeout,
+                                allowList, extensions,
+                                aOptions.mUserVerification,
+                                aConditionallyMediated, aOptions.mHints, json);
 
   // Set up the transaction state. Fallible operations should not be performed
   // below this line, as we must not leave the transaction state partially
Index: firefox-147.0.3/dom/webauthn/WebAuthnUtil.h
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnUtil.h
+++ firefox-147.0.3/dom/webauthn/WebAuthnUtil.h
@@ -32,6 +32,18 @@ bool IsValidRpId(const nsCOMPtr<nsIPrinc
 
 nsresult HashCString(const nsACString& aIn, /* out */ nsTArray<uint8_t>& aOut);
 
+uint32_t WebAuthnTimeout(const Optional<uint32_t>& aTimeout);
+
+nsresult SerializeWebAuthnCreationOptions(
+    JSContext* aCx, const nsString& aRpId,
+    const PublicKeyCredentialCreationOptions& aOptions,
+    /* out */ nsString& aOut);
+
+nsresult SerializeWebAuthnRequestOptions(
+    JSContext* aCx, const nsString& aRpId,
+    const PublicKeyCredentialRequestOptions& aOptions,
+    /* out */ nsString& aOut);
+
 }  // namespace mozilla::dom
 
 #endif  // mozilla_dom_WebAuthnUtil_h
Index: firefox-147.0.3/dom/webauthn/WebAuthnUtil.cpp
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/WebAuthnUtil.cpp
+++ firefox-147.0.3/dom/webauthn/WebAuthnUtil.cpp
@@ -7,11 +7,14 @@
 #include "mozilla/dom/WebAuthnUtil.h"
 
 #include "hasht.h"
+#include "mozilla/Base64.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/StaticPrefs_security.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozpkix/pkixutil.h"
 #include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
 #include "nsHTMLDocument.h"
 #include "nsICryptoHash.h"
 #include "nsIEffectiveTLDService.h"
@@ -269,4 +272,331 @@ nsresult HashCString(const nsACString& a
   return NS_OK;
 }
 
+uint32_t WebAuthnTimeout(const Optional<uint32_t>& aTimeout) {
+  uint32_t adjustedTimeout = 30000;
+  if (aTimeout.WasPassed()) {
+    adjustedTimeout = aTimeout.Value();
+    adjustedTimeout = std::max(15000u, adjustedTimeout);
+    adjustedTimeout = std::min(120000u, adjustedTimeout);
+  }
+  return adjustedTimeout;
+}
+
+static nsresult SerializeWebAuthnData(
+    const OwningArrayBufferViewOrArrayBuffer& aData, nsString& aOut) {
+  return ProcessTypedArrays(
+      aData, [&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
+        nsAutoCString result;
+        nsresult rv = mozilla::Base64URLEncode(
+            aData.Length(), aData.Elements(),
+            Base64URLEncodePaddingPolicy::Omit, result);
+        if (NS_SUCCEEDED(rv)) {
+          aOut.Assign(NS_ConvertUTF8toUTF16(result));
+        }
+        return rv;
+      });
+}
+
+nsresult SerializeWebAuthnCreationOptions(
+    JSContext* aCx, const nsString& aRpId,
+    const PublicKeyCredentialCreationOptions& aOptions, nsString& aOut) {
+  nsresult rv;
+  PublicKeyCredentialCreationOptionsJSON json;
+
+  json.mRp.mId.Construct(aRpId);
+
+  json.mRp.mName.Assign(aOptions.mRp.mName);
+
+  json.mUser.mName.Assign(aOptions.mUser.mName);
+
+  rv = SerializeWebAuthnData(aOptions.mUser.mId, json.mUser.mId);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  json.mUser.mDisplayName.Assign(aOptions.mUser.mDisplayName);
+
+  rv = SerializeWebAuthnData(aOptions.mChallenge, json.mChallenge);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  json.mPubKeyCredParams = aOptions.mPubKeyCredParams;
+
+  json.mTimeout.Construct(WebAuthnTimeout(aOptions.mTimeout));
+
+  for (const auto& excludeCredential : aOptions.mExcludeCredentials) {
+    PublicKeyCredentialDescriptorJSON* excludeCredentialJSON =
+        json.mExcludeCredentials.AppendElement(fallible);
+    if (!excludeCredentialJSON) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    excludeCredentialJSON->mType = excludeCredential.mType;
+    rv = SerializeWebAuthnData(excludeCredential.mId,
+                               excludeCredentialJSON->mId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (excludeCredential.mTransports.WasPassed()) {
+      excludeCredentialJSON->mTransports.Construct(
+          excludeCredential.mTransports.Value());
+    }
+  }
+
+  json.mAuthenticatorSelection.Construct(aOptions.mAuthenticatorSelection);
+
+  json.mHints = aOptions.mHints;
+
+  json.mAttestation = aOptions.mAttestation;
+
+  AuthenticationExtensionsClientInputsJSON& extensionsJSON =
+      json.mExtensions.Construct();
+
+  if (aOptions.mExtensions.mAppid.WasPassed()) {
+    extensionsJSON.mAppid.Construct(aOptions.mExtensions.mAppid.Value());
+  }
+
+  if (aOptions.mExtensions.mCredentialProtectionPolicy.WasPassed()) {
+    extensionsJSON.mCredentialProtectionPolicy.Construct(
+        aOptions.mExtensions.mCredentialProtectionPolicy.Value());
+  }
+
+  if (aOptions.mExtensions.mEnforceCredentialProtectionPolicy.WasPassed()) {
+    extensionsJSON.mEnforceCredentialProtectionPolicy.Construct(
+        aOptions.mExtensions.mEnforceCredentialProtectionPolicy.Value());
+  }
+
+  if (aOptions.mExtensions.mCredProps.WasPassed()) {
+    extensionsJSON.mCredProps.Construct(
+        aOptions.mExtensions.mCredProps.Value());
+  }
+
+  if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
+    extensionsJSON.mHmacCreateSecret.Construct(
+        aOptions.mExtensions.mHmacCreateSecret.Value());
+  }
+
+  if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+    extensionsJSON.mMinPinLength.Construct(
+        aOptions.mExtensions.mMinPinLength.Value());
+  }
+
+  if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
+    const AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
+        aOptions.mExtensions.mLargeBlob.Value();
+    AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
+        extensionsJSON.mLargeBlob.Construct();
+
+    if (largeBlobInputs.mSupport.WasPassed()) {
+      largeBlobInputsJSON.mSupport.Construct(largeBlobInputs.mSupport.Value());
+    }
+
+    if (largeBlobInputs.mRead.WasPassed()) {
+      largeBlobInputsJSON.mRead.Construct(largeBlobInputs.mRead.Value());
+    }
+
+    if (largeBlobInputs.mWrite.WasPassed()) {
+      nsString write;
+      rv = SerializeWebAuthnData(largeBlobInputs.mWrite.Value(), write);
+      NS_ENSURE_SUCCESS(rv, rv);
+      largeBlobInputsJSON.mWrite.Construct(write);
+    }
+  }
+
+  if (aOptions.mExtensions.mPrf.WasPassed()) {
+    const AuthenticationExtensionsPRFInputs& prfInputs =
+        aOptions.mExtensions.mPrf.Value();
+    AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
+        extensionsJSON.mPrf.Construct();
+
+    if (prfInputs.mEval.WasPassed()) {
+      AuthenticationExtensionsPRFValuesJSON& evalJSON =
+          prfInputsJSON.mEval.Construct();
+      rv = SerializeWebAuthnData(prfInputs.mEval.Value().mFirst,
+                                 evalJSON.mFirst);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (prfInputs.mEval.Value().mSecond.WasPassed()) {
+        nsString second;
+        rv = SerializeWebAuthnData(prfInputs.mEval.Value().mSecond.Value(),
+                                   second);
+        NS_ENSURE_SUCCESS(rv, rv);
+        evalJSON.mSecond.Construct(second);
+      }
+    }
+
+    if (prfInputs.mEvalByCredential.WasPassed()) {
+      for (const auto& entry : prfInputs.mEvalByCredential.Value().Entries()) {
+        auto* jsonEntry =
+            prfInputsJSON.mEvalByCredential.Construct().Entries().AppendElement(
+                fallible);
+        if (!jsonEntry) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        jsonEntry->mKey = entry.mKey;
+        AuthenticationExtensionsPRFValuesJSON& valuesJSON = jsonEntry->mValue;
+
+        rv = SerializeWebAuthnData(entry.mValue.mFirst, valuesJSON.mFirst);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (entry.mValue.mSecond.WasPassed()) {
+          nsString second;
+          rv = SerializeWebAuthnData(entry.mValue.mSecond.Value(), second);
+          NS_ENSURE_SUCCESS(rv, rv);
+          valuesJSON.mSecond.Construct(second);
+        }
+      }
+    }
+  }
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!ToJSValue(aCx, json, &value)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoString jsonString;
+  if (!nsContentUtils::StringifyJSON(aCx, value, jsonString,
+                                     UndefinedIsNullStringLiteral)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aOut = jsonString;
+  return NS_OK;
+}
+
+nsresult SerializeWebAuthnRequestOptions(
+    JSContext* aCx, const nsString& aRpId,
+    const PublicKeyCredentialRequestOptions& aOptions, nsString& aOut) {
+  nsresult rv;
+  PublicKeyCredentialRequestOptionsJSON json;
+
+  rv = SerializeWebAuthnData(aOptions.mChallenge, json.mChallenge);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  json.mTimeout.Construct(WebAuthnTimeout(aOptions.mTimeout));
+
+  json.mRpId.Construct(aRpId);
+
+  for (const auto& allowCredential : aOptions.mAllowCredentials) {
+    PublicKeyCredentialDescriptorJSON* allowCredentialJSON =
+        json.mAllowCredentials.AppendElement(fallible);
+    if (!allowCredentialJSON) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    allowCredentialJSON->mType = allowCredential.mType;
+    rv = SerializeWebAuthnData(allowCredential.mId, allowCredentialJSON->mId);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (allowCredential.mTransports.WasPassed()) {
+      allowCredentialJSON->mTransports.Construct(
+          allowCredential.mTransports.Value());
+    }
+  }
+
+  json.mUserVerification = aOptions.mUserVerification;
+
+  json.mHints = aOptions.mHints;
+
+  AuthenticationExtensionsClientInputsJSON& extensionsJSON =
+      json.mExtensions.Construct();
+
+  if (aOptions.mExtensions.mAppid.WasPassed()) {
+    extensionsJSON.mAppid.Construct(aOptions.mExtensions.mAppid.Value());
+  }
+
+  if (aOptions.mExtensions.mCredProps.WasPassed()) {
+    extensionsJSON.mCredProps.Construct(
+        aOptions.mExtensions.mCredProps.Value());
+  }
+
+  if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
+    extensionsJSON.mHmacCreateSecret.Construct(
+        aOptions.mExtensions.mHmacCreateSecret.Value());
+  }
+
+  if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
+    extensionsJSON.mMinPinLength.Construct(
+        aOptions.mExtensions.mMinPinLength.Value());
+  }
+
+  if (aOptions.mExtensions.mLargeBlob.WasPassed()) {
+    const AuthenticationExtensionsLargeBlobInputs& largeBlobInputs =
+        aOptions.mExtensions.mLargeBlob.Value();
+    AuthenticationExtensionsLargeBlobInputsJSON& largeBlobInputsJSON =
+        extensionsJSON.mLargeBlob.Construct();
+
+    if (largeBlobInputs.mSupport.WasPassed()) {
+      largeBlobInputsJSON.mSupport.Construct(largeBlobInputs.mSupport.Value());
+    }
+
+    if (largeBlobInputs.mRead.WasPassed()) {
+      largeBlobInputsJSON.mRead.Construct(largeBlobInputs.mRead.Value());
+    }
+
+    if (largeBlobInputs.mWrite.WasPassed()) {
+      nsString write;
+      rv = SerializeWebAuthnData(largeBlobInputs.mWrite.Value(), write);
+      NS_ENSURE_SUCCESS(rv, rv);
+      largeBlobInputsJSON.mWrite.Construct(write);
+    }
+  }
+
+  if (aOptions.mExtensions.mPrf.WasPassed()) {
+    const AuthenticationExtensionsPRFInputs& prfInputs =
+        aOptions.mExtensions.mPrf.Value();
+    AuthenticationExtensionsPRFInputsJSON& prfInputsJSON =
+        extensionsJSON.mPrf.Construct();
+
+    if (prfInputs.mEval.WasPassed()) {
+      AuthenticationExtensionsPRFValuesJSON& evalJSON =
+          prfInputsJSON.mEval.Construct();
+      rv = SerializeWebAuthnData(prfInputs.mEval.Value().mFirst,
+                                 evalJSON.mFirst);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      if (prfInputs.mEval.Value().mSecond.WasPassed()) {
+        nsString second;
+        rv = SerializeWebAuthnData(prfInputs.mEval.Value().mSecond.Value(),
+                                   second);
+        NS_ENSURE_SUCCESS(rv, rv);
+        evalJSON.mSecond.Construct(second);
+      }
+    }
+
+    if (prfInputs.mEvalByCredential.WasPassed()) {
+      for (const auto& entry : prfInputs.mEvalByCredential.Value().Entries()) {
+        auto* jsonEntry =
+            prfInputsJSON.mEvalByCredential.Construct().Entries().AppendElement(
+                fallible);
+        if (!jsonEntry) {
+          return NS_ERROR_OUT_OF_MEMORY;
+        }
+
+        jsonEntry->mKey = entry.mKey;
+        AuthenticationExtensionsPRFValuesJSON& valuesJSON = jsonEntry->mValue;
+
+        rv = SerializeWebAuthnData(entry.mValue.mFirst, valuesJSON.mFirst);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        if (entry.mValue.mSecond.WasPassed()) {
+          nsString second;
+          rv = SerializeWebAuthnData(entry.mValue.mSecond.Value(), second);
+          NS_ENSURE_SUCCESS(rv, rv);
+          valuesJSON.mSecond.Construct(second);
+        }
+      }
+    }
+  }
+
+  JS::Rooted<JS::Value> value(aCx);
+  if (!ToJSValue(aCx, json, &value)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoString jsonString;
+  if (!nsContentUtils::StringifyJSON(aCx, value, jsonString,
+                                     UndefinedIsNullStringLiteral)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aOut = jsonString;
+  return NS_OK;
+}
+
 }  // namespace mozilla::dom
Index: firefox-147.0.3/dom/webauthn/moz.build
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/moz.build
+++ firefox-147.0.3/dom/webauthn/moz.build
@@ -94,3 +94,7 @@ MOCHITEST_MANIFESTS += ["tests/mochitest
 BROWSER_CHROME_MANIFESTS += [
     "tests/browser/browser.toml",
 ]
+
+TEST_DIRS += [
+    "tests/gtest",
+]
Index: firefox-147.0.3/dom/webauthn/nsIWebAuthnArgs.idl
===================================================================
--- firefox-147.0.3.orig/dom/webauthn/nsIWebAuthnArgs.idl
+++ firefox-147.0.3/dom/webauthn/nsIWebAuthnArgs.idl
@@ -67,6 +67,8 @@ interface nsIWebAuthnRegisterArgs : nsIS
 
     readonly attribute Array<AString> hints;
 
+    readonly attribute AString json;
+
     readonly attribute boolean privateBrowsing;
 };
 
@@ -115,6 +117,8 @@ interface nsIWebAuthnSignArgs : nsISuppo
 
     readonly attribute Array<AString> hints;
 
+    readonly attribute AString json;
+
     readonly attribute boolean conditionallyMediated;
 
     readonly attribute boolean privateBrowsing;
Index: firefox-147.0.3/dom/webauthn/tests/gtest/TestWebAuthnSerialization.cpp
===================================================================
--- /dev/null
+++ firefox-147.0.3/dom/webauthn/tests/gtest/TestWebAuthnSerialization.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "js/ArrayBuffer.h"
+#include "js/CharacterEncoding.h"
+#include "js/GlobalObject.h"
+#include "js/JSON.h"
+#include "js/Realm.h"
+#include "js/RootingAPI.h"
+#include "js/String.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Base64.h"
+#include "mozilla/dom/PublicKeyCredential.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebAuthnUtil.h"
+#include "mozilla/gtest/MozHelpers.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class WebAuthnSerializationTest : public ::testing::Test {
+ protected:
+  JSContext* Context() { return mCx; }
+
+  GlobalObject CreateGlobalObject(JS::Handle<JSObject*> aGlobal) {
+    return GlobalObject(mCx, aGlobal);
+  }
+
+  bool JSONValuesEqual(JS::Handle<JS::Value> aVal1,
+                       JS::Handle<JS::Value> aVal2) {
+    if (aVal1.type() != aVal2.type()) {
+      return false;
+    }
+
+    if (aVal1.isString()) {
+      int32_t result;
+      if (!JS_CompareStrings(mCx, aVal1.toString(), aVal2.toString(),
+                             &result)) {
+        return false;
+      }
+      return result == 0;
+    }
+
+    if (aVal1.isNumber()) {
+      return aVal1.toNumber() == aVal2.toNumber();
+    }
+
+    if (aVal1.isBoolean()) {
+      return aVal1.toBoolean() == aVal2.toBoolean();
+    }
+
+    if (aVal1.isUndefined() || aVal1.isNull()) {
+      return true;
+    }
+
+    if (aVal1.isObject()) {
+      JS::Rooted<JSObject*> obj1(mCx, &aVal1.toObject());
+      JS::Rooted<JSObject*> obj2(mCx, &aVal2.toObject());
+
+      bool isArray1, isArray2;
+      if (!JS::IsArrayObject(mCx, obj1, &isArray1) ||
+          !JS::IsArrayObject(mCx, obj2, &isArray2)) {
+        return false;
+      }
+
+      if (isArray1 && isArray2) {
+        uint32_t length1, length2;
+        if (!JS::GetArrayLength(mCx, obj1, &length1) ||
+            !JS::GetArrayLength(mCx, obj2, &length2)) {
+          return false;
+        }
+        if (length1 != length2) {
+          return false;
+        }
+
+        for (uint32_t i = 0; i < length1; i++) {
+          JS::Rooted<JS::Value> elem1(mCx);
+          JS::Rooted<JS::Value> elem2(mCx);
+          if (!JS_GetElement(mCx, obj1, i, &elem1) ||
+              !JS_GetElement(mCx, obj2, i, &elem2)) {
+            return false;
+          }
+          if (!JSONValuesEqual(elem1, elem2)) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      JS::Rooted<JS::IdVector> ids1(mCx, JS::IdVector(mCx));
+      JS::Rooted<JS::IdVector> ids2(mCx, JS::IdVector(mCx));
+      if (!JS_Enumerate(mCx, obj1, &ids1) || !JS_Enumerate(mCx, obj2, &ids2)) {
+        return false;
+      }
+
+      if (ids1.length() != ids2.length()) {
+        return false;
+      }
+
+      for (size_t i = 0; i < ids1.length(); i++) {
+        JS::Rooted<JS::Value> val1(mCx);
+        JS::Rooted<JS::Value> val2(mCx);
+        if (!JS_GetPropertyById(mCx, obj1, ids1[i], &val1) ||
+            !JS_GetPropertyById(mCx, obj2, ids1[i], &val2)) {
+          return false;
+        }
+
+        if (!JSONValuesEqual(val1, val2)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  JSContext* mCx;
+};
+
+TEST_F(WebAuthnSerializationTest, JSONStringRoundTripForCreationOptions) {
+  JS::Rooted<JSObject*> global(
+      mozilla::dom::RootingCx(),
+      mozilla::dom::SimpleGlobalObject::Create(
+          mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+  mozilla::dom::AutoJSAPI jsAPI;
+  ASSERT_TRUE(jsAPI.Init(global));
+  mCx = jsAPI.cx();
+
+  const nsLiteralString inputJSONStr =
+      uR"({
+    "rp": {
+      "name": "Example",
+      "id": "example.com"
+    },
+    "user": {
+      "id": "19TVpqBBOAM",
+      "name": "username2",
+      "displayName": "another display name"
+    },
+    "challenge": "dR82FeUh5q4",
+    "pubKeyCredParams": [{
+      "type": "public-key",
+      "alg": -7
+    }],
+    "timeout": 20000,
+    "excludeCredentials": [{
+      "type": "public-key",
+      "id": "TeM2k_di7Dk",
+      "transports": ["usb"]
+    }],
+    "authenticatorSelection": {
+      "authenticatorAttachment": "platform",
+      "residentKey": "required",
+      "requireResidentKey": true,
+      "userVerification": "discouraged"
+    },
+    "hints": ["hybrid"],
+    "attestation": "indirect",
+    "extensions": {
+      "appid": "https://www.example.com/appID",
+      "credProps": true,
+      "hmacCreateSecret": true,
+      "minPinLength": true,
+      "credentialProtectionPolicy": "userVerificationOptional",
+      "enforceCredentialProtectionPolicy": true,
+      "largeBlob": {
+        "support": "required"
+      },
+      "prf": {
+        "eval": {
+          "first": "Zmlyc3Q",
+          "second": "c2Vjb25k"
+        },
+        "evalByCredential": {
+          "19TVpqBBOAM": {
+            "first": "Zmlyc3Q",
+            "second": "c2Vjb25k"
+          }
+        }
+      }
+    }
+  })"_ns;
+
+  GlobalObject globalObject = CreateGlobalObject(global);
+
+  JS::Rooted<JS::Value> inputJSONValue(mCx);
+  ASSERT_TRUE(JS_ParseJSON(mCx, inputJSONStr.get(), inputJSONStr.Length(),
+                           &inputJSONValue));
+
+  RootedDictionary<PublicKeyCredentialCreationOptionsJSON> jsonOptions(mCx);
+  ASSERT_TRUE(jsonOptions.Init(mCx, inputJSONValue));
+
+  RootedDictionary<PublicKeyCredentialCreationOptions> options(mCx);
+  IgnoredErrorResult error;
+
+  PublicKeyCredential::ParseCreationOptionsFromJSON(globalObject, jsonOptions,
+                                                    options, error);
+  ASSERT_FALSE(error.Failed());
+
+  nsString outputJSONStr;
+  nsresult rv = SerializeWebAuthnCreationOptions(mCx, options.mRp.mId.Value(),
+                                                 options, outputJSONStr);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  JS::Rooted<JS::Value> outputJSONValue(mCx);
+  ASSERT_TRUE(JS_ParseJSON(mCx, outputJSONStr.get(), outputJSONStr.Length(),
+                           &outputJSONValue));
+  ASSERT_FALSE(JS_IsExceptionPending(mCx));
+
+  EXPECT_TRUE(JSONValuesEqual(inputJSONValue, outputJSONValue));
+}
+
+TEST_F(WebAuthnSerializationTest, JSONStringRoundTripForRequestOptions) {
+  JS::Rooted<JSObject*> global(
+      mozilla::dom::RootingCx(),
+      mozilla::dom::SimpleGlobalObject::Create(
+          mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail));
+  mozilla::dom::AutoJSAPI jsAPI;
+  ASSERT_TRUE(jsAPI.Init(global));
+  mCx = jsAPI.cx();
+
+  const nsLiteralString inputJSONStr =
+      uR"({
+    "challenge": "QAfaZwEQCkQ",
+    "timeout": 25000,
+    "rpId": "example.com",
+    "allowCredentials": [{
+      "type": "public-key",
+      "id": "BTBXXGuXRTk",
+      "transports": ["smart-card"]
+    }],
+    "userVerification": "discouraged",
+    "hints": ["client-device"],
+    "extensions": {
+      "appid": "https://www.example.com/anotherAppID",
+      "largeBlob": {
+        "read": true,
+        "write": "YmxvYmRhdGE"
+      },
+      "prf": {
+        "eval": {
+          "first": "Zmlyc3Q",
+          "second": "c2Vjb25k"
+        },
+        "evalByCredential": {
+          "19TVpqBBOAM": {
+            "first": "Zmlyc3Q",
+            "second": "c2Vjb25k"
+          }
+        }
+      }
+    }
+  })"_ns;
+
+  GlobalObject globalObject = CreateGlobalObject(global);
+
+  JS::Rooted<JS::Value> inputJSONValue(mCx);
+  ASSERT_TRUE(JS_ParseJSON(mCx, inputJSONStr.get(), inputJSONStr.Length(),
+                           &inputJSONValue));
+
+  RootedDictionary<PublicKeyCredentialRequestOptionsJSON> jsonOptions(mCx);
+  ASSERT_TRUE(jsonOptions.Init(mCx, inputJSONValue));
+
+  RootedDictionary<PublicKeyCredentialRequestOptions> options(mCx);
+  IgnoredErrorResult error;
+
+  PublicKeyCredential::ParseRequestOptionsFromJSON(globalObject, jsonOptions,
+                                                   options, error);
+  ASSERT_FALSE(error.Failed());
+
+  nsString outputJSONStr;
+  nsresult rv = SerializeWebAuthnRequestOptions(mCx, options.mRpId.Value(),
+                                                options, outputJSONStr);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  JS::Rooted<JS::Value> outputJSONValue(mCx);
+  ASSERT_TRUE(JS_ParseJSON(mCx, outputJSONStr.get(), outputJSONStr.Length(),
+                           &outputJSONValue));
+  ASSERT_FALSE(JS_IsExceptionPending(mCx));
+
+  EXPECT_TRUE(JSONValuesEqual(inputJSONValue, outputJSONValue));
+}
Index: firefox-147.0.3/dom/webauthn/tests/gtest/moz.build
===================================================================
--- /dev/null
+++ firefox-147.0.3/dom/webauthn/tests/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+    "TestWebAuthnSerialization.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
Index: firefox-147.0.3/dom/webidl/WebAuthentication.webidl
===================================================================
--- firefox-147.0.3.orig/dom/webidl/WebAuthentication.webidl
+++ firefox-147.0.3/dom/webidl/WebAuthentication.webidl
@@ -89,6 +89,7 @@ partial interface PublicKeyCredential {
     [Throws, Pref="security.webauthn.enable_json_serialization_methods"] static PublicKeyCredentialCreationOptions parseCreationOptionsFromJSON(PublicKeyCredentialCreationOptionsJSON options);
 };
 
+[GenerateConversionToJS]
 dictionary PublicKeyCredentialCreationOptionsJSON {
     required PublicKeyCredentialRpEntity                    rp;
     required PublicKeyCredentialUserEntityJSON              user;
@@ -99,7 +100,6 @@ dictionary PublicKeyCredentialCreationOp
     AuthenticatorSelectionCriteria                          authenticatorSelection;
     sequence<DOMString>                                     hints = [];
     DOMString                                               attestation = "none";
-    sequence<DOMString>                                     attestationFormats = [];
     AuthenticationExtensionsClientInputsJSON                extensions;
 };
 
@@ -123,6 +123,7 @@ partial interface PublicKeyCredential {
     [Throws, Pref="security.webauthn.enable_json_serialization_methods"] static PublicKeyCredentialRequestOptions parseRequestOptionsFromJSON(PublicKeyCredentialRequestOptionsJSON options);
 };
 
+[GenerateConversionToJS]
 dictionary PublicKeyCredentialRequestOptionsJSON {
     required Base64URLString                                challenge;
     unsigned long                                           timeout;
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/WebAuthnCredentialManager.java
@@ -21,11 +21,10 @@ import android.credentials.PrepareGetCre
 import android.os.Build;
 import android.os.Bundle;
 import android.os.OutcomeReceiver;
-import android.util.Base64;
 import android.util.Log;
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.WebAuthnUtils;
 import org.mozilla.geckoview.GeckoResult;
 
@@ -69,79 +68,57 @@ public class WebAuthnCredentialManager {
   }
 
   private static Bundle getRequestBundleForMakeCredential(
-      final GeckoBundle credentialBundle,
-      final byte[] userId,
-      final byte[] challenge,
-      final int[] algs,
-      final WebAuthnUtils.WebAuthnPublicCredential[] excludeList,
-      final GeckoBundle authenticatorSelection,
-      final GeckoBundle extensions,
-      final byte[] clientDataHash) {
+      final byte[] clientDataHash, final String requestJSON) {
+    final Bundle bundle = getRequestBundle(requestJSON, clientDataHash);
+    if (bundle == null) {
+      return null;
+    }
+
+    final JSONObject json;
     try {
-      final JSONObject requestJSON =
-          WebAuthnUtils.getJSONObjectForMakeCredential(
-              credentialBundle,
-              userId,
-              challenge,
-              algs,
-              excludeList,
-              authenticatorSelection,
-              extensions);
-      final Bundle bundle = getRequestBundle(requestJSON.toString(), clientDataHash);
-      if (bundle == null) {
-        return null;
-      }
-
-      final Bundle displayInfoBundle = new Bundle();
-      displayInfoBundle.putCharSequence(
-          BUNDLE_KEY_USER_ID,
-          Base64.encodeToString(userId, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-      final GeckoBundle userBundle = credentialBundle.getBundle("user");
-      displayInfoBundle.putString(
-          BUNDLE_KEY_USER_DISPLAY_NAME, userBundle.getString("displayName", ""));
-      bundle.putBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO, displayInfoBundle);
-
-      bundle.putString(
-          BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST);
-      return bundle;
+      json = new JSONObject(requestJSON);
     } catch (final JSONException e) {
-      Log.e(LOGTAG, "Couldn't generate JSON object for request", e);
+      return null;
     }
-    return null;
+
+    final JSONObject userField = json.optJSONObject("user");
+    if (userField == null) {
+      return null;
+    }
+
+    final Bundle displayInfoBundle = new Bundle();
+    displayInfoBundle.putCharSequence(BUNDLE_KEY_USER_ID, userField.optString("id"));
+    displayInfoBundle.putString(BUNDLE_KEY_USER_DISPLAY_NAME, userField.optString("displayName"));
+    bundle.putBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO, displayInfoBundle);
+
+    bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST);
+    return bundle;
   }
 
   private static Bundle getRequestBundleForGetAssertion(
-      final byte[] challenge,
-      final WebAuthnUtils.WebAuthnPublicCredential[] allowList,
-      final GeckoBundle assertionBundle,
-      final GeckoBundle extensionsBundle,
-      final byte[] clientDataHash) {
+      final byte[] clientDataHash, final String requestJSON) {
+    final Bundle bundle = getRequestBundle(requestJSON, clientDataHash);
+    if (bundle == null) {
+      return null;
+    }
+
+    final JSONObject json;
     try {
-      final JSONObject requestJSON =
-          WebAuthnUtils.getJSONObjectForGetAssertion(
-              challenge, allowList, assertionBundle, extensionsBundle);
-      final Bundle bundle = getRequestBundle(requestJSON.toString(), clientDataHash);
-      if (bundle == null) {
-        return null;
-      }
-      bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
-      return bundle;
+      json = new JSONObject(requestJSON);
     } catch (final JSONException e) {
-      Log.e(LOGTAG, "Couldn't generate JSON object for request", e);
+      return null;
     }
-    return null;
+    final JSONArray allowList = json.optJSONArray("allowCredentials");
+    bundle.putBoolean(
+        BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, allowList != null && allowList.length() > 0);
+
+    bundle.putString(BUNDLE_KEY_SUBTYPE, BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
+    return bundle;
   }
 
   @SuppressLint("MissingPermission")
   public static GeckoResult<WebAuthnUtils.MakeCredentialResponse> makeCredential(
-      final GeckoBundle credentialBundle,
-      final byte[] userId,
-      final byte[] challenge,
-      final int[] algs,
-      final WebAuthnUtils.WebAuthnPublicCredential[] excludeList,
-      final GeckoBundle authenticatorSelection,
-      final GeckoBundle extensions,
-      final byte[] clientDataHash) {
+      final String origin, final byte[] clientDataHash, final String requestJSON) {
 
     // We use Credential Manager first. If it doesn't work, we use GMS FIDO2.
     // Credential manager may support non-discoverable keys,
@@ -158,16 +135,7 @@ public class WebAuthnCredentialManager {
       return GeckoResult.fromException(new WebAuthnUtils.Exception("NOT_SUPPORTED_ERR"));
     }
 
-    final Bundle requestBundle =
-        getRequestBundleForMakeCredential(
-            credentialBundle,
-            userId,
-            challenge,
-            algs,
-            excludeList,
-            authenticatorSelection,
-            extensions,
-            clientDataHash);
+    final Bundle requestBundle = getRequestBundleForMakeCredential(clientDataHash, requestJSON);
     if (requestBundle == null) {
       return GeckoResult.fromException(new WebAuthnUtils.Exception("UNKNOWN_ERR"));
     }
@@ -179,7 +147,7 @@ public class WebAuthnCredentialManager {
         new CreateCredentialRequest.Builder(
                 TYPE_PUBLIC_KEY_CREDENTIAL, requestBundle, requestBundle)
             .setAlwaysSendAppInfoToProvider(true)
-            .setOrigin(credentialBundle.getString("origin"))
+            .setOrigin(origin)
             .build();
 
     final CredentialManager manager =
@@ -247,24 +215,16 @@ public class WebAuthnCredentialManager {
   @SuppressLint("MissingPermission")
   public static GeckoResult<PrepareGetCredentialResponse.PendingGetCredentialHandle>
       prepareGetAssertion(
-          final byte[] challenge,
-          final WebAuthnUtils.WebAuthnPublicCredential[] allowList,
-          final GeckoBundle assertionBundle,
-          final GeckoBundle extensions,
-          final byte[] clientDataHash) {
+          final String origin, final byte[] clientDataHash, final String requestJSON) {
     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
       // No credential manager. Relay to GMS FIDO2
       return GeckoResult.fromValue(null);
     }
-    final Bundle requestBundle =
-        getRequestBundleForGetAssertion(
-            challenge, allowList, assertionBundle, extensions, clientDataHash);
+    final Bundle requestBundle = getRequestBundleForGetAssertion(clientDataHash, requestJSON);
     if (requestBundle == null) {
       return GeckoResult.fromValue(null);
     }
 
-    requestBundle.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, allowList.length > 0);
-
     final CredentialOption credentialOption =
         new CredentialOption.Builder(TYPE_PUBLIC_KEY_CREDENTIAL, requestBundle, requestBundle)
             .build();
@@ -273,7 +233,7 @@ public class WebAuthnCredentialManager {
         new GetCredentialRequest.Builder(requestBundle)
             .addCredentialOption(credentialOption)
             .setAlwaysSendAppInfoToProvider(true)
-            .setOrigin(assertionBundle.getString("origin"))
+            .setOrigin(origin)
             .build();
 
     final Context context = GeckoAppShell.getApplicationContext();
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/WebAuthnUtils.java
@@ -324,103 +324,6 @@ public class WebAuthnUtils {
     }
   }
 
-  @NonNull
-  public static JSONObject getJSONObjectForMakeCredential(
-      final GeckoBundle credentialBundle,
-      final byte[] userId,
-      final byte[] challenge,
-      final int[] algs,
-      final WebAuthnPublicCredential[] excludeList,
-      final GeckoBundle authenticatorSelection,
-      final GeckoBundle extensions)
-      throws JSONException {
-    final JSONObject json = credentialBundle.toJSONObject();
-    // origin is unnecessary for requestJSON.
-    json.remove("origin");
-    json.remove("isWebAuthn");
-
-    json.put(
-        "challenge",
-        Base64.encodeToString(challenge, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
-    final JSONObject user = json.getJSONObject("user");
-    user.put(
-        "id", Base64.encodeToString(userId, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
-    final JSONArray pubKeyCredParams = new JSONArray();
-    for (final int alg : algs) {
-      final JSONObject item = new JSONObject();
-      item.put("alg", alg);
-      item.put("type", "public-key");
-      pubKeyCredParams.put(item);
-    }
-    json.put("pubKeyCredParams", pubKeyCredParams);
-
-    final JSONArray excludeCredentials = new JSONArray();
-    for (final WebAuthnPublicCredential cred : excludeList) {
-      excludeCredentials.put(cred.toJSONObject());
-    }
-    json.put("excludeCredentials", excludeCredentials);
-
-    final JSONObject authenticatorSelectionJSON = authenticatorSelection.toJSONObject();
-    /*
-    dom/webauthn/WebAuthnHandler.cpp: WebAuthnHandler::MakeCredential set `residentKey`
-    to "required" if there is no `residentKey` and `requireResidentKey` is true, and
-    `requireResidentKey` should be true if `residentKey` is "required". So we can retrieve
-    `requireResidentKey`'s value from `residentKey`.
-    `requireResidentKey` is only used if `residentKey` isn't set, so it shouldn't be used by any
-    authenticator that follows the specs.
-     */
-    authenticatorSelectionJSON.put(
-        "requireResidentKey",
-        authenticatorSelection.getString("residentKey", "").equals("required"));
-    json.put("authenticatorSelection", authenticatorSelectionJSON);
-
-    final JSONObject extensionsJSON = extensions.toJSONObject();
-    json.put("extensions", extensionsJSON);
-
-    if (DEBUG) {
-      Log.d(LOGTAG, "getJSONObjectForMakeCredential: JSON=\"" + json.toString() + "\"");
-    }
-
-    return json;
-  }
-
-  @NonNull
-  public static JSONObject getJSONObjectForGetAssertion(
-      final byte[] challenge,
-      final WebAuthnPublicCredential[] allowList,
-      final GeckoBundle assertionBundle,
-      final GeckoBundle extensionsBundle)
-      throws JSONException {
-    final JSONObject json = assertionBundle.toJSONObject();
-    // origin is unnecessary for requestJSON.
-    json.remove("origin");
-    json.remove("isWebAuthn");
-
-    json.put(
-        "challenge",
-        Base64.encodeToString(challenge, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP));
-
-    final JSONArray allowCredentials = new JSONArray();
-    for (final WebAuthnPublicCredential cred : allowList) {
-      allowCredentials.put(cred.toJSONObject());
-    }
-    json.put("allowCredentials", allowCredentials);
-
-    if (extensionsBundle.containsKey("fidoAppId")) {
-      final JSONObject extensions = new JSONObject();
-      extensions.put("appid", extensionsBundle.getString("fidoAppId"));
-      json.put("extensions", extensions);
-    }
-
-    if (DEBUG) {
-      Log.d(LOGTAG, "getJSONObjectForGetAssertion: JSON=\"" + json.toString() + "\"");
-    }
-
-    return json;
-  }
-
   public static MakeCredentialResponse getMakeCredentialResponse(final @NonNull String responseJson)
       throws JSONException, IllegalArgumentException {
     final JSONObject json = new JSONObject(responseJson);
Index: firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
+++ firefox-147.0.3/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
@@ -307,7 +307,8 @@ import org.mozilla.gecko.util.WebAuthnUt
       final GeckoBundle authenticatorSelection,
       final GeckoBundle extensions,
       final int[] algs,
-      final ByteBuffer clientDataHash) {
+      final ByteBuffer clientDataHash,
+      final String requestJSON) {
     final ArrayList<WebAuthnUtils.WebAuthnPublicCredential> excludeArrayList;
 
     final byte[] challBytes = new byte[challenge.remaining()];
@@ -331,14 +332,7 @@ import org.mozilla.gecko.util.WebAuthnUt
 
     final GeckoResult<WebAuthnUtils.MakeCredentialResponse> result = new GeckoResult<>();
     WebAuthnCredentialManager.makeCredential(
-            credentialBundle,
-            userBytes,
-            challBytes,
-            algs,
-            excludeList,
-            authenticatorSelection,
-            extensions,
-            clientDataHashBytes)
+            credentialBundle.getString("origin"), clientDataHashBytes, requestJSON)
         .accept(
             response -> result.complete(response),
             exceptionInCredManager -> {
@@ -510,7 +504,8 @@ import org.mozilla.gecko.util.WebAuthnUt
       final ByteBuffer transportList,
       final GeckoBundle assertionBundle,
       final GeckoBundle extensions,
-      final ByteBuffer clientDataHash) {
+      final ByteBuffer clientDataHash,
+      final String requestJSON) {
     final ArrayList<WebAuthnUtils.WebAuthnPublicCredential> allowArrayList;
 
     final byte[] challBytes = new byte[challenge.remaining()];
@@ -549,7 +544,7 @@ import org.mozilla.gecko.util.WebAuthnUt
               }
 
               WebAuthnCredentialManager.prepareGetAssertion(
-                      challBytes, allowList, assertionBundle, extensions, clientDataHashBytes)
+                      assertionBundle.getString("origin"), clientDataHashBytes, requestJSON)
                   .accept(
                       pendingHandle -> {
                         if (pendingHandle != null) {
Index: firefox-147.0.3/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
===================================================================
--- firefox-147.0.3.orig/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
+++ firefox-147.0.3/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/WebAuthnUtilsTest.java
@@ -11,7 +11,6 @@ import java.util.Arrays;
 import java.util.List;
 import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -19,106 +18,6 @@ import org.robolectric.RobolectricTestRu
 @RunWith(RobolectricTestRunner.class)
 public class WebAuthnUtilsTest {
   @Test
-  public void requestJSONForMakeCredentail() throws Exception {
-    final GeckoBundle credentialBundle = new GeckoBundle(7);
-
-    final GeckoBundle rpBundle = new GeckoBundle(2);
-    rpBundle.putString("id", "example.com");
-    rpBundle.putString("name", "Example");
-    credentialBundle.putBundle("rp", rpBundle);
-
-    final GeckoBundle userBundle = new GeckoBundle(2);
-    userBundle.putString("name", "Foo");
-    userBundle.putString("displayName", "Foo");
-    credentialBundle.putBundle("user", userBundle);
-
-    credentialBundle.putString("origin", "example.com");
-    credentialBundle.putDouble("timeout", 5000.0);
-    credentialBundle.putString("attestation", "none");
-
-    final GeckoBundle authenticatorSelection = new GeckoBundle(3);
-    authenticatorSelection.putString("userVerification", "preferred");
-    authenticatorSelection.putString("authenticatorAttachment", "platform");
-    authenticatorSelection.putString("residentKey", "required");
-
-    final GeckoBundle extensions = new GeckoBundle(1);
-    extensions.putBoolean("credProps", true);
-
-    final byte[] userId = new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7};
-    final byte[] challenge =
-        new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
-    final int[] algs = new int[] {-7};
-    final WebAuthnUtils.WebAuthnPublicCredential[] excludeList =
-        new WebAuthnUtils.WebAuthnPublicCredential[0];
-
-    final JSONObject request =
-        WebAuthnUtils.getJSONObjectForMakeCredential(
-            credentialBundle,
-            userId,
-            challenge,
-            algs,
-            excludeList,
-            authenticatorSelection,
-            extensions);
-
-    final JSONObject expected =
-        new JSONObject(
-            "{"
-                + "\"attestation\":\"none\","
-                + "\"challenge\":\"AAECAwQFBgcICQoLDA0ODw\","
-                + "\"authenticatorSelection\":{"
-                + "\"userVerification\":\"preferred\",\"requireResidentKey\":true,"
-                + "\"residentKey\":\"required\",\"authenticatorAttachment\":\"platform\"},"
-                + "\"user\":{\"displayName\":\"Foo\",\"name\":\"Foo\",\"id\":\"AAECAwQFBgc\"},"
-                + "\"rp\":{\"name\":\"Example\",\"id\":\"example.com\"},"
-                + "\"excludeCredentials\":[],"
-                + "\"timeout\":5000,"
-                + "\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7}],"
-                + "\"extensions\":{\"credProps\":true}"
-                + "}");
-
-    // No method to compare JSONObject. Use GeckoBundle instead.
-    assertEquals(
-        "request JSON for MakeCredential should be matched",
-        GeckoBundle.fromJSONObject(expected),
-        GeckoBundle.fromJSONObject(request));
-  }
-
-  @Test
-  public void requestJSONForGetAssertion() throws Exception {
-    final GeckoBundle assertionBundle = new GeckoBundle(3);
-    assertionBundle.putDouble("timeout", 5000.0);
-    assertionBundle.putString("rpId", "example.com");
-    assertionBundle.putString("userVerification", "preferred");
-    final GeckoBundle extensionsBundle = new GeckoBundle(0);
-
-    final byte[] challenge =
-        new byte[] {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
-    final WebAuthnUtils.WebAuthnPublicCredential[] allowList =
-        new WebAuthnUtils.WebAuthnPublicCredential[0];
-
-    final JSONObject response =
-        WebAuthnUtils.getJSONObjectForGetAssertion(
-            challenge, allowList, assertionBundle, extensionsBundle);
-
-    final JSONObject expected =
-        new JSONObject(
-            "{"
-                + "\"allowCredentials\":[],"
-                + "\"challenge\":\"AAECAwQFBgcICQoLDA0ODw\","
-                + "\"timeout\":5000,"
-                + "\"rpId\":\"example.com\","
-                + "\"userVerification\":\"preferred\""
-                + "}");
-
-    // No method to compare JSONObject. Use GeckoBundle instead.
-    assertEquals(
-        "request JSON for GetAssertion should be matched",
-        GeckoBundle.fromJSONObject(expected),
-        GeckoBundle.fromJSONObject(response));
-  }
-
-  @Test
   public void responseJSONForMakeCredential() throws Exception {
     // attestationObject isn't valid format, but this unit test is that parsing JSON and building
     // parameters.
openSUSE Build Service is sponsored by