[HybridInAutofill] Make PasswordSuggestionGenerator reusable in Autofill

This CL refactors PasswordSuggestionGenerator to create the hybrid
passkey suggestion in a dedicated method. This allows to reuse the exact
same item in Autofill dropdowns that is used in Password dropdowns.

Prototype Screenshot: http://screen/7RacahewbUDhBVc

Prototype: crrev.com/c/6836380
Feature: AutofillReintroduceHybridPasskeyDropdownItem
Bug: 399124614
Change-Id: I257f077cc16ecb725db85e75e1148f17173bbb02
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6860605
Reviewed-by: Mohamed Amir Yosef <mamir@chromium.org>
Commit-Queue: Friedrich Hauser <friedrichh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1503976}
diff --git a/components/password_manager/core/browser/password_suggestion_generator.cc b/components/password_manager/core/browser/password_suggestion_generator.cc
index 1be8a3b2..e65264eb 100644
--- a/components/password_manager/core/browser/password_suggestion_generator.cc
+++ b/components/password_manager/core/browser/password_suggestion_generator.cc
@@ -388,6 +388,24 @@
 }
 
 #endif
+
+#if !BUILDFLAG(IS_ANDROID)
+bool ShowPasskeysFromAnotherDeviceInAutofill() {
+#if BUILDFLAG(IS_IOS)
+  return true;
+#else
+  // Show the hybrid passkey item if the context menu experiment (which moves
+  // this option) is not enabled, or if the feature to reintroduce it to the
+  // dropdown is explicitly enabled.
+  return !base::FeatureList::IsEnabled(
+             password_manager::features::
+                 kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu) ||
+         base::FeatureList::IsEnabled(
+             password_manager::features::
+                 kAutofillReintroduceHybridPasskeyDropdownItem);
+#endif  // BUILDFLAG(IS_IOS)
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
 }  // namespace
 
 PasswordSuggestionGenerator::PasswordSuggestionGenerator(
@@ -470,20 +488,8 @@
 
 #if !BUILDFLAG(IS_ANDROID)
   // Add "Use a passkey" or "Use a different passkey" button.
-  if (uses_passkeys && delegate->IsSecurityKeyOrHybridFlowAvailable()) {
-#if !BUILDFLAG(IS_IOS)
-    const bool passkey_from_another_device_in_autofill =
-        !(base::FeatureList::IsEnabled(
-            features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu));
-#else
-    const bool passkey_from_another_device_in_autofill = true;
-#endif  //! BUILDFLAG(IS_IOS)
-    if (passkey_from_another_device_in_autofill) {
-      bool listed_passkeys = delegate->GetPasskeys().has_value() &&
-                             delegate->GetPasskeys().value()->size() > 0;
-      suggestions.emplace_back(
-          CreatePasskeyFromAnotherDeviceEntry(listed_passkeys));
-    }
+  if (auto hybrid_suggestion = GetWebauthnSignInWithAnotherDeviceSuggestion()) {
+    suggestions.push_back(std::move(hybrid_suggestion.value()));
   }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
@@ -631,4 +637,23 @@
   return suggestions;
 }
 
+std::optional<autofill::Suggestion>
+PasswordSuggestionGenerator::GetWebauthnSignInWithAnotherDeviceSuggestion()
+    const {
+#if BUILDFLAG(IS_ANDROID)
+  return std::nullopt;
+#else   // BUILDFLAG(IS_ANDROID)
+  WebAuthnCredentialsDelegate* delegate =
+      password_client_->GetWebAuthnCredentialsDelegateForDriver(
+          password_manager_driver_);
+  if (!delegate || !delegate->GetPasskeys().has_value() ||
+      !delegate->IsSecurityKeyOrHybridFlowAvailable() ||
+      !ShowPasskeysFromAnotherDeviceInAutofill()) {
+    return std::nullopt;
+  }
+  return CreatePasskeyFromAnotherDeviceEntry(
+      /*listed_passkeys=*/delegate->GetPasskeys().value()->size() > 0);
+#endif  // BUILDFLAG(IS_ANDROID)
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_suggestion_generator.h b/components/password_manager/core/browser/password_suggestion_generator.h
index cedbbd6b..a191207 100644
--- a/components/password_manager/core/browser/password_suggestion_generator.h
+++ b/components/password_manager/core/browser/password_suggestion_generator.h
@@ -107,6 +107,11 @@
       base::span<const CredentialUIEntry> all_credentials,
       IsTriggeredOnPasswordForm on_password_form) const;
 
+  // Returns a `kWebauthnSignInWithAnotherDevice` suggestion if it should be
+  // shown for the current context.
+  std::optional<autofill::Suggestion>
+  GetWebauthnSignInWithAnotherDeviceSuggestion() const;
+
  private:
   const raw_ptr<PasswordManagerDriver> password_manager_driver_;
   const raw_ptr<PasswordManagerClient> password_client_;
diff --git a/components/password_manager/core/browser/password_suggestion_generator_unittest.cc b/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
index d4d4eee..9906694 100644
--- a/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
+++ b/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
@@ -1756,4 +1756,128 @@
           EqualsManagePasswordsSuggestion()));
 }
 
