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.
5 #include "components/autofill/core/browser/autofill_external_delegate.h"
8 #include "base/command_line.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/metrics/histogram.h"
11 #include "base/metrics/sparse_histogram.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "components/autofill/core/browser/autocomplete_history_manager.h"
15 #include "components/autofill/core/browser/autofill_driver.h"
16 #include "components/autofill/core/browser/autofill_manager.h"
17 #include "components/autofill/core/browser/autofill_metrics.h"
18 #include "components/autofill/core/browser/popup_item_ids.h"
19 #include "components/autofill/core/common/autofill_switches.h"
20 #include "grit/components_strings.h"
21 #include "ui/base/l10n/l10n_util.h"
23 #if defined(OS_MACOSX) && !defined(OS_IOS)
26 enum AccessAddressBookEventType
{
27 // An Autofill entry was shown that prompts the user to give Chrome access to
28 // the user's Address Book.
29 SHOWED_ACCESS_ADDRESS_BOOK_ENTRY
= 0,
31 // The user selected the Autofill entry which prompts Chrome to access the
32 // user's Address Book.
33 SELECTED_ACCESS_ADDRESS_BOOK_ENTRY
= 1,
35 // Always keep this at the end.
36 ACCESS_ADDRESS_BOOK_ENTRY_MAX
,
39 // Emits an entry for the histogram.
40 void EmitHistogram(AccessAddressBookEventType type
) {
41 UMA_HISTOGRAM_ENUMERATION(
42 "Autofill.MacAddressBook", type
, ACCESS_ADDRESS_BOOK_ENTRY_MAX
);
46 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
52 bool ShouldAutofill(const FormFieldData
& form_field
) {
53 return form_field
.should_autocomplete
||
54 !base::CommandLine::ForCurrentProcess()->HasSwitch(
55 switches::kRespectAutocompleteOffForAutofill
);
60 AutofillExternalDelegate::AutofillExternalDelegate(AutofillManager
* manager
,
61 AutofillDriver
* driver
)
65 display_warning_if_disabled_(false),
66 has_suggestion_(false),
67 has_shown_popup_for_current_edit_(false),
68 should_show_scan_credit_card_(false),
69 has_shown_address_book_prompt(false),
70 weak_ptr_factory_(this) {
74 AutofillExternalDelegate::~AutofillExternalDelegate() {}
76 void AutofillExternalDelegate::OnQuery(int query_id
,
78 const FormFieldData
& field
,
79 const gfx::RectF
& element_bounds
,
80 bool display_warning_if_disabled
) {
81 if (!query_form_
.SameFormAs(form
))
82 has_shown_address_book_prompt
= false;
86 display_warning_if_disabled_
= display_warning_if_disabled
;
88 element_bounds_
= element_bounds
;
89 should_show_scan_credit_card_
=
90 manager_
->ShouldShowScanCreditCard(query_form_
, query_field_
);
93 void AutofillExternalDelegate::OnSuggestionsReturned(
95 const std::vector
<Suggestion
>& input_suggestions
) {
96 if (query_id
!= query_id_
)
99 std::vector
<Suggestion
> suggestions(input_suggestions
);
101 // Add or hide warnings as appropriate.
102 ApplyAutofillWarnings(&suggestions
);
104 // Add a separator to go between the values and menu items.
105 suggestions
.push_back(Suggestion());
106 suggestions
.back().frontend_id
= POPUP_ITEM_ID_SEPARATOR
;
108 if (should_show_scan_credit_card_
) {
109 Suggestion
scan_credit_card(
110 l10n_util::GetStringUTF16(IDS_AUTOFILL_SCAN_CREDIT_CARD
));
111 scan_credit_card
.frontend_id
= POPUP_ITEM_ID_SCAN_CREDIT_CARD
;
112 scan_credit_card
.icon
= base::ASCIIToUTF16("scanCreditCardIcon");
113 suggestions
.push_back(scan_credit_card
);
115 if (!has_shown_popup_for_current_edit_
) {
116 AutofillMetrics::LogScanCreditCardPromptMetric(
117 AutofillMetrics::SCAN_CARD_ITEM_SHOWN
);
121 // Only include "Autofill Options" special menu item if we have Autofill
123 has_suggestion_
= false;
124 for (size_t i
= 0; i
< suggestions
.size(); ++i
) {
125 if (suggestions
[i
].frontend_id
> 0) {
126 has_suggestion_
= true;
132 ApplyAutofillOptions(&suggestions
);
134 // Remove the separator if it is the last element.
135 DCHECK_GT(suggestions
.size(), 0U);
136 if (suggestions
.back().frontend_id
== POPUP_ITEM_ID_SEPARATOR
)
137 suggestions
.pop_back();
139 // If anything else is added to modify the values after inserting the data
140 // list, AutofillPopupControllerImpl::UpdateDataListValues will need to be
142 InsertDataListValues(&suggestions
);
144 #if defined(OS_MACOSX) && !defined(OS_IOS)
145 if (suggestions
.empty() &&
146 manager_
->ShouldShowAccessAddressBookSuggestion(query_form_
,
148 Suggestion
mac_contacts(
149 l10n_util::GetStringUTF16(IDS_AUTOFILL_ACCESS_MAC_CONTACTS
));
150 mac_contacts
.icon
= base::ASCIIToUTF16("macContactsIcon");
151 mac_contacts
.frontend_id
= POPUP_ITEM_ID_MAC_ACCESS_CONTACTS
;
153 if (!has_shown_address_book_prompt
) {
154 has_shown_address_book_prompt
= true;
155 EmitHistogram(SHOWED_ACCESS_ADDRESS_BOOK_ENTRY
);
156 manager_
->ShowedAccessAddressBookPrompt();
159 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
161 if (suggestions
.empty()) {
162 // No suggestions, any popup currently showing is obsolete.
163 manager_
->client()->HideAutofillPopup();
168 if (query_field_
.is_focusable
) {
169 manager_
->client()->ShowAutofillPopup(element_bounds_
,
170 query_field_
.text_direction
,
176 void AutofillExternalDelegate::SetCurrentDataListValues(
177 const std::vector
<base::string16
>& data_list_values
,
178 const std::vector
<base::string16
>& data_list_labels
) {
179 data_list_values_
= data_list_values
;
180 data_list_labels_
= data_list_labels
;
182 manager_
->client()->UpdateAutofillPopupDataListValues(data_list_values
,
186 void AutofillExternalDelegate::OnPopupShown() {
187 manager_
->DidShowSuggestions(
188 has_suggestion_
&& !has_shown_popup_for_current_edit_
,
191 has_shown_popup_for_current_edit_
|= has_suggestion_
;
194 void AutofillExternalDelegate::OnPopupHidden() {
195 driver_
->PopupHidden();
198 void AutofillExternalDelegate::DidSelectSuggestion(
199 const base::string16
& value
,
201 ClearPreviewedForm();
203 // Only preview the data if it is a profile.
205 FillAutofillFormData(identifier
, true);
206 else if (identifier
== POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
)
207 driver_
->RendererShouldPreviewFieldWithValue(value
);
210 void AutofillExternalDelegate::DidAcceptSuggestion(const base::string16
& value
,
212 if (identifier
== POPUP_ITEM_ID_AUTOFILL_OPTIONS
) {
213 // User selected 'Autofill Options'.
214 manager_
->ShowAutofillSettings();
215 } else if (identifier
== POPUP_ITEM_ID_CLEAR_FORM
) {
216 // User selected 'Clear form'.
217 driver_
->RendererShouldClearFilledForm();
218 } else if (identifier
== POPUP_ITEM_ID_PASSWORD_ENTRY
) {
219 NOTREACHED(); // Should be handled elsewhere.
220 } else if (identifier
== POPUP_ITEM_ID_DATALIST_ENTRY
) {
221 driver_
->RendererShouldAcceptDataListSuggestion(value
);
222 } else if (identifier
== POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
) {
223 // User selected an Autocomplete, so we fill directly.
224 driver_
->RendererShouldFillFieldWithValue(value
);
225 } else if (identifier
== POPUP_ITEM_ID_MAC_ACCESS_CONTACTS
) {
226 #if defined(OS_MACOSX) && !defined(OS_IOS)
227 EmitHistogram(SELECTED_ACCESS_ADDRESS_BOOK_ENTRY
);
228 UMA_HISTOGRAM_SPARSE_SLOWLY(
229 "Autofill.MacAddressBook.NumShowsBeforeSelected",
230 manager_
->AccessAddressBookPromptCount());
232 // User wants to give Chrome access to user's address book.
233 manager_
->AccessAddressBook();
235 // There is no deterministic method for deciding whether a blocking dialog
236 // was presented. The following comments and code assume that a blocking
237 // dialog was presented, but still behave correctly if no dialog was
240 // A blocking dialog was presented, and the user has already responded to
241 // the dialog. The presentation of the dialog added an NSEvent to the
242 // NSRunLoop which will cause all windows to lose focus. When the NSEvent
243 // is processed, it will be sent to the renderer which will cause the text
244 // field to lose focus. This returns an IPC to Chrome which will dismiss
245 // the Autofill popup. We post a task which we expect to run after the
246 // NSEvent has been processed by the NSRunLoop. It pings the renderer,
247 // which returns an IPC acknowledging the ping. At that time, redisplay
248 // the popup. FIFO processing of IPCs ensures that all side effects of the
249 // NSEvent will have been processed.
251 // 10ms sits nicely under the 16ms threshold for 60 fps, and likely gives
252 // the NSApplication run loop sufficient time to process the NSEvent. In
253 // testing, a delay of 0ms was always sufficient.
254 base::TimeDelta
delay(base::TimeDelta::FromMilliseconds(10));
255 base::MessageLoop::current()->PostDelayedTask(
257 base::Bind(&AutofillExternalDelegate::PingRenderer
, GetWeakPtr()),
261 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
262 } else if (identifier
== POPUP_ITEM_ID_SCAN_CREDIT_CARD
) {
263 manager_
->client()->ScanCreditCard(base::Bind(
264 &AutofillExternalDelegate::OnCreditCardScanned
, GetWeakPtr()));
266 FillAutofillFormData(identifier
, false);
269 if (should_show_scan_credit_card_
) {
270 AutofillMetrics::LogScanCreditCardPromptMetric(
271 identifier
== POPUP_ITEM_ID_SCAN_CREDIT_CARD
272 ? AutofillMetrics::SCAN_CARD_ITEM_SELECTED
273 : AutofillMetrics::SCAN_CARD_OTHER_ITEM_SELECTED
);
276 manager_
->client()->HideAutofillPopup();
279 void AutofillExternalDelegate::RemoveSuggestion(const base::string16
& value
,
282 manager_
->RemoveAutofillProfileOrCreditCard(identifier
);
284 manager_
->RemoveAutocompleteEntry(query_field_
.name
, value
);
287 void AutofillExternalDelegate::DidEndTextFieldEditing() {
288 manager_
->client()->HideAutofillPopup();
290 has_shown_popup_for_current_edit_
= false;
293 void AutofillExternalDelegate::ClearPreviewedForm() {
294 driver_
->RendererShouldClearPreviewedForm();
297 void AutofillExternalDelegate::Reset() {
298 manager_
->client()->HideAutofillPopup();
301 void AutofillExternalDelegate::OnPingAck() {
302 // Reissue the most recent query, which will reopen the Autofill popup.
303 manager_
->OnQueryFormFieldAutofill(query_id_
,
307 display_warning_if_disabled_
);
310 base::WeakPtr
<AutofillExternalDelegate
> AutofillExternalDelegate::GetWeakPtr() {
311 return weak_ptr_factory_
.GetWeakPtr();
314 void AutofillExternalDelegate::OnCreditCardScanned(
315 const base::string16
& card_number
,
316 int expiration_month
,
317 int expiration_year
) {
318 manager_
->FillCreditCardForm(
319 query_id_
, query_form_
, query_field_
,
320 CreditCard(card_number
, expiration_month
, expiration_year
));
323 void AutofillExternalDelegate::FillAutofillFormData(int unique_id
,
325 // If the selected element is a warning we don't want to do anything.
326 if (unique_id
== POPUP_ITEM_ID_WARNING_MESSAGE
)
329 AutofillDriver::RendererFormDataAction renderer_action
= is_preview
?
330 AutofillDriver::FORM_DATA_ACTION_PREVIEW
:
331 AutofillDriver::FORM_DATA_ACTION_FILL
;
333 DCHECK(driver_
->RendererIsAvailable());
334 // Fill the values for the whole form.
335 manager_
->FillOrPreviewForm(renderer_action
,
342 void AutofillExternalDelegate::ApplyAutofillWarnings(
343 std::vector
<Suggestion
>* suggestions
) {
344 if (!ShouldAutofill(query_field_
)) {
345 // Autofill is disabled. If there were some profile or credit card
346 // suggestions to show, show a warning instead. Otherwise, clear out the
347 // list of suggestions.
348 if (!suggestions
->empty() && (*suggestions
)[0].frontend_id
> 0) {
349 // If Autofill is disabled and we had suggestions, show a warning instead.
350 suggestions
->assign(1, Suggestion(
351 l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED
)));
352 (*suggestions
)[0].frontend_id
= POPUP_ITEM_ID_WARNING_MESSAGE
;
354 suggestions
->clear();
356 } else if (suggestions
->size() > 1 &&
357 (*suggestions
)[0].frontend_id
== POPUP_ITEM_ID_WARNING_MESSAGE
) {
358 // If we received a warning instead of suggestions from Autofill but regular
359 // suggestions from autocomplete, don't show the Autofill warning.
360 suggestions
->erase(suggestions
->begin());
363 // If we were about to show a warning and we shouldn't, don't.
364 if (!suggestions
->empty() &&
365 (*suggestions
)[0].frontend_id
== POPUP_ITEM_ID_WARNING_MESSAGE
&&
366 !display_warning_if_disabled_
) {
367 suggestions
->clear();
371 void AutofillExternalDelegate::ApplyAutofillOptions(
372 std::vector
<Suggestion
>* suggestions
) {
373 // The form has been auto-filled, so give the user the chance to clear the
374 // form. Append the 'Clear form' menu item.
375 if (query_field_
.is_autofilled
) {
376 suggestions
->push_back(Suggestion(
377 l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM
)));
378 suggestions
->back().frontend_id
= POPUP_ITEM_ID_CLEAR_FORM
;
381 // Append the 'Chrome Autofill options' menu item;
382 suggestions
->push_back(Suggestion(
383 l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP
)));
384 suggestions
->back().frontend_id
= POPUP_ITEM_ID_AUTOFILL_OPTIONS
;
387 void AutofillExternalDelegate::InsertDataListValues(
388 std::vector
<Suggestion
>* suggestions
) {
389 if (data_list_values_
.empty())
392 // Insert the separator between the datalist and Autofill values (if there
394 if (!suggestions
->empty()) {
395 suggestions
->insert(suggestions
->begin(), Suggestion());
396 (*suggestions
)[0].frontend_id
= POPUP_ITEM_ID_SEPARATOR
;
399 // Insert the datalist elements at the beginning.
400 suggestions
->insert(suggestions
->begin(), data_list_values_
.size(),
402 for (size_t i
= 0; i
< data_list_values_
.size(); i
++) {
403 (*suggestions
)[i
].value
= data_list_values_
[i
];
404 (*suggestions
)[i
].label
= data_list_labels_
[i
];
405 (*suggestions
)[i
].frontend_id
= POPUP_ITEM_ID_DATALIST_ENTRY
;
409 #if defined(OS_MACOSX) && !defined(OS_IOS)
410 void AutofillExternalDelegate::PingRenderer() {
411 driver_
->PingRenderer();
413 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
415 } // namespace autofill