summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autofill
diff options
context:
space:
mode:
authorisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-12 04:03:06 +0000
committerisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-12 04:03:06 +0000
commite505713377b0ec2e061933ab181854062cb16ffc (patch)
tree06d19cd76c78ec35d6f2526060c399ce48844299 /chrome/browser/autofill
parenta09add5cc8e2e936a07f19e4da168c9ed4ed8391 (diff)
downloadchromium_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
Diffstat (limited to 'chrome/browser/autofill')
-rw-r--r--chrome/browser/autofill/autofill_field.h6
-rw-r--r--chrome/browser/autofill/autofill_manager.cc150
-rw-r--r--chrome/browser/autofill/autofill_manager.h2
-rw-r--r--chrome/browser/autofill/autofill_manager_unittest.cc161
-rw-r--r--chrome/browser/autofill/form_structure.cc161
-rw-r--r--chrome/browser/autofill/form_structure.h14
-rw-r--r--chrome/browser/autofill/form_structure_unittest.cc84
7 files changed, 397 insertions, 181 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, &section_start, &section_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),
- &section_start, &section_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) {