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)
19namespace {
20
21enum 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.
35void 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
43namespace autofill {
44
45AutofillExternalDelegate::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
58AutofillExternalDelegate::~AutofillExternalDelegate() {}
59
60void 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
75void 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
161void 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
171void 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
177void AutofillExternalDelegate::OnPopupHidden() {
178}
179
180void 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
192void 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
251void 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
259void AutofillExternalDelegate::DidEndTextFieldEditing() {
260  manager_->client()->HideAutofillPopup();
261
262  has_shown_popup_for_current_edit_ = false;
263}
264
265void AutofillExternalDelegate::ClearPreviewedForm() {
266  driver_->RendererShouldClearPreviewedForm();
267}
268
269void AutofillExternalDelegate::Reset() {
270  manager_->client()->HideAutofillPopup();
271}
272
273void 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
282base::WeakPtr<AutofillExternalDelegate> AutofillExternalDelegate::GetWeakPtr() {
283  return weak_ptr_factory_.GetWeakPtr();
284}
285
286void 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
305void 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
348void 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
370void 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)
405void AutofillExternalDelegate::PingRenderer() {
406  driver_->PingRenderer();
407}
408#endif  // defined(OS_MACOSX) && !defined(OS_IOS)
409
410}  // namespace autofill
411