+#if !BUILDFLAG(IS_ANDROID)
+TEST_F(PasswordSuggestionGeneratorTest,
+       GetWebauthnSignInWithAnotherDeviceSuggestion) {
+#if !BUILDFLAG(IS_IOS)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu);
+#endif  // !BUILDFLAG(IS_IOS)
+  const std::vector<PasskeyCredential> passkeys;
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(base::ok(&passkeys)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(true));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  ASSERT_TRUE(suggestion.has_value());
+  EXPECT_THAT(*suggestion,
+              EqualsSuggestion(
+                  SuggestionType::kWebauthnSignInWithAnotherDevice,
+                  l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USE_PASSKEY),
+                  Suggestion::Icon::kDevice));
+}
+
+TEST_F(PasswordSuggestionGeneratorTest,
+       GetWebauthnSignInWithAnotherDeviceSuggestionWithListedPasskeys) {
+#if !BUILDFLAG(IS_IOS)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu);
+#endif  // !BUILDFLAG(IS_IOS)
+  const std::vector<PasskeyCredential> passkeys = {
+      passkey_credential(PasskeyCredential::Source::kWindowsHello, "username")};
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(base::ok(&passkeys)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(true));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  ASSERT_TRUE(suggestion.has_value());
+  EXPECT_THAT(*suggestion,
+              EqualsSuggestion(SuggestionType::kWebauthnSignInWithAnotherDevice,
+                               l10n_util::GetStringUTF16(
+                                   IDS_PASSWORD_MANAGER_USE_DIFFERENT_PASSKEY),
+                               Suggestion::Icon::kDevice));
+}
+
+TEST_F(PasswordSuggestionGeneratorTest,
+       GetWebauthnSignInWithAnotherDeviceSuggestionWhenHybridFlagIsReenabled) {
+#if !BUILDFLAG(IS_IOS)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu,
+       features::kAutofillReintroduceHybridPasskeyDropdownItem},
+      {});
+#endif  // !BUILDFLAG(IS_IOS)
+  const std::vector<PasskeyCredential> passkeys;
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(base::ok(&passkeys)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(true));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  ASSERT_TRUE(suggestion.has_value());
+}
+
+#if !BUILDFLAG(IS_IOS)
+TEST_F(PasswordSuggestionGeneratorTest,
+       NoWebauthnSignInWithAnotherDeviceSuggestionWhenHybridIsOff) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu);
+  const std::vector<PasskeyCredential> passkeys;
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(base::ok(&passkeys)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(true));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  EXPECT_FALSE(suggestion.has_value());
+}
+#endif  // !BUILDFLAG(IS_IOS)
+
+TEST_F(PasswordSuggestionGeneratorTest,
+       NoWebauthnSignInWithAnotherDeviceSuggestionWhenNoPasskeys) {
+#if !BUILDFLAG(IS_IOS)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu);
+#endif  // !BUILDFLAG(IS_IOS)
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(
+          base::unexpected(WebAuthnCredentialsDelegate::
+                               PasskeysUnavailableReason::kNotReceived)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(true));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  EXPECT_FALSE(suggestion.has_value());
+}
+
+TEST_F(PasswordSuggestionGeneratorTest,
+       NoWebauthnSignInWithAnotherDeviceSuggestionWhenHybridFlowUnavailable) {
+#if !BUILDFLAG(IS_IOS)
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      features::kWebAuthnUsePasskeyFromAnotherDeviceInContextMenu);
+#endif  // !BUILDFLAG(IS_IOS)
+  const std::vector<PasskeyCredential> passkeys;
+  ON_CALL(credentials_delegate(), GetPasskeys)
+      .WillByDefault(Return(base::ok(&passkeys)));
+  ON_CALL(credentials_delegate(), IsSecurityKeyOrHybridFlowAvailable)
+      .WillByDefault(Return(false));
+
+  std::optional<Suggestion> suggestion =
+      generator().GetWebauthnSignInWithAnotherDeviceSuggestion();
+  EXPECT_FALSE(suggestion.has_value());
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 }  // namespace password_manager