| // 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/chrome_autofill_client.h" |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/branding_buildflags.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/autofill/mock_autofill_agent.h" |
| #include "chrome/browser/autofill/personal_data_manager_factory.h" |
| #include "chrome/browser/autofill/ui/ui_util.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_client_impl.h" |
| #include "chrome/browser/plus_addresses/plus_address_service_factory.h" |
| #include "chrome/browser/ssl/security_state_tab_helper.h" |
| #include "chrome/browser/ui/autofill/autofill_field_promo_controller.h" |
| #include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.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_autofill_manager_injector.h" |
| #include "components/autofill/content/browser/test_content_autofill_driver.h" |
| #include "components/autofill/core/browser/autofill_test_utils.h" |
| #include "components/autofill/core/browser/data_model/autofill_profile.h" |
| #include "components/autofill/core/browser/payments/payments_autofill_client.h" |
| #include "components/autofill/core/browser/test_autofill_clock.h" |
| #include "components/autofill/core/browser/test_browser_autofill_manager.h" |
| #include "components/autofill/core/browser/test_personal_data_manager.h" |
| #include "components/autofill/core/browser/ui/mock_autofill_popup_delegate.h" |
| #include "components/autofill/core/browser/ui/mock_fast_checkout_client.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/form_interactions_flow.h" |
| #include "components/plus_addresses/features.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/unified_consent/pref_names.h" |
| #include "components/user_education/test/mock_feature_promo_controller.h" |
| #include "mojo/public/cpp/bindings/associated_receiver_set.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/ui/android/autofill/autofill_cvc_save_message_delegate.h" |
| #include "chrome/browser/ui/android/autofill/autofill_save_card_bottom_sheet_bridge.h" |
| #include "chrome/browser/ui/android/autofill/autofill_save_card_delegate_android.h" |
| #include "components/autofill/core/browser/payments/autofill_save_card_ui_info.h" |
| #else |
| #include "chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl.h" |
| #include "chrome/browser/ui/hats/hats_service_factory.h" |
| #include "chrome/browser/ui/hats/mock_hats_service.h" |
| #endif |
| |
| namespace autofill { |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::Field; |
| using ::testing::InSequence; |
| using ::testing::Ref; |
| using ::testing::Return; |
| using user_education::test::MockFeaturePromoController; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| class MockAutofillSaveCardBottomSheetBridge |
| : public AutofillSaveCardBottomSheetBridge { |
| public: |
| MockAutofillSaveCardBottomSheetBridge() |
| : AutofillSaveCardBottomSheetBridge( |
| base::android::ScopedJavaGlobalRef<jobject>(nullptr)) {} |
| |
| MOCK_METHOD(void, |
| RequestShowContent, |
| (const AutofillSaveCardUiInfo&, |
| std::unique_ptr<AutofillSaveCardDelegateAndroid>), |
| (override)); |
| }; |
| #else |
| class MockSaveCardBubbleController : public SaveCardBubbleControllerImpl { |
| public: |
| explicit MockSaveCardBubbleController(content::WebContents* web_contents) |
| : SaveCardBubbleControllerImpl(web_contents) {} |
| ~MockSaveCardBubbleController() override = default; |
| |
| MOCK_METHOD(void, ShowConfirmationBubbleView, (bool), (override)); |
| }; |
| #endif |
| |
| class MockAutofillFieldPromoController : public AutofillFieldPromoController { |
| public: |
| ~MockAutofillFieldPromoController() override = default; |
| MOCK_METHOD(void, Show, (const gfx::RectF&), (override)); |
| MOCK_METHOD(void, Hide, (), (override)); |
| }; |
| |
| class TestChromeAutofillClient : public ChromeAutofillClient { |
| public: |
| explicit TestChromeAutofillClient(content::WebContents* web_contents) |
| : ChromeAutofillClient(web_contents) {} |
| ~TestChromeAutofillClient() override = default; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| MockFastCheckoutClient* GetFastCheckoutClient() override { |
| return &fast_checkout_client_; |
| } |
| |
| // Inject a new MockAutofillSaveCardBottomSheetBridge. |
| // Returns a pointer to the mock. |
| MockAutofillSaveCardBottomSheetBridge* |
| InjectMockAutofillSaveCardBottomSheetBridge() { |
| auto mock = std::make_unique<MockAutofillSaveCardBottomSheetBridge>(); |
| auto* pointer = mock.get(); |
| SetAutofillSaveCardBottomSheetBridgeForTesting(std::move(mock)); |
| return pointer; |
| } |
| |
| MockFastCheckoutClient fast_checkout_client_; |
| #endif |
| }; |
| |
| class ChromeAutofillClientTest : public ChromeRenderViewHostTestHarness { |
| public: |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| PreparePersonalDataManager(); |
| // Creates the AutofillDriver and AutofillManager. |
| NavigateAndCommit(GURL("about:blank")); |
| |
| auto autofill_field_promo_controller_manual_fallback = |
| std::make_unique<MockAutofillFieldPromoController>(); |
| autofill_field_promo_controller_manual_fallback_ = |
| autofill_field_promo_controller_manual_fallback.get(); |
| client()->SetAutofillFieldPromoControllerManualFallbackForTesting( |
| std::move(autofill_field_promo_controller_manual_fallback)); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| SecurityStateTabHelper::CreateForWebContents(web_contents()); |
| |
| auto save_card_bubble_controller = |
| std::make_unique<MockSaveCardBubbleController>(web_contents()); |
| web_contents()->SetUserData(save_card_bubble_controller->UserDataKey(), |
| std::move(save_card_bubble_controller)); |
| #endif |
| } |
| |
| void TearDown() override { |
| // Avoid that the raw pointer becomes dangling. |
| personal_data_manager_ = nullptr; |
| autofill_field_promo_controller_manual_fallback_ = nullptr; |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| protected: |
| TestChromeAutofillClient* client() { |
| return test_autofill_client_injector_[web_contents()]; |
| } |
| |
| TestPersonalDataManager* personal_data_manager() { |
| return personal_data_manager_; |
| } |
| |
| MockAutofillFieldPromoController* |
| autofill_field_promo_controller_manual_fallback() { |
| return autofill_field_promo_controller_manual_fallback_; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| MockSaveCardBubbleController& save_card_bubble_controller() { |
| return static_cast<MockSaveCardBubbleController&>( |
| *SaveCardBubbleControllerImpl::FromWebContents(web_contents())); |
| } |
| #endif |
| |
| private: |
| void PreparePersonalDataManager() { |
| personal_data_manager_ = |
| autofill::PersonalDataManagerFactory::GetInstance() |
| ->SetTestingSubclassFactoryAndUse( |
| profile(), base::BindRepeating([](content::BrowserContext*) { |
| return std::make_unique<TestPersonalDataManager>(); |
| })); |
| |
| personal_data_manager_->SetAutofillProfileEnabled(true); |
| personal_data_manager_->SetAutofillPaymentMethodsEnabled(true); |
| personal_data_manager_->SetAutofillWalletImportEnabled(false); |
| |
| // Enable MSBB by default. If MSBB has been explicitly turned off, Fast |
| // Checkout is not supported. |
| profile()->GetPrefs()->SetBoolean( |
| unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true); |
| } |
| |
| raw_ptr<TestPersonalDataManager> personal_data_manager_ = nullptr; |
| raw_ptr<MockAutofillFieldPromoController> |
| autofill_field_promo_controller_manual_fallback_; |
| TestAutofillClientInjector<TestChromeAutofillClient> |
| test_autofill_client_injector_; |
| base::OnceCallback<void()> setup_flags_; |
| }; |
| |
| TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_BelowMaxFlowTime) { |
| // Arbitrary fixed date to avoid using Now(). |
| base::Time july_2022 = base::Time::FromSecondsSinceUnixEpoch(1658620440); |
| base::TimeDelta below_max_flow_time = base::Minutes(10); |
| |
| autofill::TestAutofillClock test_clock(july_2022); |
| |
| FormInteractionsFlowId first_interaction_flow_id = |
| client()->GetCurrentFormInteractionsFlowId(); |
| |
| test_clock.Advance(below_max_flow_time); |
| |
| EXPECT_EQ(first_interaction_flow_id, |
| client()->GetCurrentFormInteractionsFlowId()); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AboveMaxFlowTime) { |
| // Arbitrary fixed date to avoid using Now(). |
| base::Time july_2022 = base::Time::FromSecondsSinceUnixEpoch(1658620440); |
| base::TimeDelta above_max_flow_time = base::Minutes(21); |
| |
| autofill::TestAutofillClock test_clock(july_2022); |
| |
| FormInteractionsFlowId first_interaction_flow_id = |
| client()->GetCurrentFormInteractionsFlowId(); |
| |
| test_clock.Advance(above_max_flow_time); |
| |
| EXPECT_NE(first_interaction_flow_id, |
| client()->GetCurrentFormInteractionsFlowId()); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AdvancedTwice) { |
| // Arbitrary fixed date to avoid using Now(). |
| base::Time july_2022 = base::Time::FromSecondsSinceUnixEpoch(1658620440); |
| base::TimeDelta above_half_max_flow_time = base::Minutes(15); |
| |
| autofill::TestAutofillClock test_clock(july_2022); |
| |
| FormInteractionsFlowId first_interaction_flow_id = |
| client()->GetCurrentFormInteractionsFlowId(); |
| |
| test_clock.Advance(above_half_max_flow_time); |
| |
| FormInteractionsFlowId second_interaction_flow_id = |
| client()->GetCurrentFormInteractionsFlowId(); |
| |
| test_clock.Advance(above_half_max_flow_time); |
| |
| EXPECT_EQ(first_interaction_flow_id, second_interaction_flow_id); |
| EXPECT_NE(first_interaction_flow_id, |
| client()->GetCurrentFormInteractionsFlowId()); |
| } |
| |
| // Ensure that, by default, the plus address service is not available. |
| // The positive case (feature enabled) will be tested in plus_addresses browser |
| // tests; this test is intended to ensure the default state does not behave |
| // unexpectedly. |
| TEST_F(ChromeAutofillClientTest, |
| PlusAddressDefaultFeatureStateMeansNullPlusAddressService) { |
| PlusAddressServiceFactory::GetForBrowserContext( |
| web_contents()->GetBrowserContext()); |
| EXPECT_EQ(client()->GetPlusAddressDelegate(), nullptr); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // Test that the hats service is called with the expected params. |
| // Note that Surveys are only launched on Desktop. |
| TEST_F(ChromeAutofillClientTest, TriggerUserPerceptionOfAutofillSurvey) { |
| MockHatsService* mock_hats_service = static_cast<MockHatsService*>( |
| HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse( |
| profile(), base::BindRepeating(&BuildMockHatsService))); |
| EXPECT_CALL(*mock_hats_service, CanShowAnySurvey) |
| .WillRepeatedly(Return(true)); |
| |
| SurveyBitsData expected_bits = {{"granular filling available", false}}; |
| const SurveyStringData field_filling_stats_data; |
| EXPECT_CALL(*mock_hats_service, |
| LaunchDelayedSurveyForWebContents( |
| kHatsSurveyTriggerAutofillAddressUserPerception, _, _, |
| expected_bits, Ref(field_filling_stats_data), _, _, _, _, _)); |
| |
| client()->TriggerUserPerceptionOfAutofillSurvey(field_filling_stats_data); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, |
| CreditCardUploadCompleted_ShowConfirmationBubbleView_CardSaved) { |
| EXPECT_CALL(save_card_bubble_controller(), ShowConfirmationBubbleView(true)); |
| client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(true); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, |
| CreditCardUploadCompleted_ShowConfirmationBubbleView_CardNotSaved) { |
| EXPECT_CALL(save_card_bubble_controller(), ShowConfirmationBubbleView(false)); |
| client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(false); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, EditAddressDialogFooter) { |
| EditAddressProfileDialogControllerImpl::CreateForWebContents(web_contents()); |
| auto* controller = |
| EditAddressProfileDialogControllerImpl::FromWebContents(web_contents()); |
| controller->SetViewFactoryForTest(base::BindRepeating( |
| [](content::WebContents*, EditAddressProfileDialogController*) { |
| return static_cast<AutofillBubbleBase*>(nullptr); |
| })); |
| |
| // Non-account profile |
| client()->ShowEditAddressProfileDialog(test::GetFullProfile(), |
| base::DoNothing()); |
| EXPECT_EQ(controller->GetFooterMessage(), u""); |
| |
| // Account profile |
| AutofillProfile profile2 = test::GetFullProfile(); |
| profile2.set_source_for_testing(AutofillProfile::Source::kAccount); |
| client()->ShowEditAddressProfileDialog(profile2, base::DoNothing()); |
| std::optional<AccountInfo> account = GetPrimaryAccountInfoFromBrowserContext( |
| web_contents()->GetBrowserContext()); |
| EXPECT_EQ(controller->GetFooterMessage(), |
| l10n_util::GetStringFUTF16( |
| IDS_AUTOFILL_UPDATE_PROMPT_ACCOUNT_ADDRESS_SOURCE_NOTICE, |
| base::ASCIIToUTF16(account->email))); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, AutofillManualFallbackIPH_IsShown) { |
| EXPECT_CALL(*autofill_field_promo_controller_manual_fallback(), Show); |
| client()->ShowAutofillFieldIphForManualFallbackFeature(FormFieldData{}); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, |
| AutofillManualFallbackIPH_HideOnShowAutofillPopup) { |
| auto delegate = std::make_unique<MockAutofillPopupDelegate>(); |
| |
| EXPECT_CALL(*autofill_field_promo_controller_manual_fallback(), Hide); |
| client()->ShowAutofillPopup(AutofillClient::PopupOpenArgs(), |
| delegate->GetWeakPtr()); |
| testing::Mock::VerifyAndClearExpectations( |
| autofill_field_promo_controller_manual_fallback()); |
| } |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Verify that the prompt to upload save a user's card without CVC is shown in a |
| // bottom sheet. |
| TEST_F( |
| ChromeAutofillClientTest, |
| ConfirmSaveCreditCardToCloud_CardSaveTypeIsOnlyCard_RequestsBottomSheet) { |
| TestChromeAutofillClient* autofill_client = client(); |
| auto* bottom_sheet_bridge = |
| autofill_client->InjectMockAutofillSaveCardBottomSheetBridge(); |
| |
| std::u16string expected_description; |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| expected_description = |
| u"To pay faster next time, save your card and billing address in your " |
| u"Google Account"; |
| #endif |
| |
| // Verify that `AutofillSaveCardUiInfo` has the correct attributes that |
| // indicate upload save card prompt without CVC. |
| EXPECT_CALL(*bottom_sheet_bridge, |
| RequestShowContent( |
| AllOf(Field(&AutofillSaveCardUiInfo::is_for_upload, true), |
| Field(&AutofillSaveCardUiInfo::description_text, |
| expected_description)), |
| testing::NotNull())); |
| |
| autofill_client->ConfirmSaveCreditCardToCloud( |
| CreditCard(), LegalMessageLines(), |
| ChromeAutofillClient::SaveCreditCardOptions() |
| .with_card_save_type(AutofillClient::CardSaveType::kCardSaveOnly) |
| .with_show_prompt(true), |
| base::DoNothing()); |
| } |
| |
| // Verify that the prompt to upload save a user's card with CVC is shown in a |
| // bottom sheet. |
| TEST_F(ChromeAutofillClientTest, |
| ConfirmSaveCreditCardToCloud_CardSaveTypeIsWithCvc_RequestsBottomSheet) { |
| TestChromeAutofillClient* autofill_client = client(); |
| auto* bottom_sheet_bridge = |
| autofill_client->InjectMockAutofillSaveCardBottomSheetBridge(); |
| |
| std::u16string expected_description; |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| expected_description = |
| u"To pay faster next time, save your card, encrypted security code, and " |
| u"billing address in your Google Account"; |
| #endif |
| |
| // Verify that `AutofillSaveCardUiInfo` has the correct attributes that |
| // indicate upload save card prompt with CVC. |
| EXPECT_CALL(*bottom_sheet_bridge, |
| RequestShowContent( |
| AllOf(Field(&AutofillSaveCardUiInfo::is_for_upload, true), |
| Field(&AutofillSaveCardUiInfo::description_text, |
| expected_description)), |
| testing::NotNull())); |
| |
| autofill_client->ConfirmSaveCreditCardToCloud( |
| CreditCard(), LegalMessageLines(), |
| ChromeAutofillClient::SaveCreditCardOptions() |
| .with_card_save_type(AutofillClient::CardSaveType::kCardSaveWithCvc) |
| .with_show_prompt(true), |
| base::DoNothing()); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, |
| ConfirmSaveCreditCardToCloud_DoesNotFailWithoutAWindow) { |
| TestChromeAutofillClient* autofill_client = client(); |
| |
| EXPECT_NO_FATAL_FAILURE(autofill_client->ConfirmSaveCreditCardToCloud( |
| CreditCard(), LegalMessageLines(), |
| ChromeAutofillClient::SaveCreditCardOptions().with_show_prompt(true), |
| base::DoNothing())); |
| } |
| |
| // Verify that the prompt to local save a user's card is shown in a bottom |
| // sheet. |
| TEST_F( |
| ChromeAutofillClientTest, |
| ConfirmSaveCreditCardLocally_CardSaveTypeIsOnlyCard_RequestsBottomSheet) { |
| base::test::ScopedFeatureList scoped_feature_list{ |
| features::kAutofillEnableCvcStorageAndFilling}; |
| |
| TestChromeAutofillClient* autofill_client = client(); |
| auto* bottom_sheet_bridge = |
| autofill_client->InjectMockAutofillSaveCardBottomSheetBridge(); |
| |
| // Verify that `AutofillSaveCardUiInfo` has the correct attributes that |
| // indicate local save card prompt without CVC. |
| EXPECT_CALL( |
| *bottom_sheet_bridge, |
| RequestShowContent( |
| AllOf( |
| Field(&AutofillSaveCardUiInfo::is_for_upload, false), |
| Field(&AutofillSaveCardUiInfo::description_text, |
| u"To pay faster next time, save your card to your device")), |
| testing::NotNull())); |
| |
| autofill_client->ConfirmSaveCreditCardLocally( |
| CreditCard(), |
| ChromeAutofillClient::SaveCreditCardOptions() |
| .with_card_save_type(AutofillClient::CardSaveType::kCardSaveOnly) |
| .with_show_prompt(true), |
| base::DoNothing()); |
| } |
| |
| // Verify that the prompt to local save a user's card and CVC is shown in a |
| // bottom sheet. |
| TEST_F(ChromeAutofillClientTest, |
| ConfirmSaveCreditCardLocally_CardSaveTypeIsWithCvc_RequestsBottomSheet) { |
| base::test::ScopedFeatureList scoped_feature_list{ |
| features::kAutofillEnableCvcStorageAndFilling}; |
| |
| TestChromeAutofillClient* autofill_client = client(); |
| auto* bottom_sheet_bridge = |
| autofill_client->InjectMockAutofillSaveCardBottomSheetBridge(); |
| |
| // Verify that `AutofillSaveCardUiInfo` has the correct attributes that |
| // indicate local save card prompt with CVC. |
| EXPECT_CALL(*bottom_sheet_bridge, |
| RequestShowContent( |
| AllOf(Field(&AutofillSaveCardUiInfo::is_for_upload, false), |
| Field(&AutofillSaveCardUiInfo::description_text, |
| u"To pay faster next time, save your card and " |
| u"encrypted security code to your device")), |
| testing::NotNull())); |
| |
| autofill_client->ConfirmSaveCreditCardLocally( |
| CreditCard(), |
| ChromeAutofillClient::SaveCreditCardOptions() |
| .with_card_save_type(AutofillClient::CardSaveType::kCardSaveWithCvc) |
| .with_show_prompt(true), |
| base::DoNothing()); |
| } |
| |
| TEST_F(ChromeAutofillClientTest, |
| ConfirmSaveCreditCardLocally_DoesNotFailWithoutAWindow) { |
| TestChromeAutofillClient* autofill_client = client(); |
| |
| EXPECT_NO_FATAL_FAILURE(autofill_client->ConfirmSaveCreditCardLocally( |
| CreditCard(), |
| ChromeAutofillClient::SaveCreditCardOptions().with_show_prompt(true), |
| base::DoNothing())); |
| } |
| #endif |
| } // namespace |
| } // namespace autofill |