blob: 8979ba7c93b6c4ba74ec178b0f47a03c6c60d734 [file] [log] [blame]
// 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 <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.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/feature_engagement/tracker_factory.h"
#include "chrome/browser/plus_addresses/plus_address_service_factory.h"
#include "chrome/browser/ssl/chrome_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/autofill_test_utils.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/data_model/autofill_profile_test_api.h"
#include "components/autofill/core/browser/password_form_classification.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/test_autofill_manager_waiter.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_suggestion_delegate.h"
#include "components/autofill/core/browser/ui/mock_fast_checkout_client.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/form_interactions_flow.h"
#include "components/keyed_service/core/keyed_service.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/common/feature_promo/feature_promo_result.h"
#include "components/user_education/test/mock_feature_promo_controller.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/navigation_simulator.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/fast_checkout/fast_checkout_client_impl.h"
#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"
#include "components/feature_engagement/test/mock_tracker.h" // nogncheck
#endif
namespace autofill {
namespace {
using test::CreateFormDataForRenderFrameHost;
using test::CreateTestFormField;
using ::testing::_;
using ::testing::A;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Ref;
using ::testing::Return;
using ::testing::ReturnRef;
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,
std::optional<
payments::PaymentsAutofillClient::OnConfirmationClosedCallback>),
(override));
MOCK_METHOD(void, HideSaveCardBubble, (), (override));
};
#endif
class MockAutofillFieldPromoController : public AutofillFieldPromoController {
public:
~MockAutofillFieldPromoController() override = default;
MOCK_METHOD(void, Show, (const gfx::RectF&), (override));
MOCK_METHOD(void, Hide, (), (override));
MOCK_METHOD(bool, IsMaybeShowing, (), (const override));
MOCK_METHOD(const base::Feature&, GetFeaturePromo, (), (const 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();
GetPaymentsAutofillClient()->SetAutofillSaveCardBottomSheetBridgeForTesting(
std::move(mock));
return pointer;
}
MockFastCheckoutClient fast_checkout_client_;
#endif
};
class ChromeAutofillClientTest : public ChromeRenderViewHostTestHarness {
public:
ChromeAutofillClientTest()
: ChromeRenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
PreparePersonalDataManager();
// Creates the AutofillDriver and AutofillManager.
NavigateAndCommit(GURL("about:blank"));
#if !BUILDFLAG(IS_ANDROID)
ChromeSecurityStateTabHelper::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 SetUpIphForTesting(const base::Feature& feature_promo) {
auto autofill_field_promo_controller =
std::make_unique<MockAutofillFieldPromoController>();
autofill_field_promo_controller_ = autofill_field_promo_controller.get();
ON_CALL(*autofill_field_promo_controller_, IsMaybeShowing)
.WillByDefault(Return(false));
ON_CALL(*autofill_field_promo_controller_, GetFeaturePromo)
.WillByDefault(ReturnRef(feature_promo));
client()->SetAutofillFieldPromoTesting(
std::move(autofill_field_promo_controller));
}
void TearDown() override {
// Avoid that the raw pointer becomes dangling.
personal_data_manager_ = nullptr;
autofill_field_promo_controller_ = nullptr;
ChromeRenderViewHostTestHarness::TearDown();
}
protected:
TestChromeAutofillClient* client() {
return test_autofill_client_injector_[web_contents()];
}
ContentAutofillDriver* driver(content::RenderFrameHost* rfh) {
return ContentAutofillDriver::GetForRenderFrameHost(rfh);
}
TestPersonalDataManager* personal_data_manager() {
return personal_data_manager_;
}
MockAutofillFieldPromoController* autofill_field_promo_controller() {
return autofill_field_promo_controller_;
}
#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::BindOnce([](content::BrowserContext*) {
return std::make_unique<TestPersonalDataManager>();
}));
personal_data_manager_->test_address_data_manager()
.SetAutofillProfileEnabled(true);
personal_data_manager_->test_payments_data_manager()
.SetAutofillPaymentMethodsEnabled(true);
personal_data_manager_->test_payments_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);
}
autofill::test::AutofillUnitTestEnvironment autofill_environment_{
{.disable_server_communication = true}};
raw_ptr<TestPersonalDataManager> personal_data_manager_ = nullptr;
raw_ptr<MockAutofillFieldPromoController> autofill_field_promo_controller_;
TestAutofillClientInjector<TestChromeAutofillClient>
test_autofill_client_injector_;
base::OnceCallback<void()> setup_flags_;
};
// Tests that `ClassifyAsPasswordForm()` correctly recognizes a login form on a
// single frame.
TEST_F(ChromeAutofillClientTest, ClassifiesLoginFormOnMainFrame) {
constexpr char kUrl[] = "https://www.foo.com/login.html";
NavigateAndCommit(GURL(kUrl));
ContentAutofillDriver* autofill_driver = driver(main_rfh());
ASSERT_TRUE(autofill_driver);
FormData form = CreateFormDataForRenderFrameHost(
*main_rfh(), {CreateTestFormField("Username", "username", "",
FormControlType::kInputText),
CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword)});
{
TestAutofillManagerWaiter waiter(autofill_driver->GetAutofillManager(),
{AutofillManagerEvent::kFormsSeen});
autofill_driver->renderer_events().FormsSeen(/*updated_forms=*/{form},
/*removed_forms=*/{});
ASSERT_TRUE(waiter.Wait(/*num_awaiting_calls=*/1));
}
const auto expected = PasswordFormClassification{
.type = PasswordFormClassification::Type::kLoginForm,
.username_field = form.fields()[0].global_id(),
.password_field = form.fields()[1].global_id()};
EXPECT_EQ(client()->ClassifyAsPasswordForm(
autofill_driver->GetAutofillManager(), form.global_id(),
form.fields()[0].global_id()),
expected);
}
// Tests that `ClassifyAsPasswordForm()` correctly recognizes a login form on
// a child frame.
TEST_F(ChromeAutofillClientTest, ClassifiesLoginFormOnChildFrame) {
constexpr char kUrl1[] = "https://www.foo.com/login.html";
constexpr char kUrl2[] = "https://www.foo.com/otp.html";
NavigateAndCommit(GURL(kUrl1));
content::RenderFrameHost* child_rfh =
content::RenderFrameHostTester::For(main_rfh())
->AppendChild(std::string("child"));
child_rfh = content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kUrl2), child_rfh);
ContentAutofillClient* autofill_client =
ContentAutofillClient::FromWebContents(web_contents());
ASSERT_TRUE(autofill_client);
ContentAutofillDriver* main_driver = driver(main_rfh());
ContentAutofillDriver* child_driver = driver(child_rfh);
ASSERT_TRUE(main_driver);
ASSERT_TRUE(child_driver);
FormData main_form = CreateFormDataForRenderFrameHost(
*main_rfh(), {CreateTestFormField("Search", "search", "",
FormControlType::kInputText)});
FormData child_form = CreateFormDataForRenderFrameHost(
*child_rfh, {CreateTestFormField("Username", "username", "",
FormControlType::kInputText),
CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword)});
// Ensure that the child frame is picked up as a child frame of `main_form`.
{
autofill::FrameTokenWithPredecessor child_frame_information;
child_frame_information.token = child_form.host_frame();
main_form.set_child_frames({child_frame_information});
}
{
autofill::TestAutofillManagerWaiter waiter(
main_driver->GetAutofillManager(),
{autofill::AutofillManagerEvent::kFormsSeen});
main_driver->renderer_events().FormsSeen(/*updated_forms=*/{main_form},
/*removed_forms=*/{});
child_driver->renderer_events().FormsSeen(/*updated_forms=*/{child_form},
/*removed_forms=*/{});
ASSERT_TRUE(waiter.Wait(/*num_awaiting_calls=*/2));
}
// The form fields in the main frame do not form a valid password form.
EXPECT_EQ(client()->ClassifyAsPasswordForm(main_driver->GetAutofillManager(),
main_form.global_id(),
main_form.fields()[0].global_id()),
PasswordFormClassification());
// The form fields in the child frame form a login form.
const auto expected = PasswordFormClassification{
.type = PasswordFormClassification::Type::kLoginForm,
.username_field = child_form.fields()[0].global_id(),
.password_field = child_form.fields()[1].global_id()};
EXPECT_EQ(client()->ClassifyAsPasswordForm(
main_driver->GetAutofillManager(), main_form.global_id(),
child_form.fields()[0].global_id()),
expected);
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_BelowMaxFlowTime) {
base::TimeDelta below_max_flow_time = base::Minutes(10);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(below_max_flow_time);
EXPECT_EQ(first_interaction_flow_id,
client()->GetCurrentFormInteractionsFlowId());
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AboveMaxFlowTime) {
base::TimeDelta above_max_flow_time = base::Minutes(21);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(above_max_flow_time);
EXPECT_NE(first_interaction_flow_id,
client()->GetCurrentFormInteractionsFlowId());
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AdvancedTwice) {
base::TimeDelta above_half_max_flow_time = base::Minutes(15);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(above_half_max_flow_time);
FormInteractionsFlowId second_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(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 for different
// surveys. Note that Surveys are only launched on Desktop.
TEST_F(ChromeAutofillClientTest, TriggerUserPerceptionOfAutofillAddressSurvey) {
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(FillingProduct::kAddress,
field_filling_stats_data);
}
TEST_F(ChromeAutofillClientTest,
TriggerUserPerceptionOfAutofillCreditCardSurvey) {
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
const SurveyStringData field_filling_stats_data;
EXPECT_CALL(*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillCreditCardUserPerception, _, _, _,
Ref(field_filling_stats_data), _, _, _, _, _));
client()->TriggerUserPerceptionOfAutofillSurvey(FillingProduct::kCreditCard,
field_filling_stats_data);
}
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_ShowConfirmationBubbleView_CardSaved) {
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
true, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()));
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
/*on_confirmation_closed_callback=*/std::nullopt);
}
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_ShowConfirmationBubbleView_CardNotSaved) {
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
false, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()));
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kPermanentFailure,
/*on_confirmation_closed_callback=*/std::nullopt);
}
// Test that on getting client-side timeout, save card dialog is dismissed and
// confirmation dialog is not shown.
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_NoConfirmationBubbleView_OnRequestTimeout) {
EXPECT_CALL(save_card_bubble_controller(), HideSaveCardBubble());
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
false, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()))
.Times(0);
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kClientSideTimeout,
/*on_confirmation_closed_callback=*/std::nullopt);
}
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();
test_api(profile2).set_record_type(AutofillProfile::RecordType::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_NotShownByPromoController) {
SetUpIphForTesting(feature_engagement::kIPHAutofillManualFallbackFeature);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillRepeatedly(Return(false));
EXPECT_FALSE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kManualFallback));
}
TEST_F(ChromeAutofillClientTest, AutofillManualFallbackIPH_IsShown) {
SetUpIphForTesting(feature_engagement::kIPHAutofillManualFallbackFeature);
InSequence sequence;
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(false));
EXPECT_CALL(*autofill_field_promo_controller(), Show);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(true));
EXPECT_TRUE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kManualFallback));
}
TEST_F(ChromeAutofillClientTest, AutofillImprovedPredictionsIPH_IsShown) {
SetUpIphForTesting(
feature_engagement::kIPHAutofillPredictionImprovementsFeature);
InSequence sequence;
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(false));
EXPECT_CALL(*autofill_field_promo_controller(), Show);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(true));
EXPECT_TRUE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kPredictionImprovements));
}
TEST_F(ChromeAutofillClientTest,
AutofillManualFallbackIPH_HideOnShowAutofillSuggestions) {
SetUpIphForTesting(
feature_engagement::kIPHAutofillPredictionImprovementsFeature);
auto delegate = std::make_unique<MockAutofillSuggestionDelegate>();
EXPECT_CALL(*autofill_field_promo_controller(), Hide);
client()->ShowAutofillSuggestions(AutofillClient::PopupOpenArgs(),
delegate->GetWeakPtr());
// Showing the Autofill Popup is an asynchronous task.
task_environment()->RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(autofill_field_promo_controller());
}
TEST_F(ChromeAutofillClientTest, AutofillManualFallbackIPH_NotifyFeatureUsed) {
feature_engagement::TrackerFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<feature_engagement::test::MockTracker>();
}));
EXPECT_CALL(
*static_cast<feature_engagement::test::MockTracker*>(
feature_engagement::TrackerFactory::GetForBrowserContext(profile())),
NotifyUsedEvent);
client()->NotifyIphFeatureUsed(AutofillClient::IphFeature::kManualFallback);
}
#endif
} // namespace
} // namespace autofill