Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Side by Side Diff: components/autofill/content/renderer/password_autofill_agent.cc

Issue 1292693004: [Password Manager] Autofill forms with field name and id attributes missing. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addresses Vadym's inputs. Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/test/data/password/ambiguous_password_form.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/autofill/content/renderer/password_autofill_agent.h" 5 #include "components/autofill/content/renderer/password_autofill_agent.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/i18n/case_conversion.h" 9 #include "base/i18n/case_conversion.h"
10 #include "base/memory/scoped_ptr.h" 10 #include "base/memory/scoped_ptr.h"
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
47 47
48 // The size above which we stop triggering autocomplete. 48 // The size above which we stop triggering autocomplete.
49 static const size_t kMaximumTextSizeForAutocomplete = 1000; 49 static const size_t kMaximumTextSizeForAutocomplete = 1000;
50 50
51 // Experiment information 51 // Experiment information
52 const char kFillOnAccountSelectFieldTrialName[] = "FillOnAccountSelect"; 52 const char kFillOnAccountSelectFieldTrialName[] = "FillOnAccountSelect";
53 const char kFillOnAccountSelectFieldTrialEnabledWithHighlightGroup[] = 53 const char kFillOnAccountSelectFieldTrialEnabledWithHighlightGroup[] =
54 "EnableWithHighlight"; 54 "EnableWithHighlight";
55 const char kFillOnAccountSelectFieldTrialEnabledWithNoHighlightGroup[] = 55 const char kFillOnAccountSelectFieldTrialEnabledWithNoHighlightGroup[] =
56 "EnableWithNoHighlight"; 56 "EnableWithNoHighlight";
57 const char kDummyUsernameField[] = "anonymous_username";
58 const char kDummyPasswordField[] = "anonymous_password";
57 59
58 // Maps element names to the actual elements to simplify form filling. 60 // Maps element names to the actual elements to simplify form filling.
59 typedef std::map<base::string16, blink::WebInputElement> FormInputElementMap; 61 typedef std::map<base::string16, blink::WebInputElement> FormInputElementMap;
60 62
61 // Use the shorter name when referencing SavePasswordProgressLogger::StringID 63 // Use the shorter name when referencing SavePasswordProgressLogger::StringID
62 // values to spare line breaks. The code provides enough context for that 64 // values to spare line breaks. The code provides enough context for that
63 // already. 65 // already.
64 typedef SavePasswordProgressLogger Logger; 66 typedef SavePasswordProgressLogger Logger;
65 67
66 typedef std::vector<FormInputElementMap> FormElementsList; 68 typedef std::vector<FormInputElementMap> FormElementsList;
(...skipping 10 matching lines...) Expand all
77 const std::vector<blink::WebFormControlElement>& control_elements, 79 const std::vector<blink::WebFormControlElement>& control_elements,
78 const base::string16& name) { 80 const base::string16& name) {
79 for (size_t i = 0; i < control_elements.size(); ++i) { 81 for (size_t i = 0; i < control_elements.size(); ++i) {
80 if (control_elements[i].nameForAutofill() == name) { 82 if (control_elements[i].nameForAutofill() == name) {
81 return IsWebNodeVisible(control_elements[i]); 83 return IsWebNodeVisible(control_elements[i]);
82 } 84 }
83 } 85 }
84 return false; 86 return false;
85 } 87 }
86 88
89 // Returns true if password form has username and password fields with either
90 // same or no name and id attributes supplied.
91 bool PasswordFormWithAmbiguousOrNoNameAndIdAttribute(
92 const PasswordFormFillData& fill_data) {
93 return (fill_data.username_field.name == fill_data.password_field.name) ||
94 (fill_data.username_field.name ==
95 base::ASCIIToUTF16(kDummyUsernameField) &&
96 fill_data.password_field.name ==
97 base::ASCIIToUTF16(kDummyPasswordField));
98 }
99
100 bool IsPasswordField(const FormFieldData& field) {
101 return (field.form_control_type == "password");
102 }
103
104 // Returns the |field|'s autofillable name. If no name or id attribute is
105 // specified returns a dummy name.
106 base::string16 FieldName(const FormFieldData& field,
107 bool ambiguous_or_no_name_and_id_attribute) {
108 return ambiguous_or_no_name_and_id_attribute
109 ? IsPasswordField(field) ? base::ASCIIToUTF16(kDummyPasswordField)
110 : base::ASCIIToUTF16(kDummyUsernameField)
111 : field.name;
112 }
113
87 // Utility function to find the unique entry of |control_elements| for the 114 // Utility function to find the unique entry of |control_elements| for the
88 // specified input |field|. On successful find, adds it to |result| and returns 115 // specified input |field|. On successful find, adds it to |result| and returns
89 // |true|. Otherwise clears the references from each |HTMLInputElement| from 116 // |true|. Otherwise clears the references from each |HTMLInputElement| from
90 // |result| and returns |false|. 117 // |result| and returns |false|.
91 bool FindFormInputElement( 118 bool FindFormInputElement(
92 const std::vector<blink::WebFormControlElement>& control_elements, 119 const std::vector<blink::WebFormControlElement>& control_elements,
93 const FormFieldData& field, 120 const FormFieldData& field,
121 bool ambiguous_or_no_name_and_id_attribute,
94 FormInputElementMap* result) { 122 FormInputElementMap* result) {
95 // Match the first input element, if any. 123 // Match the first input element, if any.
96 // If more than one match is made, then we have ambiguity (due to misuse
97 // of "name" attribute) so is it considered not found.
98 bool found_input = false; 124 bool found_input = false;
125 base::string16 field_name =
126 FieldName(field, ambiguous_or_no_name_and_id_attribute);
99 for (size_t i = 0; i < control_elements.size(); ++i) { 127 for (size_t i = 0; i < control_elements.size(); ++i) {
100 if (control_elements[i].nameForAutofill() != field.name) 128 if (!control_elements[i].hasHTMLTagName("input"))
101 continue; 129 continue;
102 130
103 if (!control_elements[i].hasHTMLTagName("input")) 131 // Only fill saved passwords into password fields and usernames into text
132 // fields.
133 const blink::WebInputElement input_element =
134 control_elements[i].toConst<blink::WebInputElement>();
135 bool is_password_field = IsPasswordField(field);
136 if (input_element.isPasswordField() != is_password_field)
104 continue; 137 continue;
105 138
139 // For change password form keep only the first password field entry.
dvadym 2015/09/11 14:15:34 Could you please test case when we have more than
Pritam Nikam 2015/09/12 06:51:44 Done. Added a test: PasswordManagerBrowserTestBas
140 FormInputElementMap::const_iterator old_entry = result->find(field_name);
dvadym 2015/09/11 14:15:34 It seems that we don't need to make look-up in a m
Pritam Nikam 2015/09/12 06:51:44 Done.
141 if (ambiguous_or_no_name_and_id_attribute && is_password_field &&
142 old_entry != result->end() && old_entry->second.isPasswordField()) {
143 continue;
144 }
145
106 // Check for a non-unique match. 146 // Check for a non-unique match.
107 if (found_input) { 147 if (found_input) {
108 found_input = false; 148 found_input = false;
109 break; 149 break;
110 } 150 }
111 151
112 // Only fill saved passwords into password fields and usernames into 152 (*result)[field_name] = input_element;
113 // text fields.
114 const blink::WebInputElement input_element =
115 control_elements[i].toConst<blink::WebInputElement>();
116 if (input_element.isPasswordField() !=
117 (field.form_control_type == "password"))
118 continue;
119
120 (*result)[field.name] = input_element;
121 found_input = true; 153 found_input = true;
122 } 154 }
123 155
124 // A required element was not found. This is not the right form. 156 // A required element was not found. This is not the right form.
125 // Make sure no input elements from a partially matched form in this 157 // Make sure no input elements from a partially matched form in this
126 // iteration remain in the result set. 158 // iteration remain in the result set.
127 // Note: clear will remove a reference from each InputElement. 159 // Note: clear will remove a reference from each InputElement.
128 if (!found_input) { 160 if (!found_input) {
129 result->clear(); 161 result->clear();
130 return false; 162 return false;
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
172 204
173 return group_name != 205 return group_name !=
174 kFillOnAccountSelectFieldTrialEnabledWithNoHighlightGroup; 206 kFillOnAccountSelectFieldTrialEnabledWithNoHighlightGroup;
175 } 207 }
176 208
177 // Helper to search through |control_elements| for the specified input elements 209 // Helper to search through |control_elements| for the specified input elements
178 // in |data|, and add results to |result|. 210 // in |data|, and add results to |result|.
179 bool FindFormInputElements( 211 bool FindFormInputElements(
180 const std::vector<blink::WebFormControlElement>& control_elements, 212 const std::vector<blink::WebFormControlElement>& control_elements,
181 const PasswordFormFillData& data, 213 const PasswordFormFillData& data,
214 bool ambiguous_or_no_name_and_id_attribute,
182 FormInputElementMap* result) { 215 FormInputElementMap* result) {
183 return FindFormInputElement(control_elements, data.password_field, result) && 216 return FindFormInputElement(control_elements, data.password_field,
184 (!FillDataContainsFillableUsername(data) || 217 ambiguous_or_no_name_and_id_attribute, result) &&
185 FindFormInputElement(control_elements, data.username_field, result)); 218 (!(ambiguous_or_no_name_and_id_attribute ||
219 FillDataContainsFillableUsername(data)) ||
220 FindFormInputElement(control_elements, data.username_field,
221 ambiguous_or_no_name_and_id_attribute, result));
186 } 222 }
187 223
188 // Helper to locate form elements identified by |data|. 224 // Helper to locate form elements identified by |data|.
189 void FindFormElements(content::RenderFrame* render_frame, 225 void FindFormElements(content::RenderFrame* render_frame,
190 const PasswordFormFillData& data, 226 const PasswordFormFillData& data,
227 bool ambiguous_or_no_name_and_id_attribute,
191 FormElementsList* results) { 228 FormElementsList* results) {
192 DCHECK(results); 229 DCHECK(results);
193 230
194 blink::WebDocument doc = render_frame->GetWebFrame()->document(); 231 blink::WebDocument doc = render_frame->GetWebFrame()->document();
195 if (!doc.isHTMLDocument()) 232 if (!doc.isHTMLDocument())
196 return; 233 return;
197 234
198 if (data.origin != GetCanonicalOriginForDocument(doc)) 235 if (data.origin != GetCanonicalOriginForDocument(doc))
199 return; 236 return;
200 237
201 blink::WebVector<blink::WebFormElement> forms; 238 blink::WebVector<blink::WebFormElement> forms;
202 doc.forms(forms); 239 doc.forms(forms);
203 240
204 for (size_t i = 0; i < forms.size(); ++i) { 241 for (size_t i = 0; i < forms.size(); ++i) {
205 blink::WebFormElement fe = forms[i]; 242 blink::WebFormElement fe = forms[i];
206 243
207 // Action URL must match. 244 // Action URL must match.
208 if (data.action != GetCanonicalActionForForm(fe)) 245 if (data.action != GetCanonicalActionForForm(fe))
209 continue; 246 continue;
210 247
211 std::vector<blink::WebFormControlElement> control_elements = 248 std::vector<blink::WebFormControlElement> control_elements =
212 ExtractAutofillableElementsInForm(fe); 249 ExtractAutofillableElementsInForm(fe);
213 FormInputElementMap cur_map; 250 FormInputElementMap cur_map;
214 if (FindFormInputElements(control_elements, data, &cur_map)) 251 if (FindFormInputElements(control_elements, data,
252 ambiguous_or_no_name_and_id_attribute, &cur_map))
215 results->push_back(cur_map); 253 results->push_back(cur_map);
216 } 254 }
217 // If the element to be filled are not in a <form> element, the "action" and 255 // If the element to be filled are not in a <form> element, the "action" and
218 // origin should be the same. 256 // origin should be the same.
219 if (data.action != data.origin) 257 if (data.action != data.origin)
220 return; 258 return;
221 259
222 std::vector<blink::WebFormControlElement> control_elements = 260 std::vector<blink::WebFormControlElement> control_elements =
223 GetUnownedAutofillableFormFieldElements(doc.all(), nullptr); 261 GetUnownedAutofillableFormFieldElements(doc.all(), nullptr);
224 FormInputElementMap unowned_elements_map; 262 FormInputElementMap unowned_elements_map;
225 if (FindFormInputElements(control_elements, data, &unowned_elements_map)) 263 if (FindFormInputElements(control_elements, data,
264 ambiguous_or_no_name_and_id_attribute,
265 &unowned_elements_map))
226 results->push_back(unowned_elements_map); 266 results->push_back(unowned_elements_map);
227 } 267 }
228 268
229 bool IsElementEditable(const blink::WebInputElement& element) { 269 bool IsElementEditable(const blink::WebInputElement& element) {
230 return element.isEnabled() && !element.isReadOnly(); 270 return element.isEnabled() && !element.isReadOnly();
231 } 271 }
232 272
233 bool DoUsernamesMatch(const base::string16& username1, 273 bool DoUsernamesMatch(const base::string16& username1,
234 const base::string16& username2, 274 const base::string16& username2,
235 bool exact_match) { 275 bool exact_match) {
(...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after
444 while (cur_frame->parent()) { 484 while (cur_frame->parent()) {
445 cur_frame = cur_frame->parent(); 485 cur_frame = cur_frame->parent();
446 if (!bottom_frame_origin.equals(cur_frame->securityOrigin().toString())) 486 if (!bottom_frame_origin.equals(cur_frame->securityOrigin().toString()))
447 return false; 487 return false;
448 } 488 }
449 489
450 // If we can't modify the password, don't try to set the username 490 // If we can't modify the password, don't try to set the username
451 if (!IsElementAutocompletable(password_element)) 491 if (!IsElementAutocompletable(password_element))
452 return false; 492 return false;
453 493
454 bool form_contains_fillable_username_field = 494 base::string16 username_field_name =
455 FillDataContainsFillableUsername(fill_data); 495 FieldName(fill_data.username_field,
496 PasswordFormWithAmbiguousOrNoNameAndIdAttribute(fill_data));
456 // If the form contains an autocompletable username field, try to set the 497 // If the form contains an autocompletable username field, try to set the
457 // username to the preferred name, but only if: 498 // username to the preferred name, but only if:
458 // (a) The fill-on-account-select flag is not set, and 499 // (a) The fill-on-account-select flag is not set, and
459 // (b) The username element isn't prefilled 500 // (b) The username element isn't prefilled
460 // 501 //
461 // If (a) is false, then just mark the username element as autofilled if the 502 // If (a) is false, then just mark the username element as autofilled if the
462 // user is not in the "no highlighting" group and return so the fill step is 503 // user is not in the "no highlighting" group and return so the fill step is
463 // skipped. 504 // skipped.
464 // 505 //
465 // If there is no autocompletable username field, and (a) is false, then the 506 // If there is no autocompletable username field, and (a) is false, then the
466 // username element cannot be autofilled, but the user should still be able to 507 // username element cannot be autofilled, but the user should still be able to
467 // select to fill the password element, so the password element must be marked 508 // select to fill the password element, so the password element must be marked
468 // as autofilled and the fill step should also be skipped if the user is not 509 // as autofilled and the fill step should also be skipped if the user is not
469 // in the "no highlighting" group. 510 // in the "no highlighting" group.
470 // 511 //
471 // In all other cases, do nothing. 512 // In all other cases, do nothing.
472 bool form_has_fillable_username = form_contains_fillable_username_field && 513 bool form_has_fillable_username = !username_field_name.empty() &&
473 IsElementAutocompletable(username_element); 514 IsElementAutocompletable(username_element);
474 515
475 if (ShouldFillOnAccountSelect()) { 516 if (ShouldFillOnAccountSelect()) {
476 if (!ShouldHighlightFields()) { 517 if (!ShouldHighlightFields()) {
477 return false; 518 return false;
478 } 519 }
479 520
480 if (form_has_fillable_username) { 521 if (form_has_fillable_username) {
481 username_element.setAutofilled(true); 522 username_element.setAutofilled(true);
482 } else if (username_element.isNull() || 523 } else if (username_element.isNull() ||
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
657 698
658 // If wait_for_username is true we will fill when the username loses focus. 699 // If wait_for_username is true we will fill when the username loses focus.
659 if (iter->second.fill_data.wait_for_username) 700 if (iter->second.fill_data.wait_for_username)
660 return false; 701 return false;
661 702
662 if (!element.isText() || !IsElementAutocompletable(element) || 703 if (!element.isText() || !IsElementAutocompletable(element) ||
663 !IsElementAutocompletable(iter->second.password_field)) { 704 !IsElementAutocompletable(iter->second.password_field)) {
664 return false; 705 return false;
665 } 706 }
666 707
667 if (element.nameForAutofill().isEmpty()) 708 if (element.nameForAutofill().isEmpty())
dvadym 2015/09/11 14:15:34 We need to consider how to correctly show password
Pritam Nikam 2015/09/12 06:51:44 Done.
668 return false; // If the field has no name, then we won't have values. 709 return false; // If the field has no name, then we won't have values.
669 710
670 // Don't attempt to autofill with values that are too large. 711 // Don't attempt to autofill with values that are too large.
671 if (element.value().length() > kMaximumTextSizeForAutocomplete) 712 if (element.value().length() > kMaximumTextSizeForAutocomplete)
672 return false; 713 return false;
673 714
674 // Show the popup with the list of available usernames. 715 // Show the popup with the list of available usernames.
675 ShowSuggestionPopup(iter->second.fill_data, element, false, false); 716 ShowSuggestionPopup(iter->second.fill_data, element, false, false);
676 return true; 717 return true;
677 } 718 }
(...skipping 518 matching lines...) Expand 10 before | Expand all | Expand 10 after
1196 } 1237 }
1197 1238
1198 // This is a new navigation, so require a new user gesture before filling in 1239 // This is a new navigation, so require a new user gesture before filling in
1199 // passwords. 1240 // passwords.
1200 gatekeeper_.Reset(); 1241 gatekeeper_.Reset();
1201 } 1242 }
1202 1243
1203 void PasswordAutofillAgent::OnFillPasswordForm( 1244 void PasswordAutofillAgent::OnFillPasswordForm(
1204 int key, 1245 int key,
1205 const PasswordFormFillData& form_data) { 1246 const PasswordFormFillData& form_data) {
1206 1247 bool ambiguous_or_no_name_and_id_attribute =
1248 PasswordFormWithAmbiguousOrNoNameAndIdAttribute(form_data);
1207 FormElementsList forms; 1249 FormElementsList forms;
1208 FindFormElements(render_frame(), form_data, &forms); 1250 FindFormElements(render_frame(), form_data,
1251 ambiguous_or_no_name_and_id_attribute, &forms);
1209 FormElementsList::iterator iter; 1252 FormElementsList::iterator iter;
1210 for (iter = forms.begin(); iter != forms.end(); ++iter) { 1253 for (iter = forms.begin(); iter != forms.end(); ++iter) {
1211 // Attach autocomplete listener to enable selecting alternate logins. 1254 // Attach autocomplete listener to enable selecting alternate logins.
1212 blink::WebInputElement username_element, password_element; 1255 blink::WebInputElement username_element, password_element;
1213 1256
1257 base::string16 username_field_name = FieldName(
1258 form_data.username_field, ambiguous_or_no_name_and_id_attribute);
1259 base::string16 password_field_name = FieldName(
1260 form_data.password_field, ambiguous_or_no_name_and_id_attribute);
1261
1214 // Check whether the password form has a username input field. 1262 // Check whether the password form has a username input field.
1215 bool form_contains_fillable_username_field = 1263 if (!username_field_name.empty()) {
1216 FillDataContainsFillableUsername(form_data); 1264 username_element = (*iter)[username_field_name];
1217 if (form_contains_fillable_username_field) {
1218 username_element =
1219 (*iter)[form_data.username_field.name];
1220 } 1265 }
1221 1266
1222 // No password field, bail out. 1267 // No password field, bail out.
1223 if (form_data.password_field.name.empty()) 1268 if (password_field_name.empty())
1224 break; 1269 break;
1225 1270
1226 // We might have already filled this form if there are two <form> elements 1271 // We might have already filled this form if there are two <form> elements
1227 // with identical markup. 1272 // with identical markup.
1228 if (login_to_password_info_.find(username_element) != 1273 if (login_to_password_info_.find(username_element) !=
1229 login_to_password_info_.end()) 1274 login_to_password_info_.end())
1230 continue; 1275 continue;
1231 1276
1232 // Get pointer to password element. (We currently only support single 1277 // Get pointer to password element. (We currently only support single
1233 // password forms). 1278 // password forms).
1234 password_element = (*iter)[form_data.password_field.name]; 1279 password_element = (*iter)[password_field_name];
1235 1280
1236 // If wait_for_username is true, we don't want to initially fill the form 1281 // If wait_for_username is true, we don't want to initially fill the form
1237 // until the user types in a valid username. 1282 // until the user types in a valid username.
1238 if (!form_data.wait_for_username) { 1283 if (!form_data.wait_for_username) {
1239 FillFormOnPasswordReceived( 1284 FillFormOnPasswordReceived(
1240 form_data, 1285 form_data,
1241 username_element, 1286 username_element,
1242 password_element, 1287 password_element,
1243 &nonscript_modified_values_, 1288 &nonscript_modified_values_,
1244 base::Bind(&PasswordValueGatekeeper::RegisterElement, 1289 base::Bind(&PasswordValueGatekeeper::RegisterElement,
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
1439 void PasswordAutofillAgent::LegacyPasswordAutofillAgent::DidStopLoading() { 1484 void PasswordAutofillAgent::LegacyPasswordAutofillAgent::DidStopLoading() {
1440 agent_->DidStopLoading(); 1485 agent_->DidStopLoading();
1441 } 1486 }
1442 1487
1443 void PasswordAutofillAgent::LegacyPasswordAutofillAgent:: 1488 void PasswordAutofillAgent::LegacyPasswordAutofillAgent::
1444 DidStartProvisionalLoad(blink::WebLocalFrame* navigated_frame) { 1489 DidStartProvisionalLoad(blink::WebLocalFrame* navigated_frame) {
1445 agent_->LegacyDidStartProvisionalLoad(navigated_frame); 1490 agent_->LegacyDidStartProvisionalLoad(navigated_frame);
1446 } 1491 }
1447 1492
1448 } // namespace autofill 1493 } // namespace autofill
OLDNEW
« no previous file with comments | « chrome/test/data/password/ambiguous_password_form.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698