summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-10 05:38:45 +0000
committerisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-10 05:38:45 +0000
commit1b5f69a1bc11fe1670f994c80a2ae32247c8e549 (patch)
tree3939ba8519f83b4cabd6cef81bad92f309ce9078
parentd8cef10c18dd5378ada0acc799c754ae39c06bd0 (diff)
downloadchromium_src-1b5f69a1bc11fe1670f994c80a2ae32247c8e549.zip
chromium_src-1b5f69a1bc11fe1670f994c80a2ae32247c8e549.tar.gz
chromium_src-1b5f69a1bc11fe1670f994c80a2ae32247c8e549.tar.bz2
Add preliminary Autofill support for the 'autocompletetype' attribute.
BUG=92121 TEST=unit_tests --gtest_filter=FormStructureTest.*Autocompletetype*; browser_tests --gtest_filter=FormManagerTest.WebFormControlElementToFormFieldAutocompletetype Review URL: http://codereview.chromium.org/7602006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96131 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/autofill/autofill_manager.cc10
-rw-r--r--chrome/browser/autofill/form_structure.cc195
-rw-r--r--chrome/browser/autofill/form_structure.h9
-rw-r--r--chrome/browser/autofill/form_structure_unittest.cc131
-rw-r--r--chrome/common/autofill_messages.h1
-rw-r--r--chrome/renderer/autofill/form_manager.cc8
-rw-r--r--chrome/renderer/autofill/form_manager_browsertest.cc130
-rw-r--r--chrome/test/data/autofill/heuristics/input/01_autocompletetype_attribute.html29
-rw-r--r--chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out27
-rw-r--r--webkit/glue/form_field.cc3
-rw-r--r--webkit/glue/form_field.h2
11 files changed, 535 insertions, 10 deletions
diff --git a/chrome/browser/autofill/autofill_manager.cc b/chrome/browser/autofill/autofill_manager.cc
index ce59133..d8c025b 100644
--- a/chrome/browser/autofill/autofill_manager.cc
+++ b/chrome/browser/autofill/autofill_manager.cc
@@ -423,8 +423,10 @@ void AutofillManager::OnFormSubmitted(const FormData& form) {
if (!personal_data_->profiles().empty() ||
!personal_data_->credit_cards().empty()) {
DeterminePossibleFieldTypesForUpload(&submitted_form);
- UploadFormData(submitted_form);
submitted_form.LogQualityMetrics(*metric_logger_);
+
+ if (submitted_form.ShouldBeCrowdsourced())
+ UploadFormData(submitted_form);
}
if (!submitted_form.IsAutofillable(true))
@@ -1097,9 +1099,9 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) {
form_structure->DetermineHeuristicTypes();
- // Set aside forms with method GET so that they are not included in the
- // query to the server.
- if (form_structure->ShouldBeParsed(true))
+ // Set aside forms with method GET or author-specified types, so that they
+ // are not included in the query to the server.
+ if (form_structure->ShouldBeCrowdsourced())
form_structures_.push_back(form_structure.release());
else
non_queryable_forms.push_back(form_structure.release());
diff --git a/chrome/browser/autofill/form_structure.cc b/chrome/browser/autofill/form_structure.cc
index 3f2eb00..b9e0a2e 100644
--- a/chrome/browser/autofill/form_structure.cc
+++ b/chrome/browser/autofill/form_structure.cc
@@ -4,6 +4,8 @@
#include "chrome/browser/autofill/form_structure.h"
+#include <utility>
+
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/sha1.h"
@@ -85,6 +87,177 @@ std::string EncodeFieldTypes(const FieldTypeSet& available_field_types) {
return data_presence;
}
+bool ConvertToAutofillFieldType(const AutofillField& field,
+ const string16& autocomplete_type,
+ AutofillFieldType* autofill_type) {
+ if (autocomplete_type == ASCIIToUTF16("given-name")) {
+ *autofill_type = NAME_FIRST;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("middle-name")) {
+ *autofill_type = NAME_MIDDLE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("middle-initial")) {
+ *autofill_type = NAME_MIDDLE_INITIAL;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("surname")) {
+ *autofill_type = NAME_LAST;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("full-name")) {
+ *autofill_type = NAME_FULL;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("street-address") ||
+ autocomplete_type == ASCIIToUTF16("address-line1")) {
+ *autofill_type = ADDRESS_HOME_LINE1;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("address-line2")) {
+ *autofill_type = ADDRESS_HOME_LINE2;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("locality")) {
+ *autofill_type = ADDRESS_HOME_CITY;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("administrative-area")) {
+ *autofill_type = ADDRESS_HOME_STATE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("postal-code")) {
+ *autofill_type = ADDRESS_HOME_ZIP;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("country")) {
+ *autofill_type = ADDRESS_HOME_COUNTRY;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("organization")) {
+ *autofill_type = COMPANY_NAME;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("email")) {
+ *autofill_type = EMAIL_ADDRESS;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("phone-full")) {
+ *autofill_type = PHONE_HOME_WHOLE_NUMBER;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("phone-country-code")) {
+ *autofill_type = PHONE_HOME_COUNTRY_CODE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("phone-city-code")) {
+ *autofill_type = PHONE_HOME_CITY_CODE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("phone-number")) {
+ *autofill_type = PHONE_HOME_NUMBER;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("fax-full")) {
+ *autofill_type = PHONE_FAX_WHOLE_NUMBER;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("fax-country-code")) {
+ *autofill_type = PHONE_FAX_COUNTRY_CODE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("fax-city-code")) {
+ *autofill_type = PHONE_FAX_CITY_CODE;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("fax-number")) {
+ *autofill_type = PHONE_FAX_NUMBER;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("cc-full-name")) {
+ *autofill_type = CREDIT_CARD_NAME;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("cc-number")) {
+ *autofill_type = CREDIT_CARD_NUMBER;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("cc-exp-month")) {
+ *autofill_type = CREDIT_CARD_EXP_MONTH;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("cc-exp-year")) {
+ if (field.max_length == 2)
+ *autofill_type = CREDIT_CARD_EXP_2_DIGIT_YEAR;
+ else
+ *autofill_type = CREDIT_CARD_EXP_4_DIGIT_YEAR;
+ return true;
+ }
+
+ if (autocomplete_type == ASCIIToUTF16("cc-exp")) {
+ // TODO(isherman): Choose variant based on HTML5 validation regex.
+ *autofill_type = CREDIT_CARD_EXP_DATE_2_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)
@@ -93,7 +266,8 @@ FormStructure::FormStructure(const FormData& form)
target_url_(form.action),
autofill_count_(0),
upload_required_(USE_UPLOAD_RATES),
- server_experiment_id_("no server response") {
+ server_experiment_id_("no server response"),
+ has_author_specified_types_(false) {
// Copy the form fields.
std::vector<webkit_glue::FormField>::const_iterator field;
for (field = form.fields.begin();
@@ -125,7 +299,14 @@ void FormStructure::DetermineHeuristicTypes() {
autofill_count_ = 0;
FieldTypeMap field_type_map;
- FormField::ParseFormFields(fields_.get(), &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);
for (size_t index = 0; index < field_count(); index++) {
AutofillField* field = fields_[index];
@@ -140,8 +321,6 @@ void FormStructure::DetermineHeuristicTypes() {
}
field->set_heuristic_type(heuristic_autofill_type);
-
- AutofillType autofill_type(field->type());
}
}
@@ -149,8 +328,8 @@ bool FormStructure::EncodeUploadRequest(
const FieldTypeSet& available_field_types,
bool form_was_autofilled,
std::string* encoded_xml) const {
- if (!ShouldBeParsed(true)) {
- NOTREACHED(); // Caller should've checked for search pages.
+ if (!ShouldBeCrowdsourced()) {
+ NOTREACHED();
return false;
}
@@ -403,6 +582,10 @@ bool FormStructure::ShouldBeParsed(bool require_method_post) const {
return !require_method_post || (method_ == POST);
}
+bool FormStructure::ShouldBeCrowdsourced() const {
+ return !has_author_specified_types_ && ShouldBeParsed(true);
+}
+
void FormStructure::UpdateFromCache(const FormStructure& cached_form) {
// Map from field signatures to cached fields.
std::map<std::string, const AutofillField*> cached_fields;
diff --git a/chrome/browser/autofill/form_structure.h b/chrome/browser/autofill/form_structure.h
index d94be92..92e4de9 100644
--- a/chrome/browser/autofill/form_structure.h
+++ b/chrome/browser/autofill/form_structure.h
@@ -96,6 +96,11 @@ class FormStructure {
// |require_method_post| is true.
bool ShouldBeParsed(bool require_method_post) const;
+ // Returns true if we should query the crowdsourcing server to determine this
+ // form's field types. If the form includes author-specified types, this will
+ // return false.
+ bool ShouldBeCrowdsourced() const;
+
// Sets the field types and experiment id to be those set for |cached_form|.
void UpdateFromCache(const FormStructure& cached_form);
@@ -181,6 +186,10 @@ class FormStructure {
// GET or POST.
RequestMethod method_;
+ // Whether the form includes any field types explicitly specified by the site
+ // author, via the |autocompletetype| attribute.
+ bool has_author_specified_types_;
+
DISALLOW_COPY_AND_ASSIGN(FormStructure);
};
diff --git a/chrome/browser/autofill/form_structure_unittest.cc b/chrome/browser/autofill/form_structure_unittest.cc
index 4bfeb77..c6ff870 100644
--- a/chrome/browser/autofill/form_structure_unittest.cc
+++ b/chrome/browser/autofill/form_structure_unittest.cc
@@ -339,6 +339,137 @@ TEST(FormStructureTest, HeuristicsContactInfo) {
EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(8)->heuristic_type());
}
+// Verify that we can correctly process the 'autocompletetype' attribute.
+TEST(FormStructureTest, HeuristicsAutocompletetype) {
+ scoped_ptr<FormStructure> form_structure;
+ FormData form;
+ form.method = ASCIIToUTF16("post");
+
+ FormField field;
+ field.form_control_type = ASCIIToUTF16("text");
+
+ field.label = string16();
+ field.name = ASCIIToUTF16("field1");
+ field.autocomplete_type = ASCIIToUTF16("given-name");
+ form.fields.push_back(field);
+
+ field.label = string16();
+ field.name = ASCIIToUTF16("field2");
+ field.autocomplete_type = ASCIIToUTF16("surname");
+ form.fields.push_back(field);
+
+ field.label = string16();
+ field.name = ASCIIToUTF16("field3");
+ field.autocomplete_type = ASCIIToUTF16("email");
+ 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(3U, form_structure->field_count());
+ ASSERT_EQ(3U, form_structure->autofill_count());
+
+ EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type());
+ EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type());
+ EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type());
+}
+
+// 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;
+ FormData form;
+ form.method = ASCIIToUTF16("post");
+
+ // Start with a regular contact form.
+ FormField field;
+ field.form_control_type = ASCIIToUTF16("text");
+
+ field.label = ASCIIToUTF16("First Name");
+ field.name = ASCIIToUTF16("firstname");
+ form.fields.push_back(field);
+
+ field.label = ASCIIToUTF16("Last Name");
+ field.name = ASCIIToUTF16("lastname");
+ form.fields.push_back(field);
+
+ field.label = ASCIIToUTF16("Email");
+ field.name = ASCIIToUTF16("email");
+ form.fields.push_back(field);
+
+ form_structure.reset(new FormStructure(form));
+ form_structure->DetermineHeuristicTypes();
+ EXPECT_TRUE(form_structure->IsAutofillable(true));
+ EXPECT_TRUE(form_structure->ShouldBeCrowdsourced());
+
+ ASSERT_EQ(3U, form_structure->field_count());
+ ASSERT_EQ(3U, form_structure->autofill_count());
+
+ EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type());
+ EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type());
+ EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type());
+
+ // Now update the first form field to include an 'autocompletetype' attribute.
+ form.fields.front().autocomplete_type = ASCIIToUTF16("x-other");
+ form_structure.reset(new FormStructure(form));
+ form_structure->DetermineHeuristicTypes();
+ EXPECT_FALSE(form_structure->IsAutofillable(true));
+ EXPECT_FALSE(form_structure->ShouldBeCrowdsourced());
+
+ ASSERT_EQ(3U, form_structure->field_count());
+ ASSERT_EQ(0U, form_structure->autofill_count());
+
+ EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(0)->heuristic_type());
+ EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(1)->heuristic_type());
+ EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type());
+}
+
+// Verify that we can correctly process fallback types listed in the
+// 'autocompletetype' attribute.
+TEST(FormStructureTest, HeuristicsAutocompletetypeWithFallbacks) {
+ scoped_ptr<FormStructure> form_structure;
+ FormData form;
+ form.method = ASCIIToUTF16("post");
+
+ FormField field;
+ field.form_control_type = ASCIIToUTF16("text");
+
+ // Skip over any sections and "x"-prefixed types.
+ field.label = string16();
+ field.name = ASCIIToUTF16("field1");
+ field.autocomplete_type =
+ ASCIIToUTF16("section-full-name x-given-name-initial given-name");
+ form.fields.push_back(field);
+
+ // Stop processing once we see a known type.
+ field.label = string16();
+ field.name = ASCIIToUTF16("field2");
+ field.autocomplete_type = ASCIIToUTF16("section-full-name surname full-name");
+ form.fields.push_back(field);
+
+ // Skip over unknown types even if they are not prefixed with "x-".
+ field.label = string16();
+ field.name = ASCIIToUTF16("field3");
+ field.autocomplete_type =
+ ASCIIToUTF16("section-shipping mobile-phone-full phone-full");
+ 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(3U, form_structure->field_count());
+ ASSERT_EQ(3U, form_structure->autofill_count());
+
+ EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type());
+ EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type());
+ EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER,
+ form_structure->field(2)->heuristic_type());
+}
+
TEST(FormStructureTest, HeuristicsSample8) {
scoped_ptr<FormStructure> form_structure;
FormData form;
diff --git a/chrome/common/autofill_messages.h b/chrome/common/autofill_messages.h
index 42575bd..a9750ef 100644
--- a/chrome/common/autofill_messages.h
+++ b/chrome/common/autofill_messages.h
@@ -22,6 +22,7 @@ IPC_STRUCT_TRAITS_BEGIN(webkit_glue::FormField)
IPC_STRUCT_TRAITS_MEMBER(name)
IPC_STRUCT_TRAITS_MEMBER(value)
IPC_STRUCT_TRAITS_MEMBER(form_control_type)
+ IPC_STRUCT_TRAITS_MEMBER(autocomplete_type)
IPC_STRUCT_TRAITS_MEMBER(max_length)
IPC_STRUCT_TRAITS_MEMBER(is_autofilled)
IPC_STRUCT_TRAITS_MEMBER(option_values)
diff --git a/chrome/renderer/autofill/form_manager.cc b/chrome/renderer/autofill/form_manager.cc
index e33ef38..3465f93 100644
--- a/chrome/renderer/autofill/form_manager.cc
+++ b/chrome/renderer/autofill/form_manager.cc
@@ -598,6 +598,14 @@ void FormManager::WebFormControlElementToFormField(
// WebFormElementToFormData.
field->name = element.nameForAutofill();
field->form_control_type = element.formControlType();
+ field->autocomplete_type = element.getAttribute("x-autocompletetype");
+ TrimWhitespace(field->autocomplete_type, TRIM_ALL, &field->autocomplete_type);
+ if (field->autocomplete_type.size() > kMaxDataLength) {
+ // Discard overly long attribute values to avoid DOS-ing the browser
+ // process. However, send over a default string to indicate that the
+ // attribute was present.
+ field->autocomplete_type = ASCIIToUTF16("x-max-data-length-exceeded");
+ }
if (!IsAutofillableElement(element))
return;
diff --git a/chrome/renderer/autofill/form_manager_browsertest.cc b/chrome/renderer/autofill/form_manager_browsertest.cc
index f39f050..856cdba 100644
--- a/chrome/renderer/autofill/form_manager_browsertest.cc
+++ b/chrome/renderer/autofill/form_manager_browsertest.cc
@@ -331,6 +331,136 @@ TEST_F(FormManagerTest, WebFormControlElementToFormFieldInvalidType) {
EXPECT_FORM_FIELD_EQUALS(expected, result);
}
+// We should be able to extract the autocompletetype attribute.
+TEST_F(FormManagerTest, WebFormControlElementToFormFieldAutocompletetype) {
+ std::string html =
+ "<INPUT type=\"text\" id=\"absent\"/>"
+ "<INPUT type=\"text\" id=\"empty\" x-autocompletetype=\"\"/>"
+ "<INPUT type=\"text\" id=\"whitespace\" x-autocompletetype=\" \"/>"
+ "<INPUT type=\"text\" id=\"regular\" x-autocompletetype=\"email\"/>"
+ "<INPUT type=\"text\" id=\"multi-valued\" "
+ " x-autocompletetype=\"x-confirm-email email\"/>"
+ "<INPUT type=\"text\" id=\"unprefixed\" autocompletetype=\"email\"/>"
+ "<SELECT id=\"select\" x-autocompletetype=\"state\"/>"
+ " <OPTION value=\"CA\">California</OPTION>"
+ " <OPTION value=\"TX\">Texas</OPTION>"
+ "</SELECT>";
+ html +=
+ "<INPUT type=\"text\" id=\"malicious\" x-autocompletetype=\"" +
+ std::string(10000, 'x') + "\"/>";
+ LoadHTML(html.c_str());
+
+ WebFrame* frame = GetMainFrame();
+ ASSERT_NE(static_cast<WebFrame*>(NULL), frame);
+
+ // An absent attribute is equivalent to an empty one.
+ WebElement web_element = frame->document().getElementById("absent");
+ WebFormControlElement element = web_element.to<WebFormControlElement>();
+ FormField result1;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result1);
+
+ FormField expected;
+ expected.name = ASCIIToUTF16("absent");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = string16();
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result1);
+
+ web_element = frame->document().getElementById("empty");
+ element = web_element.to<WebFormControlElement>();
+ FormField result2;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result2);
+ expected.name = ASCIIToUTF16("empty");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = string16();
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result2);
+
+ // The renderer should trim whitespace.
+ web_element = frame->document().getElementById("whitespace");
+ element = web_element.to<WebFormControlElement>();
+ FormField result3;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result3);
+ expected.name = ASCIIToUTF16("whitespace");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = string16();
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result3);
+
+ // Common case: exactly one type specified.
+ web_element = frame->document().getElementById("regular");
+ element = web_element.to<WebFormControlElement>();
+ FormField result4;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result4);
+ expected.name = ASCIIToUTF16("regular");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = ASCIIToUTF16("email");
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result4);
+
+ // Verify that we correctly extract fallback types as well.
+ web_element = frame->document().getElementById("multi-valued");
+ element = web_element.to<WebFormControlElement>();
+ FormField result5;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result5);
+ expected.name = ASCIIToUTF16("multi-valued");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = ASCIIToUTF16("x-confirm-email email");
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result5);
+
+ // The attribute is not yet part of the HTML standard, so we only recognize
+ // the prefixed version -- 'x-autocompletetype' -- and not the unprefixed one.
+ web_element = frame->document().getElementById("unprefixed");
+ element = web_element.to<WebFormControlElement>();
+ FormField result6;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result6);
+ expected.name = ASCIIToUTF16("unprefixed");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = string16();
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result6);
+
+ // <select> elements should behave no differently from text fields here.
+ web_element = frame->document().getElementById("select");
+ element = web_element.to<WebFormControlElement>();
+ FormField result7;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result7);
+ expected.name = ASCIIToUTF16("select");
+ expected.form_control_type = ASCIIToUTF16("select-one");
+ expected.autocomplete_type = ASCIIToUTF16("state");
+ expected.max_length = 0;
+ EXPECT_FORM_FIELD_EQUALS(expected, result7);
+
+ // Very long attribute values should be replaced by a default string, to
+ // prevent malicious websites from DOSing the browser process.
+ web_element = frame->document().getElementById("malicious");
+ element = web_element.to<WebFormControlElement>();
+ FormField result8;
+ FormManager::WebFormControlElementToFormField(element,
+ FormManager::EXTRACT_NONE,
+ &result8);
+ expected.name = ASCIIToUTF16("malicious");
+ expected.form_control_type = ASCIIToUTF16("text");
+ expected.autocomplete_type = ASCIIToUTF16("x-max-data-length-exceeded");
+ expected.max_length = WebInputElement::defaultMaxLength();
+ EXPECT_FORM_FIELD_EQUALS(expected, result8);
+}
+
TEST_F(FormManagerTest, WebFormElementToFormData) {
LoadHTML("<FORM name=\"TestForm\" action=\"http://cnn.com\" method=\"post\">"
" <INPUT type=\"text\" id=\"firstname\" value=\"John\"/>"
diff --git a/chrome/test/data/autofill/heuristics/input/01_autocompletetype_attribute.html b/chrome/test/data/autofill/heuristics/input/01_autocompletetype_attribute.html
new file mode 100644
index 0000000..8413064
--- /dev/null
+++ b/chrome/test/data/autofill/heuristics/input/01_autocompletetype_attribute.html
@@ -0,0 +1,29 @@
+<form action="autocompletetype.html" method="post">
+ <input type="text" id="fn" x-autocompletetype="given-name"><br>
+ <input type="text" id="mn" x-autocompletetype="middle-name"><br>
+ <input type="text" id="mi" x-autocompletetype="middle-initial"><br>
+ <input type="text" id="ln" x-autocompletetype="surname"><br>
+ <input type="text" id="nn" x-autocompletetype="full-name"><br>
+ <input type="text" id="cm" x-autocompletetype="organization"><br>
+ <input type="text" id="af" x-autocompletetype="street-address"><br>
+ <input type="text" id="a1" x-autocompletetype="address-line1"><br>
+ <input type="text" id="a2" x-autocompletetype="address-line2"><br>
+ <input type="text" id="ct" x-autocompletetype="locality"><br>
+ <input type="text" id="zc" x-autocompletetype="postal-code"><br>
+ <input type="text" id="st" x-autocompletetype="administrative-area"><br>
+ <input type="text" id="em" x-autocompletetype="email"><br>
+ <input type="text" id="pf" x-autocompletetype="phone-full"><br>
+ <input type="text" id="pc" x-autocompletetype="phone-country-code"><br>
+ <input type="text" id="pa" x-autocompletetype="phone-city-code"><br>
+ <input type="text" id="pn" x-autocompletetype="phone-number"><br>
+ <input type="text" id="ff" x-autocompletetype="fax-full"><br>
+ <input type="text" id="fc" x-autocompletetype="fax-country-code"><br>
+ <input type="text" id="fa" x-autocompletetype="fax-city-code"><br>
+ <input type="text" id="fx" x-autocompletetype="fax-number"><br>
+ <input type="text" id="c1" x-autocompletetype="cc-full-name"><br>
+ <input type="text" id="c2" x-autocompletetype="cc-number"><br>
+ <input type="text" id="c3" x-autocompletetype="cc-exp-month"><br>
+ <input type="text" id="c4" x-autocompletetype="cc-exp-year"><br>
+ <input type="text" id="c5" x-autocompletetype="cc-exp-year" maxlength="2"><br>
+ <input type="text" id="c6" x-autocompletetype="cc-exp"><br>
+</form>
diff --git a/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out b/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out
new file mode 100644
index 0000000..f9e2358
--- /dev/null
+++ b/chrome/test/data/autofill/heuristics/output/01_autocompletetype_attribute.out
@@ -0,0 +1,27 @@
+NAME_FIRST | fn | |
+NAME_MIDDLE | mn | |
+NAME_MIDDLE_INITIAL | mi | |
+NAME_LAST | ln | |
+NAME_FULL | nn | |
+COMPANY_NAME | cm | |
+ADDRESS_HOME_LINE1 | af | |
+ADDRESS_HOME_LINE1 | a1 | |
+ADDRESS_HOME_LINE2 | a2 | |
+ADDRESS_HOME_CITY | ct | |
+ADDRESS_HOME_ZIP | zc | |
+ADDRESS_HOME_STATE | st | |
+EMAIL_ADDRESS | em | |
+PHONE_HOME_WHOLE_NUMBER | pf | |
+PHONE_HOME_COUNTRY_CODE | pc | |
+PHONE_HOME_CITY_CODE | pa | |
+PHONE_HOME_NUMBER | pn | |
+PHONE_FAX_WHOLE_NUMBER | ff | |
+PHONE_FAX_COUNTRY_CODE | fc | |
+PHONE_FAX_CITY_CODE | fa | |
+PHONE_FAX_NUMBER | fx | |
+CREDIT_CARD_NAME | c1 | |
+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 | |
diff --git a/webkit/glue/form_field.cc b/webkit/glue/form_field.cc
index 798c5b2..6c8faff 100644
--- a/webkit/glue/form_field.cc
+++ b/webkit/glue/form_field.cc
@@ -33,6 +33,7 @@ bool FormField::operator==(const FormField& field) const {
return (label == field.label &&
name == field.name &&
form_control_type == field.form_control_type &&
+ autocomplete_type == field.autocomplete_type &&
max_length == field.max_length);
}
@@ -50,6 +51,8 @@ std::ostream& operator<<(std::ostream& os, const FormField& field) {
<< " "
<< UTF16ToUTF8(field.form_control_type)
<< " "
+ << UTF16ToUTF8(field.autocomplete_type)
+ << " "
<< field.max_length
<< " "
<< (field.is_autofilled ? "true" : "false");
diff --git a/webkit/glue/form_field.h b/webkit/glue/form_field.h
index 622dabf..bde8a53 100644
--- a/webkit/glue/form_field.h
+++ b/webkit/glue/form_field.h
@@ -28,6 +28,7 @@ struct FormField {
string16 name;
string16 value;
string16 form_control_type;
+ string16 autocomplete_type;
int max_length;
bool is_autofilled;
@@ -50,6 +51,7 @@ std::ostream& operator<<(std::ostream& os, const FormField& field);
EXPECT_EQ(expected.name, actual.name); \
EXPECT_EQ(expected.value, actual.value); \
EXPECT_EQ(expected.form_control_type, actual.form_control_type); \
+ EXPECT_EQ(expected.autocomplete_type, actual.autocomplete_type); \
EXPECT_EQ(expected.max_length, actual.max_length); \
EXPECT_EQ(expected.is_autofilled, actual.is_autofilled); \
} while (0)