OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |