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

Side by Side Diff: chrome/renderer/autofill/password_autofill_manager.cc

Issue 12434004: Move remaining Autofill code to //components/autofill. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix long lines Created 7 years, 9 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/renderer/autofill/password_autofill_manager.h"
6
7 #include "base/bind.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/message_loop.h"
10 #include "chrome/renderer/autofill/form_autofill_util.h"
11 #include "components/autofill/common/autofill_messages.h"
12 #include "components/autofill/common/form_field_data.h"
13 #include "components/autofill/common/password_form_fill_data.h"
14 #include "content/public/common/password_form.h"
15 #include "content/public/renderer/password_form_conversion_utils.h"
16 #include "content/public/renderer/render_view.h"
17 #include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h"
18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h"
19 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
20 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h"
21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h"
22 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
23 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
24 #include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
26 #include "ui/base/keycodes/keyboard_codes.h"
27
28 namespace {
29
30 // The size above which we stop triggering autocomplete.
31 static const size_t kMaximumTextSizeForAutocomplete = 1000;
32
33 // Maps element names to the actual elements to simplify form filling.
34 typedef std::map<string16, WebKit::WebInputElement>
35 FormInputElementMap;
36
37 // Utility struct for form lookup and autofill. When we parse the DOM to look up
38 // a form, in addition to action and origin URL's we have to compare all
39 // necessary form elements. To avoid having to look these up again when we want
40 // to fill the form, the FindFormElements function stores the pointers
41 // in a FormElements* result, referenced to ensure they are safe to use.
42 struct FormElements {
43 WebKit::WebFormElement form_element;
44 FormInputElementMap input_elements;
45 };
46
47 typedef std::vector<FormElements*> FormElementsList;
48
49 // Helper to search the given form element for the specified input elements
50 // in |data|, and add results to |result|.
51 static bool FindFormInputElements(WebKit::WebFormElement* fe,
52 const FormData& data,
53 FormElements* result) {
54 // Loop through the list of elements we need to find on the form in order to
55 // autofill it. If we don't find any one of them, abort processing this
56 // form; it can't be the right one.
57 for (size_t j = 0; j < data.fields.size(); j++) {
58 WebKit::WebVector<WebKit::WebNode> temp_elements;
59 fe->getNamedElements(data.fields[j].name, temp_elements);
60
61 // Match the first input element, if any.
62 // |getNamedElements| may return non-input elements where the names match,
63 // so the results are filtered for input elements.
64 // If more than one match is made, then we have ambiguity (due to misuse
65 // of "name" attribute) so is it considered not found.
66 bool found_input = false;
67 for (size_t i = 0; i < temp_elements.size(); ++i) {
68 if (temp_elements[i].to<WebKit::WebElement>().hasTagName("input")) {
69 // Check for a non-unique match.
70 if (found_input) {
71 found_input = false;
72 break;
73 }
74
75 // This element matched, add it to our temporary result. It's possible
76 // there are multiple matches, but for purposes of identifying the form
77 // one suffices and if some function needs to deal with multiple
78 // matching elements it can get at them through the FormElement*.
79 // Note: This assignment adds a reference to the InputElement.
80 result->input_elements[data.fields[j].name] =
81 temp_elements[i].to<WebKit::WebInputElement>();
82 found_input = true;
83 }
84 }
85
86 // A required element was not found. This is not the right form.
87 // Make sure no input elements from a partially matched form in this
88 // iteration remain in the result set.
89 // Note: clear will remove a reference from each InputElement.
90 if (!found_input) {
91 result->input_elements.clear();
92 return false;
93 }
94 }
95 return true;
96 }
97
98 // Helper to locate form elements identified by |data|.
99 void FindFormElements(WebKit::WebView* view,
100 const FormData& data,
101 FormElementsList* results) {
102 DCHECK(view);
103 DCHECK(results);
104 WebKit::WebFrame* main_frame = view->mainFrame();
105 if (!main_frame)
106 return;
107
108 GURL::Replacements rep;
109 rep.ClearQuery();
110 rep.ClearRef();
111
112 // Loop through each frame.
113 for (WebKit::WebFrame* f = main_frame; f; f = f->traverseNext(false)) {
114 WebKit::WebDocument doc = f->document();
115 if (!doc.isHTMLDocument())
116 continue;
117
118 GURL full_origin(doc.url());
119 if (data.origin != full_origin.ReplaceComponents(rep))
120 continue;
121
122 WebKit::WebVector<WebKit::WebFormElement> forms;
123 doc.forms(forms);
124
125 for (size_t i = 0; i < forms.size(); ++i) {
126 WebKit::WebFormElement fe = forms[i];
127
128 GURL full_action(f->document().completeURL(fe.action()));
129 if (full_action.is_empty()) {
130 // The default action URL is the form's origin.
131 full_action = full_origin;
132 }
133
134 // Action URL must match.
135 if (data.action != full_action.ReplaceComponents(rep))
136 continue;
137
138 scoped_ptr<FormElements> curr_elements(new FormElements);
139 if (!FindFormInputElements(&fe, data, curr_elements.get()))
140 continue;
141
142 // We found the right element.
143 // Note: this assignment adds a reference to |fe|.
144 curr_elements->form_element = fe;
145 results->push_back(curr_elements.release());
146 }
147 }
148 }
149
150 bool IsElementEditable(const WebKit::WebInputElement& element) {
151 return element.isEnabled() && !element.isReadOnly();
152 }
153
154 void FillForm(FormElements* fe, const FormData& data) {
155 if (!fe->form_element.autoComplete())
156 return;
157
158 std::map<string16, string16> data_map;
159 for (size_t i = 0; i < data.fields.size(); i++)
160 data_map[data.fields[i].name] = data.fields[i].value;
161
162 for (FormInputElementMap::iterator it = fe->input_elements.begin();
163 it != fe->input_elements.end(); ++it) {
164 WebKit::WebInputElement element = it->second;
165 // Don't fill a form that has pre-filled values distinct from the ones we
166 // want to fill with.
167 if (!element.value().isEmpty() && element.value() != data_map[it->first])
168 return;
169 }
170
171 for (FormInputElementMap::iterator it = fe->input_elements.begin();
172 it != fe->input_elements.end(); ++it) {
173 WebKit::WebInputElement element = it->second;
174 if (!IsElementEditable(element))
175 continue; // Don't fill uneditable fields.
176
177 // TODO(tkent): Check maxlength and pattern.
178 element.setValue(data_map[it->first]);
179 element.setAutofilled(true);
180 element.dispatchFormControlChangeEvent();
181 }
182 }
183
184 void SetElementAutofilled(WebKit::WebInputElement* element, bool autofilled) {
185 if (element->isAutofilled() == autofilled)
186 return;
187 element->setAutofilled(autofilled);
188 // Notify any changeEvent listeners.
189 element->dispatchFormControlChangeEvent();
190 }
191
192 bool DoUsernamesMatch(const string16& username1,
193 const string16& username2,
194 bool exact_match) {
195 if (exact_match)
196 return username1 == username2;
197 return StartsWith(username1, username2, true);
198 }
199
200 } // namespace
201
202 namespace autofill {
203
204 ////////////////////////////////////////////////////////////////////////////////
205 // PasswordAutofillManager, public:
206
207 PasswordAutofillManager::PasswordAutofillManager(
208 content::RenderView* render_view)
209 : content::RenderViewObserver(render_view),
210 disable_popup_(false),
211 web_view_(render_view->GetWebView()),
212 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
213 }
214
215 PasswordAutofillManager::~PasswordAutofillManager() {
216 }
217
218 bool PasswordAutofillManager::TextFieldDidEndEditing(
219 const WebKit::WebInputElement& element) {
220 LoginToPasswordInfoMap::const_iterator iter =
221 login_to_password_info_.find(element);
222 if (iter == login_to_password_info_.end())
223 return false;
224
225 const PasswordFormFillData& fill_data =
226 iter->second.fill_data;
227
228 // If wait_for_username is false, we should have filled when the text changed.
229 if (!fill_data.wait_for_username)
230 return false;
231
232 WebKit::WebInputElement password = iter->second.password_field;
233 if (!IsElementEditable(password))
234 return false;
235
236 WebKit::WebInputElement username = element; // We need a non-const.
237
238 // Do not set selection when ending an editing session, otherwise it can
239 // mess with focus.
240 FillUserNameAndPassword(&username, &password, fill_data, true, false);
241 return true;
242 }
243
244 bool PasswordAutofillManager::TextDidChangeInTextField(
245 const WebKit::WebInputElement& element) {
246 LoginToPasswordInfoMap::const_iterator iter =
247 login_to_password_info_.find(element);
248 if (iter == login_to_password_info_.end())
249 return false;
250
251 // The input text is being changed, so any autofilled password is now
252 // outdated.
253 WebKit::WebInputElement username = element; // We need a non-const.
254 WebKit::WebInputElement password = iter->second.password_field;
255 SetElementAutofilled(&username, false);
256 if (password.isAutofilled()) {
257 password.setValue(string16());
258 SetElementAutofilled(&password, false);
259 }
260
261 // If wait_for_username is true we will fill when the username loses focus.
262 if (iter->second.fill_data.wait_for_username)
263 return false;
264
265 if (!IsElementEditable(element) ||
266 !element.isText() ||
267 !element.autoComplete()) {
268 return false;
269 }
270
271 // Don't inline autocomplete if the user is deleting, that would be confusing.
272 // But refresh the popup. Note, since this is ours, return true to signal
273 // no further processing is required.
274 if (iter->second.backspace_pressed_last) {
275 ShowSuggestionPopup(iter->second.fill_data, username);
276 return true;
277 }
278
279 WebKit::WebString name = element.nameForAutofill();
280 if (name.isEmpty())
281 return false; // If the field has no name, then we won't have values.
282
283 // Don't attempt to autofill with values that are too large.
284 if (element.value().length() > kMaximumTextSizeForAutocomplete)
285 return false;
286
287 // The caret position should have already been updated.
288 PerformInlineAutocomplete(element, password, iter->second.fill_data);
289 return true;
290 }
291
292 bool PasswordAutofillManager::TextFieldHandlingKeyDown(
293 const WebKit::WebInputElement& element,
294 const WebKit::WebKeyboardEvent& event) {
295 // If using the new Autofill UI that lives in the browser, it will handle
296 // keypresses before this function. This is not currently an issue but if
297 // the keys handled there or here change, this issue may appear.
298
299 LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(element);
300 if (iter == login_to_password_info_.end())
301 return false;
302
303 int win_key_code = event.windowsKeyCode;
304 iter->second.backspace_pressed_last =
305 (win_key_code == ui::VKEY_BACK || win_key_code == ui::VKEY_DELETE);
306 return true;
307 }
308
309 bool PasswordAutofillManager::DidAcceptAutofillSuggestion(
310 const WebKit::WebNode& node,
311 const WebKit::WebString& value) {
312 WebKit::WebInputElement input;
313 PasswordInfo password;
314 if (!FindLoginInfo(node, &input, &password))
315 return false;
316
317 // Set the incoming |value| in the text field and |FillUserNameAndPassword|
318 // will do the rest.
319 input.setValue(value, true);
320 return FillUserNameAndPassword(&input, &password.password_field,
321 password.fill_data, true, true);
322 }
323
324 bool PasswordAutofillManager::DidSelectAutofillSuggestion(
325 const WebKit::WebNode& node) {
326 WebKit::WebInputElement input;
327 PasswordInfo password;
328 return FindLoginInfo(node, &input, &password);
329 }
330
331 bool PasswordAutofillManager::DidClearAutofillSelection(
332 const WebKit::WebNode& node) {
333 WebKit::WebInputElement input;
334 PasswordInfo password;
335 return FindLoginInfo(node, &input, &password);
336 }
337
338 void PasswordAutofillManager::SendPasswordForms(WebKit::WebFrame* frame,
339 bool only_visible) {
340 // Make sure that this security origin is allowed to use password manager.
341 WebKit::WebSecurityOrigin origin = frame->document().securityOrigin();
342 if (!origin.canAccessPasswordManager())
343 return;
344
345 WebKit::WebVector<WebKit::WebFormElement> forms;
346 frame->document().forms(forms);
347
348 std::vector<content::PasswordForm> password_forms;
349 for (size_t i = 0; i < forms.size(); ++i) {
350 const WebKit::WebFormElement& form = forms[i];
351
352 // If requested, ignore non-rendered forms, e.g. those styled with
353 // display:none.
354 if (only_visible && !form.hasNonEmptyBoundingBox())
355 continue;
356
357 scoped_ptr<content::PasswordForm> password_form(
358 content::CreatePasswordForm(form));
359 if (password_form.get())
360 password_forms.push_back(*password_form);
361 }
362
363 if (password_forms.empty() && !only_visible) {
364 // We need to send the PasswordFormsRendered message regardless of whether
365 // there are any forms visible, as this is also the code path that triggers
366 // showing the infobar.
367 return;
368 }
369
370 if (only_visible) {
371 Send(new AutofillHostMsg_PasswordFormsRendered(
372 routing_id(), password_forms));
373 } else {
374 Send(new AutofillHostMsg_PasswordFormsParsed(routing_id(), password_forms));
375 }
376 }
377
378 bool PasswordAutofillManager::OnMessageReceived(const IPC::Message& message) {
379 bool handled = true;
380 IPC_BEGIN_MESSAGE_MAP(PasswordAutofillManager, message)
381 IPC_MESSAGE_HANDLER(AutofillMsg_FillPasswordForm, OnFillPasswordForm)
382 IPC_MESSAGE_UNHANDLED(handled = false)
383 IPC_END_MESSAGE_MAP()
384 return handled;
385 }
386
387 void PasswordAutofillManager::DidFinishDocumentLoad(WebKit::WebFrame* frame) {
388 // The |frame| contents have been parsed, but not yet rendered. Let the
389 // PasswordManager know that forms are loaded, even though we can't yet tell
390 // whether they're visible.
391 SendPasswordForms(frame, false);
392 }
393
394 void PasswordAutofillManager::DidFinishLoad(WebKit::WebFrame* frame) {
395 // The |frame| contents have been rendered. Let the PasswordManager know
396 // which of the loaded frames are actually visible to the user. This also
397 // triggers the "Save password?" infobar if the user just submitted a password
398 // form.
399 SendPasswordForms(frame, true);
400 }
401
402 void PasswordAutofillManager::FrameDetached(WebKit::WebFrame* frame) {
403 FrameClosing(frame);
404 }
405
406 void PasswordAutofillManager::FrameWillClose(WebKit::WebFrame* frame) {
407 FrameClosing(frame);
408 }
409
410
411 ////////////////////////////////////////////////////////////////////////////////
412 // PageClickListener implementation:
413
414 bool PasswordAutofillManager::InputElementClicked(
415 const WebKit::WebInputElement& element,
416 bool was_focused,
417 bool is_focused) {
418 // TODO(jcivelli): http://crbug.com/51644 Implement behavior.
419 return false;
420 }
421
422 bool PasswordAutofillManager::InputElementLostFocus() {
423 return false;
424 }
425
426 void PasswordAutofillManager::OnFillPasswordForm(
427 const PasswordFormFillData& form_data,
428 bool disable_popup) {
429 disable_popup_ = disable_popup;
430
431 FormElementsList forms;
432 // We own the FormElements* in forms.
433 FindFormElements(render_view()->GetWebView(), form_data.basic_data, &forms);
434 FormElementsList::iterator iter;
435 for (iter = forms.begin(); iter != forms.end(); ++iter) {
436 scoped_ptr<FormElements> form_elements(*iter);
437
438 // If wait_for_username is true, we don't want to initially fill the form
439 // until the user types in a valid username.
440 if (!form_data.wait_for_username)
441 FillForm(form_elements.get(), form_data.basic_data);
442
443 // Attach autocomplete listener to enable selecting alternate logins.
444 // First, get pointers to username element.
445 WebKit::WebInputElement username_element =
446 form_elements->input_elements[form_data.basic_data.fields[0].name];
447
448 // Get pointer to password element. (We currently only support single
449 // password forms).
450 WebKit::WebInputElement password_element =
451 form_elements->input_elements[form_data.basic_data.fields[1].name];
452
453 // We might have already filled this form if there are two <form> elements
454 // with identical markup.
455 if (login_to_password_info_.find(username_element) !=
456 login_to_password_info_.end())
457 continue;
458
459 PasswordInfo password_info;
460 password_info.fill_data = form_data;
461 password_info.password_field = password_element;
462 login_to_password_info_[username_element] = password_info;
463
464 FormData form;
465 FormFieldData field;
466 FindFormAndFieldForInputElement(
467 username_element, &form, &field, REQUIRE_NONE);
468 Send(new AutofillHostMsg_AddPasswordFormMapping(
469 routing_id(),
470 field,
471 form_data));
472 }
473 }
474
475 ////////////////////////////////////////////////////////////////////////////////
476 // PasswordAutofillManager, private:
477
478 void PasswordAutofillManager::GetSuggestions(
479 const PasswordFormFillData& fill_data,
480 const string16& input,
481 std::vector<string16>* suggestions) {
482 if (StartsWith(fill_data.basic_data.fields[0].value, input, false))
483 suggestions->push_back(fill_data.basic_data.fields[0].value);
484
485 PasswordFormFillData::LoginCollection::const_iterator iter;
486 for (iter = fill_data.additional_logins.begin();
487 iter != fill_data.additional_logins.end(); ++iter) {
488 if (StartsWith(iter->first, input, false))
489 suggestions->push_back(iter->first);
490 }
491 }
492
493 bool PasswordAutofillManager::ShowSuggestionPopup(
494 const PasswordFormFillData& fill_data,
495 const WebKit::WebInputElement& user_input) {
496 WebKit::WebFrame* frame = user_input.document().frame();
497 if (!frame)
498 return false;
499
500 WebKit::WebView* webview = frame->view();
501 if (!webview)
502 return false;
503
504 std::vector<string16> suggestions;
505 GetSuggestions(fill_data, user_input.value(), &suggestions);
506
507 if (disable_popup_) {
508 FormData form;
509 FormFieldData field;
510 FindFormAndFieldForInputElement(
511 user_input, &form, &field, REQUIRE_NONE);
512
513 WebKit::WebInputElement selected_element = user_input;
514 gfx::Rect bounding_box(selected_element.boundsInViewportSpace());
515
516 float scale = web_view_->pageScaleFactor();
517 gfx::RectF bounding_box_scaled(bounding_box.x() * scale,
518 bounding_box.y() * scale,
519 bounding_box.width() * scale,
520 bounding_box.height() * scale);
521 Send(new AutofillHostMsg_ShowPasswordSuggestions(routing_id(),
522 field,
523 bounding_box_scaled,
524 suggestions));
525 return !suggestions.empty();
526 }
527
528
529 if (suggestions.empty()) {
530 webview->hidePopups();
531 return false;
532 }
533
534 std::vector<string16> labels(suggestions.size());
535 std::vector<string16> icons(suggestions.size());
536 std::vector<int> ids(suggestions.size(),
537 WebKit::WebAutofillClient::MenuItemIDPasswordEntry);
538 webview->applyAutofillSuggestions(
539 user_input, suggestions, labels, icons, ids);
540 return true;
541 }
542
543 bool PasswordAutofillManager::FillUserNameAndPassword(
544 WebKit::WebInputElement* username_element,
545 WebKit::WebInputElement* password_element,
546 const PasswordFormFillData& fill_data,
547 bool exact_username_match,
548 bool set_selection) {
549 string16 current_username = username_element->value();
550 // username and password will contain the match found if any.
551 string16 username;
552 string16 password;
553
554 // Look for any suitable matches to current field text.
555 if (DoUsernamesMatch(fill_data.basic_data.fields[0].value, current_username,
556 exact_username_match)) {
557 username = fill_data.basic_data.fields[0].value;
558 password = fill_data.basic_data.fields[1].value;
559 } else {
560 // Scan additional logins for a match.
561 PasswordFormFillData::LoginCollection::const_iterator iter;
562 for (iter = fill_data.additional_logins.begin();
563 iter != fill_data.additional_logins.end(); ++iter) {
564 if (DoUsernamesMatch(iter->first, current_username,
565 exact_username_match)) {
566 username = iter->first;
567 password = iter->second;
568 break;
569 }
570 }
571 }
572 if (password.empty())
573 return false; // No match was found.
574
575 // Input matches the username, fill in required values.
576 username_element->setValue(username);
577
578 if (set_selection) {
579 username_element->setSelectionRange(current_username.length(),
580 username.length());
581 }
582
583 SetElementAutofilled(username_element, true);
584 if (IsElementEditable(*password_element))
585 password_element->setValue(password);
586 SetElementAutofilled(password_element, true);
587 return true;
588 }
589
590 void PasswordAutofillManager::PerformInlineAutocomplete(
591 const WebKit::WebInputElement& username_input,
592 const WebKit::WebInputElement& password_input,
593 const PasswordFormFillData& fill_data) {
594 DCHECK(!fill_data.wait_for_username);
595
596 // We need non-const versions of the username and password inputs.
597 WebKit::WebInputElement username = username_input;
598 WebKit::WebInputElement password = password_input;
599
600 // Don't inline autocomplete if the caret is not at the end.
601 // TODO(jcivelli): is there a better way to test the caret location?
602 if (username.selectionStart() != username.selectionEnd() ||
603 username.selectionEnd() != static_cast<int>(username.value().length())) {
604 return;
605 }
606
607 // Show the popup with the list of available usernames.
608 ShowSuggestionPopup(fill_data, username);
609
610
611 #if !defined(OS_ANDROID)
612 // Fill the user and password field with the most relevant match. Android
613 // only fills in the fields after the user clicks on the suggestion popup.
614 FillUserNameAndPassword(&username, &password, fill_data, false, true);
615 #endif
616 }
617
618 void PasswordAutofillManager::FrameClosing(const WebKit::WebFrame* frame) {
619 for (LoginToPasswordInfoMap::iterator iter = login_to_password_info_.begin();
620 iter != login_to_password_info_.end();) {
621 if (iter->first.document().frame() == frame)
622 login_to_password_info_.erase(iter++);
623 else
624 ++iter;
625 }
626 }
627
628 bool PasswordAutofillManager::FindLoginInfo(
629 const WebKit::WebNode& node,
630 WebKit::WebInputElement* found_input,
631 PasswordInfo* found_password) {
632 if (!node.isElementNode())
633 return false;
634
635 WebKit::WebElement element = node.toConst<WebKit::WebElement>();
636 if (!element.hasTagName("input"))
637 return false;
638
639 WebKit::WebInputElement input = element.to<WebKit::WebInputElement>();
640 LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(input);
641 if (iter == login_to_password_info_.end())
642 return false;
643
644 *found_input = input;
645 *found_password = iter->second;
646 return true;
647 }
648
649 } // namespace autofill
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698