| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/autofill/autofill_context_menu_manager.h" |
| |
| #include <memory> |
| |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "chrome/browser/autofill/autofill_uitest_util.h" |
| #include "chrome/browser/autofill/personal_data_manager_factory.h" |
| #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/autofill/content/browser/content_autofill_driver_factory.h" |
| #include "components/autofill/content/browser/test_autofill_client_injector.h" |
| #include "components/autofill/content/browser/test_autofill_driver_injector.h" |
| #include "components/autofill/content/browser/test_content_autofill_client.h" |
| #include "components/autofill/core/browser/autofill_test_utils.h" |
| #include "components/autofill/core/browser/browser_autofill_manager.h" |
| #include "components/autofill/core/browser/personal_data_manager_test_utils.h" |
| #include "components/autofill/core/browser/test_autofill_manager_waiter.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/variations/service/variations_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::Not; |
| |
| ACTION_P(QuitMessageLoop, loop) { |
| loop->Quit(); |
| } |
| |
| // Checks if the context menu model contains any entries with manual fallback |
| // labels or command id. `arg` must be of type ui::SimpleMenuModel. |
| MATCHER(ContainsAnyAutofillFallbackEntries, "") { |
| for (size_t i = 0; i < arg->GetItemCount(); i++) { |
| if (arg->GetCommandIdAt(i) == |
| IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS) { |
| return true; |
| } |
| const std::u16string label = arg->GetLabelAt(i); |
| if (label == l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_TITLE)) { |
| return true; |
| } |
| if (label == l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS)) { |
| return true; |
| } |
| if (label == l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_PAYMENTS)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Checks if the context menu model contains the address manual fallback |
| // entries with correct UI strings. `arg` must be of type ui::SimpleMenuModel. |
| MATCHER(OnlyAddressFallbackAdded, "") { |
| EXPECT_EQ(arg->GetItemCount(), 3u); |
| return arg->GetTypeAt(0) == ui::MenuModel::ItemType::TYPE_TITLE && |
| arg->GetLabelAt(0) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_TITLE) && |
| arg->GetLabelAt(1) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS) && |
| arg->GetTypeAt(2) == ui::MenuModel::ItemType::TYPE_SEPARATOR; |
| } |
| |
| // Checks if the context menu model contains the payments manual fallback |
| // entries with correct UI strings. `arg` must be of type ui::SimpleMenuModel. |
| MATCHER(OnlyPaymentsFallbackAdded, "") { |
| EXPECT_EQ(arg->GetItemCount(), 3u); |
| return arg->GetTypeAt(0) == ui::MenuModel::ItemType::TYPE_TITLE && |
| arg->GetLabelAt(0) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_TITLE) && |
| arg->GetLabelAt(1) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_PAYMENTS) && |
| arg->GetTypeAt(2) == ui::MenuModel::ItemType::TYPE_SEPARATOR; |
| } |
| |
| // Checks if the context menu model contains the address and payments manual |
| // fallback entries with correct UI strings. `arg` must be of type |
| // ui::SimpleMenuModel. |
| MATCHER(AddressAndPaymentsFallbacksAdded, "") { |
| EXPECT_EQ(arg->GetItemCount(), 4u); |
| return arg->GetTypeAt(0) == ui::MenuModel::ItemType::TYPE_TITLE && |
| arg->GetLabelAt(0) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_TITLE) && |
| arg->GetLabelAt(1) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS) && |
| arg->GetLabelAt(2) == |
| l10n_util::GetStringUTF16( |
| IDS_CONTENT_CONTEXT_AUTOFILL_FALLBACK_PAYMENTS) && |
| arg->GetTypeAt(3) == ui::MenuModel::ItemType::TYPE_SEPARATOR; |
| } |
| |
| // Generates a ContextMenuParams for the Autofill context menu options. |
| content::ContextMenuParams CreateContextMenuParams( |
| std::optional<autofill::FormRendererId> form_renderer_id = std::nullopt, |
| autofill::FieldRendererId field_render_id = autofill::FieldRendererId(0)) { |
| content::ContextMenuParams rv; |
| rv.is_editable = true; |
| rv.page_url = GURL("http://test.page/"); |
| rv.form_control_type = blink::mojom::FormControlType::kInputText; |
| if (form_renderer_id) { |
| rv.form_renderer_id = form_renderer_id->value(); |
| } |
| rv.field_renderer_id = field_render_id.value(); |
| return rv; |
| } |
| |
| class MockAutofillDriver : public ContentAutofillDriver { |
| public: |
| using ContentAutofillDriver::ContentAutofillDriver; |
| |
| // Mock methods to enable testability. |
| MOCK_METHOD(void, |
| OnContextMenuShownInField, |
| (const FormGlobalId& form_global_id, |
| const FieldGlobalId& field_global_id), |
| (override)); |
| MOCK_METHOD(void, |
| RendererShouldTriggerSuggestions, |
| (const FieldGlobalId& field_id, |
| AutofillSuggestionTriggerSource trigger_source), |
| (override)); |
| }; |
| |
| } // namespace |
| |
| // TODO(crbug.com/1493968): Simplify test setup. |
| class BaseAutofillContextMenuManagerTest : public InProcessBrowserTest { |
| public: |
| BaseAutofillContextMenuManagerTest() = default; |
| |
| BaseAutofillContextMenuManagerTest( |
| const BaseAutofillContextMenuManagerTest&) = delete; |
| BaseAutofillContextMenuManagerTest& operator=( |
| const BaseAutofillContextMenuManagerTest&) = delete; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL("about:blank"))); |
| personal_data_ = PersonalDataManagerFactory::GetForProfile(profile()); |
| |
| menu_model_ = std::make_unique<ui::SimpleMenuModel>(nullptr); |
| render_view_context_menu_ = std::make_unique<TestRenderViewContextMenu>( |
| *main_rfh(), content::ContextMenuParams()); |
| render_view_context_menu_->Init(); |
| autofill_context_menu_manager_ = |
| std::make_unique<AutofillContextMenuManager>( |
| personal_data_, render_view_context_menu_.get(), menu_model_.get()); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams()); |
| } |
| |
| void AddAutofillProfile(const autofill::AutofillProfile& profile) { |
| size_t profile_count = personal_data_->GetProfiles().size(); |
| PersonalDataChangedWaiter waiter(*personal_data_); |
| personal_data_->AddProfile(profile); |
| std::move(waiter).Wait(); |
| EXPECT_EQ(profile_count + 1, personal_data_->GetProfiles().size()); |
| } |
| |
| void AddCreditCard(const autofill::CreditCard& card) { |
| if (card.record_type() != autofill::CreditCard::RecordType::kLocalCard) { |
| personal_data_->AddServerCreditCardForTest( |
| std::make_unique<autofill::CreditCard>(card)); |
| return; |
| } |
| size_t card_count = personal_data_->GetCreditCards().size(); |
| PersonalDataChangedWaiter waiter(*personal_data_); |
| personal_data_->AddCreditCard(card); |
| std::move(waiter).Wait(); |
| EXPECT_EQ(card_count + 1, personal_data_->GetCreditCards().size()); |
| } |
| |
| content::RenderFrameHost* main_rfh() { |
| return web_contents()->GetPrimaryMainFrame(); |
| } |
| |
| content::WebContents* web_contents() const { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| Profile* profile() { return browser()->profile(); } |
| |
| void TearDownOnMainThread() override { |
| autofill_context_menu_manager_.reset(); |
| render_view_context_menu_.reset(); |
| personal_data_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| TestContentAutofillClient* autofill_client() { |
| return autofill_client_injector_[web_contents()]; |
| } |
| |
| MockAutofillDriver* driver() { return autofill_driver_injector_[main_rfh()]; } |
| |
| BrowserAutofillManager& autofill_manager() { |
| return static_cast<BrowserAutofillManager&>(driver()->GetAutofillManager()); |
| } |
| |
| ui::SimpleMenuModel* menu_model() const { return menu_model_.get(); } |
| |
| AutofillContextMenuManager* autofill_context_menu_manager() const { |
| return autofill_context_menu_manager_.get(); |
| } |
| |
| // Sets the `form` and the `form.fields`'s `host_frame`. Since this test |
| // fixture has its own render frame host, which is used by the |
| // `autofill_context_menu_manager()`, this is necessary to identify the forms |
| // correctly by their global ids. |
| void SetHostFramesOfFormAndFields(FormData& form) { |
| LocalFrameToken frame_token = |
| LocalFrameToken(main_rfh()->GetFrameToken().value()); |
| form.host_frame = frame_token; |
| for (FormFieldData& field : form.fields) { |
| field.host_frame = frame_token; |
| } |
| } |
| |
| // Makes the form identifiable by its global id and adds the `form` to the |
| // `driver()`'s manager. |
| void AttachForm(FormData& form) { |
| SetHostFramesOfFormAndFields(form); |
| TestAutofillManagerWaiter waiter(autofill_manager(), |
| {AutofillManagerEvent::kFormsSeen}); |
| autofill_manager().OnFormsSeen(/*updated_forms=*/{form}, |
| /*removed_forms=*/{}); |
| ASSERT_TRUE(waiter.Wait()); |
| } |
| |
| // Creates a form with classifiable fields and registers it with the manager. |
| FormData CreateAndAttachClassifiedForm() { |
| FormData form = test::CreateTestAddressFormData(); |
| AttachForm(form); |
| return form; |
| } |
| |
| // Creates a form where every field has unrecognized autocomplete attribute |
| // and registers it with the manager. |
| FormData CreateAndAttachAutocompleteUnrecognizedForm() { |
| FormData form = test::CreateTestAddressFormData(); |
| for (FormFieldData& field : form.fields) { |
| field.parsed_autocomplete = |
| AutocompleteParsingResult{.field_type = HtmlFieldType::kUnrecognized}; |
| } |
| AttachForm(form); |
| return form; |
| } |
| |
| // Creates a form with unclassifiable fields and registers it with the |
| // manager. |
| FormData CreateAndAttachUnclassifiedForm() { |
| FormData form = test::CreateTestAddressFormData(); |
| for (FormFieldData& field : form.fields) { |
| field.label = u"unclassifiable"; |
| field.name = u"unclassifiable"; |
| } |
| AttachForm(form); |
| return form; |
| } |
| |
| protected: |
| test::AutofillBrowserTestEnvironment autofill_test_environment_; |
| raw_ptr<PersonalDataManager> personal_data_ = nullptr; |
| TestAutofillClientInjector<TestContentAutofillClient> |
| autofill_client_injector_; |
| TestAutofillDriverInjector<MockAutofillDriver> autofill_driver_injector_; |
| std::unique_ptr<TestRenderViewContextMenu> render_view_context_menu_; |
| std::unique_ptr<ui::SimpleMenuModel> menu_model_; |
| std::unique_ptr<AutofillContextMenuManager> autofill_context_menu_manager_; |
| }; |
| |
| class AutocompleteUnrecognizedFieldsTest |
| : public BaseAutofillContextMenuManagerTest { |
| public: |
| AutocompleteUnrecognizedFieldsTest() { |
| feature_.InitAndDisableFeature( |
| features::kAutofillForUnclassifiedFieldsAvailable); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_; |
| }; |
| |
| // Tests that the Autofill's ContentAutofillDriver is called to record metrics |
| // when the context menu is triggered on a field. |
| IN_PROC_BROWSER_TEST_F(AutocompleteUnrecognizedFieldsTest, |
| RecordContextMenuIsShownOnField) { |
| FormRendererId form_renderer_id(test::MakeFormRendererId()); |
| FieldRendererId field_renderer_id(test::MakeFieldRendererId()); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form_renderer_id, field_renderer_id)); |
| |
| FormGlobalId form_global_id{ |
| LocalFrameToken(main_rfh()->GetFrameToken().value()), form_renderer_id}; |
| FieldGlobalId field_global_id{ |
| LocalFrameToken(main_rfh()->GetFrameToken().value()), field_renderer_id}; |
| |
| EXPECT_CALL(*driver(), |
| OnContextMenuShownInField(form_global_id, field_global_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| } |
| |
| // Tests that when triggering the context menu on an unclassified field, the |
| // fallback entry is not part of the menu. |
| IN_PROC_BROWSER_TEST_F(AutocompleteUnrecognizedFieldsTest, |
| UnclassifiedFormShown_FallbackOptionsNotPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), Not(ContainsAnyAutofillFallbackEntries())); |
| } |
| |
| // Tests that when triggering the context menu on an ac=unrecognized field, the |
| // fallback entry is not part of the menu if the user has no AutofillProfiles |
| // stored. |
| IN_PROC_BROWSER_TEST_F( |
| AutocompleteUnrecognizedFieldsTest, |
| AutocompleteUnrecognizedFormShown_NoAutofillProfiles_FallbackOptionsNotPresent) { |
| FormData form = CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), Not(ContainsAnyAutofillFallbackEntries())); |
| } |
| |
| // Tests that when triggering the context menu on an ac=unrecognized field, the |
| // fallback entry is not part of the menu if there's no suitable AutofillProfile |
| // data to fill in. |
| IN_PROC_BROWSER_TEST_F( |
| AutocompleteUnrecognizedFieldsTest, |
| AutocompleteUnrecognizedFormShown_NoSuitableData_FallbackOptionsNotPresent) { |
| AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode); |
| profile.SetRawInfo(COMPANY_NAME, u"company"); |
| AddAutofillProfile(profile); |
| FormData form = CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), Not(ContainsAnyAutofillFallbackEntries())); |
| } |
| |
| // Tests that when triggering the context menu on a classified field, the |
| // fallback entry is part of the menu. |
| IN_PROC_BROWSER_TEST_F(AutocompleteUnrecognizedFieldsTest, |
| ClassifiedFormShown_FallbackOptionsNotPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachClassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), OnlyAddressFallbackAdded()); |
| } |
| |
| // Tests that when triggering the context menu on an ac=unrecognized field, the |
| // fallback entry is part of the menu. |
| IN_PROC_BROWSER_TEST_F( |
| AutocompleteUnrecognizedFieldsTest, |
| AutocompleteUnrecognizedFormShown_FallbackOptionsPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), OnlyAddressFallbackAdded()); |
| } |
| |
| // Tests that when the fallback entry for ac=unrecognized fields is selected, |
| // suggestions are triggered with suggestion trigger source |
| // `kManualFallbackAddress`. |
| IN_PROC_BROWSER_TEST_F(AutocompleteUnrecognizedFieldsTest, |
| AutocompleteUnrecognizedFallback_TriggerSuggestions) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| // Expect that when the entry is selected, suggestions are triggered from that |
| // field. |
| EXPECT_CALL( |
| *driver(), |
| RendererShouldTriggerSuggestions( |
| FieldGlobalId{LocalFrameToken(main_rfh()->GetFrameToken().value()), |
| form.fields[0].renderer_id}, |
| AutofillSuggestionTriggerSource::kManualFallbackAddress)); |
| autofill_context_menu_manager()->ExecuteCommand( |
| IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS); |
| } |
| |
| class UnclassifiedFieldsTest : public BaseAutofillContextMenuManagerTest { |
| private: |
| base::test::ScopedFeatureList feature_{ |
| features::kAutofillForUnclassifiedFieldsAvailable}; |
| }; |
| |
| // Tests that when triggering the context menu on an unclassified form, the |
| // manual fallback entries are not added if user has no address or credit card |
| // data stored. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| NoUserData_ManualFallbacksNotPresent) { |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), Not(ContainsAnyAutofillFallbackEntries())); |
| } |
| |
| // Tests that when triggering the context menu on an unclassified form, address |
| // manual fallback entries are added when the user has address data stored. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| HasAddressData_AddressManualFallbackAdded) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), OnlyAddressFallbackAdded()); |
| } |
| |
| // Tests that when triggering the context menu on an unclassified form, payments |
| // manual fallback entries are added when the user has credit card data stored. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| HasCreditCardData_PaymentsManualFallbackAdded) { |
| AddCreditCard(test::GetCreditCard()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), OnlyPaymentsFallbackAdded()); |
| } |
| |
| // Tests that when triggering the context menu on an unclassified form, the |
| // fallback entry is part of the menu. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| UnclassifiedFormShown_ManualFallbacksPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| AddCreditCard(test::GetCreditCard()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), AddressAndPaymentsFallbacksAdded()); |
| } |
| |
| // Tests that when triggering the context menu on an autocomplete unrecognized |
| // field, the fallback entry is part of the menu. |
| IN_PROC_BROWSER_TEST_F( |
| UnclassifiedFieldsTest, |
| AutocompleteUnrecognizedFieldShown_ManualFallbacksPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| AddCreditCard(test::GetCreditCard()); |
| FormData form = CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), AddressAndPaymentsFallbacksAdded()); |
| } |
| |
| // Tests that when triggering the context menu on a classified form, the |
| // fallback entry is part of the menu. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| ClassifiedFormShown_ManualFallbacksPresent) { |
| AddAutofillProfile(test::GetFullProfile()); |
| AddCreditCard(test::GetCreditCard()); |
| FormData form = CreateAndAttachClassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| EXPECT_THAT(menu_model(), AddressAndPaymentsFallbacksAdded()); |
| } |
| |
| // Tests that when the address manual fallback entry for the unclassified fields |
| // is selected, suggestions are triggered. |
| IN_PROC_BROWSER_TEST_F( |
| UnclassifiedFieldsTest, |
| UnclassifiedFormShown_AddressFallbackTriggersSuggestion) { |
| AddAutofillProfile(test::GetFullProfile()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| // Expect that when the entry is selected, suggestions are triggered. |
| EXPECT_CALL( |
| *driver(), |
| RendererShouldTriggerSuggestions( |
| FieldGlobalId{LocalFrameToken(main_rfh()->GetFrameToken().value()), |
| form.fields[0].renderer_id}, |
| AutofillSuggestionTriggerSource::kManualFallbackAddress)); |
| autofill_context_menu_manager()->ExecuteCommand( |
| IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS); |
| } |
| |
| // Tests that when the payments manual fallback entry for the unclassified |
| // fields is selected, suggestions are triggered with correct field global id |
| // and suggestions trigger source. |
| IN_PROC_BROWSER_TEST_F(UnclassifiedFieldsTest, |
| UnclassifiedFormShown_PaymentsFallbackTriggersFallback) { |
| AddCreditCard(test::GetCreditCard()); |
| FormData form = CreateAndAttachUnclassifiedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| // Expect that when the entry is selected, suggestions are triggered from that |
| // field. |
| EXPECT_CALL( |
| *driver(), |
| RendererShouldTriggerSuggestions( |
| FieldGlobalId{LocalFrameToken(main_rfh()->GetFrameToken().value()), |
| form.fields[0].renderer_id}, |
| AutofillSuggestionTriggerSource::kManualFallbackPayments)); |
| autofill_context_menu_manager()->ExecuteCommand( |
| IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_PAYMENTS); |
| } |
| |
| // Test parameter data for asserting metrics emission when triggering Autofill |
| // via manual fallback. |
| struct ManualFallbackMetricsTestParams { |
| // Fallback option displayed in the context menu (address, payments etc). |
| const AutofillSuggestionTriggerSource manual_fallback_option; |
| // Whether the option above was accepted by the user. |
| const bool option_accepted; |
| // Whether the field where manual fallback was used is classified or not. If |
| // false, an address field with ac=unrecognized in used. |
| const bool is_field_unclassified; |
| const std::string test_name; |
| }; |
| |
| // Test fixture that covers metrics emitted when Autofill is triggered via the |
| // context menu. |
| class ManualFallbackMetricsTest |
| : public BaseAutofillContextMenuManagerTest, |
| public ::testing::WithParamInterface<ManualFallbackMetricsTestParams> { |
| public: |
| // Returns the expected metric that should be emitted depending on the |
| // option displayed in the context menu and whether the user accepted it. |
| std::string GetExplicitlyTriggeredMetricName() const { |
| const ManualFallbackMetricsTestParams& params = GetParam(); |
| std::string classified_or_unclassified_field_metric_name_substr = |
| params.is_field_unclassified |
| ? "NotClassifiedAsTargetFilling" |
| : "ClassifiedFieldAutocompleteUnrecognized"; |
| return "Autofill.ManualFallback.ExplicitlyTriggered." + |
| classified_or_unclassified_field_metric_name_substr + |
| GetFillingProductBucketName(); |
| } |
| |
| // Similar to the method above, but for the total bucket. |
| std::string GetExpectedTotalMetricName() const { |
| const ManualFallbackMetricsTestParams& params = GetParam(); |
| if (params.is_field_unclassified) { |
| return "Autofill.ManualFallback.ExplicitlyTriggered." |
| "NotClassifiedAsTargetFilling.Total"; |
| } |
| return "Autofill.ManualFallback.ExplicitlyTriggered.Total" + |
| GetFillingProductBucketName(); |
| } |
| |
| private: |
| // Returns the expected bucket (Address or CreditCard) depending on the |
| // fallback option being tested. |
| std::string GetFillingProductBucketName() const { |
| return GetParam().manual_fallback_option == |
| AutofillSuggestionTriggerSource::kManualFallbackAddress |
| ? ".Address" |
| : ".CreditCard"; |
| } |
| base::test::ScopedFeatureList feature_{ |
| features::kAutofillForUnclassifiedFieldsAvailable}; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(ManualFallbackMetricsTest, |
| EmitExplicitlyTriggeredMetric) { |
| const ManualFallbackMetricsTestParams& params = GetParam(); |
| const bool is_address_manual_fallback = |
| params.manual_fallback_option == |
| AutofillSuggestionTriggerSource::kManualFallbackAddress; |
| if (is_address_manual_fallback) { |
| AddAutofillProfile(test::GetFullProfile()); |
| } else { |
| AddCreditCard(test::GetCreditCard()); |
| } |
| FormData form = params.is_field_unclassified |
| ? CreateAndAttachUnclassifiedForm() |
| : CreateAndAttachAutocompleteUnrecognizedForm(); |
| autofill_context_menu_manager()->set_params_for_testing( |
| CreateContextMenuParams(form.renderer_id, form.fields[0].renderer_id)); |
| autofill_context_menu_manager()->AppendItems(); |
| |
| if (params.option_accepted) { |
| autofill_context_menu_manager()->ExecuteCommand( |
| is_address_manual_fallback |
| ? IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_ADDRESS |
| : IDC_CONTENT_CONTEXT_AUTOFILL_FALLBACK_PAYMENTS); |
| } |
| // Expect that when the autofill_manager() is destroyed, the explicitly |
| // triggered metric is emitted correctly. |
| base::HistogramTester histogram_tester; |
| autofill_manager().Reset(); |
| |
| histogram_tester.ExpectUniqueSample(GetExplicitlyTriggeredMetricName(), |
| params.option_accepted, 1); |
| histogram_tester.ExpectUniqueSample(GetExpectedTotalMetricName(), |
| params.option_accepted, 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| BaseAutofillContextMenuManagerTest, |
| ManualFallbackMetricsTest, |
| ::testing::ValuesIn(std::vector<ManualFallbackMetricsTestParams>( |
| {{ |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackAddress, |
| .option_accepted = true, |
| .is_field_unclassified = true, |
| .test_name = "UnclassifiedField_Address_Accepted", |
| }, |
| { |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackAddress, |
| .option_accepted = false, |
| .is_field_unclassified = true, |
| .test_name = "UnclassifiedField_Address_NotAccepted", |
| }, |
| |
| { |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackPayments, |
| .option_accepted = true, |
| .is_field_unclassified = true, |
| .test_name = "UnclassifiedField_Payments_Accepted", |
| }, |
| { |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackPayments, |
| .option_accepted = false, |
| .is_field_unclassified = true, |
| .test_name = "UnclassifiedField_Payments_NotAccepted", |
| }, |
| |
| { |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackAddress, |
| .option_accepted = true, |
| // This effectively means testing manual fallback on |
| // ac=unrecognized fields. |
| .is_field_unclassified = false, |
| .test_name = "ClassifiedField_Address_NotAccepted", |
| }, |
| { |
| .manual_fallback_option = |
| AutofillSuggestionTriggerSource::kManualFallbackAddress, |
| .option_accepted = false, |
| // This effectively means testing manual fallback on |
| // ac=unrecognized fields. |
| .is_field_unclassified = false, |
| .test_name = "ClassifiedField_Address_Accepted", |
| }})), |
| [](const ::testing::TestParamInfo<ManualFallbackMetricsTest::ParamType>& |
| info) { return info.param.test_name; }); |
| |
| } // namespace autofill |