Chromium Code Reviews| Index: chrome/browser/autofill/form_structure.cc |
| diff --git a/chrome/browser/autofill/form_structure.cc b/chrome/browser/autofill/form_structure.cc |
| index 07af74814c5b73571643406b3e44d9b7dbed6f24..84ccbe6a9176625884720c9e9e4677eb8b726ec7 100644 |
| --- a/chrome/browser/autofill/form_structure.cc |
| +++ b/chrome/browser/autofill/form_structure.cc |
| @@ -85,147 +85,138 @@ std::string EncodeFieldTypes(const FieldTypeSet& available_field_types) { |
| return data_presence; |
| } |
| -bool UpdateFromAutocompleteType(const string16& autocomplete_type, |
| - AutofillField* field) { |
| - if (autocomplete_type == ASCIIToUTF16("given-name")) { |
| - field->set_heuristic_type(NAME_FIRST); |
| - return true; |
| - } |
| +// Returns |true| iff the |token| is a type hint for a contact field, as |
| +// specified in the implementation section of http://is.gd/whatwg_autocomplete |
| +// Note that "fax" and "pager" are intentionally ignored, as Chrome does not |
| +// support filling either type of information. |
| +bool IsContactTypeHint(const std::string& token) { |
| + return |
| + token == "home" || |
| + token == "work" || |
| + token == "mobile" || |
| + token == "pager"; |
| +} |
| - if (autocomplete_type == ASCIIToUTF16("middle-name")) { |
| - field->set_heuristic_type(NAME_MIDDLE); |
| - return true; |
| +// Returns |true| iff the |token| is a type hint appropriate for a field of the |
| +// given |field_type|, as specified in the implementation section of |
| +// http://is.gd/whatwg_autocomplete |
| +bool ContactTypeHintMatchesFieldType(const std::string& token, |
| + AutofillFieldType field_type) { |
| + // The "home" and "work" type hints are only appropriate for email and phone |
| + // number field types. |
| + if (token == "home" || token == "work") { |
| + return field_type == EMAIL_ADDRESS || |
| + (field_type >= PHONE_HOME_NUMBER && |
| + field_type <= PHONE_HOME_WHOLE_NUMBER); |
| } |
| - if (autocomplete_type == ASCIIToUTF16("middle-initial")) { |
| - field->set_heuristic_type(NAME_MIDDLE_INITIAL); |
| - return true; |
| + // The "mobile" type hint is only appropriate for phone number field types. |
| + // Note that "fax" and "pager" are intentionally ignored, as Chrome does not |
| + // support filling either type of information. |
| + if (token == "mobile") { |
| + return field_type >= PHONE_HOME_NUMBER && |
| + field_type <= PHONE_HOME_WHOLE_NUMBER; |
| } |
| - if (autocomplete_type == ASCIIToUTF16("surname")) { |
| - field->set_heuristic_type(NAME_LAST); |
| - return true; |
| - } |
| + return false; |
| +} |
| - if (autocomplete_type == ASCIIToUTF16("full-name")) { |
| - field->set_heuristic_type(NAME_FULL); |
| - return true; |
| +// Returns the Chrome Autofill-supported field type corresponding to the given |
| +// |autocomplete_type|, if there is one, in the context of the given |field|. |
| +// Chrome Autofill supports a subset of the field types listed at |
| +// http://is.gd/whatwg_autocomplete |
| +AutofillFieldType FieldTypeFromAutocompleteType( |
| + const std::string& autocomplete_type, |
| + const AutofillField& field) { |
| + if (autocomplete_type == "name") |
| + return NAME_FULL; |
| + |
| + if (autocomplete_type == "given-name") |
| + return NAME_FIRST; |
| + |
| + if (autocomplete_type == "additional-name") { |
| + if (field.max_length == 1) |
| + return NAME_MIDDLE_INITIAL; |
| + else |
| + return NAME_MIDDLE; |
| } |
| - if (autocomplete_type == ASCIIToUTF16("street-address") || |
| - autocomplete_type == ASCIIToUTF16("address-line1")) { |
| - field->set_heuristic_type(ADDRESS_HOME_LINE1); |
| - return true; |
| - } |
| + if (autocomplete_type == "family-name") |
| + return NAME_LAST; |
| - if (autocomplete_type == ASCIIToUTF16("address-line2")) { |
| - field->set_heuristic_type(ADDRESS_HOME_LINE2); |
| - return true; |
| - } |
| + if (autocomplete_type == "honorific-suffix") |
| + return NAME_SUFFIX; |
| - if (autocomplete_type == ASCIIToUTF16("locality") || |
| - autocomplete_type == ASCIIToUTF16("city")) { |
| - field->set_heuristic_type(ADDRESS_HOME_CITY); |
| - return true; |
| - } |
| + if (autocomplete_type == "organization") |
| + return COMPANY_NAME; |
| - if (autocomplete_type == ASCIIToUTF16("administrative-area") || |
| - autocomplete_type == ASCIIToUTF16("state") || |
| - autocomplete_type == ASCIIToUTF16("province") || |
| - autocomplete_type == ASCIIToUTF16("region")) { |
| - field->set_heuristic_type(ADDRESS_HOME_STATE); |
| - return true; |
| - } |
| + if (autocomplete_type == "street-address" || |
| + autocomplete_type == "address-line1") |
| + return ADDRESS_HOME_LINE1; |
| - if (autocomplete_type == ASCIIToUTF16("postal-code")) { |
| - field->set_heuristic_type(ADDRESS_HOME_ZIP); |
| - return true; |
| - } |
| + if (autocomplete_type == "address-line2") |
| + return ADDRESS_HOME_LINE2; |
| - if (autocomplete_type == ASCIIToUTF16("country")) { |
| - field->set_heuristic_type(ADDRESS_HOME_COUNTRY); |
| - return true; |
| - } |
| + if (autocomplete_type == "locality") |
| + return ADDRESS_HOME_CITY; |
| - if (autocomplete_type == ASCIIToUTF16("organization")) { |
| - field->set_heuristic_type(COMPANY_NAME); |
| - return true; |
| - } |
| + if (autocomplete_type == "region") |
| + return ADDRESS_HOME_STATE; |
| - if (autocomplete_type == ASCIIToUTF16("email")) { |
| - field->set_heuristic_type(EMAIL_ADDRESS); |
| - return true; |
| - } |
| + if (autocomplete_type == "country") |
| + return ADDRESS_HOME_COUNTRY; |
| - if (autocomplete_type == ASCIIToUTF16("phone-full")) { |
| - field->set_heuristic_type(PHONE_HOME_WHOLE_NUMBER); |
| - return true; |
| - } |
| + if (autocomplete_type == "postal-code") |
| + return ADDRESS_HOME_ZIP; |
| - if (autocomplete_type == ASCIIToUTF16("phone-country-code")) { |
| - field->set_heuristic_type(PHONE_HOME_COUNTRY_CODE); |
| - return true; |
| - } |
| + if (autocomplete_type == "cc-name") |
| + return CREDIT_CARD_NAME; |
| - if (autocomplete_type == ASCIIToUTF16("phone-national")) { |
| - field->set_heuristic_type(PHONE_HOME_CITY_AND_NUMBER); |
| - return true; |
| - } |
| + if (autocomplete_type == "cc-number") |
| + return CREDIT_CARD_NUMBER; |
| - if (autocomplete_type == ASCIIToUTF16("phone-area-code")) { |
| - field->set_heuristic_type(PHONE_HOME_CITY_CODE); |
| - return true; |
| + if (autocomplete_type == "cc-exp") { |
| + if (field.max_length == 5) |
| + return CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; |
| + else |
| + return CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; |
| } |
| - if (autocomplete_type == ASCIIToUTF16("phone-local")) { |
| - field->set_heuristic_type(PHONE_HOME_NUMBER); |
| - return true; |
| - } |
| + if (autocomplete_type == "cc-exp-month") |
| + return CREDIT_CARD_EXP_MONTH; |
| - if (autocomplete_type == ASCIIToUTF16("phone-local-prefix")) { |
| - field->set_heuristic_type(PHONE_HOME_NUMBER); |
| - field->set_phone_part(AutofillField::PHONE_PREFIX); |
| - return true; |
| + if (autocomplete_type == "cc-exp-year") { |
| + if (field.max_length == 2) |
| + return CREDIT_CARD_EXP_2_DIGIT_YEAR; |
| + else |
| + return CREDIT_CARD_EXP_4_DIGIT_YEAR; |
| } |
| - if (autocomplete_type == ASCIIToUTF16("phone-local-suffix")) { |
| - field->set_heuristic_type(PHONE_HOME_NUMBER); |
| - field->set_phone_part(AutofillField::PHONE_SUFFIX); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel") |
| + return PHONE_HOME_WHOLE_NUMBER; |
| - if (autocomplete_type == ASCIIToUTF16("cc-full-name")) { |
| - field->set_heuristic_type(CREDIT_CARD_NAME); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel-country-code") |
| + return PHONE_HOME_COUNTRY_CODE; |
| - if (autocomplete_type == ASCIIToUTF16("cc-number")) { |
| - field->set_heuristic_type(CREDIT_CARD_NUMBER); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel-national") |
| + return PHONE_HOME_CITY_AND_NUMBER; |
| - if (autocomplete_type == ASCIIToUTF16("cc-exp-month")) { |
| - field->set_heuristic_type(CREDIT_CARD_EXP_MONTH); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel-area-code") |
| + return PHONE_HOME_CITY_CODE; |
| - if (autocomplete_type == ASCIIToUTF16("cc-exp-year")) { |
| - if (field->max_length == 2) |
| - field->set_heuristic_type(CREDIT_CARD_EXP_2_DIGIT_YEAR); |
| - else |
| - field->set_heuristic_type(CREDIT_CARD_EXP_4_DIGIT_YEAR); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel-local") |
| + return PHONE_HOME_NUMBER; |
| - if (autocomplete_type == ASCIIToUTF16("cc-exp")) { |
| - if (field->max_length == 5) |
| - field->set_heuristic_type(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR); |
| - else |
| - field->set_heuristic_type(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR); |
| - return true; |
| - } |
| + if (autocomplete_type == "tel-local-prefix") |
| + return PHONE_HOME_NUMBER; |
| - return false; |
| + if (autocomplete_type == "tel-local-suffix") |
| + return PHONE_HOME_NUMBER; |
| + |
| + if (autocomplete_type == "email") |
| + return EMAIL_ADDRESS; |
| + |
| + return UNKNOWN_TYPE; |
| } |
| } // namespace |
| @@ -273,12 +264,13 @@ FormStructure::FormStructure(const FormData& form) |
| FormStructure::~FormStructure() {} |
| void FormStructure::DetermineHeuristicTypes() { |
| - // 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. |
| + // First, try to detect field types based on each field's |autocomplete| |
| + // attribute value. If there is at least one form field that specifies an |
| + // autocomplete type hint, don't try to apply other heuristics to match fields |
| + // in this form. |
| bool has_author_specified_sections; |
| - ParseAutocompletetypeAttributes(&has_author_specified_types_, |
| - &has_author_specified_sections); |
| + ParseFieldTypesFromAutocompleteAttributes(&has_author_specified_types_, |
| + &has_author_specified_sections); |
| if (!has_author_specified_types_) { |
| FieldTypeMap field_type_map; |
| @@ -549,7 +541,7 @@ bool FormStructure::ShouldBeParsed(bool require_method_post) const { |
| bool has_text_field = false; |
| for (std::vector<AutofillField*>::const_iterator it = begin(); |
| it != end() && !has_text_field; ++it) { |
| - has_text_field |= (*it)->form_control_type != ASCIIToUTF16("select-one"); |
| + has_text_field |= (*it)->form_control_type != "select-one"; |
| } |
| if (!has_text_field) |
| return false; |
| @@ -576,7 +568,7 @@ void FormStructure::UpdateFromCache(const FormStructure& cached_form) { |
| std::map<std::string, const AutofillField*>::const_iterator |
| cached_field = cached_fields.find(field->FieldSignature()); |
| if (cached_field != cached_fields.end()) { |
| - if (field->form_control_type != ASCIIToUTF16("select-one") && |
| + if (field->form_control_type != "select-one" && |
| field->value == cached_field->second->value) { |
| // From the perspective of learning user data, text fields containing |
| // default values are equivalent to empty fields. |
| @@ -694,7 +686,7 @@ void FormStructure::LogQualityMetrics( |
| // TODO(isherman): <select> fields don't support |is_autofilled()|, so we |
| // have to skip them for the remaining metrics. |
| - if (field->form_control_type == ASCIIToUTF16("select-one")) |
| + if (field->form_control_type == "select-one") |
| continue; |
| if (field->is_autofilled) { |
| @@ -878,32 +870,111 @@ bool FormStructure::EncodeFormRequest( |
| return true; |
| } |
| -void FormStructure::ParseAutocompletetypeAttributes(bool* found_attribute, |
| - bool* found_sections) { |
| - *found_attribute = false; |
| +void FormStructure::ParseFieldTypesFromAutocompleteAttributes( |
| + bool* found_types, |
| + bool* found_sections) { |
| + const std::string kDefaultSection = "-default"; |
| + |
| + *found_types = false; |
| *found_sections = false; |
| - for (std::vector<AutofillField*>::iterator field = fields_.begin(); |
| - field != fields_.end(); ++field) { |
| - if ((*field)->autocomplete_type.empty()) |
| + for (std::vector<AutofillField*>::iterator it = fields_.begin(); |
| + it != fields_.end(); ++it) { |
| + AutofillField* field = *it; |
| + |
| + // To prevent potential section name collisions, add a default suffix for |
| + // other fields. Without this, 'autocomplete' attribute values |
| + // "section--shipping street-address" and "shipping street-address" would be |
| + // parsed identically, given the section handling code below. We do this |
| + // before any validation so that fields with invalid attributes still end up |
| + // in the default section. These default section names will be overridden |
| + // by subsequent heuristic parsing steps if there are no author-specified |
| + // section names. |
| + field->set_section(kDefaultSection); |
| + |
| + // Canonicalize the attribute value by trimming whitespace, collapsing |
| + // non-space characters (e.g. tab) to spaces, and converting to lowercase. |
| + std::string autocomplete_attribute = |
| + CollapseWhitespaceASCII(field->autocomplete_attribute, false); |
| + autocomplete_attribute = StringToLowerASCII(autocomplete_attribute); |
| + |
| + // The autocomplete attribute is overloaded: it can specify either a field |
| + // type hint or whether autocomplete should be enabled at all. Ignore the |
| + // latter type of attribute value. |
| + if (autocomplete_attribute.empty() || |
| + autocomplete_attribute == "on" || |
| + autocomplete_attribute == "off") { |
| + continue; |
| + } |
| + |
| + // Any other value, even it is invalid, is considered to be a type hint. |
| + // This allows a website's author to specify an attribute like |
| + // autocomplete="other" on a field to disable all Autofill heuristics for |
| + // the form. |
| + *found_types = true; |
| + |
| + // Tokenize the attribute value. Per the spec, the tokens are parsed in |
| + // reverse order. |
| + std::vector<std::string> tokens; |
| + Tokenize(autocomplete_attribute, " ", &tokens); |
| + |
| + // The final token must be the field type. |
| + // If it is not one of the known types, abort. |
| + DCHECK(!tokens.empty()); |
| + std::string field_type_token = tokens.back(); |
| + tokens.pop_back(); |
| + AutofillFieldType field_type = |
| + FieldTypeFromAutocompleteType(field_type_token, *field); |
| + if (field_type == UNKNOWN_TYPE) |
| continue; |
| - *found_attribute = true; |
| - std::vector<string16> types; |
| - Tokenize((*field)->autocomplete_type, ASCIIToUTF16(" "), &types); |
| + // The preceding token, if any, may be a type hint. |
| + if (!tokens.empty() && IsContactTypeHint(tokens.back())) { |
| + // If it is, it must match the field type; otherwise, abort. |
| + // Note that an invalid token invalidates the entire attribute value, even |
| + // if the other tokens are valid. |
| + if (!ContactTypeHintMatchesFieldType(tokens.back(), field_type)) |
| + continue; |
| - // 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())); |
| + // Chrome Autofill ignores these type hints. |
| + tokens.pop_back(); |
| } |
| - // Look for specified types. |
| - for (std::vector<string16>::const_iterator type = types.begin(); |
| - type != types.end(); ++type) { |
| - if (UpdateFromAutocompleteType(*type, *field)) |
| - break; |
| + // The preceding token, if any, may be a fixed string that is either |
| + // "shipping" or "billing". Chrome Autofill treats these as implicit |
| + // section name suffixes. |
| + DCHECK_EQ(kDefaultSection, field->section()); |
| + std::string section = field->section(); |
| + if (!tokens.empty() && |
| + (tokens.back() == "shipping" || tokens.back() == "billing")) { |
| + section = "-" + tokens.back(); |
| + tokens.pop_back(); |
| + } |
| + |
| + // The preceding token, if any, may be a named section. |
| + const std::string kSectionPrefix = "section-"; |
| + if (!tokens.empty() && |
| + StartsWithASCII(tokens.back(), kSectionPrefix, true)) { |
| + // Prepend this section name to the suffix set in the preceding block. |
| + section = tokens.back().substr(kSectionPrefix.size()) + section; |
| + tokens.pop_back(); |
| } |
| + |
| + // No other tokens are allowed. If there are any remaining, abort. |
| + if (!tokens.empty()) |
| + continue; |
| + |
| + if (section != kDefaultSection) { |
|
Dan Beam
2012/10/23 00:28:59
so autocomplete="section- address" means there's *
Ilya Sherman
2012/10/23 01:06:12
Yeah, that's what would currently happen. Honestl
|
| + *found_sections = true; |
| + field->set_section(section); |
| + } |
| + |
| + // No errors encountered while parsing! |
| + // Update the |field|'s type based on what was parsed from the attribute. |
| + field->set_heuristic_type(field_type); |
| + if (field_type_token == "tel-local-prefix") |
| + field->set_phone_part(AutofillField::PHONE_PREFIX); |
| + else if (field_type_token == "tel-local-suffix") |
| + field->set_phone_part(AutofillField::PHONE_SUFFIX); |
| } |
| } |
| @@ -953,7 +1024,7 @@ void FormStructure::IdentifySections(bool has_author_specified_sections) { |
| } |
| seen_types.insert(current_type); |
| - (*field)->set_section(current_section); |
| + (*field)->set_section(UTF16ToUTF8(current_section)); |
| } |
| } |
| @@ -964,8 +1035,8 @@ void FormStructure::IdentifySections(bool has_author_specified_sections) { |
| AutofillType::FieldTypeGroup field_type_group = |
| AutofillType((*field)->type()).group(); |
| if (field_type_group == AutofillType::CREDIT_CARD) |
| - (*field)->set_section((*field)->section() + ASCIIToUTF16("-cc")); |
| + (*field)->set_section((*field)->section() + "-cc"); |
| else |
| - (*field)->set_section((*field)->section() + ASCIIToUTF16("-default")); |
| + (*field)->set_section((*field)->section() + "-default"); |
| } |
| } |