blob: 28f456fb374e42df83cf703b8981dcc77177d63a [file] [log] [blame]
// 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"
#include "base/mac/bundle_locations.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/ui/mock_autofill_popup_delegate.h"
#include "components/autofill/core/browser/ui/popup_item_ids.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/ios/browser/autofill_driver_ios.h"
#include "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
#include "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
#include "components/prefs/pref_service.h"
#include "ios/web/public/test/fakes/fake_browser_state.h"
#include "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"
#include "ios/web/public/test/web_test.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using autofill::AutofillJavaScriptFeature;
using autofill::FieldDataManager;
using autofill::FieldRendererId;
using autofill::FormRendererId;
using autofill::POPUP_ITEM_ID_CLEAR_FORM;
using autofill::POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS;
using autofill::PopupType;
using base::test::ios::WaitUntilCondition;
@interface AutofillAgent (Testing)
- (void)updateFieldManagerWithFillingResults:(NSString*)jsonString;
@end
// Test fixture for AutofillAgent testing.
class AutofillAgentTests : public web::WebTest {
public:
AutofillAgentTests() {}
AutofillAgentTests(const AutofillAgentTests&) = delete;
AutofillAgentTests& operator=(const AutofillAgentTests&) = delete;
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()});
fake_web_state_.SetBrowserState(GetBrowserState());
fake_web_state_.SetContentIsHTML(true);
auto frames_manager = std::make_unique<web::FakeWebFramesManager>();
fake_web_frames_manager_ = frames_manager.get();
web::ContentWorld content_world =
AutofillJavaScriptFeature::GetInstance()->GetSupportedContentWorld();
fake_web_state_.SetWebFramesManager(content_world,
std::move(frames_manager));
GURL url("https://example.com");
fake_web_state_.SetCurrentURL(url);
auto main_frame = web::FakeWebFrame::Create("frameID", true, url);
main_frame->set_browser_state(GetBrowserState());
fake_main_frame_ = main_frame.get();
AddWebFrame(std::move(main_frame));
prefs_ = autofill::test::PrefServiceForTesting();
autofill::prefs::SetAutofillProfileEnabled(prefs_.get(), true);
autofill::prefs::SetAutofillCreditCardEnabled(prefs_.get(), true);
UniqueIDDataTabHelper::CreateForWebState(&fake_web_state_);
autofill_agent_ =
[[AutofillAgent alloc] initWithPrefService:prefs_.get()
webState:&fake_web_state_];
}
std::unique_ptr<web::FakeWebFrame> CreateMainWebFrame() {
std::unique_ptr<web::FakeWebFrame> frame =
web::FakeWebFrame::CreateMainWebFrame(GURL());
frame->set_browser_state(GetBrowserState());
return frame;
}
std::unique_ptr<web::FakeWebFrame> CreateChildWebFrame() {
std::unique_ptr<web::FakeWebFrame> frame =
web::FakeWebFrame::CreateChildWebFrame(GURL());
frame->set_browser_state(GetBrowserState());
return frame;
}
// The client_ needs to outlive the fake_web_state_, which owns the
// frames.
autofill::TestAutofillClient client_;
web::FakeWebState fake_web_state_;
web::FakeWebFrame* fake_main_frame_ = nullptr;
web::FakeWebFramesManager* fake_web_frames_manager_ = nullptr;
std::unique_ptr<PrefService> prefs_;
AutofillAgent* autofill_agent_;
};
// 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::string locale("en");
autofill::AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_,
&client_, nil, locale);
autofill::FormData form;
form.url = GURL("https://myform.com");
form.action = GURL("https://myform.com/submit");
form.name = u"CC form";
form.unique_renderer_id = FormRendererId(1);
autofill::FormFieldData field;
field.form_control_type = "text";
field.label = u"Card number";
field.name = u"number";
field.name_attribute = field.name;
field.id_attribute = u"number";
field.value = u"number_value";
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(2);
form.fields.push_back(field);
field.label = u"Name on Card";
field.name = u"name";
field.name_attribute = field.name;
field.id_attribute = u"name";
field.value = u"name_value";
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(3);
form.fields.push_back(field);
field.label = u"Expiry Month";
field.name = u"expiry_month";
field.name_attribute = field.name;
field.id_attribute = u"expiry_month";
field.value = u"01";
field.is_autofilled = false;
field.unique_renderer_id = FieldRendererId(4);
form.fields.push_back(field);
field.label = u"Unknown field";
field.name = u"unknown";
field.name_attribute = field.name;
field.id_attribute = u"unknown";
field.value = u"";
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(5);
form.fields.push_back(field);
[autofill_agent_ fillFormData:form
inFrame:fake_web_frames_manager_->GetMainWebFrame()];
fake_web_state_.WasShown();
EXPECT_EQ(u"__gCrWeb.autofill.fillForm({\"fields\":{\"2\":{\"section\":\"-"
u"default\",\"value\":\"number_value\"},\"3\":{\"section\":\"-"
u"default\",\"value\":\"name_value\"}},\"formName\":\"CC "
u"form\",\"formRendererID\":1}, 0);",
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"
uniqueFormID:FormRendererId(1)
fieldIdentifier:@"address"
uniqueFieldID:FieldRendererId(2)
fieldType:@"text"
type:@"focus"
typedValue:@""
frameID:@"frameID"];
[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.
WaitUntilCondition(^bool() {
return completion_handler_called;
});
EXPECT_FALSE(completion_handler_success);
}
// Tests that "Show credit cards from account" opt-in is shown.
TEST_F(AutofillAgentTests, onSuggestionsReady_ShowAccountCards) {
__block NSArray<FormSuggestion*>* completion_handler_suggestions = nil;
__block BOOL completion_handler_called = NO;
autofill::MockAutofillPopupDelegate mock_delegate;
EXPECT_CALL(mock_delegate, GetPopupType);
EXPECT_CALL(mock_delegate, OnPopupShown);
// Make the suggestions available to AutofillAgent.
std::vector<autofill::Suggestion> autofillSuggestions;
autofillSuggestions.push_back(
autofill::Suggestion("", "", "", POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS));
[autofill_agent_ showAutofillPopup:autofillSuggestions
popupDelegate:mock_delegate.GetWeakPtr()];
// 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"
uniqueFormID:FormRendererId(1)
fieldIdentifier:@"address"
uniqueFieldID:FieldRendererId(2)
fieldType:@"text"
type:@"focus"
typedValue:@""
frameID:@"frameID"];
[autofill_agent_ retrieveSuggestionsForForm:form_query
webState:&fake_web_state_
completionHandler:completionHandler];
fake_web_state_.WasShown();
// Wait until the expected handler is called.
WaitUntilCondition(^bool() {
return completion_handler_called;
});
// "Show credit cards from account" should be the only suggestion.
EXPECT_EQ(1U, completion_handler_suggestions.count);
EXPECT_EQ(POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS,
completion_handler_suggestions[0].identifier);
}
// Tests that only credit card suggestions would have icons.
TEST_F(AutofillAgentTests,
showAutofillPopup_ShowIconForCreditCardSuggestionsOnly) {
__block NSString* completion_handler_icon_name = nil;
// Mock different popup types.
testing::NiceMock<autofill::MockAutofillPopupDelegate> mock_delegate;
EXPECT_CALL(mock_delegate, GetPopupType)
.WillOnce(testing::Return(PopupType::kCreditCards))
.WillOnce(testing::Return(PopupType::kAddresses))
.WillOnce(testing::Return(PopupType::kUnspecified));
// Initialize suggestion.
std::vector<autofill::Suggestion> autofillSuggestions = {
autofill::Suggestion("", "", "icon", POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS)};
// Completion handler to retrieve suggestions.
auto completionHandler = ^(NSArray<FormSuggestion*>* suggestions,
id<FormSuggestionProvider> delegate) {
completion_handler_icon_name = [suggestions[0].icon copy];
};
// Make credit card suggestion.
[autofill_agent_ showAutofillPopup:autofillSuggestions
popupDelegate:mock_delegate.GetWeakPtr()];
[autofill_agent_ retrieveSuggestionsForForm:nil
webState:&fake_web_state_
completionHandler:completionHandler];
EXPECT_NSEQ(@"icon", completion_handler_icon_name);
// Make address suggestion.
[autofill_agent_ showAutofillPopup:autofillSuggestions
popupDelegate:mock_delegate.GetWeakPtr()];
[autofill_agent_ retrieveSuggestionsForForm:nil
webState:&fake_web_state_
completionHandler:completionHandler];
EXPECT_EQ(nil, completion_handler_icon_name);
// Make unspecified suggestion.
[autofill_agent_ showAutofillPopup:autofillSuggestions
popupDelegate:mock_delegate.GetWeakPtr()];
[autofill_agent_ retrieveSuggestionsForForm:nil
webState:&fake_web_state_
completionHandler:completionHandler];
EXPECT_EQ(nil, completion_handler_icon_name);
}
// 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("", "", "", 123));
autofillSuggestions.push_back(autofill::Suggestion("", "", "", 321));
autofillSuggestions.push_back(
autofill::Suggestion("", "", "", POPUP_ITEM_ID_CLEAR_FORM));
[autofill_agent_
showAutofillPopup:autofillSuggestions
popupDelegate:base::WeakPtr<autofill::AutofillPopupDelegate>()];
// 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"
uniqueFormID:FormRendererId(1)
fieldIdentifier:@"address"
uniqueFieldID:FieldRendererId(2)
fieldType:@"text"
type:@"focus"
typedValue:@""
frameID:@"frameID"];
[autofill_agent_ retrieveSuggestionsForForm:form_query
webState:&fake_web_state_
completionHandler:completionHandler];
fake_web_state_.WasShown();
// Wait until the expected handler is called.
WaitUntilCondition(^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(POPUP_ITEM_ID_CLEAR_FORM,
completion_handler_suggestions[0].identifier);
EXPECT_EQ(123, completion_handler_suggestions[1].identifier);
EXPECT_EQ(321, completion_handler_suggestions[2].identifier);
}
// 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("", "", "", 123));
autofillSuggestions.push_back(autofill::Suggestion("", "", "", 321));
autofillSuggestions.push_back(
autofill::Suggestion("", "", "", POPUP_ITEM_ID_CLEAR_FORM));
[autofill_agent_
showAutofillPopup:autofillSuggestions
popupDelegate:base::WeakPtr<autofill::AutofillPopupDelegate>()];
// 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"
uniqueFormID:FormRendererId(1)
fieldIdentifier:@"address"
uniqueFieldID:FieldRendererId(2)
fieldType:@"text"
type:@"focus"
typedValue:@""
frameID:@"frameID"];
[autofill_agent_ retrieveSuggestionsForForm:form_query
webState:&fake_web_state_
completionHandler:completionHandler];
fake_web_state_.WasShown();
// Wait until the expected handler is called.
WaitUntilCondition(^bool() {
return completion_handler_called;
});
EXPECT_EQ(3U, completion_handler_suggestions.count);
EXPECT_EQ(POPUP_ITEM_ID_CLEAR_FORM,
completion_handler_suggestions[0].identifier);
EXPECT_EQ(123, completion_handler_suggestions[1].identifier);
EXPECT_EQ(321, completion_handler_suggestions[2].identifier);
}
// Test that every frames are processed whatever is the order of pageloading
// callbacks. The main frame should always be processed first.
TEST_F(AutofillAgentTests, FrameInitializationOrderFrames) {
std::string locale("en");
autofill::AutofillDriverIOSFactory::CreateForWebState(&fake_web_state_,
&client_, nil, locale);
// Remove the current main frame.
RemoveWebFrame(fake_main_frame_->GetFrameId());
// Both frames available, then page loaded.
fake_web_state_.SetLoading(true);
auto main_frame_unique = CreateMainWebFrame();
web::FakeWebFrame* main_frame = main_frame_unique.get();
AddWebFrame(std::move(main_frame_unique));
autofill::AutofillDriverIOS* main_frame_driver =
autofill::AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_,
main_frame);
EXPECT_TRUE(main_frame_driver->IsInAnyMainFrame());
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));
autofill::AutofillDriverIOS* iframe_driver =
autofill::AutofillDriverIOS::FromWebStateAndWebFrame(&fake_web_state_,
iframe);
EXPECT_FALSE(iframe_driver->IsInAnyMainFrame());
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
main_frame_unique = CreateMainWebFrame();
main_frame = main_frame_unique.get();
main_frame_driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
&fake_web_state_, main_frame);
iframe_unique = CreateChildWebFrame();
iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
EXPECT_TRUE(main_frame_driver->is_processed());
}));
iframe = iframe_unique.get();
iframe_driver = autofill::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
main_frame_unique = CreateMainWebFrame();
main_frame = main_frame_unique.get();
main_frame_driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
&fake_web_state_, main_frame);
iframe_unique = CreateChildWebFrame();
iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
EXPECT_TRUE(main_frame_driver->is_processed());
}));
iframe = iframe_unique.get();
iframe_driver = autofill::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
main_frame_unique = CreateMainWebFrame();
main_frame = main_frame_unique.get();
main_frame_driver = autofill::AutofillDriverIOS::FromWebStateAndWebFrame(
&fake_web_state_, main_frame);
iframe_unique = CreateChildWebFrame();
iframe_unique->set_call_java_script_function_callback(base::BindRepeating(^{
EXPECT_TRUE(main_frame_driver->is_processed());
}));
iframe = iframe_unique.get();
iframe_driver = autofill::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, UpdateFieldManagerWithFillingResults) {
auto test_recorder = std::make_unique<ukm::TestAutoSetUkmRecorder>();
[autofill_agent_ updateFieldManagerWithFillingResults:@"{\"2\":\"Val1\"}"];
// Check recorded FieldDataManager data.
UniqueIDDataTabHelper* uniqueIDDataTabHelper =
UniqueIDDataTabHelper::FromWebState(&fake_web_state_);
scoped_refptr<FieldDataManager> fieldDataManager =
uniqueIDDataTabHelper->GetFieldDataManager();
EXPECT_TRUE(fieldDataManager->WasAutofilledOnUserTrigger(FieldRendererId(2)));
// 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);
}