Commit e5057133 authored by isherman@chromium.org's avatar isherman@chromium.org

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
parent a09add5c
......@@ -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_;
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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) {
......
......@@ -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);
}
}
......@@ -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_;
......
......@@ -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) {
......
......@@ -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 | |
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment