| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| #include <vector> |
| |
| #import <UIKit/UIKit.h> |
| |
| #include "base/guid.h" |
| #include "base/ios/ios_util.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/task_scheduler/task_scheduler.h" |
| #import "base/test/ios/wait_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "components/autofill/core/browser/autofill_manager.h" |
| #include "components/autofill/core/browser/autofill_metrics.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/personal_data_manager.h" |
| #import "components/autofill/ios/browser/autofill_agent.h" |
| #include "components/autofill/ios/browser/autofill_driver_ios.h" |
| #import "components/autofill/ios/browser/form_suggestion.h" |
| #import "components/autofill/ios/browser/js_suggestion_manager.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar.h" |
| #include "components/infobars/core/infobar_manager.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/security_state/ios/ssl_status_input_event_data.h" |
| #import "ios/chrome/browser/autofill/form_suggestion_controller.h" |
| #include "ios/chrome/browser/autofill/personal_data_manager_factory.h" |
| #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" |
| #include "ios/chrome/browser/infobars/infobar_manager_impl.h" |
| #include "ios/chrome/browser/ssl/ios_security_state_tab_helper.h" |
| #import "ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h" |
| #import "ios/chrome/browser/ui/autofill/form_input_accessory_mediator.h" |
| #include "ios/chrome/browser/ui/settings/personal_data_manager_data_changed_observer.h" |
| #include "ios/chrome/browser/web/chrome_web_client.h" |
| #import "ios/chrome/browser/web/chrome_web_test.h" |
| #include "ios/chrome/browser/web_data_service_factory.h" |
| #import "ios/web/public/navigation_item.h" |
| #import "ios/web/public/navigation_manager.h" |
| #include "ios/web/public/ssl_status.h" |
| #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| #include "ios/web/public/web_state/web_frame.h" |
| #include "ios/web/public/web_state/web_frame_util.h" |
| #import "ios/web/public/web_state/web_frames_manager.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #import "testing/gtest_mac.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| // Real FormSuggestionController is wrapped to register the addition of |
| // suggestions. |
| @interface TestSuggestionController : FormSuggestionController |
| |
| @property(nonatomic, copy) NSArray* suggestions; |
| @property(nonatomic, assign) BOOL suggestionRetrievalComplete; |
| |
| @end |
| |
| @implementation TestSuggestionController |
| |
| @synthesize suggestions = _suggestions; |
| @synthesize suggestionRetrievalComplete = _suggestionRetrievalComplete; |
| |
| - (void)retrieveSuggestionsForForm:(const autofill::FormActivityParams&)params |
| webState:(web::WebState*)webState { |
| self.suggestionRetrievalComplete = NO; |
| [super retrieveSuggestionsForForm:params webState:webState]; |
| } |
| |
| - (void)updateKeyboardWithSuggestions:(NSArray*)suggestions { |
| self.suggestions = suggestions; |
| self.suggestionRetrievalComplete = YES; |
| } |
| |
| - (void)onNoSuggestionsAvailable { |
| self.suggestionRetrievalComplete = YES; |
| }; |
| |
| @end |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // The profile-type form used by tests. |
| NSString* const kProfileFormHtml = |
| @"<form action='/submit' method='post'>" |
| "Name <input type='text' name='name'>" |
| "Address <input type='text' name='address'>" |
| "City <input type='text' name='city'>" |
| "State <input type='text' name='state'>" |
| "Zip <input type='text' name='zip'>" |
| "<input type='submit' id='submit' value='Submit'>" |
| "</form>"; |
| |
| // A minimal form with a name. |
| NSString* const kMinimalFormWithNameHtml = @"<form id='form1'>" |
| "<input name='name'>" |
| "<input name='address'>" |
| "<input name='city'>" |
| "</form>"; |
| |
| // The key/value-type form used by tests. |
| NSString* const kKeyValueFormHtml = |
| @"<form action='/submit' method='post'>" |
| "Greeting <input type='text' name='greeting'>" |
| "Dummy field <input type='text' name='dummy'>" |
| "<input type='submit' id='submit' value='Submit'>" |
| "</form>"; |
| |
| // The credit card-type form used by tests. |
| NSString* const kCreditCardFormHtml = |
| @"<form action='/submit' method='post'>" |
| "Name on card: <input type='text' name='name'>" |
| "Credit card number: <input type='text' name='CCNo'>" |
| "Expiry Month: <input type='text' name='CCExpiresMonth'>" |
| "Expiry Year: <input type='text' name='CCExpiresYear'>" |
| "<input type='submit' id='submit' value='Submit'>" |
| "</form>"; |
| |
| // An HTML page without a card-type form. |
| static NSString* kNoCreditCardFormHtml = |
| @"<form><input type=\"text\" autofocus autocomplete=\"username\"></form>"; |
| |
| // A credit card-type form with the autofocus attribute (which is detected at |
| // page load). |
| NSString* const kCreditCardAutofocusFormHtml = |
| @"<form><input type=\"text\" autofocus autocomplete=\"cc-number\"></form>"; |
| |
| // FAIL if a field with the supplied |name| and |fieldType| is not present on |
| // the |form|. |
| void CheckField(const FormStructure& form, |
| ServerFieldType fieldType, |
| const char* name) { |
| for (const auto& field : form) { |
| if (field->heuristic_type() == fieldType) { |
| EXPECT_EQ(base::UTF8ToUTF16(name), field->unique_name()); |
| return; |
| } |
| } |
| FAIL() << "Missing field " << name; |
| } |
| |
| // Forces rendering of a UIView. This is used in tests to make sure that UIKit |
| // optimizations don't have the views return the previous values (such as |
| // zoomScale). |
| void ForceViewRendering(UIView* view) { |
| EXPECT_TRUE(view); |
| CALayer* layer = view.layer; |
| EXPECT_TRUE(layer); |
| const CGFloat kArbitraryNonZeroPositiveValue = 19; |
| const CGSize arbitraryNonEmptyArea = CGSizeMake( |
| kArbitraryNonZeroPositiveValue, kArbitraryNonZeroPositiveValue); |
| UIGraphicsBeginImageContext(arbitraryNonEmptyArea); |
| CGContext* context = UIGraphicsGetCurrentContext(); |
| EXPECT_TRUE(context); |
| [layer renderInContext:context]; |
| UIGraphicsEndImageContext(); |
| } |
| |
| // WebDataServiceConsumer for receiving vectors of strings and making them |
| // available to tests. |
| class TestConsumer : public WebDataServiceConsumer { |
| public: |
| void OnWebDataServiceRequestDone( |
| WebDataServiceBase::Handle handle, |
| std::unique_ptr<WDTypedResult> result) override { |
| DCHECK_EQ(result->GetType(), AUTOFILL_VALUE_RESULT); |
| result_ = static_cast<WDResult<std::vector<base::string16>>*>(result.get()) |
| ->GetValue(); |
| } |
| std::vector<base::string16> result_; |
| }; |
| |
| // Text fixture to test autofill. |
| class AutofillControllerTest : public ChromeWebTest { |
| public: |
| AutofillControllerTest() |
| : ChromeWebTest(std::make_unique<ChromeWebClient>()) {} |
| ~AutofillControllerTest() override {} |
| |
| protected: |
| void SetUp() override; |
| void TearDown() override; |
| |
| void SetUpForSuggestions(NSString* data); |
| |
| // Adds key value data to the Personal Data Manager and loads test page. |
| void SetUpKeyValueData(); |
| |
| // Blocks until suggestion retrieval has completed. |
| // If |wait_for_trigger| is yes, wait for the call to |
| // |retrieveSuggestionsForForm| to avoid considering a former call. |
| void WaitForSuggestionRetrieval(BOOL wait_for_trigger); |
| |
| // Blocks until |expected_size| forms have been fecthed. |
| bool WaitForFormFetched(AutofillManager* manager, |
| size_t expected_size) WARN_UNUSED_RESULT; |
| |
| // Fails if the specified metric was not registered the given number of times. |
| void ExpectMetric(const std::string& histogram_name, int sum); |
| |
| // Fails if the specified user happiness metric was not registered. |
| void ExpectHappinessMetric(AutofillMetrics::UserHappinessMetric metric); |
| |
| TestSuggestionController* suggestion_controller() { |
| return suggestion_controller_; |
| } |
| |
| private: |
| // Histogram tester for these tests. |
| std::unique_ptr<base::HistogramTester> histogram_tester_; |
| |
| std::unique_ptr<autofill::ChromeAutofillClientIOS> autofill_client_; |
| |
| AutofillAgent* autofill_agent_; |
| |
| // Retrieves suggestions according to form events. |
| TestSuggestionController* suggestion_controller_; |
| |
| // Retrieves accessory views according to form events. |
| FormInputAccessoryMediator* accessory_mediator_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AutofillControllerTest); |
| }; |
| |
| void AutofillControllerTest::SetUp() { |
| ChromeWebTest::SetUp(); |
| |
| // Profile import requires a PersonalDataManager which itself needs the |
| // WebDataService; this is not initialized on a TestChromeBrowserState by |
| // default. |
| chrome_browser_state_->CreateWebDataService(); |
| |
| IOSSecurityStateTabHelper::CreateForWebState(web_state()); |
| |
| autofill_agent_ = [[AutofillAgent alloc] |
| initWithPrefService:chrome_browser_state_->GetPrefs() |
| webState:web_state()]; |
| suggestion_controller_ = |
| [[TestSuggestionController alloc] initWithWebState:web_state() |
| providers:@[ autofill_agent_ ]]; |
| |
| InfoBarManagerImpl::CreateForWebState(web_state()); |
| infobars::InfoBarManager* infobar_manager = |
| InfoBarManagerImpl::FromWebState(web_state()); |
| autofill_client_.reset(new autofill::ChromeAutofillClientIOS( |
| chrome_browser_state_.get(), web_state(), infobar_manager, |
| autofill_agent_, |
| /*password_generation_manager=*/nullptr)); |
| |
| std::string locale("en"); |
| autofill::AutofillDriverIOS::PrepareForWebStateWebFrameAndDelegate( |
| web_state(), autofill_client_.get(), /*autofill_agent=*/nil, locale, |
| autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER); |
| |
| accessory_mediator_ = |
| [[FormInputAccessoryMediator alloc] initWithConsumer:nil |
| webStateList:NULL |
| personalDataManager:NULL |
| passwordStore:NULL]; |
| |
| [accessory_mediator_ injectWebState:web_state()]; |
| [accessory_mediator_ injectProviders:@[ suggestion_controller_ ]]; |
| auto suggestionManager = base::mac::ObjCCastStrict<JsSuggestionManager>( |
| [web_state()->GetJSInjectionReceiver() |
| instanceOfClass:[JsSuggestionManager class]]); |
| [accessory_mediator_ injectSuggestionManager:suggestionManager]; |
| |
| histogram_tester_.reset(new base::HistogramTester()); |
| } |
| |
| void AutofillControllerTest::TearDown() { |
| [suggestion_controller_ detachFromWebState]; |
| |
| ChromeWebTest::TearDown(); |
| } |
| |
| void AutofillControllerTest::WaitForSuggestionRetrieval(BOOL wait_for_trigger) { |
| // Wait for the message queue to ensure that JS events fired in the tests |
| // trigger TestSuggestionController's retrieveSuggestionsForFormNamed: method |
| // and set suggestionRetrievalComplete to NO. |
| if (wait_for_trigger) { |
| WaitForCondition(^bool { |
| return ![suggestion_controller() suggestionRetrievalComplete]; |
| }); |
| } |
| // Now we can wait for suggestionRetrievalComplete to be set to YES. |
| WaitForCondition(^bool { |
| return [suggestion_controller() suggestionRetrievalComplete]; |
| }); |
| } |
| |
| bool AutofillControllerTest::WaitForFormFetched(AutofillManager* manager, |
| size_t expected_size) { |
| return base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForPageLoadTimeout, ^bool { |
| return manager->form_structures().size() == expected_size; |
| }); |
| } |
| |
| void AutofillControllerTest::ExpectMetric(const std::string& histogram_name, |
| int sum) { |
| histogram_tester_->ExpectBucketCount(histogram_name, sum, 1); |
| } |
| |
| void AutofillControllerTest::ExpectHappinessMetric( |
| AutofillMetrics::UserHappinessMetric metric) { |
| histogram_tester_->ExpectBucketCount("Autofill.UserHappiness", metric, 1); |
| } |
| |
| // Checks that viewing an HTML page containing a form results in the form being |
| // registered as a FormStructure by the AutofillManager. |
| TEST_F(AutofillControllerTest, ReadForm) { |
| LoadHtml(kProfileFormHtml); |
| web::WebFrame* main_frame = web::GetMainWebFrame(web_state()); |
| AutofillManager* autofill_manager = |
| AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), main_frame) |
| ->autofill_manager(); |
| EXPECT_TRUE(WaitForFormFetched(autofill_manager, 1)); |
| const auto& forms = autofill_manager->form_structures(); |
| const auto& form = *(forms.begin()->second); |
| CheckField(form, NAME_FULL, "name_1"); |
| CheckField(form, ADDRESS_HOME_LINE1, "address_1"); |
| CheckField(form, ADDRESS_HOME_CITY, "city_1"); |
| CheckField(form, ADDRESS_HOME_STATE, "state_1"); |
| CheckField(form, ADDRESS_HOME_ZIP, "zip_1"); |
| ExpectMetric("Autofill.IsEnabled.PageLoad", 1); |
| ExpectHappinessMetric(AutofillMetrics::FORMS_LOADED); |
| }; |
| |
| // Checks that viewing an HTML page containing a form with an 'id' results in |
| // the form being registered as a FormStructure by the AutofillManager, and the |
| // name is correctly set. |
| TEST_F(AutofillControllerTest, ReadFormName) { |
| LoadHtml(kMinimalFormWithNameHtml); |
| web::WebFrame* main_frame = web::GetMainWebFrame(web_state()); |
| AutofillManager* autofill_manager = |
| AutofillDriverIOS::FromWebStateAndWebFrame(web_state(), main_frame) |
| ->autofill_manager(); |
| EXPECT_TRUE(WaitForFormFetched(autofill_manager, 1)); |
| const auto& forms = autofill_manager->form_structures(); |
| const auto& form = *(forms.begin()->second); |
| EXPECT_EQ(base::UTF8ToUTF16("form1"), form.ToFormData().name); |
| }; |
| |
| // Checks that an HTML page containing a profile-type form which is submitted |
| // with scripts (simulating user form submission) results in a profile being |
| // successfully imported into the PersonalDataManager. |
| TEST_F(AutofillControllerTest, ProfileImport) { |
| PersonalDataManager* personal_data_manager = |
| PersonalDataManagerFactory::GetForBrowserState( |
| ios::ChromeBrowserState::FromBrowserState(GetBrowserState())); |
| // Check there are no registered profiles already. |
| EXPECT_EQ(0U, personal_data_manager->GetProfiles().size()); |
| LoadHtml(kProfileFormHtml); |
| ExecuteJavaScript(@"document.forms[0].name.value = 'Homer Simpson'"); |
| ExecuteJavaScript(@"document.forms[0].address.value = '123 Main Street'"); |
| ExecuteJavaScript(@"document.forms[0].city.value = 'Springfield'"); |
| ExecuteJavaScript(@"document.forms[0].state.value = 'IL'"); |
| ExecuteJavaScript(@"document.forms[0].zip.value = '55123'"); |
| ExecuteJavaScript(@"submit.click()"); |
| WaitForCondition(^bool { |
| return personal_data_manager->GetProfiles().size(); |
| }); |
| const std::vector<AutofillProfile*>& profiles = |
| personal_data_manager->GetProfiles(); |
| if (profiles.size() != 1) |
| FAIL() << "Not exactly one profile found after attempted import"; |
| const AutofillProfile& profile = *profiles[0]; |
| EXPECT_EQ(base::UTF8ToUTF16("Homer Simpson"), |
| profile.GetInfo(AutofillType(NAME_FULL), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("123 Main Street"), |
| profile.GetInfo(AutofillType(ADDRESS_HOME_LINE1), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("Springfield"), |
| profile.GetInfo(AutofillType(ADDRESS_HOME_CITY), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("IL"), |
| profile.GetInfo(AutofillType(ADDRESS_HOME_STATE), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("55123"), |
| profile.GetInfo(AutofillType(ADDRESS_HOME_ZIP), "en-US")); |
| }; |
| |
| void AutofillControllerTest::SetUpForSuggestions(NSString* data) { |
| PersonalDataManager* personal_data_manager = |
| PersonalDataManagerFactory::GetForBrowserState( |
| ios::ChromeBrowserState::FromBrowserState(GetBrowserState())); |
| AutofillProfile profile(base::GenerateGUID(), "https://www.example.com/"); |
| profile.SetRawInfo(NAME_FULL, base::UTF8ToUTF16("Homer Simpson")); |
| profile.SetRawInfo(ADDRESS_HOME_LINE1, base::UTF8ToUTF16("123 Main Street")); |
| profile.SetRawInfo(ADDRESS_HOME_CITY, base::UTF8ToUTF16("Springfield")); |
| profile.SetRawInfo(ADDRESS_HOME_STATE, base::UTF8ToUTF16("IL")); |
| profile.SetRawInfo(ADDRESS_HOME_ZIP, base::UTF8ToUTF16("55123")); |
| EXPECT_EQ(0U, personal_data_manager->GetProfiles().size()); |
| personal_data_manager->SaveImportedProfile(profile); |
| EXPECT_EQ(1U, personal_data_manager->GetProfiles().size()); |
| |
| LoadHtml(data); |
| WaitForBackgroundTasks(); |
| } |
| |
| // Checks that focusing on a text element of a profile-type form will result in |
| // suggestions being sent to the AutofillAgent, once data has been loaded into a |
| // test data manager. |
| TEST_F(AutofillControllerTest, ProfileSuggestions) { |
| SetUpForSuggestions(kProfileFormHtml); |
| ForceViewRendering(web_state()->GetView()); |
| ExecuteJavaScript(@"document.forms[0].name.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExpectMetric("Autofill.AddressSuggestionsCount", 1); |
| ExpectHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"Homer Simpson", suggestion.value); |
| }; |
| |
| // Tests that the system is able to offer suggestions for an anonymous form when |
| // there is another anonymous form on the page. |
| TEST_F(AutofillControllerTest, ProfileSuggestionsTwoAnonymousForms) { |
| SetUpForSuggestions( |
| [NSString stringWithFormat:@"%@%@", kProfileFormHtml, kProfileFormHtml]); |
| ForceViewRendering(web_state()->GetView()); |
| ExecuteJavaScript(@"document.forms[0].name.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExpectMetric("Autofill.AddressSuggestionsCount", 1); |
| ExpectHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"Homer Simpson", suggestion.value); |
| }; |
| |
| // Checks that focusing on a select element in a profile-type form will result |
| // in suggestions being sent to the AutofillAgent, once data has been loaded |
| // into a test data manager. |
| TEST_F(AutofillControllerTest, ProfileSuggestionsFromSelectField) { |
| SetUpForSuggestions(kProfileFormHtml); |
| ForceViewRendering(web_state()->GetView()); |
| ExecuteJavaScript(@"document.forms[0].state.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExpectMetric("Autofill.AddressSuggestionsCount", 1); |
| ExpectHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"IL", suggestion.value); |
| }; |
| |
| // Checks that multiple profiles will offer a matching number of suggestions. |
| TEST_F(AutofillControllerTest, MultipleProfileSuggestions) { |
| PersonalDataManager* personal_data_manager = |
| PersonalDataManagerFactory::GetForBrowserState( |
| ios::ChromeBrowserState::FromBrowserState(GetBrowserState())); |
| AutofillProfile profile(base::GenerateGUID(), "https://www.example.com/"); |
| profile.SetRawInfo(NAME_FULL, base::UTF8ToUTF16("Homer Simpson")); |
| profile.SetRawInfo(ADDRESS_HOME_LINE1, base::UTF8ToUTF16("123 Main Street")); |
| profile.SetRawInfo(ADDRESS_HOME_CITY, base::UTF8ToUTF16("Springfield")); |
| profile.SetRawInfo(ADDRESS_HOME_STATE, base::UTF8ToUTF16("IL")); |
| profile.SetRawInfo(ADDRESS_HOME_ZIP, base::UTF8ToUTF16("55123")); |
| EXPECT_EQ(0U, personal_data_manager->GetProfiles().size()); |
| personal_data_manager->SaveImportedProfile(profile); |
| EXPECT_EQ(1U, personal_data_manager->GetProfiles().size()); |
| AutofillProfile profile2(base::GenerateGUID(), "https://www.example.com/"); |
| profile2.SetRawInfo(NAME_FULL, base::UTF8ToUTF16("Larry Page")); |
| profile2.SetRawInfo(ADDRESS_HOME_LINE1, |
| base::UTF8ToUTF16("1600 Amphitheatre Parkway")); |
| profile2.SetRawInfo(ADDRESS_HOME_CITY, base::UTF8ToUTF16("Mountain View")); |
| profile2.SetRawInfo(ADDRESS_HOME_STATE, base::UTF8ToUTF16("CA")); |
| profile2.SetRawInfo(ADDRESS_HOME_ZIP, base::UTF8ToUTF16("94043")); |
| personal_data_manager->SaveImportedProfile(profile2); |
| EXPECT_EQ(2U, personal_data_manager->GetProfiles().size()); |
| LoadHtml(kProfileFormHtml); |
| base::TaskScheduler::GetInstance()->FlushForTesting(); |
| WaitForBackgroundTasks(); |
| ForceViewRendering(web_state()->GetView()); |
| ExecuteJavaScript(@"document.forms[0].name.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExpectMetric("Autofill.AddressSuggestionsCount", 2); |
| ExpectHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN); |
| EXPECT_EQ(2U, [suggestion_controller() suggestions].count); |
| } |
| |
| // Check that an HTML page containing a key/value type form which is submitted |
| // with scripts (simulating user form submission) results in data being |
| // successfully registered. |
| TEST_F(AutofillControllerTest, KeyValueImport) { |
| LoadHtml(kKeyValueFormHtml); |
| ExecuteJavaScript(@"document.forms[0].greeting.value = 'Hello'"); |
| scoped_refptr<AutofillWebDataService> web_data_service = |
| ios::WebDataServiceFactory::GetAutofillWebDataForBrowserState( |
| chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS); |
| __block TestConsumer consumer; |
| const int limit = 1; |
| consumer.result_ = {base::ASCIIToUTF16("Should"), base::ASCIIToUTF16("get"), |
| base::ASCIIToUTF16("overwritten")}; |
| web_data_service->GetFormValuesForElementName( |
| base::UTF8ToUTF16("greeting"), base::string16(), limit, &consumer); |
| base::TaskScheduler::GetInstance()->FlushForTesting(); |
| WaitForBackgroundTasks(); |
| // No value should be returned before anything is loaded via form submission. |
| ASSERT_EQ(0U, consumer.result_.size()); |
| ExecuteJavaScript(@"submit.click()"); |
| WaitForCondition(^bool { |
| web_data_service->GetFormValuesForElementName( |
| base::UTF8ToUTF16("greeting"), base::string16(), limit, &consumer); |
| return consumer.result_.size(); |
| }); |
| base::TaskScheduler::GetInstance()->FlushForTesting(); |
| WaitForBackgroundTasks(); |
| // One result should be returned, matching the filled value. |
| ASSERT_EQ(1U, consumer.result_.size()); |
| EXPECT_EQ(base::UTF8ToUTF16("Hello"), consumer.result_[0]); |
| }; |
| |
| void AutofillControllerTest::SetUpKeyValueData() { |
| scoped_refptr<AutofillWebDataService> web_data_service = |
| ios::WebDataServiceFactory::GetAutofillWebDataForBrowserState( |
| chrome_browser_state_.get(), ServiceAccessType::EXPLICIT_ACCESS); |
| // Load value into database. |
| std::vector<FormFieldData> values; |
| FormFieldData fieldData; |
| fieldData.name = base::UTF8ToUTF16("greeting"); |
| fieldData.value = base::UTF8ToUTF16("Bonjour"); |
| values.push_back(fieldData); |
| web_data_service->AddFormFields(values); |
| |
| // Load test page. |
| LoadHtml(kKeyValueFormHtml); |
| WaitForBackgroundTasks(); |
| } |
| |
| // Checks that focusing on an element of a key/value type form then typing the |
| // first letter of a suggestion will result in suggestions being sent to the |
| // AutofillAgent, once data has been loaded into a test data manager. |
| TEST_F(AutofillControllerTest, KeyValueSuggestions) { |
| SetUpKeyValueData(); |
| |
| // Focus element. |
| ExecuteJavaScript(@"document.forms[0].greeting.value='B'"); |
| ExecuteJavaScript(@"document.forms[0].greeting.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"Bonjour", suggestion.value); |
| }; |
| |
| // Checks that typing events (simulated in script) result in suggestions. Note |
| // that the field is not explicitly focused before typing starts; this can |
| // happen in practice and should not result in a crash or incorrect behavior. |
| TEST_F(AutofillControllerTest, KeyValueTypedSuggestions) { |
| SetUpKeyValueData(); |
| ExecuteJavaScript(@"document.forms[0].greeting.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExecuteJavaScript(@"event = document.createEvent('TextEvent');"); |
| ExecuteJavaScript( |
| @"event.initTextEvent('textInput', true, true, window, 'B');"); |
| ExecuteJavaScript(@"document.forms[0].greeting.dispatchEvent(event);"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"Bonjour", suggestion.value); |
| } |
| |
| // Checks that focusing on and typing on one field, then changing focus before |
| // typing again, result in suggestions. |
| TEST_F(AutofillControllerTest, KeyValueFocusChange) { |
| SetUpKeyValueData(); |
| |
| // Focus the dummy field and confirm no suggestions are presented. |
| ExecuteJavaScript(@"document.forms[0].dummy.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(0U, [suggestion_controller() suggestions].count); |
| |
| // Enter 'B' in the dummy field and confirm no suggestions are presented. |
| ExecuteJavaScript(@"event = document.createEvent('TextEvent');"); |
| ExecuteJavaScript( |
| @"event.initTextEvent('textInput', true, true, window, 'B');"); |
| ExecuteJavaScript(@"document.forms[0].dummy.dispatchEvent(event);"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(0U, [suggestion_controller() suggestions].count); |
| |
| // Enter 'B' in the greeting field and confirm that one suggestion ("Bonjour") |
| // is presented. |
| ExecuteJavaScript(@"document.forms[0].greeting.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| ExecuteJavaScript(@"event = document.createEvent('TextEvent');"); |
| ExecuteJavaScript( |
| @"event.initTextEvent('textInput', true, true, window, 'B');"); |
| ExecuteJavaScript(@"document.forms[0].greeting.dispatchEvent(event);"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(1U, [suggestion_controller() suggestions].count); |
| FormSuggestion* suggestion = [suggestion_controller() suggestions][0]; |
| EXPECT_NSEQ(@"Bonjour", suggestion.value); |
| } |
| |
| // Checks that focusing on an element of a key/value type form without typing |
| // won't result in suggestions being sent to the AutofillAgent, once data has |
| // been loaded into a test data manager. |
| TEST_F(AutofillControllerTest, NoKeyValueSuggestionsWithoutTyping) { |
| SetUpKeyValueData(); |
| |
| // Focus element. |
| ExecuteJavaScript(@"document.forms[0].greeting.focus()"); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/YES); |
| EXPECT_EQ(0U, [suggestion_controller() suggestions].count); |
| } |
| |
| // Checks that an HTML page containing a credit card-type form which is |
| // submitted with scripts (simulating user form submission) results in a credit |
| // card being successfully imported into the PersonalDataManager. |
| TEST_F(AutofillControllerTest, CreditCardImport) { |
| InfoBarManagerImpl::CreateForWebState(web_state()); |
| PersonalDataManager* personal_data_manager = |
| PersonalDataManagerFactory::GetForBrowserState( |
| ios::ChromeBrowserState::FromBrowserState(GetBrowserState())); |
| |
| // Check there are no registered profiles already. |
| EXPECT_EQ(0U, personal_data_manager->GetCreditCards().size()); |
| LoadHtml(kCreditCardFormHtml); |
| ExecuteJavaScript(@"document.forms[0].name.value = 'Superman'"); |
| ExecuteJavaScript(@"document.forms[0].CCNo.value = '4000-4444-4444-4444'"); |
| ExecuteJavaScript(@"document.forms[0].CCExpiresMonth.value = '11'"); |
| ExecuteJavaScript(@"document.forms[0].CCExpiresYear.value = '2999'"); |
| ExecuteJavaScript(@"submit.click()"); |
| infobars::InfoBarManager* infobar_manager = |
| InfoBarManagerImpl::FromWebState(web_state()); |
| WaitForCondition(^bool() { |
| return infobar_manager->infobar_count(); |
| }); |
| ExpectMetric("Autofill.CreditCardInfoBar.Local", |
| AutofillMetrics::INFOBAR_SHOWN); |
| ASSERT_EQ(1U, infobar_manager->infobar_count()); |
| infobars::InfoBarDelegate* infobar = |
| infobar_manager->infobar_at(0)->delegate(); |
| ConfirmInfoBarDelegate* confirm_infobar = infobar->AsConfirmInfoBarDelegate(); |
| |
| // This call cause a modification of the PersonalDataManager, so wait until |
| // the asynchronous task complete in addition to waiting for the UI update. |
| PersonalDataManagerDataChangedObserver observer(personal_data_manager); |
| confirm_infobar->Accept(); |
| observer.Wait(); |
| |
| const std::vector<CreditCard*>& credit_cards = |
| personal_data_manager->GetCreditCards(); |
| ASSERT_EQ(1U, credit_cards.size()); |
| const CreditCard& credit_card = *credit_cards[0]; |
| EXPECT_EQ(base::UTF8ToUTF16("Superman"), |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_NAME_FULL), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("4000444444444444"), |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_NUMBER), "en-US")); |
| EXPECT_EQ(base::UTF8ToUTF16("11"), |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_EXP_MONTH), "en-US")); |
| EXPECT_EQ( |
| base::UTF8ToUTF16("2999"), |
| credit_card.GetInfo(AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), "en-US")); |
| }; |
| |
| // Checks that an HTTP page containing a credit card results in a navigation |
| // entry with the "credit card interaction" bit set to true. |
| TEST_F(AutofillControllerTest, HttpCreditCard) { |
| LoadHtml(kCreditCardAutofocusFormHtml, GURL("http://chromium.test")); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/NO); |
| |
| web::SSLStatus ssl_status = |
| web_state()->GetNavigationManager()->GetLastCommittedItem()->GetSSL(); |
| security_state::SSLStatusInputEventData* input_events = |
| static_cast<security_state::SSLStatusInputEventData*>( |
| ssl_status.user_data.get()); |
| EXPECT_TRUE(input_events && |
| input_events->input_events()->credit_card_field_edited); |
| }; |
| |
| // Checks that an HTTP page without a credit card form does not result in a |
| // navigation entry with the "credit card interaction" bit set to true. |
| TEST_F(AutofillControllerTest, HttpNoCreditCard) { |
| LoadHtml(kNoCreditCardFormHtml, GURL("http://chromium.test")); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/NO); |
| |
| web::SSLStatus ssl_status = |
| web_state()->GetNavigationManager()->GetLastCommittedItem()->GetSSL(); |
| security_state::SSLStatusInputEventData* input_events = |
| static_cast<security_state::SSLStatusInputEventData*>( |
| ssl_status.user_data.get()); |
| EXPECT_FALSE(input_events && |
| input_events->input_events()->credit_card_field_edited); |
| }; |
| |
| // Checks that an HTTPS page containing a credit card form does not result in a |
| // navigation entry with the "credit card interaction" bit set to true. |
| TEST_F(AutofillControllerTest, HttpsCreditCard) { |
| LoadHtml(kCreditCardAutofocusFormHtml, GURL("https://chromium.test")); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/NO); |
| |
| web::SSLStatus ssl_status = |
| web_state()->GetNavigationManager()->GetLastCommittedItem()->GetSSL(); |
| security_state::SSLStatusInputEventData* input_events = |
| static_cast<security_state::SSLStatusInputEventData*>( |
| ssl_status.user_data.get()); |
| EXPECT_FALSE(input_events && |
| input_events->input_events()->credit_card_field_edited); |
| }; |
| |
| // Checks that an HTTPS page without a credit card form does not result in a |
| // navigation entry with the "credit card interaction" bit set to true. |
| TEST_F(AutofillControllerTest, HttpsNoCreditCard) { |
| LoadHtml(kNoCreditCardFormHtml, GURL("https://chromium.test")); |
| WaitForSuggestionRetrieval(/*wait_for_trigger=*/NO); |
| |
| web::SSLStatus ssl_status = |
| web_state()->GetNavigationManager()->GetLastCommittedItem()->GetSSL(); |
| security_state::SSLStatusInputEventData* input_events = |
| static_cast<security_state::SSLStatusInputEventData*>( |
| ssl_status.user_data.get()); |
| EXPECT_FALSE(input_events && |
| input_events->input_events()->credit_card_field_edited); |
| }; |
| |
| } // namespace |
| |
| } // namespace autofill |