| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "components/autofill/ios/browser/autofill_agent.h" |
| |
| #import <string> |
| #import <variant> |
| |
| #import "base/apple/bundle_locations.h" |
| #import "base/json/json_writer.h" |
| #import "base/memory/raw_ptr.h" |
| #import "base/memory/weak_ptr.h" |
| #import "base/strings/strcat.h" |
| #import "base/strings/string_number_conversions.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/strings/utf_string_conversions.h" |
| #import "base/test/gtest_util.h" |
| #import "base/test/ios/wait_util.h" |
| #import "base/test/test_timeouts.h" |
| #import "base/values.h" |
| #import "components/autofill/core/browser/autofill_field.h" |
| #import "components/autofill/core/browser/data_model/payments/credit_card.h" |
| #import "components/autofill/core/browser/filling/filling_product.h" |
| #import "components/autofill/core/browser/foundations/test_autofill_client.h" |
| #import "components/autofill/core/browser/suggestions/suggestion.h" |
| #import "components/autofill/core/browser/suggestions/suggestion_type.h" |
| #import "components/autofill/core/browser/test_utils/autofill_test_utils.h" |
| #import "components/autofill/core/browser/ui/mock_autofill_suggestion_delegate.h" |
| #import "components/autofill/core/common/autofill_features.h" |
| #import "components/autofill/core/common/autofill_payments_features.h" |
| #import "components/autofill/core/common/autofill_prefs.h" |
| #import "components/autofill/core/common/field_data_manager.h" |
| #import "components/autofill/core/common/form_data.h" |
| #import "components/autofill/core/common/form_field_data.h" |
| #import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #import "components/autofill/core/common/unique_ids.h" |
| #import "components/autofill/ios/browser/autofill_driver_ios.h" |
| #import "components/autofill/ios/browser/autofill_driver_ios_factory.h" |
| #import "components/autofill/ios/browser/autofill_java_script_feature.h" |
| #import "components/autofill/ios/browser/autofill_util.h" |
| #import "components/autofill/ios/browser/mock_password_autofill_agent_delegate.h" |
| #import "components/autofill/ios/browser/password_autofill_agent.h" |
| #import "components/autofill/ios/browser/test_autofill_client_ios.h" |
| #import "components/autofill/ios/common/field_data_manager_factory_ios.h" |
| #import "components/autofill/ios/form_util/form_handlers_java_script_feature.h" |
| #import "components/autofill/ios/form_util/form_util_java_script_feature.h" |
| #import "components/prefs/pref_service.h" |
| #import "ios/web/public/test/fakes/fake_browser_state.h" |
| #import "ios/web/public/test/fakes/fake_web_frame.h" |
| #import "ios/web/public/test/fakes/fake_web_frames_manager.h" |
| #import "ios/web/public/test/fakes/fake_web_state.h" |
| #import "ios/web/public/test/task_observer_util.h" |
| #import "ios/web/public/test/web_test.h" |
| #import "services/metrics/public/cpp/ukm_builders.h" |
| #import "testing/gmock/include/gmock/gmock.h" |
| #import "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #import "ui/base/resource/resource_bundle.h" |
| #import "ui/gfx/image/image_unittest_util.h" |
| #import "url/gurl.h" |
| |
| using autofill::AutofillJavaScriptFeature; |
| using autofill::FieldDataManager; |
| using autofill::FieldRendererId; |
| using autofill::FillingProduct; |
| using autofill::FormRendererId; |
| using autofill::Section; |
| using autofill::SuggestionType; |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| |
| namespace { |
| |
| using autofill::AutofillDriverIOS; |
| using autofill::AutofillDriverIOSFactory; |
| |
| constexpr char kTestFrameId[] = "11111111111111111111111111111111"; |
| |
| // Returns the minimal FormData content for testing filling. |
| std::vector<autofill::FormFieldData::FillData> |
| MinimalFormFieldDataForFilling() { |
| autofill::FormFieldData field; |
| field.set_value(u"test-username"); |
| field.set_host_form_id(FormRendererId(1)); |
| field.set_renderer_id(FieldRendererId(2)); |
| field.set_is_autofilled(true); |
| return {autofill::FormFieldData::FillData(std::move(field))}; |
| } |
| |
| // Returns a simple form suggestion that only consists of a `value` and a `type` |
| FormSuggestion* SimpleFormSuggestion(std::u16string value, |
| autofill::SuggestionType type) { |
| return [FormSuggestion suggestionWithValue:base::SysUTF16ToNSString(value) |
| displayDescription:@"" |
| icon:nil |
| type:type |
| payload:autofill::Suggestion::Payload() |
| requiresReauth:NO]; |
| } |
| |
| } // namespace |
| |
| @interface AutofillAgent (Testing) |
| - (void)updateFieldManagerWithFillingResults:(NSString*)jsonString |
| inFrame:(web::WebFrame*)frame; |
| @end |
| |
| // Test fixture for AutofillAgent testing. |
| class AutofillAgentTests : public web::WebTest { |
| public: |
| AutofillAgentTests() = default; |
| |
| AutofillAgentTests(const AutofillAgentTests&) = delete; |
| AutofillAgentTests& operator=(const AutofillAgentTests&) = delete; |
| |
| // This *should* be true so that the tests mimic production code behavior, but |
| // one legacy test crashes if we pass a non-nil AutofillAgent. |
| virtual bool should_set_autofill_driver_ios_bridge() const { return true; } |
| |
| void AddWebFrame(std::unique_ptr<web::WebFrame> frame) { |
| fake_web_frames_manager_->AddWebFrame(std::move(frame)); |
| } |
| |
| void RemoveWebFrame(const std::string& frame_id) { |
| fake_web_frames_manager_->RemoveWebFrame(frame_id); |
| } |
| |
| void SetUp() override { |
| web::WebTest::SetUp(); |
| |
| OverrideJavaScriptFeatures( |
| {autofill::AutofillJavaScriptFeature::GetInstance(), |
| autofill::FormHandlersJavaScriptFeature::GetInstance(), |
| autofill::FormUtilJavaScriptFeature::GetInstance()}); |
| |
| fake_web_state_.SetBrowserState(GetBrowserState()); |
| fake_web_state_.SetContentIsHTML(true); |
| |
| for (auto content_world : {web::ContentWorld::kIsolatedWorld, |
| web::ContentWorld::kPageContentWorld}) { |
| auto frames_manager = std::make_unique<web::FakeWebFramesManager>(); |
| fake_web_state_.SetWebFramesManager(content_world, |
| std::move(frames_manager)); |
| } |
| |
| fake_web_frames_manager_ = static_cast<web::FakeWebFramesManager*>( |
| fake_web_state_.GetWebFramesManager( |
| AutofillJavaScriptFeature::GetInstance() |
| ->GetSupportedContentWorld())); |
| |
| GURL url("https://example.com"); |
| fake_web_state_.SetCurrentURL(url); |
| auto main_frame = web::FakeWebFrame::Create(kTestFrameId, true, url); |
| main_frame->set_browser_state(GetBrowserState()); |
| fake_main_frame_ = main_frame.get(); |
| AddWebFrame(std::move(main_frame)); |
| |
| autofill::PasswordAutofillAgent::CreateForWebState(&fake_web_state_, |
| &delegate_mock_); |
| |
| prefs_ = autofill::test::PrefServiceForTesting(); |
| autofill::prefs::SetAutofillProfileEnabled(prefs_.get(), true); |
| autofill::prefs::SetAutofillPaymentMethodsEnabled(prefs_.get(), true); |
| autofill_agent_ = |
| [[AutofillAgent alloc] initWithPrefService:prefs_.get() |
| webState:&fake_web_state_]; |
| |
| client_ = std::make_unique<autofill::TestAutofillClientIOS>( |
| &fake_web_state_, |
| should_set_autofill_driver_ios_bridge() ? autofill_agent_ : nil); |
| } |
| |
| std::unique_ptr<web::FakeWebFrame> CreateMainWebFrame() { |
| std::unique_ptr<web::FakeWebFrame> frame = |
| web::FakeWebFrame::CreateMainWebFrame(); |
| frame->set_browser_state(GetBrowserState()); |
| return frame; |
| } |
| |
| std::unique_ptr<web::FakeWebFrame> CreateChildWebFrame() { |
| std::unique_ptr<web::FakeWebFrame> frame = |
| web::FakeWebFrame::CreateChildWebFrame(); |
| frame->set_browser_state(GetBrowserState()); |
| return frame; |
| } |
| |
| // The prefs_ must outlive the fake_web_state_ and the autofill_agent_, |
| // the latter of which can be de-allocated as part of de-allocating the |
| // fake_web_state_. |
| std::unique_ptr<PrefService> prefs_; |
| // The client_ needs to outlive the fake_web_state_, which owns the |
| // frames. |
| std::unique_ptr<autofill::TestAutofillClientIOS> client_; |
| web::FakeWebState fake_web_state_; |
| raw_ptr<web::FakeWebFrame, DanglingUntriaged> fake_main_frame_ = nullptr; |
| raw_ptr<web::FakeWebFramesManager> fake_web_frames_manager_ = nullptr; |
| AutofillAgent* autofill_agent_; |
| autofill::MockPasswordAutofillAgentDelegate delegate_mock_; |
| }; |
| |
| // Tests that form's name and fields' identifiers, values, and whether they are |
| // autofilled are sent to the JS. Fields with empty values and those that are |
| // not autofilled are skipped. Tests logic based on renderer ids usage. |
| TEST_F(AutofillAgentTests, |
| OnFormDataFilledTestWithFrameMessagingUsingRendererIDs) { |
| std::vector<autofill::FormFieldData::FillData> fill_data; |
| autofill::FormFieldData field; |
| field.set_form_control_type(autofill::FormControlType::kInputText); |
| field.set_label(u"Card number"); |
| field.set_name(u"number"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"number"); |
| field.set_value(u"number_value"); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(2)); |
| fill_data.push_back(autofill::FormFieldData::FillData(field)); |
| field.set_label(u"Name on Card"); |
| field.set_name(u"name"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"name"); |
| field.set_value(u"name_value"); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(3)); |
| fill_data.push_back(autofill::FormFieldData::FillData(field)); |
| field.set_label(u"Expiry Month"); |
| field.set_name(u"expiry_month"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"expiry_month"); |
| field.set_value(u"01"); |
| field.set_is_autofilled(false); |
| field.set_renderer_id(FieldRendererId(4)); |
| fill_data.push_back(autofill::FormFieldData::FillData(field)); |
| field.set_label(u"Unknown field"); |
| field.set_name(u"unknown"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"unknown"); |
| field.set_value(u""); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(5)); |
| fill_data.push_back(autofill::FormFieldData::FillData(field)); |
| |
| [autofill_agent_ fillData:fill_data |
| section:Section() |
| inFrame:fake_web_frames_manager_->GetMainWebFrame()]; |
| fake_web_state_.WasShown(); |
| |
| EXPECT_EQ(u"__gCrWeb.callFunctionInGcrWeb('autofill', 'fillForm', " |
| u"[{\"fields\":{\"2\":{\"hostFormId\":0,\"section\":\"-default\"," |
| u"\"value\":\"number_value\"},\"3\":{\"hostFormId\":0,\"section\":" |
| u"\"-default\",\"value\":\"name_value\"}}}, 0]);", |
| fake_main_frame_->GetLastJavaScriptCall()); |
| } |
| |
| // Tests that `fillSpecificFormField` in `autofill_agent_` dispatches the |
| // correct javascript call to the autofill controller. |
| TEST_F(AutofillAgentTests, FillSpecificFormField) { |
| autofill::FormFieldData field; |
| field.set_form_control_type(autofill::FormControlType::kInputText); |
| field.set_label(u"Card number"); |
| field.set_name(u"number"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"number"); |
| field.set_value(u"number_value"); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(2)); |
| |
| [autofill_agent_ |
| fillSpecificFormField:field.renderer_id() |
| withValue:u"mattwashere" |
| inFrame:fake_web_frames_manager_->GetMainWebFrame()]; |
| fake_web_state_.WasShown(); |
| EXPECT_EQ( |
| u"__gCrWeb.callFunctionInGcrWeb('autofill', 'fillSpecificFormField', " |
| u"[{\"renderer_id\":2,\"value\":\"mattwashere\"}]);", |
| fake_main_frame_->GetLastJavaScriptCall()); |
| } |
| |
| // Test that the updates are applied when filling specific form field is done |
| // successfully. |
| TEST_F(AutofillAgentTests, |
| FillSpecificFormField_UpdateWithResults_WhenSuccess) { |
| std::vector<autofill::FormFieldData::FillData> fields = |
| MinimalFormFieldDataForFilling(); |
| const std::u16string& field_value = fields[0].value; |
| const FieldRendererId field_id = fields[0].renderer_id; |
| |
| // Set the result returned from filling. |
| base::Value result(true); |
| fake_main_frame_->AddJsResultForFunctionCall( |
| &result, "autofill.fillSpecificFormField"); |
| |
| EXPECT_CALL(delegate_mock_, DidFillField(fake_main_frame_.get(), |
| std::optional<FormRendererId>(), |
| field_id, field_value)); |
| |
| // Declare the page as shown to allow filling. |
| fake_web_state_.WasShown(); |
| |
| // Fill form data. |
| [autofill_agent_ fillSpecificFormField:field_id |
| withValue:field_value |
| inFrame:fake_main_frame_]; |
| |
| // Run queues to yield the filling results. |
| web::test::WaitForBackgroundTasks(); |
| |
| // Check that the field value update was propagated to the FieldDataManager of |
| // the web frame. |
| FieldDataManager* fieldDataManager = |
| autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_); |
| EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field_id)); |
| } |
| |
| // Test that the updates aren't applied when filling specific form field has |
| // failed. |
| TEST_F(AutofillAgentTests, |
| FillSpecificFormField_UpdateWithResults_WhenFailure) { |
| std::vector<autofill::FormFieldData::FillData> fields = |
| MinimalFormFieldDataForFilling(); |
| const std::u16string& field_value = fields[0].value; |
| const FieldRendererId field_id = fields[0].renderer_id; |
| |
| // Set the result returned from filling. |
| base::Value result(false); |
| fake_main_frame_->AddJsResultForFunctionCall( |
| &result, "autofill.fillSpecificFormField"); |
| |
| EXPECT_CALL(delegate_mock_, DidFillField).Times(0); |
| |
| // Declare the page as shown to allow filling. |
| fake_web_state_.WasShown(); |
| |
| // Fill form data. |
| [autofill_agent_ fillSpecificFormField:field_id |
| withValue:field_value |
| inFrame:fake_main_frame_]; |
| |
| // Run queues to yield the filling results. |
| web::test::WaitForBackgroundTasks(); |
| |
| // Check that the field value update was not propagated to the |
| // FieldDataManager. |
| FieldDataManager* fieldDataManager = |
| autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_); |
| EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field_id)); |
| } |
| |
| // Tests that `ApplyFieldAction` in `AutofillDriverIOS` dispatches the |
| // correct javascript call to the autofill controller. |
| TEST_F(AutofillAgentTests, DriverFillSpecificFormField) { |
| autofill::FormFieldData field; |
| field.set_form_control_type(autofill::FormControlType::kInputText); |
| field.set_label(u"Card number"); |
| field.set_name(u"number"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"number"); |
| field.set_value(u"number_value"); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(2)); |
| |
| AutofillDriverIOS* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame( |
| &fake_web_state_, fake_web_frames_manager_->GetMainWebFrame()); |
| field.set_host_frame(main_frame_driver->GetFrameToken()); |
| |
| autofill::FormData form; |
| form.set_host_frame(main_frame_driver->GetFrameToken()); |
| form.set_renderer_id(autofill::FormRendererId(1)); |
| field.set_host_form_id(form.renderer_id()); |
| form.set_fields({field}); |
| main_frame_driver->FormsSeen({form}, {}); |
| |
| main_frame_driver->ApplyFieldAction( |
| autofill::mojom::FieldActionType::kReplaceAll, |
| autofill::mojom::ActionPersistence::kFill, field.global_id(), |
| u"mattwashere"); |
| |
| fake_web_state_.WasShown(); |
| EXPECT_EQ( |
| u"__gCrWeb.callFunctionInGcrWeb('autofill', 'fillSpecificFormField', " |
| u"[{\"renderer_id\":2,\"value\":\"mattwashere\"}]);", |
| fake_main_frame_->GetLastJavaScriptCall()); |
| } |
| |
| // Tests that `ApplyFieldAction` with `ActionPersistence::kPreview`in |
| // `AutofillDriverIOS` does not dispatch a JS call. |
| TEST_F(AutofillAgentTests, DriverPreviewSpecificFormField) { |
| autofill::FormFieldData field; |
| field.set_form_control_type(autofill::FormControlType::kInputText); |
| field.set_label(u"Card number"); |
| field.set_name(u"number"); |
| field.set_name_attribute(field.name()); |
| field.set_id_attribute(u"number"); |
| field.set_value(u"number_value"); |
| field.set_is_autofilled(true); |
| field.set_renderer_id(FieldRendererId(2)); |
| |
| AutofillDriverIOS* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame( |
| &fake_web_state_, fake_web_frames_manager_->GetMainWebFrame()); |
| field.set_host_frame(main_frame_driver->GetFrameToken()); |
| |
| autofill::FormData form; |
| form.set_host_frame(main_frame_driver->GetFrameToken()); |
| form.set_renderer_id(autofill::FormRendererId(1)); |
| field.set_host_form_id(form.renderer_id()); |
| form.set_fields({field}); |
| main_frame_driver->FormsSeen({form}, {}); |
| |
| // Preview is not currently supported; no JS should be run. |
| main_frame_driver->ApplyFieldAction( |
| autofill::mojom::FieldActionType::kReplaceAll, |
| autofill::mojom::ActionPersistence::kPreview, field.global_id(), |
| u"mattwashere"); |
| |
| fake_web_state_.WasShown(); |
| EXPECT_EQ(u"", fake_main_frame_->GetLastJavaScriptCall()); |
| } |
| |
| // Tests that when a non user initiated form activity is registered the |
| // completion callback passed to the call to check if suggestions are available |
| // is invoked with no suggestions. |
| TEST_F(AutofillAgentTests, |
| CheckIfSuggestionsAvailable_NonUserInitiatedActivity) { |
| __block BOOL completion_handler_success = NO; |
| __block BOOL completion_handler_called = NO; |
| |
| FormSuggestionProviderQuery* form_query = [[FormSuggestionProviderQuery alloc] |
| initWithFormName:@"form" |
| formRendererID:FormRendererId(1) |
| fieldIdentifier:@"address" |
| fieldRendererID:FieldRendererId(2) |
| fieldType:@"text" |
| type:@"focus" |
| typedValue:@"" |
| frameID:base::SysUTF8ToNSString(kTestFrameId) |
| onlyPassword:NO]; |
| [autofill_agent_ checkIfSuggestionsAvailableForForm:form_query |
| hasUserGesture:NO |
| webState:&fake_web_state_ |
| completionHandler:^(BOOL success) { |
| completion_handler_success = success; |
| completion_handler_called = YES; |
| }]; |
| fake_web_state_.WasShown(); |
| |
| // Wait until the expected handler is called. |
| ASSERT_TRUE( |
| WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() { |
| return completion_handler_called; |
| })); |
| EXPECT_FALSE(completion_handler_success); |
| } |
| |
| // Tests that virtual cards are being served as suggestions with the |
| // wanted string values of (main_text, ' ', minor_text) where the main_text |
| // is the 'Virtual card' string and the minor_text is the card name + last 4 or |
| // the card holder's name |
| TEST_F(AutofillAgentTests, showAutofillPopup_ShowVirtualCards) { |
| __block NSUInteger suggestion_array_size = 0; |
| __block FormSuggestion* virtual_card_suggestion = nil; |
| __block FormSuggestion* credit_card_suggestion = nil; |
| UIImage* visa_icon = |
| ui::ResourceBundle::GetSharedInstance() |
| .GetNativeImageNamed(autofill::CreditCard::IconResourceId("visaCC")) |
| .ToUIImage(); |
| NSString* expiration_date_display_description = base::SysUTF8ToNSString( |
| autofill::test::NextMonth() + "/" + autofill::test::NextYear().substr(2)); |
| // Mock different popup types. |
| testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate; |
| EXPECT_CALL(mock_delegate, GetMainFillingProduct) |
| .WillOnce(testing::Return(FillingProduct::kCreditCard)) |
| .WillOnce(testing::Return(FillingProduct::kCreditCard)); |
| |
| const std::string expiration_date_label = base::StrCat( |
| {autofill::test::NextMonth(), "/", autofill::test::NextYear().substr(2)}); |
| |
| // Initialize suggestion. |
| std::vector<std::string> minor_texts = {"Quicksilver ••1111"}; |
| std::vector<autofill::Suggestion> autofillSuggestions = { |
| autofill::Suggestion("Virtual card", minor_texts, expiration_date_label, |
| autofill::Suggestion::Icon::kCardVisa, |
| autofill::SuggestionType::kVirtualCreditCardEntry), |
| autofill::Suggestion("Quicksilver ••1111", expiration_date_label, |
| autofill::Suggestion::Icon::kCardVisa, |
| autofill::SuggestionType::kCreditCardEntry), |
| }; |
| |
| // Completion handler to retrieve suggestions. |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| suggestion_array_size = suggestions.count; |
| virtual_card_suggestion = [FormSuggestion |
| suggestionWithValue:[suggestions[0].value copy] |
| minorValue:[suggestions[0].minorValue copy] |
| displayDescription:[suggestions[0].displayDescription copy] |
| icon:[suggestions[0].icon copy] |
| type:suggestions[0].type |
| payload:suggestions[0].payload |
| fieldByFieldFillingTypeUsed:autofill::EMPTY_TYPE |
| requiresReauth:suggestions[0].requiresReauth |
| acceptanceA11yAnnouncement:[suggestions[0] |
| .acceptanceA11yAnnouncement copy]]; |
| credit_card_suggestion = [FormSuggestion |
| suggestionWithValue:[suggestions[1].value copy] |
| minorValue:[suggestions[1].minorValue copy] |
| displayDescription:[suggestions[1].displayDescription copy] |
| icon:[suggestions[1].icon copy] |
| type:suggestions[1].type |
| payload:suggestions[1].payload |
| fieldByFieldFillingTypeUsed:autofill::EMPTY_TYPE |
| requiresReauth:suggestions[1].requiresReauth |
| acceptanceA11yAnnouncement:[suggestions[1] |
| .acceptanceA11yAnnouncement copy]]; |
| }; |
| |
| // Make credit card suggestion. |
| [autofill_agent_ showAutofillPopup:autofillSuggestions |
| suggestionDelegate:mock_delegate.GetWeakPtr()]; |
| [autofill_agent_ retrieveSuggestionsForForm:nil |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| |
| // Confirm both suggestions present |
| ASSERT_EQ(2U, suggestion_array_size); |
| |
| // Confirm virtual card suggestion properties |
| EXPECT_NSEQ(@"Virtual card", virtual_card_suggestion.value); |
| EXPECT_NSEQ(@"Quicksilver ••1111", virtual_card_suggestion.minorValue); |
| EXPECT_NSEQ(expiration_date_display_description, |
| virtual_card_suggestion.displayDescription); |
| EXPECT_TRUE( |
| gfx::test::PlatformImagesEqual(virtual_card_suggestion.icon, visa_icon)); |
| EXPECT_EQ(autofill::SuggestionType::kVirtualCreditCardEntry, |
| virtual_card_suggestion.type); |
| EXPECT_EQ(autofill::Suggestion::Payload(), virtual_card_suggestion.payload); |
| EXPECT_EQ(false, virtual_card_suggestion.requiresReauth); |
| EXPECT_NSEQ(nil, virtual_card_suggestion.acceptanceA11yAnnouncement); |
| |
| // Confirm credit card suggestion properties |
| EXPECT_NSEQ(@"Quicksilver ••1111", credit_card_suggestion.value); |
| EXPECT_NSEQ(nil, credit_card_suggestion.minorValue); |
| EXPECT_NSEQ(expiration_date_display_description, |
| credit_card_suggestion.displayDescription); |
| EXPECT_TRUE( |
| gfx::test::PlatformImagesEqual(credit_card_suggestion.icon, visa_icon)); |
| EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry, |
| credit_card_suggestion.type); |
| EXPECT_EQ(autofill::Suggestion::Payload(), credit_card_suggestion.payload); |
| EXPECT_EQ(false, credit_card_suggestion.requiresReauth); |
| EXPECT_NSEQ(nil, credit_card_suggestion.acceptanceA11yAnnouncement); |
| } |
| |
| // Tests that an empty network icon in a credit card suggestion will not cause |
| // any problems. Regression test for crbug.com/1446933 |
| TEST_F(AutofillAgentTests, showAutofillPopup_EmptyIconInCreditCardSuggestion) { |
| // Deliberately initialize this as non-nil, as we are expecting it to be set |
| // to nil by the test. |
| __block UIImage* completion_handler_icon = gfx::test::CreatePlatformImage(); |
| ASSERT_NE(nil, completion_handler_icon); |
| |
| testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate; |
| EXPECT_CALL(mock_delegate, GetMainFillingProduct) |
| .WillRepeatedly(testing::Return(FillingProduct::kCreditCard)); |
| |
| std::vector<autofill::Suggestion> autofillSuggestions = { |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kCreditCardEntry)}; |
| |
| // Completion handler to retrieve suggestions. |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| completion_handler_icon = [suggestions[0].icon copy]; |
| }; |
| |
| // Make credit card suggestion. |
| [autofill_agent_ showAutofillPopup:autofillSuggestions |
| suggestionDelegate:mock_delegate.GetWeakPtr()]; |
| [autofill_agent_ retrieveSuggestionsForForm:nil |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| EXPECT_EQ(nil, completion_handler_icon); |
| } |
| |
| // Verify that plus address suggestions are handled appropriately in |
| // `showAutofillPopup`. |
| TEST_F(AutofillAgentTests, showAutofillPopup_PlusAddresses) { |
| __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil; |
| __block BOOL completion_handler_called = NO; |
| testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate; |
| |
| const std::string createSuggestionText = "create"; |
| const std::string fillExistingSuggestionText = "existing"; |
| std::vector<autofill::Suggestion> autofillSuggestions = { |
| autofill::Suggestion(createSuggestionText, "", |
| autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kCreateNewPlusAddress), |
| autofill::Suggestion(fillExistingSuggestionText, "", |
| autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kFillExistingPlusAddress)}; |
| |
| // Completion handler to retrieve suggestions. |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| completion_handler_suggestions = [suggestions copy]; |
| completion_handler_called = YES; |
| }; |
| |
| // Make plus address suggestions and note the conversion to `FormSuggestion` |
| // objects. |
| [autofill_agent_ showAutofillPopup:autofillSuggestions |
| suggestionDelegate:mock_delegate.GetWeakPtr()]; |
| [autofill_agent_ retrieveSuggestionsForForm:nil |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| |
| // Wait until the expected handler is called. |
| ASSERT_TRUE( |
| WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() { |
| return completion_handler_called; |
| })); |
| |
| // The plus address suggestions should be handled by the conversion to |
| // `FormSuggestion` objects. |
| EXPECT_EQ(2U, completion_handler_suggestions.count); |
| EXPECT_EQ(SuggestionType::kCreateNewPlusAddress, |
| completion_handler_suggestions[0].type); |
| EXPECT_NSEQ(base::SysUTF8ToNSString(createSuggestionText), |
| completion_handler_suggestions[0].value); |
| EXPECT_EQ(autofill::SuggestionType::kFillExistingPlusAddress, |
| completion_handler_suggestions[1].type); |
| EXPECT_NSEQ(base::SysUTF8ToNSString(fillExistingSuggestionText), |
| completion_handler_suggestions[1].value); |
| } |
| |
| // Tests that for credit cards, a custom icon is preferred over the default |
| // icon. |
| TEST_F(AutofillAgentTests, |
| showAutofillPopup_PreferCustomIconForCreditCardSuggestions) { |
| autofill::Suggestion::Icon suggestion_network_icon = |
| autofill::Suggestion::Icon::kCardVisa; |
| UIImage* network_icon_image = |
| ui::ResourceBundle::GetSharedInstance() |
| .GetNativeImageNamed( |
| autofill::CreditCard::IconResourceId(suggestion_network_icon)) |
| .ToUIImage(); |
| gfx::Image custom_icon = gfx::test::CreateImage(5, 5); |
| |
| testing::NiceMock<autofill::MockAutofillSuggestionDelegate> mock_delegate; |
| EXPECT_CALL(mock_delegate, GetMainFillingProduct) |
| .WillRepeatedly(testing::Return(FillingProduct::kCreditCard)); |
| |
| // Completion handler to retrieve suggestions. |
| __block UIImage* completion_handler_icon = nil; |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| completion_handler_icon = [suggestions[0].icon copy]; |
| }; |
| |
| // Initialize suggestion, initially without a custom icon. |
| std::vector<autofill::Suggestion> autofillSuggestions = { |
| autofill::Suggestion("", "", suggestion_network_icon, |
| autofill::SuggestionType::kCreditCardEntry)}; |
| ASSERT_TRUE( |
| std::holds_alternative<gfx::Image>(autofillSuggestions[0].custom_icon)); |
| ASSERT_TRUE( |
| std::get<gfx::Image>(autofillSuggestions[0].custom_icon).IsEmpty()); |
| |
| // When the custom icon is not present, the default icon should be used. |
| [autofill_agent_ showAutofillPopup:autofillSuggestions |
| suggestionDelegate:mock_delegate.GetWeakPtr()]; |
| [autofill_agent_ retrieveSuggestionsForForm:nil |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| EXPECT_TRUE(gfx::test::PlatformImagesEqual(completion_handler_icon, |
| network_icon_image)); |
| |
| // Now set a custom icon, which should override the default. |
| autofillSuggestions[0].custom_icon = custom_icon; |
| [autofill_agent_ showAutofillPopup:autofillSuggestions |
| suggestionDelegate:mock_delegate.GetWeakPtr()]; |
| [autofill_agent_ retrieveSuggestionsForForm:nil |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| EXPECT_TRUE(gfx::test::PlatformImagesEqual(completion_handler_icon, |
| custom_icon.ToUIImage())); |
| } |
| |
| // Tests that when Autofill suggestions are made available to AutofillAgent |
| // "Clear Form" is moved to the start of the list and the order of other |
| // suggestions remains unchanged. |
| TEST_F(AutofillAgentTests, onSuggestionsReady_ClearForm) { |
| __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil; |
| __block BOOL completion_handler_called = NO; |
| |
| // Make the suggestions available to AutofillAgent. |
| std::vector<autofill::Suggestion> autofillSuggestions; |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kAddressEntry)); |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kAddressEntry)); |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| SuggestionType::kUndoOrClear)); |
| [autofill_agent_ |
| showAutofillPopup:autofillSuggestions |
| suggestionDelegate:base::WeakPtr<autofill::AutofillSuggestionDelegate>()]; |
| |
| // Retrieves the suggestions. |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| completion_handler_suggestions = [suggestions copy]; |
| completion_handler_called = YES; |
| }; |
| FormSuggestionProviderQuery* form_query = [[FormSuggestionProviderQuery alloc] |
| initWithFormName:@"form" |
| formRendererID:FormRendererId(1) |
| fieldIdentifier:@"address" |
| fieldRendererID:FieldRendererId(2) |
| fieldType:@"text" |
| type:@"focus" |
| typedValue:@"" |
| frameID:base::SysUTF8ToNSString(kTestFrameId) |
| onlyPassword:NO]; |
| [autofill_agent_ retrieveSuggestionsForForm:form_query |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| fake_web_state_.WasShown(); |
| |
| // Wait until the expected handler is called. |
| ASSERT_TRUE( |
| WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() { |
| return completion_handler_called; |
| })); |
| |
| // "Clear Form" should appear as the first suggestion. Otherwise, the order of |
| // suggestions should not change. |
| EXPECT_EQ(3U, completion_handler_suggestions.count); |
| EXPECT_EQ(SuggestionType::kUndoOrClear, |
| completion_handler_suggestions[0].type); |
| EXPECT_EQ(autofill::SuggestionType::kAddressEntry, |
| completion_handler_suggestions[1].type); |
| EXPECT_EQ(autofill::SuggestionType::kAddressEntry, |
| completion_handler_suggestions[2].type); |
| } |
| |
| // Tests that when Autofill suggestions are made available to AutofillAgent |
| // GPay icon remains as the first suggestion. |
| TEST_F(AutofillAgentTests, onSuggestionsReady_ClearFormWithGPay) { |
| __block NSArray<FormSuggestion*>* completion_handler_suggestions = nil; |
| __block BOOL completion_handler_called = NO; |
| |
| // Make the suggestions available to AutofillAgent. |
| std::vector<autofill::Suggestion> autofillSuggestions; |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kCreditCardEntry)); |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| autofill::SuggestionType::kCreditCardEntry)); |
| autofillSuggestions.push_back( |
| autofill::Suggestion("", "", autofill::Suggestion::Icon::kNoIcon, |
| SuggestionType::kUndoOrClear)); |
| [autofill_agent_ |
| showAutofillPopup:autofillSuggestions |
| suggestionDelegate:base::WeakPtr<autofill::AutofillSuggestionDelegate>()]; |
| |
| // Retrieves the suggestions. |
| auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions, |
| id<FormSuggestionProvider> delegate) { |
| completion_handler_suggestions = [suggestions copy]; |
| completion_handler_called = YES; |
| }; |
| FormSuggestionProviderQuery* form_query = [[FormSuggestionProviderQuery alloc] |
| initWithFormName:@"form" |
| formRendererID:FormRendererId(1) |
| fieldIdentifier:@"address" |
| fieldRendererID:FieldRendererId(2) |
| fieldType:@"text" |
| type:@"focus" |
| typedValue:@"" |
| frameID:base::SysUTF8ToNSString(kTestFrameId) |
| onlyPassword:NO]; |
| [autofill_agent_ retrieveSuggestionsForForm:form_query |
| webState:&fake_web_state_ |
| completionHandler:completionHandler]; |
| fake_web_state_.WasShown(); |
| |
| // Wait until the expected handler is called. |
| ASSERT_TRUE( |
| WaitUntilConditionOrTimeout(TestTimeouts::action_timeout(), ^bool() { |
| return completion_handler_called; |
| })); |
| |
| EXPECT_EQ(3U, completion_handler_suggestions.count); |
| EXPECT_EQ(SuggestionType::kUndoOrClear, |
| completion_handler_suggestions[0].type); |
| EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry, |
| completion_handler_suggestions[1].type); |
| EXPECT_EQ(autofill::SuggestionType::kCreditCardEntry, |
| completion_handler_suggestions[2].type); |
| } |
| |
| // Test that every frames are processed whatever is the order of pageloading |
| // callbacks. The main frame should always be processed first. |
| class AutofillAgentTestFrameInitializationOrderFrames |
| : public AutofillAgentTests { |
| public: |
| // If we do pass `autofill_agent_` to `client_` (which would then pass it on |
| // to the AutofillDriverIOS objects), then the test fixture crashes during |
| // destruction. |
| // |
| // TODO(crbug.com/40100455): Understand what happens at destruction and fix |
| // this. Then eliminate should_set_autofill_driver_ios_bridge(). |
| bool should_set_autofill_driver_ios_bridge() const override { return false; } |
| |
| void SetUp() override { |
| AutofillAgentTests::SetUp(); |
| RemoveWebFrame(fake_main_frame_->GetFrameId()); |
| } |
| }; |
| |
| // Both frames available, then page loaded. |
| TEST_F(AutofillAgentTestFrameInitializationOrderFrames, |
| BothFramesAvailableThenPageLoaded) { |
| fake_web_state_.SetLoading(true); |
| std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame(); |
| web::FakeWebFrame* main_frame = main_frame_unique.get(); |
| AddWebFrame(std::move(main_frame_unique)); |
| AutofillDriverIOS* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame); |
| auto iframe_unique = CreateChildWebFrame(); |
| iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{ |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| })); |
| web::FakeWebFrame* iframe = iframe_unique.get(); |
| AddWebFrame(std::move(iframe_unique)); |
| AutofillDriverIOS* iframe_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe); |
| EXPECT_FALSE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| fake_web_state_.SetLoading(false); |
| fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_TRUE(iframe_driver->is_processed()); |
| RemoveWebFrame(main_frame->GetFrameId()); |
| RemoveWebFrame(iframe->GetFrameId()); |
| } |
| |
| // Main frame available, then page loaded, then iframe available. |
| TEST_F(AutofillAgentTestFrameInitializationOrderFrames, |
| MainFrameAvailableThenPageLoadedThenIframeAvailable) { |
| std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame(); |
| web::FakeWebFrame* main_frame = main_frame_unique.get(); |
| auto* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame); |
| std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame(); |
| iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{ |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| })); |
| web::FakeWebFrame* iframe = iframe_unique.get(); |
| auto* iframe_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe); |
| fake_web_state_.SetLoading(true); |
| AddWebFrame(std::move(main_frame_unique)); |
| EXPECT_FALSE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| fake_web_state_.SetLoading(false); |
| fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| AddWebFrame(std::move(iframe_unique)); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_TRUE(iframe_driver->is_processed()); |
| RemoveWebFrame(main_frame->GetFrameId()); |
| RemoveWebFrame(iframe->GetFrameId()); |
| } |
| |
| // Page loaded, then main frame, then iframe. |
| TEST_F(AutofillAgentTestFrameInitializationOrderFrames, |
| PageLoadedThenMainFrameThenIframe) { |
| std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame(); |
| web::FakeWebFrame* main_frame = main_frame_unique.get(); |
| auto* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame); |
| std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame(); |
| iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{ |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| })); |
| web::FakeWebFrame* iframe = iframe_unique.get(); |
| auto* iframe_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe); |
| fake_web_state_.SetLoading(true); |
| fake_web_state_.SetLoading(false); |
| fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS); |
| EXPECT_FALSE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| AddWebFrame(std::move(main_frame_unique)); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| AddWebFrame(std::move(iframe_unique)); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_TRUE(iframe_driver->is_processed()); |
| RemoveWebFrame(main_frame->GetFrameId()); |
| RemoveWebFrame(iframe->GetFrameId()); |
| } |
| |
| // Page loaded, then iframe, then main frame. |
| TEST_F(AutofillAgentTestFrameInitializationOrderFrames, |
| PageLoadedThenIframeThenMainFrame) { |
| std::unique_ptr<web::FakeWebFrame> main_frame_unique = CreateMainWebFrame(); |
| web::FakeWebFrame* main_frame = main_frame_unique.get(); |
| auto* main_frame_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, main_frame); |
| std::unique_ptr<web::FakeWebFrame> iframe_unique = CreateChildWebFrame(); |
| iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{ |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| })); |
| web::FakeWebFrame* iframe = iframe_unique.get(); |
| auto* iframe_driver = |
| AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_, iframe); |
| fake_web_state_.SetLoading(true); |
| fake_web_state_.SetLoading(false); |
| fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS); |
| EXPECT_FALSE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| AddWebFrame(std::move(iframe_unique)); |
| EXPECT_FALSE(main_frame_driver->is_processed()); |
| EXPECT_FALSE(iframe_driver->is_processed()); |
| AddWebFrame(std::move(main_frame_unique)); |
| EXPECT_TRUE(main_frame_driver->is_processed()); |
| EXPECT_TRUE(iframe_driver->is_processed()); |
| RemoveWebFrame(main_frame->GetFrameId()); |
| RemoveWebFrame(iframe->GetFrameId()); |
| } |
| |
| TEST_F(AutofillAgentTests, FillData_UpdateWithResults) { |
| auto test_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| |
| std::vector<autofill::FormFieldData::FillData> fields = |
| MinimalFormFieldDataForFilling(); |
| const std::u16string& field_value = fields[0].value; |
| const FormRendererId form_id = fields[0].host_form_id; |
| const FieldRendererId field_id = fields[0].renderer_id; |
| |
| // Set the result returned from filling. |
| std::string serializedResult; |
| ASSERT_TRUE(base::JSONWriter::Write( |
| base::Value::Dict().Set(base::NumberToString(field_id.value()), |
| base::UTF16ToUTF8(field_value)), |
| &serializedResult)); |
| base::Value result(serializedResult); |
| fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm"); |
| |
| EXPECT_CALL(delegate_mock_, |
| DidFillField(fake_main_frame_.get(), |
| std::make_optional<FormRendererId>(form_id), |
| field_id, field_value)); |
| |
| // Declare the page as shown to allow filling. |
| fake_web_state_.WasShown(); |
| |
| // Fill form data. |
| [autofill_agent_ fillData:fields section:Section() inFrame:fake_main_frame_]; |
| |
| // Run queues to yield the filling results. |
| web::test::WaitForBackgroundTasks(); |
| |
| // Check that the field value update was propagated to the FieldDataManager of |
| // the web frame. |
| FieldDataManager* fieldDataManager = |
| autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_); |
| EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field_id)); |
| |
| // Check recorded UKM. |
| auto entries = test_recorder->GetEntriesByName( |
| ukm::builders::Autofill_FormFillSuccessIOS::kEntryName); |
| // Expect one recorded metric. |
| ASSERT_EQ(1u, entries.size()); |
| test_recorder->ExpectEntryMetric(entries[0], "FormFillSuccess", true); |
| } |
| |
| // Tests that if there is an unknown field id in the results, the agent isn't |
| // notified. |
| TEST_F(AutofillAgentTests, FillData_UnknowFieldIdInResults) { |
| std::vector<autofill::FormFieldData::FillData> fields = |
| MinimalFormFieldDataForFilling(); |
| const FieldRendererId unknown_field_id = FieldRendererId(101); |
| |
| // Set the result returned from filling. |
| std::string serializedResult; |
| ASSERT_TRUE(base::JSONWriter::Write( |
| base::Value::Dict().Set(base::NumberToString(unknown_field_id.value()), |
| base::UTF16ToUTF8(fields[0].value)), |
| &serializedResult)); |
| base::Value result(serializedResult); |
| fake_main_frame_->AddJsResultForFunctionCall(&result, "autofill.fillForm"); |
| |
| EXPECT_CALL(delegate_mock_, DidFillField).Times(0); |
| |
| // Declare the page as shown to allow filling. |
| fake_web_state_.WasShown(); |
| |
| // Fill form data. |
| [autofill_agent_ fillData:fields section:Section() inFrame:fake_main_frame_]; |
| |
| // Run queues to yield the filling results. |
| web::test::WaitForBackgroundTasks(); |
| } |
| |
| // Tests selecting an autocomplete suggestion. |
| TEST_F(AutofillAgentTests, DidSelectSuggestion_AutocompleteEntry) { |
| FormRendererId form_id(1); |
| FieldRendererId field1_id(2); |
| const std::u16string field1_value = u"test-value"; |
| |
| // Set the result returned from filling as a success. |
| base::Value result(true); |
| fake_main_frame_->AddJsResultForFunctionCall(&result, |
| "autofill.fillActiveFormField"); |
| |
| // Declare the page as shown to allow field filling. |
| fake_web_state_.WasShown(); |
| |
| // Select suggestion to trigger field filling. |
| __block BOOL completion_handler_called = NO; |
| FormSuggestion* form_suggestion = SimpleFormSuggestion( |
| field1_value, autofill::SuggestionType::kAutocompleteEntry); |
| [autofill_agent_ didSelectSuggestion:form_suggestion |
| atIndex:0 |
| form:@"single-username-form" |
| formRendererID:form_id |
| fieldIdentifier:@"username-field-1" |
| fieldRendererID:field1_id |
| frameID:base::SysUTF8ToNSString(kTestFrameId) |
| completionHandler:^() { |
| completion_handler_called = YES; |
| }]; |
| |
| EXPECT_CALL(delegate_mock_, |
| DidFillField(fake_main_frame_.get(), |
| std::make_optional<FormRendererId>(form_id), |
| field1_id, field1_value)); |
| |
| // Run queues to yield the field filling results from the JS call. |
| web::test::WaitForBackgroundTasks(); |
| |
| // Check that the field value update was propagated to the FieldDataManager of |
| // the web frame. |
| FieldDataManager* fieldDataManager = |
| autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_); |
| EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id)); |
| |
| // Check that the completion handler was called after handling the results |
| // from the JS call. |
| EXPECT_TRUE(completion_handler_called); |
| } |
| |
| TEST_F(AutofillAgentTests, DidSelectSuggestion_ClearFormEntry) { |
| FormRendererId form_id(1); |
| FieldRendererId field1_id(2); |
| FieldRendererId field2_id(3); |
| |
| // Set the result returned from filling. |
| std::string serializedResult; |
| ASSERT_TRUE(base::JSONWriter::Write( |
| base::Value::List() |
| .Append(base::Value(base::NumberToString(field1_id.value()))) |
| .Append(base::Value(base::NumberToString(field2_id.value()))), |
| &serializedResult)); |
| base::Value result(serializedResult); |
| fake_main_frame_->AddJsResultForFunctionCall( |
| &result, "autofill.clearAutofilledFields"); |
| |
| // Declare the page as shown to allow field filling. |
| fake_web_state_.WasShown(); |
| |
| // Select suggestion to trigger field filling. |
| __block BOOL completion_handler_called = NO; |
| FormSuggestion* form_suggestion = |
| SimpleFormSuggestion(u"", autofill::SuggestionType::kUndoOrClear); |
| [autofill_agent_ didSelectSuggestion:form_suggestion |
| atIndex:0 |
| form:@"single-username-form" |
| formRendererID:form_id |
| fieldIdentifier:@"username-field-1" |
| fieldRendererID:field1_id |
| frameID:base::SysUTF8ToNSString(kTestFrameId) |
| completionHandler:^() { |
| completion_handler_called = YES; |
| }]; |
| |
| EXPECT_CALL(delegate_mock_, |
| DidFillField(fake_main_frame_.get(), |
| std::make_optional<FormRendererId>(form_id), |
| field1_id, ::testing::IsEmpty())); |
| EXPECT_CALL(delegate_mock_, |
| DidFillField(fake_main_frame_.get(), |
| std::make_optional<FormRendererId>(form_id), |
| field2_id, ::testing::IsEmpty())); |
| |
| // Run queues to yield the field filling results from the JS call. |
| web::test::WaitForBackgroundTasks(); |
| |
| // Check that the cleared field IDs aren't labeled as filled. |
| FieldDataManager* fieldDataManager = |
| autofill::FieldDataManagerFactoryIOS::FromWebFrame(fake_main_frame_); |
| EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field1_id)); |
| EXPECT_FALSE(fieldDataManager->WasAutofilledOnUserTrigger(field2_id)); |
| |
| // Check that the completion handler was called after handling the results |
| // from the JS call. |
| EXPECT_TRUE(completion_handler_called); |
| } |