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
3 // found in the LICENSE file.
4
5 #include "components/autofill/core/browser/autofill_external_delegate.h"
6
7 #include "base/message_loop/message_loop.h"
8 #include "base/metrics/histogram.h"
9 #include "base/metrics/sparse_histogram.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "components/autofill/core/browser/autocomplete_history_manager.h"
12 #include "components/autofill/core/browser/autofill_driver.h"
13 #include "components/autofill/core/browser/autofill_manager.h"
14 #include "components/autofill/core/browser/popup_item_ids.h"
15 #include "grit/components_strings.h"
16 #include "ui/base/l10n/l10n_util.h"
17
18 #if defined(OS_MACOSX) && !defined(OS_IOS)
19 namespace {
20
21 enum AccessAddressBookEventType {
22 // An Autofill entry was shown that prompts the user to give Chrome access to
23 // the user's Address Book.
24 SHOWED_ACCESS_ADDRESS_BOOK_ENTRY = 0,
25
26 // The user selected the Autofill entry which prompts Chrome to access the
27 // user's Address Book.
28 SELECTED_ACCESS_ADDRESS_BOOK_ENTRY = 1,
29
30 // Always keep this at the end.
31 ACCESS_ADDRESS_BOOK_ENTRY_MAX,
32 };
33
34 // Emits an entry for the histogram.
EmitHistogram(AccessAddressBookEventType type)35 void EmitHistogram(AccessAddressBookEventType type) {
36 UMA_HISTOGRAM_ENUMERATION(
37 "Autofill.MacAddressBook", type, ACCESS_ADDRESS_BOOK_ENTRY_MAX);
38 }
39
40 } // namespace
41 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
42
43 namespace autofill {
44
AutofillExternalDelegate(AutofillManager * manager,AutofillDriver * driver)45 AutofillExternalDelegate::AutofillExternalDelegate(AutofillManager* manager,
46 AutofillDriver* driver)
47 : manager_(manager),
48 driver_(driver),
49 query_id_(0),
50 display_warning_if_disabled_(false),
51 has_suggestion_(false),
52 has_shown_popup_for_current_edit_(false),
53 weak_ptr_factory_(this),
54 has_shown_address_book_prompt(false) {
55 DCHECK(manager);
56 }
57
~AutofillExternalDelegate()58 AutofillExternalDelegate::~AutofillExternalDelegate() {}
59
OnQuery(int query_id,const FormData & form,const FormFieldData & field,const gfx::RectF & element_bounds,bool display_warning_if_disabled)60 void AutofillExternalDelegate::OnQuery(int query_id,
61 const FormData& form,
62 const FormFieldData& field,
63 const gfx::RectF& element_bounds,
64 bool display_warning_if_disabled) {
65 if (query_form_ != form)
66 has_shown_address_book_prompt = false;
67
68 query_form_ = form;
69 query_field_ = field;
70 display_warning_if_disabled_ = display_warning_if_disabled;
71 query_id_ = query_id;
72 element_bounds_ = element_bounds;
73 }
74
OnSuggestionsReturned(int query_id,const std::vector<base::string16> & suggested_values,const std::vector<base::string16> & suggested_labels,const std::vector<base::string16> & suggested_icons,const std::vector<int> & suggested_unique_ids)75 void AutofillExternalDelegate::OnSuggestionsReturned(
76 int query_id,
77 const std::vector<base::string16>& suggested_values,
78 const std::vector<base::string16>& suggested_labels,
79 const std::vector<base::string16>& suggested_icons,
80 const std::vector<int>& suggested_unique_ids) {
81 if (query_id != query_id_)
82 return;
83
84 std::vector<base::string16> values(suggested_values);
85 std::vector<base::string16> labels(suggested_labels);
86 std::vector<base::string16> icons(suggested_icons);
87 std::vector<int> ids(suggested_unique_ids);
88
89 // Add or hide warnings as appropriate.
90 ApplyAutofillWarnings(&values, &labels, &icons, &ids);
91
92 // Add a separator to go between the values and menu items.
93 values.push_back(base::string16());
94 labels.push_back(base::string16());
95 icons.push_back(base::string16());
96 ids.push_back(POPUP_ITEM_ID_SEPARATOR);
97
98 // Only include "Autofill Options" special menu item if we have Autofill
99 // suggestions.
100 has_suggestion_ = false;
101 for (size_t i = 0; i < ids.size(); ++i) {
102 if (ids[i] > 0) {
103 has_suggestion_ = true;
104 break;
105 }
106 }
107
108 if (has_suggestion_)
109 ApplyAutofillOptions(&values, &labels, &icons, &ids);
110
111 // Remove the separator if it is the last element.
112 DCHECK_GT(ids.size(), 0U);
113 if (ids.back() == POPUP_ITEM_ID_SEPARATOR) {
114 values.pop_back();
115 labels.pop_back();
116 icons.pop_back();
117 ids.pop_back();
118 }
119
120 // If anything else is added to modify the values after inserting the data
121 // list, AutofillPopupControllerImpl::UpdateDataListValues will need to be
122 // updated to match.
123 InsertDataListValues(&values, &labels, &icons, &ids);
124
125 #if defined(OS_MACOSX) && !defined(OS_IOS)
126 if (values.empty() &&
127 manager_->ShouldShowAccessAddressBookSuggestion(query_form_,
128 query_field_)) {
129 values.push_back(
130 l10n_util::GetStringUTF16(IDS_AUTOFILL_ACCESS_MAC_CONTACTS));
131 labels.push_back(base::string16());
132 icons.push_back(base::ASCIIToUTF16("macContactsIcon"));
133 ids.push_back(POPUP_ITEM_ID_MAC_ACCESS_CONTACTS);
134
135 if (!has_shown_address_book_prompt) {
136 has_shown_address_book_prompt = true;
137 EmitHistogram(SHOWED_ACCESS_ADDRESS_BOOK_ENTRY);
138 manager_->ShowedAccessAddressBookPrompt();
139 }
140 }
141 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
142
143 if (values.empty()) {
144 // No suggestions, any popup currently showing is obsolete.
145 manager_->client()->HideAutofillPopup();
146 return;
147 }
148
149 // Send to display.
150 if (query_field_.is_focusable) {
151 manager_->client()->ShowAutofillPopup(element_bounds_,
152 query_field_.text_direction,
153 values,
154 labels,
155 icons,
156 ids,
157 GetWeakPtr());
158 }
159 }
160
SetCurrentDataListValues(const std::vector<base::string16> & data_list_values,const std::vector<base::string16> & data_list_labels)161 void AutofillExternalDelegate::SetCurrentDataListValues(
162 const std::vector<base::string16>& data_list_values,
163 const std::vector<base::string16>& data_list_labels) {
164 data_list_values_ = data_list_values;
165 data_list_labels_ = data_list_labels;
166
167 manager_->client()->UpdateAutofillPopupDataListValues(data_list_values,
168 data_list_labels);
169 }
170
OnPopupShown()171 void AutofillExternalDelegate::OnPopupShown() {
172 manager_->DidShowSuggestions(
173 has_suggestion_ && !has_shown_popup_for_current_edit_);
174 has_shown_popup_for_current_edit_ |= has_suggestion_;
175 }
176
OnPopupHidden()177 void AutofillExternalDelegate::OnPopupHidden() {
178 }
179
DidSelectSuggestion(const base::string16 & value,int identifier)180 void AutofillExternalDelegate::DidSelectSuggestion(
181 const base::string16& value,
182 int identifier) {
183 ClearPreviewedForm();
184
185 // Only preview the data if it is a profile.
186 if (identifier > 0)
187 FillAutofillFormData(identifier, true);
188 else if (identifier == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY)
189 driver_->RendererShouldPreviewFieldWithValue(value);
190 }
191
DidAcceptSuggestion(const base::string16 & value,int identifier)192 void AutofillExternalDelegate::DidAcceptSuggestion(const base::string16& value,
193 int identifier) {
194 if (identifier == POPUP_ITEM_ID_AUTOFILL_OPTIONS) {
195 // User selected 'Autofill Options'.
196 manager_->ShowAutofillSettings();
197 } else if (identifier == POPUP_ITEM_ID_CLEAR_FORM) {
198 // User selected 'Clear form'.
199 driver_->RendererShouldClearFilledForm();
200 } else if (identifier == POPUP_ITEM_ID_PASSWORD_ENTRY) {
201 NOTREACHED(); // Should be handled elsewhere.
202 } else if (identifier == POPUP_ITEM_ID_DATALIST_ENTRY) {
203 driver_->RendererShouldAcceptDataListSuggestion(value);
204 } else if (identifier == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
205 // User selected an Autocomplete, so we fill directly.
206 driver_->RendererShouldFillFieldWithValue(value);
207 } else if (identifier == POPUP_ITEM_ID_MAC_ACCESS_CONTACTS) {
208 #if defined(OS_MACOSX) && !defined(OS_IOS)
209 EmitHistogram(SELECTED_ACCESS_ADDRESS_BOOK_ENTRY);
210 UMA_HISTOGRAM_SPARSE_SLOWLY(
211 "Autofill.MacAddressBook.NumShowsBeforeSelected",
212 manager_->AccessAddressBookPromptCount());
213
214 // User wants to give Chrome access to user's address book.
215 manager_->AccessAddressBook();
216
217 // There is no deterministic method for deciding whether a blocking dialog
218 // was presented. The following comments and code assume that a blocking
219 // dialog was presented, but still behave correctly if no dialog was
220 // presented.
221
222 // A blocking dialog was presented, and the user has already responded to
223 // the dialog. The presentation of the dialog added an NSEvent to the
224 // NSRunLoop which will cause all windows to lose focus. When the NSEvent
225 // is processed, it will be sent to the renderer which will cause the text
226 // field to lose focus. This returns an IPC to Chrome which will dismiss
227 // the Autofill popup. We post a task which we expect to run after the
228 // NSEvent has been processed by the NSRunLoop. It pings the renderer,
229 // which returns an IPC acknowledging the ping. At that time, redisplay
230 // the popup. FIFO processing of IPCs ensures that all side effects of the
231 // NSEvent will have been processed.
232
233 // 10ms sits nicely under the 16ms threshold for 60 fps, and likely gives
234 // the NSApplication run loop sufficient time to process the NSEvent. In
235 // testing, a delay of 0ms was always sufficient.
236 base::TimeDelta delay(base::TimeDelta::FromMilliseconds(10));
237 base::MessageLoop::current()->PostDelayedTask(
238 FROM_HERE,
239 base::Bind(&AutofillExternalDelegate::PingRenderer, GetWeakPtr()),
240 delay);
241 #else
242 NOTREACHED();
243 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
244 } else {
245 FillAutofillFormData(identifier, false);
246 }
247
248 manager_->client()->HideAutofillPopup();
249 }
250
RemoveSuggestion(const base::string16 & value,int identifier)251 void AutofillExternalDelegate::RemoveSuggestion(const base::string16& value,
252 int identifier) {
253 if (identifier > 0)
254 manager_->RemoveAutofillProfileOrCreditCard(identifier);
255 else
256 manager_->RemoveAutocompleteEntry(query_field_.name, value);
257 }
258
DidEndTextFieldEditing()259 void AutofillExternalDelegate::DidEndTextFieldEditing() {
260 manager_->client()->HideAutofillPopup();
261
262 has_shown_popup_for_current_edit_ = false;
263 }
264
ClearPreviewedForm()265 void AutofillExternalDelegate::ClearPreviewedForm() {
266 driver_->RendererShouldClearPreviewedForm();
267 }
268
Reset()269 void AutofillExternalDelegate::Reset() {
270 manager_->client()->HideAutofillPopup();
271 }
272
OnPingAck()273 void AutofillExternalDelegate::OnPingAck() {
274 // Reissue the most recent query, which will reopen the Autofill popup.
275 manager_->OnQueryFormFieldAutofill(query_id_,
276 query_form_,
277 query_field_,
278 element_bounds_,
279 display_warning_if_disabled_);
280 }
281
GetWeakPtr()282 base::WeakPtr<AutofillExternalDelegate> AutofillExternalDelegate::GetWeakPtr() {
283 return weak_ptr_factory_.GetWeakPtr();
284 }
285
FillAutofillFormData(int unique_id,bool is_preview)286 void AutofillExternalDelegate::FillAutofillFormData(int unique_id,
287 bool is_preview) {
288 // If the selected element is a warning we don't want to do anything.
289 if (unique_id == POPUP_ITEM_ID_WARNING_MESSAGE)
290 return;
291
292 AutofillDriver::RendererFormDataAction renderer_action = is_preview ?
293 AutofillDriver::FORM_DATA_ACTION_PREVIEW :
294 AutofillDriver::FORM_DATA_ACTION_FILL;
295
296 DCHECK(driver_->RendererIsAvailable());
297 // Fill the values for the whole form.
298 manager_->FillOrPreviewForm(renderer_action,
299 query_id_,
300 query_form_,
301 query_field_,
302 unique_id);
303 }
304
ApplyAutofillWarnings(std::vector<base::string16> * values,std::vector<base::string16> * labels,std::vector<base::string16> * icons,std::vector<int> * unique_ids)305 void AutofillExternalDelegate::ApplyAutofillWarnings(
306 std::vector<base::string16>* values,
307 std::vector<base::string16>* labels,
308 std::vector<base::string16>* icons,
309 std::vector<int>* unique_ids) {
310 if (!query_field_.should_autocomplete) {
311 // Autofill is disabled. If there were some profile or credit card
312 // suggestions to show, show a warning instead. Otherwise, clear out the
313 // list of suggestions.
314 if (!unique_ids->empty() && (*unique_ids)[0] > 0) {
315 // If Autofill is disabled and we had suggestions, show a warning instead.
316 values->assign(
317 1, l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED));
318 labels->assign(1, base::string16());
319 icons->assign(1, base::string16());
320 unique_ids->assign(1, POPUP_ITEM_ID_WARNING_MESSAGE);
321 } else {
322 values->clear();
323 labels->clear();
324 icons->clear();
325 unique_ids->clear();
326 }
327 } else if (unique_ids->size() > 1 &&
328 (*unique_ids)[0] == POPUP_ITEM_ID_WARNING_MESSAGE) {
329 // If we received a warning instead of suggestions from Autofill but regular
330 // suggestions from autocomplete, don't show the Autofill warning.
331 values->erase(values->begin());
332 labels->erase(labels->begin());
333 icons->erase(icons->begin());
334 unique_ids->erase(unique_ids->begin());
335 }
336
337 // If we were about to show a warning and we shouldn't, don't.
338 if (!unique_ids->empty() &&
339 (*unique_ids)[0] == POPUP_ITEM_ID_WARNING_MESSAGE &&
340 !display_warning_if_disabled_) {
341 values->clear();
342 labels->clear();
343 icons->clear();
344 unique_ids->clear();
345 }
346 }
347
ApplyAutofillOptions(std::vector<base::string16> * values,std::vector<base::string16> * labels,std::vector<base::string16> * icons,std::vector<int> * unique_ids)348 void AutofillExternalDelegate::ApplyAutofillOptions(
349 std::vector<base::string16>* values,
350 std::vector<base::string16>* labels,
351 std::vector<base::string16>* icons,
352 std::vector<int>* unique_ids) {
353 // The form has been auto-filled, so give the user the chance to clear the
354 // form. Append the 'Clear form' menu item.
355 if (query_field_.is_autofilled) {
356 values->push_back(
357 l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM));
358 labels->push_back(base::string16());
359 icons->push_back(base::string16());
360 unique_ids->push_back(POPUP_ITEM_ID_CLEAR_FORM);
361 }
362
363 // Append the 'Chrome Autofill options' menu item;
364 values->push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP));
365 labels->push_back(base::string16());
366 icons->push_back(base::string16());
367 unique_ids->push_back(POPUP_ITEM_ID_AUTOFILL_OPTIONS);
368 }
369
InsertDataListValues(std::vector<base::string16> * values,std::vector<base::string16> * labels,std::vector<base::string16> * icons,std::vector<int> * unique_ids)370 void AutofillExternalDelegate::InsertDataListValues(
371 std::vector<base::string16>* values,
372 std::vector<base::string16>* labels,
373 std::vector<base::string16>* icons,
374 std::vector<int>* unique_ids) {
375 if (data_list_values_.empty())
376 return;
377
378 // Insert the separator between the datalist and Autofill values (if there
379 // are any).
380 if (!values->empty()) {
381 values->insert(values->begin(), base::string16());
382 labels->insert(labels->begin(), base::string16());
383 icons->insert(icons->begin(), base::string16());
384 unique_ids->insert(unique_ids->begin(), POPUP_ITEM_ID_SEPARATOR);
385 }
386
387 // Insert the datalist elements.
388 values->insert(values->begin(),
389 data_list_values_.begin(),
390 data_list_values_.end());
391 labels->insert(labels->begin(),
392 data_list_labels_.begin(),
393 data_list_labels_.end());
394
395 // Set the values that all datalist elements share.
396 icons->insert(icons->begin(),
397 data_list_values_.size(),
398 base::string16());
399 unique_ids->insert(unique_ids->begin(),
400 data_list_values_.size(),
401 POPUP_ITEM_ID_DATALIST_ENTRY);
402 }
403
404 #if defined(OS_MACOSX) && !defined(OS_IOS)
PingRenderer()405 void AutofillExternalDelegate::PingRenderer() {
406 driver_->PingRenderer();
407 }
408 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
409
410 } // namespace autofill
411