diff options
author | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-12 04:03:06 +0000 |
---|---|---|
committer | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-12 04:03:06 +0000 |
commit | e505713377b0ec2e061933ab181854062cb16ffc (patch) | |
tree | 06d19cd76c78ec35d6f2526060c399ce48844299 | |
parent | a09add5cc8e2e936a07f19e4da168c9ed4ed8391 (diff) | |
download | chromium_src-e505713377b0ec2e061933ab181854062cb16ffc.zip chromium_src-e505713377b0ec2e061933ab181854062cb16ffc.tar.gz chromium_src-e505713377b0ec2e061933ab181854062cb16ffc.tar.bz2 |
Add preliminary Autofill support for 'autocompletetype' sections.
BUG=92121
TEST=unit_tests --gtest_filter=AutofillManagerTest.FillFormWithAuthorSpecifiedSections:FormStructureTest.HeuristicsAutocompletetypeWithSections
Review URL: http://codereview.chromium.org/7613015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96506 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/autofill/autofill_field.h | 6 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager.cc | 150 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager.h | 2 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager_unittest.cc | 161 | ||||
-rw-r--r-- | chrome/browser/autofill/form_structure.cc | 161 | ||||
-rw-r--r-- | chrome/browser/autofill/form_structure.h | 14 | ||||
-rw-r--r-- | chrome/browser/autofill/form_structure_unittest.cc | 84 | ||||
-rw-r--r-- | chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out | 2 |
8 files changed, 398 insertions, 182 deletions
diff --git a/chrome/browser/autofill/autofill_field.h b/chrome/browser/autofill/autofill_field.h index a875c99..08ed126 100644 --- a/chrome/browser/autofill/autofill_field.h +++ b/chrome/browser/autofill/autofill_field.h @@ -22,11 +22,13 @@ class AutofillField : public webkit_glue::FormField { const string16& unique_name() const { return unique_name_; } + const string16& section() const { return section_; } AutofillFieldType heuristic_type() const { return heuristic_type_; } AutofillFieldType server_type() const { return server_type_; } const FieldTypeSet& possible_types() const { return possible_types_; } // Sets the heuristic type of this field, validating the input. + void set_section(const string16& section) { section_ = section; } void set_heuristic_type(AutofillFieldType type); void set_server_type(AutofillFieldType type) { server_type_ = type; } void set_possible_types(const FieldTypeSet& possible_types) { @@ -52,6 +54,10 @@ class AutofillField : public webkit_glue::FormField { // The unique name of this field, generated by Autofill. string16 unique_name_; + // The unique identifier for the section (e.g. billing vs. shipping address) + // that this field belongs to. + string16 section_; + // The type of the field, as determined by the Autofill server. AutofillFieldType server_type_; diff --git a/chrome/browser/autofill/autofill_manager.cc b/chrome/browser/autofill/autofill_manager.cc index d8c025b..390c8d1 100644 --- a/chrome/browser/autofill/autofill_manager.cc +++ b/chrome/browser/autofill/autofill_manager.cc @@ -98,124 +98,29 @@ void RemoveDuplicateSuggestions(std::vector<string16>* values, unique_ids->swap(unique_ids_copy); } -// Precondition: |form| should be the cached version of the form that is to be -// autofilled, and |field| should be the field in the |form| that corresponds to -// the initiating field. |is_filling_credit_card| should be true if filling -// credit card data, false otherwise. -// Fills |section_start| and |section_end| so that [section_start, section_end) -// gives the bounds of logical section within |form| that includes |field|. -// Logical sections are identified by two heuristics: -// 1. The fields in the section must all be profile or credit card fields, -// depending on whether |is_filling_credit_card| is true. -// 2. A logical section should not include multiple fields of the same autofill -// type (except for adjacent repeated fields and phone/fax numbers, as -// described below). -void FindSectionBounds(const FormStructure& form, - const AutofillField& field, - bool is_filling_credit_card, - size_t* section_start, - size_t* section_end) { - DCHECK(section_start); - DCHECK(section_end); - - // By default, the relevant section is the entire form. - *section_start = 0; - *section_end = form.field_count(); - - std::set<AutofillFieldType> seen_types; - bool initiating_field_is_in_current_section = false; - AutofillFieldType previous_type = UNKNOWN_TYPE; - for (size_t i = 0; i < form.field_count(); ++i) { - const AutofillField* current_field = form.field(i); - const AutofillFieldType current_type = - AutofillType::GetEquivalentFieldType(current_field->type()); - - bool already_saw_current_type = seen_types.count(current_type) > 0; - // Forms often ask for multiple phone numbers -- e.g. both a daytime and - // evening phone number. Our phone and fax number detection is also - // generally a little off. Hence, ignore both field types as a signal here. - AutofillType::FieldTypeGroup current_type_group = - AutofillType(current_type).group(); - if (current_type_group == AutofillType::PHONE_HOME || - current_type_group == AutofillType::PHONE_FAX) - already_saw_current_type = false; - - // Some forms have adjacent fields of the same type. Two common examples: - // * Forms with two email fields, where the second is meant to "confirm" - // the first. - // * Forms with a <select> menu for states in some countries, and a - // freeform <input> field for states in other countries. (Usually, only - // one of these two will be visible for any given choice of country.) - // Generally, adjacent fields of the same type belong in the same logical - // section. - if (current_type == previous_type) - already_saw_current_type = false; - - previous_type = current_type; - - // Fields of unknown type don't help us to distinguish sections. - if (current_type == UNKNOWN_TYPE) - continue; - - // If we are filling credit card data, the relevant section should include - // only credit card fields; and similarly for profile data. - bool is_credit_card_field = current_type_group == AutofillType::CREDIT_CARD; - bool is_appropriate_type = is_credit_card_field == is_filling_credit_card; - - if (already_saw_current_type || !is_appropriate_type) { - if (initiating_field_is_in_current_section) { - // We reached the end of the section containing the initiating field. - *section_end = i; - break; - } - - // We reached the end of a section, so start a new section. - seen_types.clear(); - - // Only include the current field in the new section if it matches the - // type of data we are filling. - if (is_appropriate_type) { - *section_start = i; - } else { - *section_start = i + 1; - continue; - } - } - - seen_types.insert(current_type); - - if (current_field == &field) - initiating_field_is_in_current_section = true; - } - - // We should have found the initiating field. - DCHECK(initiating_field_is_in_current_section); -} - // Precondition: |form_structure| and |form| should correspond to the same -// logical form. Returns true if the relevant portion of |form| is auto-filled. -// The "relevant" fields in |form| are ones corresponding to fields in -// |form_structure| with indices in the range [section_start, section_end). +// logical form. Returns true if any field in the given |section| within |form| +// is auto-filled. bool SectionIsAutofilled(const FormStructure* form_structure, const webkit_glue::FormData& form, - size_t section_start, - size_t section_end) { + const string16& section) { // TODO(isherman): It would be nice to share most of this code with the loop - // in |FillAutofillFormData()|, but I don't see a particularly clean way to do - // that. + // in |OnFillAutofillFormData()|, but I don't see a particularly clean way to + // do that. // The list of fields in |form_structure| and |form.fields| often match // directly and we can fill these corresponding fields; however, when the // |form_structure| and |form.fields| do not match directly we search // ahead in the |form_structure| for the matching field. - for (size_t i = section_start, j = 0; - i < section_end && j < form.fields.size(); + for (size_t i = 0, j = 0; + i < form_structure->field_count() && j < form.fields.size(); j++) { size_t k = i; // Search forward in the |form_structure| for a corresponding field. while (k < form_structure->field_count() && - *form_structure->field(k) != form.fields[j]) { + (form_structure->field(k)->section() != section || + *form_structure->field(k) != form.fields[j])) { k++; } @@ -223,12 +128,11 @@ bool SectionIsAutofilled(const FormStructure* form_structure, if (k >= form_structure->field_count()) continue; - AutofillType autofill_type(form_structure->field(k)->type()); if (form.fields[j].is_autofilled) return true; - // We found a matching field in the |form_structure| so we - // proceed to the next |form| field, and the next |form_structure|. + // We found a matching field in the |form_structure|, so on the next + // iteration we should proceed to the next |form_structure| field. ++i; } @@ -495,14 +399,9 @@ void AutofillManager::OnQueryFormFieldAutofill( icons.assign(1, string16()); unique_ids.assign(1, -1); } else { - size_t section_start, section_end; - FindSectionBounds(*form_structure, *autofill_field, - is_filling_credit_card, §ion_start, §ion_end); - - bool section_is_autofilled = SectionIsAutofilled(form_structure, - form, - section_start, - section_end); + bool section_is_autofilled = + SectionIsAutofilled(form_structure, form, + autofill_field->section()); if (section_is_autofilled) { // If the relevant section is auto-filled and the renderer is querying // for suggestions, then the user is editing the value of a field. @@ -590,16 +489,11 @@ void AutofillManager::OnFillAutofillFormData(int query_id, if (!profile && !credit_card) return; - // Find the section of the form that we are autofilling. - size_t section_start, section_end; - FindSectionBounds(*form_structure, *autofill_field, (credit_card != NULL), - §ion_start, §ion_end); - FormData result = form; // If the relevant section is auto-filled, we should fill |field| but not the // rest of the form. - if (SectionIsAutofilled(form_structure, form, section_start, section_end)) { + if (SectionIsAutofilled(form_structure, form, autofill_field->section())) { for (std::vector<FormField>::iterator iter = result.fields.begin(); iter != result.fields.end(); ++iter) { if ((*iter) == field) { @@ -628,19 +522,21 @@ void AutofillManager::OnFillAutofillFormData(int query_id, // ahead in the |form_structure| for the matching field. // See unit tests: AutofillManagerTest.FormChangesRemoveField and // AutofillManagerTest.FormChangesAddField for usage. - for (size_t i = section_start, j = 0; - i < section_end && j < result.fields.size(); + for (size_t i = 0, j = 0; + i < form_structure->field_count() && j < result.fields.size(); j++) { size_t k = i; // Search forward in the |form_structure| for a corresponding field. - while (k < section_end && *form_structure->field(k) != result.fields[j]) { + while (k < form_structure->field_count() && + (form_structure->field(k)->section() != autofill_field->section() || + *form_structure->field(k) != result.fields[j])) { k++; } // If we've found a match then fill the |result| field with the found // field in the |form_structure|. - if (k >= section_end) + if (k >= form_structure->field_count()) continue; AutofillFieldType field_type = form_structure->field(k)->type(); @@ -663,8 +559,8 @@ void AutofillManager::OnFillAutofillFormData(int query_id, } } - // We found a matching field in the |form_structure| so we - // proceed to the next |result| field, and the next |form_structure|. + // We found a matching field in the |form_structure|, so on the next + // iteration we should proceed to the next |form_structure| field. ++i; } diff --git a/chrome/browser/autofill/autofill_manager.h b/chrome/browser/autofill/autofill_manager.h index ce2d133..a9bdc9f 100644 --- a/chrome/browser/autofill/autofill_manager.h +++ b/chrome/browser/autofill/autofill_manager.h @@ -241,6 +241,8 @@ class AutofillManager : public TabContentsObserver, FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillAddressForm); FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillAddressAndCreditCardForm); FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillFormWithMultipleSections); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + FillFormWithAuthorSpecifiedSections); FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillFormWithMultipleEmails); FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillAutofilledForm); FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, FillPhoneNumber); diff --git a/chrome/browser/autofill/autofill_manager_unittest.cc b/chrome/browser/autofill/autofill_manager_unittest.cc index 2796187..aaefd4c 100644 --- a/chrome/browser/autofill/autofill_manager_unittest.cc +++ b/chrome/browser/autofill/autofill_manager_unittest.cc @@ -318,7 +318,6 @@ void ExpectFilledForm(int page_id, form_size += kCreditCardFormSize; ASSERT_EQ(form_size, filled_form.fields.size()); - FormField field; if (has_address_fields) { ExpectFilledField("First Name", "firstname", first, "text", filled_form.fields[0]); @@ -1844,6 +1843,166 @@ TEST_F(AutofillManagerTest, FillFormWithMultipleSections) { } } +// Test that we correctly fill a form that has author-specified sections, which +// might not match our expected section breakdown. +TEST_F(AutofillManagerTest, FillFormWithAuthorSpecifiedSections) { + // Create a form with a billing section and an unnamed section, interleaved. + // The billing section includes both address and credit card fields. + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("https://myform.com/form.html"); + form.action = GURL("https://myform.com/submit.html"); + form.user_submitted = true; + + FormField field; + + autofill_test::CreateTestFormField("", "country", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing country"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "firstname", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("given-name"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "lastname", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("surname"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "address", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing street-address"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "city", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing locality"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "state", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing administrative-area"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "zip", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing postal-code"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccname", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing cc-full-name"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccnumber", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing cc-number"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccexp", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("section-billing cc-exp"); + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "email", "", "text", &field); + field.autocomplete_type = ASCIIToUTF16("email"); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the unnamed section. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[1], + autofill_manager_->PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Unnamed section"); + EXPECT_EQ(kDefaultPageID, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "", "text", results.fields[0]); + ExpectFilledField("", "firstname", "Elvis", "text", results.fields[1]); + ExpectFilledField("", "lastname", "Presley", "text", results.fields[2]); + ExpectFilledField("", "address", "", "text", results.fields[3]); + ExpectFilledField("", "city", "", "text", results.fields[4]); + ExpectFilledField("", "state", "", "text", results.fields[5]); + ExpectFilledField("", "zip", "", "text", results.fields[6]); + ExpectFilledField("", "ccname", "", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", results.fields[8]); + ExpectFilledField("", "ccexp", "", "text", results.fields[9]); + ExpectFilledField("", "email", "theking@gmail.com", "text", + results.fields[10]); + } + + // Fill the address portion of the billing section. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000001", 0); + FillAutofillFormData(kPageID2, form, form.fields[0], + autofill_manager_->PackGUIDs(empty, guid2)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Billing address"); + EXPECT_EQ(kPageID2, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "United States", "text", + results.fields[0]); + ExpectFilledField("", "firstname", "", "text", results.fields[1]); + ExpectFilledField("", "lastname", "", "text", results.fields[2]); + ExpectFilledField("", "address", "3734 Elvis Presley Blvd.", "text", + results.fields[3]); + ExpectFilledField("", "city", "Memphis", "text", results.fields[4]); + ExpectFilledField("", "state", "Tennessee", "text", results.fields[5]); + ExpectFilledField("", "zip", "38116", "text", results.fields[6]); + ExpectFilledField("", "ccname", "", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", results.fields[8]); + ExpectFilledField("", "ccexp", "", "text", results.fields[9]); + ExpectFilledField("", "email", "", "text", results.fields[10]); + } + + // Fill the credit card portion of the billing section. + const int kPageID3 = 3; + GUIDPair guid3("00000000-0000-0000-0000-000000000004", 0); + FillAutofillFormData(kPageID3, form, form.fields[form.fields.size() - 2], + autofill_manager_->PackGUIDs(guid3, empty)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Credit card"); + EXPECT_EQ(kPageID3, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "", "text", results.fields[0]); + ExpectFilledField("", "firstname", "", "text", results.fields[1]); + ExpectFilledField("", "lastname", "", "text", results.fields[2]); + ExpectFilledField("", "address", "", "text", results.fields[3]); + ExpectFilledField("", "city", "", "text", results.fields[4]); + ExpectFilledField("", "state", "", "text", results.fields[5]); + ExpectFilledField("", "zip", "", "text", results.fields[6]); + ExpectFilledField("", "ccname", "Elvis Presley", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "4234567890123456", "text", + results.fields[8]); + ExpectFilledField("", "ccexp", "04/2012", "text", results.fields[9]); + ExpectFilledField("", "email", "", "text", results.fields[10]); + } +} + // Test that we correctly fill a form that has a single logical section with // multiple email address fields. TEST_F(AutofillManagerTest, FillFormWithMultipleEmails) { diff --git a/chrome/browser/autofill/form_structure.cc b/chrome/browser/autofill/form_structure.cc index b9e0a2e..076e3c4 100644 --- a/chrome/browser/autofill/form_structure.cc +++ b/chrome/browser/autofill/form_structure.cc @@ -221,43 +221,13 @@ bool ConvertToAutofillFieldType(const AutofillField& field, if (autocomplete_type == ASCIIToUTF16("cc-exp")) { // TODO(isherman): Choose variant based on HTML5 validation regex. - *autofill_type = CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; + *autofill_type = CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; return true; } return false; } -// Classifies each field in |fields| based upon its |autocompletetype| -// attribute, if the attribute is available. The association is stored into -// |map|. Returns |true| if the attribute is available (and non-empty) for at -// least one field. -bool ParseAutocompletetypeAttributes(const std::vector<AutofillField*>& fields, - FieldTypeMap* map) { - bool found_attribute = false; - for (std::vector<AutofillField*>::const_iterator field = fields.begin(); - field != fields.end(); ++field) { - if ((*field)->autocomplete_type.empty()) - continue; - - found_attribute = true; - std::vector<string16> types; - Tokenize((*field)->autocomplete_type, ASCIIToUTF16(" "), &types); - - // TODO(isherman): Handle sections: http://crbug.com/92121 - for (std::vector<string16>::const_iterator type = types.begin(); - type != types.end(); ++type) { - AutofillFieldType autofill_type = UNKNOWN_TYPE; - if (ConvertToAutofillFieldType(**field, *type, &autofill_type)) { - map->insert(make_pair((*field)->unique_name(), autofill_type)); - break; - } - } - } - - return found_attribute; -} - } // namespace FormStructure::FormStructure(const FormData& form) @@ -296,31 +266,39 @@ FormStructure::FormStructure(const FormData& form) FormStructure::~FormStructure() {} void FormStructure::DetermineHeuristicTypes() { - autofill_count_ = 0; - - FieldTypeMap field_type_map; - // First, try to detect field types based on the fields' |autocompletetype| // attributes. If there is at least one form field with this attribute, don't // try to apply other heuristics to match fields in this form. - has_author_specified_types_ = - ParseAutocompletetypeAttributes(fields_.get(), &field_type_map); - if (!has_author_specified_types_) - FormField::ParseFormFields(fields_.get(), &field_type_map); + bool found_sections; + ParseAutocompletetypeAttributes(&has_author_specified_types_, + &found_sections); - for (size_t index = 0; index < field_count(); index++) { - AutofillField* field = fields_[index]; - FieldTypeMap::iterator iter = field_type_map.find(field->unique_name()); - - AutofillFieldType heuristic_autofill_type; - if (iter == field_type_map.end()) { - heuristic_autofill_type = UNKNOWN_TYPE; - } else { - heuristic_autofill_type = iter->second; - ++autofill_count_; + if (!has_author_specified_types_) { + FieldTypeMap field_type_map; + FormField::ParseFormFields(fields_.get(), &field_type_map); + for (size_t index = 0; index < field_count(); index++) { + AutofillField* field = fields_[index]; + FieldTypeMap::iterator iter = field_type_map.find(field->unique_name()); + if (iter != field_type_map.end()) + field->set_heuristic_type(iter->second); } + } + + UpdateAutofillCount(); - field->set_heuristic_type(heuristic_autofill_type); + if (!found_sections) + IdentifySections(); + + // Ensure that credit card and address fields are in separate sections. + // This simplifies the section-aware logic in autofill_manager.cc. + for (std::vector<AutofillField*>::iterator field = fields_->begin(); + field != fields_->end(); ++field) { + AutofillType::FieldTypeGroup field_type_group = + AutofillType((*field)->type()).group(); + if (field_type_group == AutofillType::CREDIT_CARD) + (*field)->set_section((*field)->section() + ASCIIToUTF16("-cc")); + else + (*field)->set_section((*field)->section() + ASCIIToUTF16("-default")); } } @@ -852,3 +830,86 @@ bool FormStructure::EncodeFormRequest( } return true; } + +void FormStructure::ParseAutocompletetypeAttributes(bool* found_attribute, + bool* found_sections) { + *found_attribute = false; + *found_sections = false; + for (std::vector<AutofillField*>::iterator field = fields_->begin(); + field != fields_->end(); ++field) { + if ((*field)->autocomplete_type.empty()) + continue; + + *found_attribute = true; + std::vector<string16> types; + Tokenize((*field)->autocomplete_type, ASCIIToUTF16(" "), &types); + + // Look for a named section. + const string16 kSectionPrefix = ASCIIToUTF16("section-"); + if (!types.empty() && StartsWith(types.front(), kSectionPrefix, true)) { + *found_sections = true; + (*field)->set_section(types.front().substr(kSectionPrefix.size())); + } + + // Look for specified types. + for (std::vector<string16>::const_iterator type = types.begin(); + type != types.end(); ++type) { + AutofillFieldType autofill_type = UNKNOWN_TYPE; + if (ConvertToAutofillFieldType(**field, *type, &autofill_type)) { + (*field)->set_heuristic_type(autofill_type); + break; + } + } + } +} + +void FormStructure::IdentifySections() { + if (fields_.empty()) + return; + + // Name sections after the first field in the section. + string16 current_section = fields_->front()->unique_name(); + + // Keep track of the types we've seen in this section. + std::set<AutofillFieldType> seen_types; + AutofillFieldType previous_type = UNKNOWN_TYPE; + + for (std::vector<AutofillField*>::iterator field = fields_->begin(); + field != fields_->end(); ++field) { + const AutofillFieldType current_type = + AutofillType::GetEquivalentFieldType((*field)->type()); + + bool already_saw_current_type = seen_types.count(current_type) > 0; + + // Forms often ask for multiple phone numbers -- e.g. both a daytime and + // evening phone number. Our phone and fax number detection is also + // generally a little off. Hence, ignore both field types as a signal here. + AutofillType::FieldTypeGroup current_type_group = + AutofillType(current_type).group(); + if (current_type_group == AutofillType::PHONE_HOME || + current_type_group == AutofillType::PHONE_FAX) + already_saw_current_type = false; + + // Some forms have adjacent fields of the same type. Two common examples: + // * Forms with two email fields, where the second is meant to "confirm" + // the first. + // * Forms with a <select> menu for states in some countries, and a + // freeform <input> field for states in other countries. (Usually, only + // one of these two will be visible for any given choice of country.) + // Generally, adjacent fields of the same type belong in the same logical + // section. + if (current_type == previous_type) + already_saw_current_type = false; + + previous_type = current_type; + + if (current_type != UNKNOWN_TYPE && already_saw_current_type) { + // We reached the end of a section, so start a new section. + seen_types.clear(); + current_section = (*field)->unique_name(); + } + + seen_types.insert(current_type); + (*field)->set_section(current_section); + } +} diff --git a/chrome/browser/autofill/form_structure.h b/chrome/browser/autofill/form_structure.h index 92e4de9..11c0b2c 100644 --- a/chrome/browser/autofill/form_structure.h +++ b/chrome/browser/autofill/form_structure.h @@ -155,6 +155,20 @@ class FormStructure { bool EncodeFormRequest(EncodeRequestType request_type, buzz::XmlElement* encompassing_xml_element) const; + // Classifies each field in |fields_| based upon its |autocompletetype| + // attribute, if the attribute is available. The association is stored into + // |map|. Fills |found_attribute| with |true| if the attribute is available + // (and non-empty) for at least one field. Fills |found_sections| with |true| + // if the attribute specifies a section for at least one field. + void ParseAutocompletetypeAttributes(bool* found_attribute, + bool* found_sections); + + // Classifies each field in |fields_| into a logical section. + // Sections are identified by the heuristic that a logical section should not + // include multiple fields of the same autofill type (with some exceptions, as + // described in the implementation). + void IdentifySections(); + // The name of the form. string16 form_name_; diff --git a/chrome/browser/autofill/form_structure_unittest.cc b/chrome/browser/autofill/form_structure_unittest.cc index c6ff870..487662e 100644 --- a/chrome/browser/autofill/form_structure_unittest.cc +++ b/chrome/browser/autofill/form_structure_unittest.cc @@ -339,7 +339,7 @@ TEST(FormStructureTest, HeuristicsContactInfo) { EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(8)->heuristic_type()); } -// Verify that we can correctly process the 'autocompletetype' attribute. +// Verify that we can correctly process the |autocompletetype| attribute. TEST(FormStructureTest, HeuristicsAutocompletetype) { scoped_ptr<FormStructure> form_structure; FormData form; @@ -376,7 +376,7 @@ TEST(FormStructureTest, HeuristicsAutocompletetype) { EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type()); } -// If at least one field includes the 'autocompletetype' attribute, we should +// If at least one field includes the |autocompletetype| attribute, we should // not try to apply any other heuristics. TEST(FormStructureTest, AutocompletetypeOverridesOtherHeuristics) { scoped_ptr<FormStructure> form_structure; @@ -426,8 +426,80 @@ TEST(FormStructureTest, AutocompletetypeOverridesOtherHeuristics) { EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); } +// Verify that we can correctly process sections listed in the |autocomplete| +// attribute. +TEST(FormStructureTest, HeuristicsAutocompletetypeWithSections) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormField field; + field.form_control_type = ASCIIToUTF16("text"); + + // We expect "shipping" and "billing" to be the most common sections. + field.label = string16(); + field.name = ASCIIToUTF16("field1"); + field.autocomplete_type = ASCIIToUTF16("section-shipping given-name"); + form.fields.push_back(field); + + // Some field will have no section specified. These fall into the default + // section, with an empty name. + field.label = string16(); + field.name = ASCIIToUTF16("field2"); + field.autocomplete_type = ASCIIToUTF16("surname"); + form.fields.push_back(field); + + // We allow arbitrary section names. + field.label = string16(); + field.name = ASCIIToUTF16("field3"); + field.autocomplete_type = ASCIIToUTF16("section-foo address-line1"); + form.fields.push_back(field); + + // Specifying "section-" is equivalent to not specifying a section. + field.label = string16(); + field.name = ASCIIToUTF16("field4"); + field.autocomplete_type = ASCIIToUTF16("section- address-line2"); + form.fields.push_back(field); + + // We don't do anything clever to try to coalesce sections; it's up to site + // authors to avoid typos. + field.label = string16(); + field.name = ASCIIToUTF16("field5"); + field.autocomplete_type = ASCIIToUTF16("section--shipping locality"); + form.fields.push_back(field); + + // Credit card fields are implicitly in a separate section from other fields. + field.label = string16(); + field.name = ASCIIToUTF16("field6"); + field.autocomplete_type = ASCIIToUTF16("section-shipping cc-number"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form)); + form_structure->DetermineHeuristicTypes(); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(6U, form_structure->field_count()); + ASSERT_EQ(6U, form_structure->autofill_count()); + + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("shipping-default"), + form_structure->field(0)->section()); + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("-default"), form_structure->field(1)->section()); + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(2)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("foo-default"), form_structure->field(2)->section()); + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(3)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("-default"), form_structure->field(3)->section()); + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(4)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("-shipping-default"), + form_structure->field(4)->section()); + EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(5)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("shipping-cc"), form_structure->field(5)->section()); +} + // Verify that we can correctly process fallback types listed in the -// 'autocompletetype' attribute. +// |autocompletetype| attribute. TEST(FormStructureTest, HeuristicsAutocompletetypeWithFallbacks) { scoped_ptr<FormStructure> form_structure; FormData form; @@ -465,9 +537,15 @@ TEST(FormStructureTest, HeuristicsAutocompletetypeWithFallbacks) { ASSERT_EQ(3U, form_structure->autofill_count()); EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("full-name-default"), + form_structure->field(0)->section()); EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("full-name-default"), + form_structure->field(1)->section()); EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, form_structure->field(2)->heuristic_type()); + EXPECT_EQ(ASCIIToUTF16("shipping-default"), + form_structure->field(2)->section()); } TEST(FormStructureTest, HeuristicsSample8) { diff --git a/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out b/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out index f9e2358..b7a8200 100644 --- a/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out +++ b/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out @@ -24,4 +24,4 @@ CREDIT_CARD_NUMBER | c2 | | CREDIT_CARD_EXP_MONTH | c3 | | CREDIT_CARD_EXP_4_DIGIT_YEAR | c4 | | CREDIT_CARD_EXP_2_DIGIT_YEAR | c5 | | -CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR | c6 | | +CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | c6 | | |