OLD | NEW |
| (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 "components/autofill/renderer/autofill_agent.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/command_line.h" | |
9 #include "base/message_loop.h" | |
10 #include "base/string_util.h" | |
11 #include "base/strings/string_split.h" | |
12 #include "base/time.h" | |
13 #include "base/utf_string_conversions.h" | |
14 #include "components/autofill/common/autocheckout_status.h" | |
15 #include "components/autofill/common/autofill_constants.h" | |
16 #include "components/autofill/common/autofill_messages.h" | |
17 #include "components/autofill/common/autofill_switches.h" | |
18 #include "components/autofill/common/form_data.h" | |
19 #include "components/autofill/common/form_data_predictions.h" | |
20 #include "components/autofill/common/form_field_data.h" | |
21 #include "components/autofill/common/web_element_descriptor.h" | |
22 #include "components/autofill/renderer/form_autofill_util.h" | |
23 #include "components/autofill/renderer/page_click_tracker.h" | |
24 #include "components/autofill/renderer/password_autofill_agent.h" | |
25 #include "content/public/common/password_form.h" | |
26 #include "content/public/common/ssl_status.h" | |
27 #include "content/public/renderer/render_view.h" | |
28 #include "grit/component_resources.h" | |
29 #include "third_party/WebKit/public/platform/WebRect.h" | |
30 #include "third_party/WebKit/public/platform/WebURLRequest.h" | |
31 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h" | |
32 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" | |
33 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement
.h" | |
34 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" | |
35 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" | |
36 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" | |
37 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" | |
38 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeCollection.h" | |
39 #include "third_party/WebKit/Source/WebKit/chromium/public/WebOptionElement.h" | |
40 #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" | |
41 #include "ui/base/keycodes/keyboard_codes.h" | |
42 #include "ui/base/l10n/l10n_util.h" | |
43 | |
44 using WebKit::WebAutofillClient; | |
45 using WebKit::WebFormControlElement; | |
46 using WebKit::WebFormElement; | |
47 using WebKit::WebFrame; | |
48 using WebKit::WebInputElement; | |
49 using WebKit::WebKeyboardEvent; | |
50 using WebKit::WebNode; | |
51 using WebKit::WebNodeCollection; | |
52 using WebKit::WebOptionElement; | |
53 using WebKit::WebString; | |
54 | |
55 namespace { | |
56 | |
57 // The size above which we stop triggering autofill for an input text field | |
58 // (so to avoid sending long strings through IPC). | |
59 const size_t kMaximumTextSizeForAutofill = 1000; | |
60 | |
61 // The maximum number of data list elements to send to the browser process | |
62 // via IPC (to prevent long IPC messages). | |
63 const size_t kMaximumDataListSizeForAutofill = 30; | |
64 | |
65 const int kAutocheckoutClickTimeout = 3; | |
66 | |
67 void AppendDataListSuggestions(const WebKit::WebInputElement& element, | |
68 std::vector<base::string16>* values, | |
69 std::vector<base::string16>* labels, | |
70 std::vector<base::string16>* icons, | |
71 std::vector<int>* item_ids) { | |
72 WebNodeCollection options = element.dataListOptions(); | |
73 if (options.isNull()) | |
74 return; | |
75 | |
76 base::string16 prefix = element.editingValue(); | |
77 if (element.isMultiple() && | |
78 element.formControlType() == WebString::fromUTF8("email")) { | |
79 std::vector<base::string16> parts; | |
80 base::SplitStringDontTrim(prefix, ',', &parts); | |
81 if (parts.size() > 0) | |
82 TrimWhitespace(parts[parts.size() - 1], TRIM_LEADING, &prefix); | |
83 } | |
84 for (WebOptionElement option = options.firstItem().to<WebOptionElement>(); | |
85 !option.isNull(); option = options.nextItem().to<WebOptionElement>()) { | |
86 if (!StartsWith(option.value(), prefix, false) || | |
87 option.value() == prefix || | |
88 !element.isValidValue(option.value())) | |
89 continue; | |
90 | |
91 values->push_back(option.value()); | |
92 if (option.value() != option.label()) | |
93 labels->push_back(option.label()); | |
94 else | |
95 labels->push_back(base::string16()); | |
96 icons->push_back(base::string16()); | |
97 item_ids->push_back(WebAutofillClient::MenuItemIDDataListEntry); | |
98 } | |
99 } | |
100 | |
101 // Trim the vectors before sending them to the browser process to ensure we | |
102 // don't send too much data through the IPC. | |
103 void TrimDataListsForIPC(std::vector<base::string16>* values, | |
104 std::vector<base::string16>* labels, | |
105 std::vector<base::string16>* icons, | |
106 std::vector<int>* unique_ids) { | |
107 // Limit the size of the vectors. | |
108 if (values->size() > kMaximumDataListSizeForAutofill) { | |
109 values->resize(kMaximumDataListSizeForAutofill); | |
110 labels->resize(kMaximumDataListSizeForAutofill); | |
111 icons->resize(kMaximumDataListSizeForAutofill); | |
112 unique_ids->resize(kMaximumDataListSizeForAutofill); | |
113 } | |
114 | |
115 // Limit the size of the strings in the vectors | |
116 for (size_t i = 0; i < values->size(); ++i) { | |
117 if ((*values)[i].length() > kMaximumTextSizeForAutofill) | |
118 (*values)[i].resize(kMaximumTextSizeForAutofill); | |
119 | |
120 if ((*labels)[i].length() > kMaximumTextSizeForAutofill) | |
121 (*labels)[i].resize(kMaximumTextSizeForAutofill); | |
122 | |
123 if ((*icons)[i].length() > kMaximumTextSizeForAutofill) | |
124 (*icons)[i].resize(kMaximumTextSizeForAutofill); | |
125 } | |
126 } | |
127 | |
128 gfx::RectF GetScaledBoundingBox(float scale, WebInputElement* element) { | |
129 gfx::Rect bounding_box(element->boundsInViewportSpace()); | |
130 return gfx::RectF(bounding_box.x() * scale, | |
131 bounding_box.y() * scale, | |
132 bounding_box.width() * scale, | |
133 bounding_box.height() * scale); | |
134 } | |
135 | |
136 } // namespace | |
137 | |
138 namespace autofill { | |
139 | |
140 AutofillAgent::AutofillAgent(content::RenderView* render_view, | |
141 PasswordAutofillAgent* password_autofill_agent) | |
142 : content::RenderViewObserver(render_view), | |
143 password_autofill_agent_(password_autofill_agent), | |
144 autofill_query_id_(0), | |
145 autofill_action_(AUTOFILL_NONE), | |
146 topmost_frame_(NULL), | |
147 web_view_(render_view->GetWebView()), | |
148 display_warning_if_disabled_(false), | |
149 was_query_node_autofilled_(false), | |
150 has_shown_autofill_popup_for_current_edit_(false), | |
151 did_set_node_text_(false), | |
152 autocheckout_click_in_progress_(false), | |
153 is_autocheckout_supported_(false), | |
154 has_new_forms_for_browser_(false), | |
155 ignore_text_changes_(false), | |
156 weak_ptr_factory_(this) { | |
157 render_view->GetWebView()->setAutofillClient(this); | |
158 | |
159 // The PageClickTracker is a RenderViewObserver, and hence will be freed when | |
160 // the RenderView is destroyed. | |
161 new PageClickTracker(render_view, this); | |
162 } | |
163 | |
164 AutofillAgent::~AutofillAgent() {} | |
165 | |
166 bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { | |
167 bool handled = true; | |
168 IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) | |
169 IPC_MESSAGE_HANDLER(AutofillMsg_GetAllForms, OnGetAllForms) | |
170 IPC_MESSAGE_HANDLER(AutofillMsg_SuggestionsReturned, OnSuggestionsReturned) | |
171 IPC_MESSAGE_HANDLER(AutofillMsg_FormDataFilled, OnFormDataFilled) | |
172 IPC_MESSAGE_HANDLER(AutofillMsg_FieldTypePredictionsAvailable, | |
173 OnFieldTypePredictionsAvailable) | |
174 IPC_MESSAGE_HANDLER(AutofillMsg_SetAutofillActionFill, | |
175 OnSetAutofillActionFill) | |
176 IPC_MESSAGE_HANDLER(AutofillMsg_ClearForm, | |
177 OnClearForm) | |
178 IPC_MESSAGE_HANDLER(AutofillMsg_SetAutofillActionPreview, | |
179 OnSetAutofillActionPreview) | |
180 IPC_MESSAGE_HANDLER(AutofillMsg_ClearPreviewedForm, | |
181 OnClearPreviewedForm) | |
182 IPC_MESSAGE_HANDLER(AutofillMsg_SetNodeText, | |
183 OnSetNodeText) | |
184 IPC_MESSAGE_HANDLER(AutofillMsg_AcceptDataListSuggestion, | |
185 OnAcceptDataListSuggestion) | |
186 IPC_MESSAGE_HANDLER(AutofillMsg_AcceptPasswordAutofillSuggestion, | |
187 OnAcceptPasswordAutofillSuggestion) | |
188 IPC_MESSAGE_HANDLER(AutofillMsg_RequestAutocompleteResult, | |
189 OnRequestAutocompleteResult) | |
190 IPC_MESSAGE_HANDLER(AutofillMsg_FillFormsAndClick, | |
191 OnFillFormsAndClick) | |
192 IPC_MESSAGE_HANDLER(AutofillMsg_AutocheckoutSupported, | |
193 OnAutocheckoutSupported) | |
194 IPC_MESSAGE_UNHANDLED(handled = false) | |
195 IPC_END_MESSAGE_MAP() | |
196 return handled; | |
197 } | |
198 | |
199 void AutofillAgent::DidFinishDocumentLoad(WebFrame* frame) { | |
200 // Record timestamp on document load. This is used to record overhead of | |
201 // Autofill feature. | |
202 forms_seen_timestamp_ = base::TimeTicks::Now(); | |
203 | |
204 // The document has now been fully loaded. Scan for forms to be sent up to | |
205 // the browser. | |
206 std::vector<FormData> forms; | |
207 bool has_more_forms = false; | |
208 if (!frame->parent()) { | |
209 topmost_frame_ = frame; | |
210 form_elements_.clear(); | |
211 has_more_forms = form_cache_.ExtractFormsAndFormElements( | |
212 *frame, kRequiredAutofillFields, &forms, &form_elements_); | |
213 } else { | |
214 form_cache_.ExtractForms(*frame, &forms); | |
215 } | |
216 | |
217 autofill::FormsSeenState state = has_more_forms ? | |
218 autofill::PARTIAL_FORMS_SEEN : autofill::NO_SPECIAL_FORMS_SEEN; | |
219 | |
220 // Always communicate to browser process for topmost frame. | |
221 if (!forms.empty() || !frame->parent()) { | |
222 Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, | |
223 forms_seen_timestamp_, | |
224 state)); | |
225 } | |
226 } | |
227 | |
228 void AutofillAgent::DidStartProvisionalLoad(WebFrame* frame) { | |
229 if (!frame->parent()) { | |
230 is_autocheckout_supported_ = false; | |
231 topmost_frame_ = NULL; | |
232 if (click_timer_.IsRunning()) { | |
233 click_timer_.Stop(); | |
234 autocheckout_click_in_progress_ = true; | |
235 } | |
236 } | |
237 } | |
238 | |
239 void AutofillAgent::DidFailProvisionalLoad(WebFrame* frame, | |
240 const WebKit::WebURLError& error) { | |
241 if (!frame->parent() && autocheckout_click_in_progress_) { | |
242 autocheckout_click_in_progress_ = false; | |
243 ClickFailed(); | |
244 } | |
245 } | |
246 | |
247 void AutofillAgent::DidCommitProvisionalLoad(WebFrame* frame, | |
248 bool is_new_navigation) { | |
249 in_flight_request_form_.reset(); | |
250 if (!frame->parent()) | |
251 autocheckout_click_in_progress_ = false; | |
252 } | |
253 | |
254 void AutofillAgent::FrameDetached(WebFrame* frame) { | |
255 form_cache_.ResetFrame(*frame); | |
256 if (!frame->parent()) { | |
257 // |frame| is about to be destroyed so we need to clear |top_most_frame_|. | |
258 topmost_frame_ = NULL; | |
259 click_timer_.Stop(); | |
260 } | |
261 } | |
262 | |
263 void AutofillAgent::WillSubmitForm(WebFrame* frame, | |
264 const WebFormElement& form) { | |
265 FormData form_data; | |
266 if (WebFormElementToFormData(form, | |
267 WebFormControlElement(), | |
268 REQUIRE_AUTOCOMPLETE, | |
269 static_cast<ExtractMask>( | |
270 EXTRACT_VALUE | EXTRACT_OPTION_TEXT), | |
271 &form_data, | |
272 NULL)) { | |
273 Send(new AutofillHostMsg_FormSubmitted(routing_id(), form_data, | |
274 base::TimeTicks::Now())); | |
275 } | |
276 } | |
277 | |
278 void AutofillAgent::ZoomLevelChanged() { | |
279 // Any time the zoom level changes, the page's content moves, so any Autofill | |
280 // popups should be hidden. This is only needed for the new Autofill UI | |
281 // because WebKit already knows to hide the old UI when this occurs. | |
282 HideHostAutofillUi(); | |
283 } | |
284 | |
285 void AutofillAgent::FocusedNodeChanged(const WebKit::WebNode& node) { | |
286 if (node.isNull() || !node.isElementNode()) | |
287 return; | |
288 | |
289 WebKit::WebElement web_element = node.toConst<WebKit::WebElement>(); | |
290 | |
291 if (!web_element.document().frame()) | |
292 return; | |
293 | |
294 const WebInputElement* element = toWebInputElement(&web_element); | |
295 | |
296 if (!element || !element->isEnabled() || element->isReadOnly() || | |
297 !element->isTextField() || element->isPasswordField()) | |
298 return; | |
299 | |
300 element_ = *element; | |
301 | |
302 MaybeShowAutocheckoutBubble(); | |
303 } | |
304 | |
305 void AutofillAgent::MaybeShowAutocheckoutBubble() { | |
306 if (element_.isNull() || !element_.focused()) | |
307 return; | |
308 | |
309 FormData form; | |
310 FormFieldData field; | |
311 // This must be called to short circuit this method if it fails. | |
312 if (!FindFormAndFieldForInputElement(element_, &form, &field, REQUIRE_NONE)) | |
313 return; | |
314 | |
315 form.ssl_status = render_view()->GetSSLStatusOfFrame( | |
316 element_.document().frame()); | |
317 | |
318 Send(new AutofillHostMsg_MaybeShowAutocheckoutBubble( | |
319 routing_id(), | |
320 form, | |
321 GetScaledBoundingBox(web_view_->pageScaleFactor(), &element_))); | |
322 } | |
323 | |
324 void AutofillAgent::DidChangeScrollOffset(WebKit::WebFrame*) { | |
325 HideAutofillUi(); | |
326 } | |
327 | |
328 void AutofillAgent::didRequestAutocomplete(WebKit::WebFrame* frame, | |
329 const WebFormElement& form) { | |
330 FormData form_data; | |
331 if (!in_flight_request_form_.isNull() || | |
332 !WebFormElementToFormData(form, | |
333 WebFormControlElement(), | |
334 REQUIRE_AUTOCOMPLETE, | |
335 EXTRACT_OPTIONS, | |
336 &form_data, | |
337 NULL)) { | |
338 WebFormElement(form).finishRequestAutocomplete( | |
339 WebFormElement::AutocompleteResultErrorDisabled); | |
340 return; | |
341 } | |
342 | |
343 // Cancel any pending Autofill requests and hide any currently showing popups. | |
344 ++autofill_query_id_; | |
345 HideAutofillUi(); | |
346 | |
347 in_flight_request_form_ = form; | |
348 // Add SSL Status in the formdata to let browser process alert user | |
349 // appropriately using browser UI. | |
350 form_data.ssl_status = render_view()->GetSSLStatusOfFrame(frame); | |
351 Send(new AutofillHostMsg_RequestAutocomplete( | |
352 routing_id(), | |
353 form_data, | |
354 frame->document().url())); | |
355 } | |
356 | |
357 void AutofillAgent::setIgnoreTextChanges(bool ignore) { | |
358 ignore_text_changes_ = ignore; | |
359 } | |
360 | |
361 void AutofillAgent::InputElementClicked(const WebInputElement& element, | |
362 bool was_focused, | |
363 bool is_focused) { | |
364 if (was_focused) | |
365 ShowSuggestions(element, true, false, true); | |
366 } | |
367 | |
368 void AutofillAgent::InputElementLostFocus() { | |
369 HideHostAutofillUi(); | |
370 } | |
371 | |
372 void AutofillAgent::didAcceptAutofillSuggestion(const WebNode& node, | |
373 const WebString& value, | |
374 const WebString& label, | |
375 int item_id, | |
376 unsigned index) { | |
377 if (password_autofill_agent_->DidAcceptAutofillSuggestion(node, value)) | |
378 return; | |
379 | |
380 DCHECK(node == element_); | |
381 | |
382 switch (item_id) { | |
383 case WebAutofillClient::MenuItemIDWarningMessage: | |
384 case WebAutofillClient::MenuItemIDSeparator: | |
385 NOTREACHED(); | |
386 break; | |
387 case WebAutofillClient::MenuItemIDAutofillOptions: | |
388 // User selected 'Autofill Options'. | |
389 Send(new AutofillHostMsg_ShowAutofillDialog(routing_id())); | |
390 break; | |
391 case WebAutofillClient::MenuItemIDClearForm: | |
392 // User selected 'Clear form'. | |
393 form_cache_.ClearFormWithElement(element_); | |
394 break; | |
395 case WebAutofillClient::MenuItemIDAutocompleteEntry: | |
396 case WebAutofillClient::MenuItemIDPasswordEntry: | |
397 // User selected an Autocomplete or password entry, so we fill directly. | |
398 SetNodeText(value, &element_); | |
399 break; | |
400 case WebAutofillClient::MenuItemIDDataListEntry: | |
401 AcceptDataListSuggestion(value); | |
402 break; | |
403 default: | |
404 // A positive item_id is a unique id for an autofill (vs. autocomplete) | |
405 // suggestion. | |
406 DCHECK_GT(item_id, 0); | |
407 // Fill the values for the whole form. | |
408 FillAutofillFormData(node, item_id, AUTOFILL_FILL); | |
409 } | |
410 } | |
411 | |
412 void AutofillAgent::didSelectAutofillSuggestion(const WebNode& node, | |
413 const WebString& value, | |
414 const WebString& label, | |
415 int item_id) { | |
416 if (password_autofill_agent_->DidSelectAutofillSuggestion(node)) | |
417 return; | |
418 | |
419 didClearAutofillSelection(node); | |
420 | |
421 if (item_id > 0) | |
422 FillAutofillFormData(node, item_id, AUTOFILL_PREVIEW); | |
423 } | |
424 | |
425 void AutofillAgent::didClearAutofillSelection(const WebNode& node) { | |
426 if (password_autofill_agent_->DidClearAutofillSelection(node)) | |
427 return; | |
428 | |
429 if (!element_.isNull() && node == element_) { | |
430 ClearPreviewedFormWithElement(element_, was_query_node_autofilled_); | |
431 } else { | |
432 // TODO(isherman): There seem to be rare cases where this code *is* | |
433 // reachable: see [ http://crbug.com/96321#c6 ]. Ideally we would | |
434 // understand those cases and fix the code to avoid them. However, so far I | |
435 // have been unable to reproduce such a case locally. If you hit this | |
436 // NOTREACHED(), please file a bug against me. | |
437 NOTREACHED(); | |
438 } | |
439 } | |
440 | |
441 void AutofillAgent::removeAutocompleteSuggestion(const WebString& name, | |
442 const WebString& value) { | |
443 Send(new AutofillHostMsg_RemoveAutocompleteEntry(routing_id(), name, value)); | |
444 } | |
445 | |
446 void AutofillAgent::textFieldDidEndEditing(const WebInputElement& element) { | |
447 password_autofill_agent_->TextFieldDidEndEditing(element); | |
448 has_shown_autofill_popup_for_current_edit_ = false; | |
449 Send(new AutofillHostMsg_DidEndTextFieldEditing(routing_id())); | |
450 } | |
451 | |
452 void AutofillAgent::textFieldDidChange(const WebInputElement& element) { | |
453 if (ignore_text_changes_) | |
454 return; | |
455 | |
456 if (did_set_node_text_) { | |
457 did_set_node_text_ = false; | |
458 return; | |
459 } | |
460 | |
461 // We post a task for doing the Autofill as the caret position is not set | |
462 // properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and | |
463 // it is needed to trigger autofill. | |
464 weak_ptr_factory_.InvalidateWeakPtrs(); | |
465 base::MessageLoop::current()->PostTask( | |
466 FROM_HERE, | |
467 base::Bind(&AutofillAgent::TextFieldDidChangeImpl, | |
468 weak_ptr_factory_.GetWeakPtr(), | |
469 element)); | |
470 } | |
471 | |
472 void AutofillAgent::TextFieldDidChangeImpl(const WebInputElement& element) { | |
473 // If the element isn't focused then the changes don't matter. This check is | |
474 // required to properly handle IME interactions. | |
475 if (!element.focused()) | |
476 return; | |
477 | |
478 if (password_autofill_agent_->TextDidChangeInTextField(element)) { | |
479 element_ = element; | |
480 return; | |
481 } | |
482 | |
483 ShowSuggestions(element, false, true, false); | |
484 | |
485 FormData form; | |
486 FormFieldData field; | |
487 if (FindFormAndFieldForInputElement(element, &form, &field, REQUIRE_NONE)) { | |
488 Send(new AutofillHostMsg_TextFieldDidChange(routing_id(), form, field, | |
489 base::TimeTicks::Now())); | |
490 } | |
491 } | |
492 | |
493 void AutofillAgent::textFieldDidReceiveKeyDown(const WebInputElement& element, | |
494 const WebKeyboardEvent& event) { | |
495 if (password_autofill_agent_->TextFieldHandlingKeyDown(element, event)) { | |
496 element_ = element; | |
497 return; | |
498 } | |
499 | |
500 if (event.windowsKeyCode == ui::VKEY_DOWN || | |
501 event.windowsKeyCode == ui::VKEY_UP) | |
502 ShowSuggestions(element, true, true, true); | |
503 } | |
504 | |
505 void AutofillAgent::OnSuggestionsReturned( | |
506 int query_id, | |
507 const std::vector<base::string16>& values, | |
508 const std::vector<base::string16>& labels, | |
509 const std::vector<base::string16>& icons, | |
510 const std::vector<int>& unique_ids) { | |
511 if (query_id != autofill_query_id_) | |
512 return; | |
513 | |
514 if (element_.isNull() || !element_.isFocusable()) | |
515 return; | |
516 | |
517 std::vector<base::string16> v(values); | |
518 std::vector<base::string16> l(labels); | |
519 std::vector<base::string16> i(icons); | |
520 std::vector<int> ids(unique_ids); | |
521 | |
522 if (!element_.autoComplete() && !v.empty()) { | |
523 // If autofill is disabled and we had suggestions, show a warning instead. | |
524 v.assign(1, l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED)); | |
525 l.assign(1, base::string16()); | |
526 i.assign(1, base::string16()); | |
527 ids.assign(1, WebAutofillClient::MenuItemIDWarningMessage); | |
528 } else if (ids.size() > 1 && | |
529 ids[0] == WebAutofillClient::MenuItemIDWarningMessage) { | |
530 // If we received an autofill warning plus some autocomplete suggestions, | |
531 // remove the autofill warning. | |
532 v.erase(v.begin()); | |
533 l.erase(l.begin()); | |
534 i.erase(i.begin()); | |
535 ids.erase(ids.begin()); | |
536 } | |
537 | |
538 // If we were about to show a warning and we shouldn't, don't. | |
539 if (!display_warning_if_disabled_ && !v.empty() && | |
540 ids[0] == WebAutofillClient::MenuItemIDWarningMessage) { | |
541 v.clear(); | |
542 l.clear(); | |
543 i.clear(); | |
544 ids.clear(); | |
545 } | |
546 | |
547 // Only include "Autofill Options" special menu item if we have Autofill | |
548 // items, identified by |unique_ids| having at least one valid value. | |
549 bool has_autofill_item = false; | |
550 for (size_t i = 0; i < ids.size(); ++i) { | |
551 if (ids[i] > 0) { | |
552 has_autofill_item = true; | |
553 break; | |
554 } | |
555 } | |
556 | |
557 if (has_autofill_item) { | |
558 v.push_back(base::string16()); | |
559 l.push_back(base::string16()); | |
560 i.push_back(base::string16()); | |
561 ids.push_back(WebAutofillClient::MenuItemIDSeparator); | |
562 | |
563 if (FormWithElementIsAutofilled(element_)) { | |
564 // The form has been auto-filled, so give the user the chance to clear the | |
565 // form. Append the 'Clear form' menu item. | |
566 v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM)); | |
567 l.push_back(base::string16()); | |
568 i.push_back(base::string16()); | |
569 ids.push_back(WebAutofillClient::MenuItemIDClearForm); | |
570 } | |
571 | |
572 // Append the 'Chrome Autofill options' menu item; | |
573 v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP)); | |
574 l.push_back(base::string16()); | |
575 i.push_back(base::string16()); | |
576 ids.push_back(WebAutofillClient::MenuItemIDAutofillOptions); | |
577 } | |
578 | |
579 CombineDataListEntriesAndShow(element_, v, l, i, ids, has_autofill_item); | |
580 } | |
581 | |
582 void AutofillAgent::CombineDataListEntriesAndShow( | |
583 const WebKit::WebInputElement& element, | |
584 const std::vector<base::string16>& values, | |
585 const std::vector<base::string16>& labels, | |
586 const std::vector<base::string16>& icons, | |
587 const std::vector<int>& item_ids, | |
588 bool has_autofill_item) { | |
589 std::vector<base::string16> v; | |
590 std::vector<base::string16> l; | |
591 std::vector<base::string16> i; | |
592 std::vector<int> ids; | |
593 | |
594 AppendDataListSuggestions(element, &v, &l, &i, &ids); | |
595 | |
596 // If there are both <datalist> items and Autofill suggestions, add a | |
597 // separator between them. | |
598 if (!v.empty() && !values.empty()) { | |
599 v.push_back(base::string16()); | |
600 l.push_back(base::string16()); | |
601 i.push_back(base::string16()); | |
602 ids.push_back(WebAutofillClient::MenuItemIDSeparator); | |
603 } | |
604 | |
605 // Append the Autofill suggestions. | |
606 v.insert(v.end(), values.begin(), values.end()); | |
607 l.insert(l.end(), labels.begin(), labels.end()); | |
608 i.insert(i.end(), icons.begin(), icons.end()); | |
609 ids.insert(ids.end(), item_ids.begin(), item_ids.end()); | |
610 | |
611 if (v.empty()) { | |
612 // No suggestions, any popup currently showing is obsolete. | |
613 HideAutofillUi(); | |
614 return; | |
615 } | |
616 | |
617 WebKit::WebView* web_view = render_view()->GetWebView(); | |
618 if (!web_view) | |
619 return; | |
620 | |
621 // Send to WebKit for display. | |
622 web_view->applyAutofillSuggestions(element, v, l, i, ids); | |
623 | |
624 Send(new AutofillHostMsg_DidShowAutofillSuggestions( | |
625 routing_id(), | |
626 has_autofill_item && !has_shown_autofill_popup_for_current_edit_)); | |
627 has_shown_autofill_popup_for_current_edit_ |= has_autofill_item; | |
628 } | |
629 | |
630 void AutofillAgent::AcceptDataListSuggestion( | |
631 const base::string16& suggested_value) { | |
632 base::string16 new_value = suggested_value; | |
633 // If this element takes multiple values then replace the last part with | |
634 // the suggestion. | |
635 if (element_.isMultiple() && | |
636 element_.formControlType() == WebString::fromUTF8("email")) { | |
637 std::vector<base::string16> parts; | |
638 | |
639 base::SplitStringDontTrim(element_.editingValue(), ',', &parts); | |
640 if (parts.size() == 0) | |
641 parts.push_back(base::string16()); | |
642 | |
643 base::string16 last_part = parts.back(); | |
644 // We want to keep just the leading whitespace. | |
645 for (size_t i = 0; i < last_part.size(); ++i) { | |
646 if (!IsWhitespace(last_part[i])) { | |
647 last_part = last_part.substr(0, i); | |
648 break; | |
649 } | |
650 } | |
651 last_part.append(suggested_value); | |
652 parts[parts.size() - 1] = last_part; | |
653 | |
654 new_value = JoinString(parts, ','); | |
655 } | |
656 SetNodeText(new_value, &element_); | |
657 } | |
658 | |
659 void AutofillAgent::OnFormDataFilled(int query_id, | |
660 const FormData& form) { | |
661 if (!render_view()->GetWebView() || query_id != autofill_query_id_) | |
662 return; | |
663 | |
664 was_query_node_autofilled_ = element_.isAutofilled(); | |
665 | |
666 switch (autofill_action_) { | |
667 case AUTOFILL_FILL: | |
668 FillForm(form, element_); | |
669 Send(new AutofillHostMsg_DidFillAutofillFormData(routing_id(), | |
670 base::TimeTicks::Now())); | |
671 break; | |
672 case AUTOFILL_PREVIEW: | |
673 PreviewForm(form, element_); | |
674 Send(new AutofillHostMsg_DidPreviewAutofillFormData(routing_id())); | |
675 break; | |
676 default: | |
677 NOTREACHED(); | |
678 } | |
679 autofill_action_ = AUTOFILL_NONE; | |
680 } | |
681 | |
682 void AutofillAgent::OnFieldTypePredictionsAvailable( | |
683 const std::vector<FormDataPredictions>& forms) { | |
684 for (size_t i = 0; i < forms.size(); ++i) { | |
685 form_cache_.ShowPredictions(forms[i]); | |
686 } | |
687 } | |
688 | |
689 void AutofillAgent::OnSetAutofillActionFill() { | |
690 autofill_action_ = AUTOFILL_FILL; | |
691 } | |
692 | |
693 void AutofillAgent::OnClearForm() { | |
694 form_cache_.ClearFormWithElement(element_); | |
695 } | |
696 | |
697 void AutofillAgent::OnSetAutofillActionPreview() { | |
698 autofill_action_ = AUTOFILL_PREVIEW; | |
699 } | |
700 | |
701 void AutofillAgent::OnClearPreviewedForm() { | |
702 didClearAutofillSelection(element_); | |
703 } | |
704 | |
705 void AutofillAgent::OnSetNodeText(const base::string16& value) { | |
706 SetNodeText(value, &element_); | |
707 } | |
708 | |
709 void AutofillAgent::OnAcceptDataListSuggestion(const base::string16& value) { | |
710 AcceptDataListSuggestion(value); | |
711 } | |
712 | |
713 void AutofillAgent::OnAcceptPasswordAutofillSuggestion( | |
714 const base::string16& value) { | |
715 // We need to make sure this is handled here because the browser process | |
716 // skipped it handling because it believed it would be handled here. If it | |
717 // isn't handled here then the browser logic needs to be updated. | |
718 bool handled = password_autofill_agent_->DidAcceptAutofillSuggestion( | |
719 element_, | |
720 value); | |
721 DCHECK(handled); | |
722 } | |
723 | |
724 void AutofillAgent::OnGetAllForms() { | |
725 form_elements_.clear(); | |
726 | |
727 // Force fetch all non empty forms. | |
728 std::vector<FormData> forms; | |
729 form_cache_.ExtractFormsAndFormElements( | |
730 *topmost_frame_, 0, &forms, &form_elements_); | |
731 | |
732 // OnGetAllForms should only be called if AutofillAgent reported to | |
733 // AutofillManager that there are more forms | |
734 DCHECK(!forms.empty()); | |
735 | |
736 // Report to AutofillManager that all forms are being sent. | |
737 Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, | |
738 forms_seen_timestamp_, | |
739 NO_SPECIAL_FORMS_SEEN)); | |
740 } | |
741 | |
742 void AutofillAgent::OnRequestAutocompleteResult( | |
743 WebFormElement::AutocompleteResult result, const FormData& form_data) { | |
744 if (in_flight_request_form_.isNull()) | |
745 return; | |
746 | |
747 if (result == WebFormElement::AutocompleteResultSuccess) | |
748 FillFormIncludingNonFocusableElements(form_data, in_flight_request_form_); | |
749 | |
750 in_flight_request_form_.finishRequestAutocomplete(result); | |
751 in_flight_request_form_.reset(); | |
752 } | |
753 | |
754 void AutofillAgent::OnFillFormsAndClick( | |
755 const std::vector<FormData>& forms, | |
756 const WebElementDescriptor& click_element_descriptor) { | |
757 DCHECK_EQ(forms.size(), form_elements_.size()); | |
758 | |
759 // Fill the form. | |
760 for (size_t i = 0; i < forms.size(); ++i) | |
761 FillFormIncludingNonFocusableElements(forms[i], form_elements_[i]); | |
762 | |
763 // Exit early if there is nothing to click. | |
764 if (click_element_descriptor.retrieval_method == WebElementDescriptor::NONE) | |
765 return; | |
766 | |
767 // It's possible that clicking the element to proceed in an Autocheckout | |
768 // flow will not actually proceed to the next step in the flow, e.g. there | |
769 // is a new required field that Autocheckout does not know how to fill. In | |
770 // order to capture this case and present the user with an error a timer is | |
771 // set that informs the browser of the error. |click_timer_| has to be started | |
772 // before clicking so it can start before DidStartProvisionalLoad started. | |
773 click_timer_.Start(FROM_HERE, | |
774 base::TimeDelta::FromSeconds(kAutocheckoutClickTimeout), | |
775 this, | |
776 &AutofillAgent::ClickFailed); | |
777 if (!ClickElement(topmost_frame_->document(), | |
778 click_element_descriptor)) { | |
779 click_timer_.Stop(); | |
780 Send(new AutofillHostMsg_ClickFailed(routing_id(), | |
781 MISSING_ADVANCE)); | |
782 } | |
783 } | |
784 | |
785 void AutofillAgent::OnAutocheckoutSupported() { | |
786 is_autocheckout_supported_ = true; | |
787 if (has_new_forms_for_browser_) | |
788 MaybeSendDynamicFormsSeen(); | |
789 MaybeShowAutocheckoutBubble(); | |
790 } | |
791 | |
792 void AutofillAgent::ClickFailed() { | |
793 Send(new AutofillHostMsg_ClickFailed(routing_id(), | |
794 CANNOT_PROCEED)); | |
795 } | |
796 | |
797 void AutofillAgent::ShowSuggestions(const WebInputElement& element, | |
798 bool autofill_on_empty_values, | |
799 bool requires_caret_at_end, | |
800 bool display_warning_if_disabled) { | |
801 if (!element.isEnabled() || element.isReadOnly() || !element.isTextField() || | |
802 element.isPasswordField() || !element.suggestedValue().isEmpty()) | |
803 return; | |
804 | |
805 // Don't attempt to autofill with values that are too large or if filling | |
806 // criteria are not met. | |
807 WebString value = element.editingValue(); | |
808 if (value.length() > kMaximumTextSizeForAutofill || | |
809 (!autofill_on_empty_values && value.isEmpty()) || | |
810 (requires_caret_at_end && | |
811 (element.selectionStart() != element.selectionEnd() || | |
812 element.selectionEnd() != static_cast<int>(value.length())))) { | |
813 // Any popup currently showing is obsolete. | |
814 HideAutofillUi(); | |
815 return; | |
816 } | |
817 | |
818 element_ = element; | |
819 if (password_autofill_agent_->ShowSuggestions(element)) | |
820 return; | |
821 | |
822 // If autocomplete is disabled at the form level, then we might want to show a | |
823 // warning in place of suggestions. However, if autocomplete is disabled | |
824 // specifically for this field, we never want to show a warning. Otherwise, | |
825 // we might interfere with custom popups (e.g. search suggestions) used by the | |
826 // website. Note that we cannot use the WebKit method element.autoComplete() | |
827 // as it does not allow us to distinguish the case where autocomplete is | |
828 // disabled for *both* the element and for the form. | |
829 // Also, if the field has no name, then we won't have values. | |
830 const base::string16 autocomplete_attribute = | |
831 element.getAttribute("autocomplete"); | |
832 if (LowerCaseEqualsASCII(autocomplete_attribute, "off") || | |
833 element.nameForAutofill().isEmpty()) { | |
834 CombineDataListEntriesAndShow(element, std::vector<base::string16>(), | |
835 std::vector<base::string16>(), | |
836 std::vector<base::string16>(), | |
837 std::vector<int>(), false); | |
838 return; | |
839 } | |
840 | |
841 QueryAutofillSuggestions(element, display_warning_if_disabled); | |
842 } | |
843 | |
844 void AutofillAgent::QueryAutofillSuggestions(const WebInputElement& element, | |
845 bool display_warning_if_disabled) { | |
846 if (!element.document().frame()) | |
847 return; | |
848 | |
849 static int query_counter = 0; | |
850 autofill_query_id_ = query_counter++; | |
851 display_warning_if_disabled_ = display_warning_if_disabled; | |
852 | |
853 // If autocomplete is disabled at the form level, we want to see if there | |
854 // would have been any suggestions were it enabled, so that we can show a | |
855 // warning. Otherwise, we want to ignore fields that disable autocomplete, so | |
856 // that the suggestions list does not include suggestions for these form | |
857 // fields -- see comment 1 on http://crbug.com/69914 | |
858 // Rather than testing the form's autocomplete enabled state, we test the | |
859 // element's state. The DCHECK below ensures that this is equivalent. | |
860 DCHECK(element.autoComplete() || !element.form().autoComplete()); | |
861 const RequirementsMask requirements = | |
862 element.autoComplete() ? REQUIRE_AUTOCOMPLETE : REQUIRE_NONE; | |
863 | |
864 FormData form; | |
865 FormFieldData field; | |
866 if (!FindFormAndFieldForInputElement(element, &form, &field, requirements)) { | |
867 // If we didn't find the cached form, at least let autocomplete have a shot | |
868 // at providing suggestions. | |
869 WebFormControlElementToFormField(element, EXTRACT_VALUE, &field); | |
870 } | |
871 | |
872 gfx::RectF bounding_box_scaled = | |
873 GetScaledBoundingBox(web_view_->pageScaleFactor(), &element_); | |
874 | |
875 // Find the datalist values and send them to the browser process. | |
876 std::vector<base::string16> data_list_values; | |
877 std::vector<base::string16> data_list_labels; | |
878 std::vector<base::string16> data_list_icons; | |
879 std::vector<int> data_list_unique_ids; | |
880 AppendDataListSuggestions(element_, | |
881 &data_list_values, | |
882 &data_list_labels, | |
883 &data_list_icons, | |
884 &data_list_unique_ids); | |
885 | |
886 TrimDataListsForIPC(&data_list_values, | |
887 &data_list_labels, | |
888 &data_list_icons, | |
889 &data_list_unique_ids); | |
890 | |
891 Send(new AutofillHostMsg_SetDataList(routing_id(), | |
892 data_list_values, | |
893 data_list_labels, | |
894 data_list_icons, | |
895 data_list_unique_ids)); | |
896 | |
897 // Add SSL Status in the formdata to let browser process alert user | |
898 // appropriately using browser UI. | |
899 form.ssl_status = render_view()->GetSSLStatusOfFrame( | |
900 element.document().frame()); | |
901 Send(new AutofillHostMsg_QueryFormFieldAutofill(routing_id(), | |
902 autofill_query_id_, | |
903 form, | |
904 field, | |
905 bounding_box_scaled, | |
906 display_warning_if_disabled)); | |
907 } | |
908 | |
909 void AutofillAgent::FillAutofillFormData(const WebNode& node, | |
910 int unique_id, | |
911 AutofillAction action) { | |
912 DCHECK_GT(unique_id, 0); | |
913 | |
914 static int query_counter = 0; | |
915 autofill_query_id_ = query_counter++; | |
916 | |
917 FormData form; | |
918 FormFieldData field; | |
919 if (!FindFormAndFieldForInputElement(node.toConst<WebInputElement>(), &form, | |
920 &field, REQUIRE_AUTOCOMPLETE)) { | |
921 return; | |
922 } | |
923 | |
924 autofill_action_ = action; | |
925 Send(new AutofillHostMsg_FillAutofillFormData( | |
926 routing_id(), autofill_query_id_, form, field, unique_id)); | |
927 } | |
928 | |
929 void AutofillAgent::SetNodeText(const base::string16& value, | |
930 WebKit::WebInputElement* node) { | |
931 did_set_node_text_ = true; | |
932 base::string16 substring = value; | |
933 substring = substring.substr(0, node->maxLength()); | |
934 | |
935 node->setEditingValue(substring); | |
936 } | |
937 | |
938 void AutofillAgent::HideAutofillUi() { | |
939 WebKit::WebView* web_view = render_view()->GetWebView(); | |
940 if (web_view) | |
941 web_view->hidePopups(); | |
942 | |
943 HideHostAutofillUi(); | |
944 } | |
945 | |
946 void AutofillAgent::HideHostAutofillUi() { | |
947 Send(new AutofillHostMsg_HideAutofillUi(routing_id())); | |
948 } | |
949 | |
950 void AutofillAgent::didAssociateFormControls( | |
951 const WebKit::WebVector<WebKit::WebNode>& nodes) { | |
952 for (size_t i = 0; i < nodes.size(); ++i) { | |
953 WebKit::WebNode node = nodes[i]; | |
954 if (node.document().frame() == topmost_frame_) { | |
955 forms_seen_timestamp_ = base::TimeTicks::Now(); | |
956 has_new_forms_for_browser_ = true; | |
957 break; | |
958 } | |
959 } | |
960 | |
961 if (has_new_forms_for_browser_ && is_autocheckout_supported_) | |
962 MaybeSendDynamicFormsSeen(); | |
963 } | |
964 | |
965 void AutofillAgent::MaybeSendDynamicFormsSeen() { | |
966 has_new_forms_for_browser_ = false; | |
967 form_elements_.clear(); | |
968 std::vector<FormData> forms; | |
969 // This will only be called for Autocheckout flows, so send all forms to | |
970 // save an IPC. | |
971 form_cache_.ExtractFormsAndFormElements( | |
972 *topmost_frame_, 0, &forms, &form_elements_); | |
973 autofill::FormsSeenState state = autofill::DYNAMIC_FORMS_SEEN; | |
974 | |
975 if (!forms.empty()) { | |
976 if (click_timer_.IsRunning()) | |
977 click_timer_.Stop(); | |
978 Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, | |
979 forms_seen_timestamp_, | |
980 state)); | |
981 } | |
982 } | |
983 | |
984 } // namespace autofill | |
OLD | NEW |