| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/password_manager/core/browser/password_suggestion_generator.h" |
| |
| #include <vector> |
| |
| #include "components/autofill/core/browser/ui/suggestion.h" |
| #include "components/autofill/core/browser/ui/suggestion_test_helpers.h" |
| #include "components/autofill/core/common/autofill_test_utils.h" |
| #include "components/autofill/core/common/password_form_fill_data.h" |
| #include "components/password_manager/core/browser/password_manager_test_utils.h" |
| #include "components/password_manager/core/browser/stub_password_manager_client.h" |
| #include "components/password_manager/core/browser/stub_password_manager_driver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/image/image.h" |
| #include "url/gurl.h" |
| |
| namespace password_manager { |
| |
| using autofill::EqualsSuggestion; |
| using autofill::PasswordFormFillData; |
| using autofill::Suggestion; |
| using autofill::SuggestionType; |
| using testing::AllOf; |
| using testing::ElementsAre; |
| using testing::Field; |
| using testing::Matcher; |
| |
| Matcher<Suggestion> EqualsManualFallbackSuggestion( |
| SuggestionType id, |
| const std::u16string& main_text, |
| const std::u16string& additional_label, |
| Suggestion::Icon icon, |
| bool is_acceptable, |
| const Suggestion::Payload& payload) { |
| return AllOf( |
| EqualsSuggestion(id, main_text, icon), |
| Field("additional_label", &Suggestion::additional_label, |
| additional_label), |
| Field("is_acceptable", &Suggestion::is_acceptable, is_acceptable), |
| Field("payload", &Suggestion::payload, payload)); |
| } |
| |
| Matcher<Suggestion> EqualsManageManagePasswordsSuggestion( |
| bool has_webauthn_credential = false) { |
| return AllOf(EqualsSuggestion( |
| SuggestionType::kAllSavedPasswordsEntry, |
| has_webauthn_credential |
| ? l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANAGE_PASSWORDS_AND_PASSKEYS) |
| : l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANAGE_PASSWORDS), |
| Suggestion::Icon::kSettings), |
| Field("trailing_icon", &Suggestion::trailing_icon, |
| Suggestion::Icon::kGooglePasswordManager)); |
| } |
| |
| // TODO(b/323316649): Write more tests for password suggestion generation. |
| class PasswordSuggestionGeneratorTest : public testing::Test { |
| public: |
| PasswordSuggestionGeneratorTest() : generator_(&driver(), &client()) {} |
| |
| const gfx::Image& favicon() const { return favicon_; } |
| |
| StubPasswordManagerDriver& driver() { return driver_; } |
| |
| StubPasswordManagerClient& client() { return client_; } |
| |
| PasswordSuggestionGenerator& generator() { return generator_; } |
| |
| PasswordForm password_form() const { |
| return CreateEntry("username@example.com", "password", |
| GURL("https://google.com/"), |
| PasswordForm::MatchType::kExact); |
| } |
| |
| PasswordForm password_form_no_username() const { |
| return CreateEntry("", "password", GURL("https://google.com/"), |
| PasswordForm::MatchType::kExact); |
| } |
| |
| CredentialUIEntry credential_ui_entry() const { |
| return CredentialUIEntry(password_form()); |
| } |
| |
| CredentialUIEntry credential_ui_entry_no_username() const { |
| return CredentialUIEntry(password_form_no_username()); |
| } |
| |
| CredentialUIEntry android_credential_ui_entry() const { |
| PasswordForm form; |
| form.username_value = u"username@example.com"; |
| form.password_value = u"password"; |
| const std::string url = |
| "android://" |
| "Jzj5T2E45Hb33D-lk-" |
| "EHZVCrb7a064dEicTwrTYQYGXO99JqE2YERhbMP1qLogwJiy87OsBzC09Gk094Z-U_hg==" |
| "@com.netflix.mediaclient/"; |
| form.url = GURL(url); |
| form.signon_realm = url; |
| // This field is populated for Android credentials. |
| form.app_display_name = "Netflix"; |
| return CredentialUIEntry(form); |
| } |
| |
| std::vector<Suggestion> GenerateSuggestedPasswordsSection( |
| const std::vector<PasswordForm> suggested_credentials, |
| IsTriggeredOnPasswordForm on_password_form) { |
| return generator().GetManualFallbackSuggestions( |
| base::make_span(suggested_credentials), |
| base::span<const CredentialUIEntry>(), on_password_form); |
| } |
| |
| std::vector<Suggestion> GenerateAllPasswordsSection( |
| const std::vector<CredentialUIEntry>& all_credentials, |
| IsTriggeredOnPasswordForm on_password_form) { |
| return generator().GetManualFallbackSuggestions( |
| base::span<const PasswordForm>(), base::make_span(all_credentials), |
| on_password_form); |
| } |
| |
| std::vector<Suggestion> GenerateBothSections( |
| const std::vector<PasswordForm> suggested_credentials, |
| const std::vector<CredentialUIEntry>& all_credentials, |
| IsTriggeredOnPasswordForm on_password_form) { |
| return generator().GetManualFallbackSuggestions( |
| base::make_span(suggested_credentials), |
| base::make_span(all_credentials), on_password_form); |
| } |
| |
| private: |
| gfx::Image favicon_; |
| |
| StubPasswordManagerClient client_; |
| StubPasswordManagerDriver driver_; |
| PasswordSuggestionGenerator generator_; |
| }; |
| |
| // Test that no suggestions are generated from an empty `PasswordFormFillData`. |
| TEST_F(PasswordSuggestionGeneratorTest, NoPasswordFormFillData) { |
| std::vector<Suggestion> suggestions = generator().GetSuggestionsForDomain( |
| {}, favicon(), /*username_filter=*/u"", OffersGeneration(false), |
| ShowPasswordSuggestions(true), ShowWebAuthnCredentials(false)); |
| |
| EXPECT_TRUE(suggestions.empty()); |
| } |
| |
| // Manual fallback suggestions are only relevant for desktop platform. |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| TEST_F(PasswordSuggestionGeneratorTest, ManualFallback_NoCredentials) { |
| std::vector<Suggestion> suggestions = GenerateBothSections( |
| std::vector<PasswordForm>(), std::vector<CredentialUIEntry>(), |
| IsTriggeredOnPasswordForm(true)); |
| EXPECT_TRUE(suggestions.empty()); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_SuggestedPasswords_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateSuggestedPasswordsSection( |
| {password_form()}, IsTriggeredOnPasswordForm(true)); |
| |
| EXPECT_THAT(suggestions, |
| ElementsAre(EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"username@example.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails( |
| u"password", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {credential_ui_entry()}, IsTriggeredOnPasswordForm(true)); |
| |
| EXPECT_THAT(suggestions, |
| ElementsAre(EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"username@example.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails( |
| u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_AndroidCredential_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {android_credential_ui_entry()}, IsTriggeredOnPasswordForm(true)); |
| |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre(EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"Netflix", |
| u"username@example.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails( |
| u"password", u"Netflix", /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_SuggestedPasswords_NoUsername_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateSuggestedPasswordsSection( |
| {password_form_no_username()}, IsTriggeredOnPasswordForm(true)); |
| |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre( |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN), |
| Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_NoUsername_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {credential_ui_entry_no_username()}, IsTriggeredOnPasswordForm(true)); |
| |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre( |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN), |
| Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_SuggestedPasswords_NonPasswordForm_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateSuggestedPasswordsSection( |
| {password_form()}, IsTriggeredOnPasswordForm(false)); |
| |
| EXPECT_THAT(suggestions, |
| ElementsAre(EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"username@example.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/false, |
| Suggestion::PasswordSuggestionDetails( |
| u"password", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_NonPasswordForm_SuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {credential_ui_entry()}, IsTriggeredOnPasswordForm(false)); |
| |
| EXPECT_THAT(suggestions, |
| ElementsAre(EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"username@example.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/false, |
| Suggestion::PasswordSuggestionDetails( |
| u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_AllDomainsAreUsed) { |
| PasswordForm form_1 = |
| CreateEntry("example@google.com", "password", GURL("https://google.com/"), |
| PasswordForm::MatchType::kExact); |
| PasswordForm form_2 = |
| CreateEntry("example@google.com", "password", GURL("https://amazon.com/"), |
| PasswordForm::MatchType::kExact); |
| CredentialUIEntry entry({std::move(form_1), std::move(form_2)}); |
| std::vector<Suggestion> suggestions = |
| GenerateAllPasswordsSection({entry}, IsTriggeredOnPasswordForm(true)); |
| |
| // Only the first domain is used to create the suggestion. |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre( |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"amazon.com", |
| u"example@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"password", u"amazon.com", |
| /*is_cross_domain=*/true)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"example@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_SortedByDomain) { |
| PasswordForm form_1 = |
| CreateEntry("first@google.com", "first", GURL("https://google.com/"), |
| PasswordForm::MatchType::kExact); |
| PasswordForm form_2 = |
| CreateEntry("second@google.com", "first", GURL("https://microsoft.com/"), |
| PasswordForm::MatchType::kExact); |
| PasswordForm form_3 = |
| CreateEntry("third@google.com", "second", GURL("https://netflix.com/"), |
| PasswordForm::MatchType::kExact); |
| PasswordForm form_4 = |
| CreateEntry("fourth@google.com", "second", GURL("https://amazon.com/"), |
| PasswordForm::MatchType::kExact); |
| |
| std::vector<Suggestion> suggestions = |
| GenerateAllPasswordsSection({CredentialUIEntry({std::move(form_1)}), |
| CredentialUIEntry({std::move(form_2)}), |
| CredentialUIEntry({std::move(form_3)}), |
| CredentialUIEntry({std::move(form_4)})}, |
| IsTriggeredOnPasswordForm(true)); |
| |
| // Manual fallback suggestions are sorted by domain name. |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre( |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"amazon.com", |
| u"fourth@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"second", u"amazon.com", |
| /*is_cross_domain=*/true)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"first@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"first", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"microsoft.com", |
| u"second@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"first", u"microsoft.com", |
| /*is_cross_domain=*/true)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"netflix.com", |
| u"third@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"second", u"netflix.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_SuggestedPasswords_ChildSuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateSuggestedPasswordsSection( |
| {password_form()}, IsTriggeredOnPasswordForm(true)); |
| |
| // 1 password suggestion and 2 footer suggestions. |
| EXPECT_EQ(suggestions.size(), 3u); |
| EXPECT_THAT( |
| suggestions[0].children, |
| ElementsAre( |
| EqualsSuggestion(SuggestionType::kPasswordFieldByFieldFilling, |
| u"username@example.com"), |
| EqualsSuggestion( |
| SuggestionType::kFillPassword, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY), |
| Suggestion::Icon::kNoIcon, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsSuggestion( |
| SuggestionType::kViewPasswordDetails, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_VIEW_DETAILS_ENTRY), |
| Suggestion::Icon::kKey))); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_ChildSuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {credential_ui_entry()}, IsTriggeredOnPasswordForm(true)); |
| |
| // 1 password suggestion and 2 footer suggestions. |
| EXPECT_EQ(suggestions.size(), 3u); |
| EXPECT_THAT( |
| suggestions[0].children, |
| ElementsAre( |
| EqualsSuggestion(SuggestionType::kPasswordFieldByFieldFilling, |
| u"username@example.com"), |
| EqualsSuggestion( |
| SuggestionType::kFillPassword, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY), |
| Suggestion::Icon::kNoIcon, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsSuggestion( |
| SuggestionType::kViewPasswordDetails, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_VIEW_DETAILS_ENTRY), |
| Suggestion::Icon::kKey))); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_SuggestedPasswords_NoUsername_ChildSuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateSuggestedPasswordsSection( |
| {password_form_no_username()}, IsTriggeredOnPasswordForm(true)); |
| |
| // 1 password suggestion and 2 footer suggestions. |
| EXPECT_EQ(suggestions.size(), 3u); |
| EXPECT_THAT( |
| suggestions[0].children, |
| ElementsAre( |
| EqualsSuggestion( |
| SuggestionType::kFillPassword, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY), |
| Suggestion::Icon::kNoIcon, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsSuggestion( |
| SuggestionType::kViewPasswordDetails, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_VIEW_DETAILS_ENTRY), |
| Suggestion::Icon::kKey))); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_AllPasswords_NoUsername_ChildSuggestionContent) { |
| std::vector<Suggestion> suggestions = GenerateAllPasswordsSection( |
| {credential_ui_entry_no_username()}, IsTriggeredOnPasswordForm(true)); |
| |
| // 1 password suggestion and 2 footer suggestions. |
| EXPECT_EQ(suggestions.size(), 3u); |
| EXPECT_THAT( |
| suggestions[0].children, |
| ElementsAre( |
| EqualsSuggestion( |
| SuggestionType::kFillPassword, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY), |
| Suggestion::Icon::kNoIcon, |
| Suggestion::PasswordSuggestionDetails(u"password", u"google.com", |
| /*is_cross_domain=*/true)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsSuggestion( |
| SuggestionType::kViewPasswordDetails, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_VIEW_DETAILS_ENTRY), |
| Suggestion::Icon::kKey))); |
| } |
| |
| TEST_F(PasswordSuggestionGeneratorTest, |
| ManualFallback_BothSections_OnlyAllPasswordsSectionIsSorted) { |
| PasswordForm form_1 = |
| CreateEntry("first@google.com", "first", GURL("https://microsoft.com/"), |
| PasswordForm::MatchType::kExact); |
| |
| PasswordForm form_2 = |
| CreateEntry("second@google.com", "second", GURL("https://google.com/"), |
| PasswordForm::MatchType::kExact); |
| |
| PasswordForm form_3 = |
| CreateEntry("third@google.com", "third", GURL("https://amazon.com/"), |
| PasswordForm::MatchType::kExact); |
| |
| std::vector<Suggestion> suggestions = GenerateBothSections( |
| {form_1, form_2}, |
| {CredentialUIEntry({form_1}), CredentialUIEntry({form_3})}, |
| IsTriggeredOnPasswordForm(true)); |
| |
| // Expected suggestions are: |
| // 0. Suggested passwords section title. |
| // 1. Suggestion for microsoft.com. |
| // 2. Suggestion for google.com. |
| // 3. All passwords section title. |
| // 4. Suggestion for microsoft.com. |
| // 5. Suggestion for amazon.com. |
| // 6. Footer section separator. |
| // 7. "Manage passwords" suggestion. |
| EXPECT_THAT( |
| suggestions, |
| ElementsAre( |
| EqualsSuggestion( |
| SuggestionType::kTitle, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_SUGGESTED_PASSWORDS_SECTION_TITLE)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"microsoft.com", |
| u"first@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"first", u"microsoft.com", |
| /*is_cross_domain=*/false)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"google.com", |
| u"second@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"second", u"google.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion( |
| SuggestionType::kTitle, |
| l10n_util::GetStringUTF16( |
| IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_ALL_PASSWORDS_SECTION_TITLE)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"amazon.com", |
| u"third@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"third", u"amazon.com", |
| /*is_cross_domain=*/true)), |
| EqualsManualFallbackSuggestion( |
| SuggestionType::kPasswordEntry, u"microsoft.com", |
| u"first@google.com", Suggestion::Icon::kGlobe, |
| /*is_acceptable=*/true, |
| Suggestion::PasswordSuggestionDetails(u"first", u"microsoft.com", |
| /*is_cross_domain=*/false)), |
| EqualsSuggestion(SuggestionType::kSeparator), |
| EqualsManageManagePasswordsSuggestion())); |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| } // namespace password_manager |