Home | History | Annotate | Download | only in browser
      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.
     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 
     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 
     58 AutofillExternalDelegate::~AutofillExternalDelegate() {}
     59 
     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 
     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 
    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 
    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 
    177 void AutofillExternalDelegate::OnPopupHidden() {
    178 }
    179 
    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 
    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 
    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 
    259 void AutofillExternalDelegate::DidEndTextFieldEditing() {
    260   manager_->client()->HideAutofillPopup();
    261 
    262   has_shown_popup_for_current_edit_ = false;
    263 }
    264 
    265 void AutofillExternalDelegate::ClearPreviewedForm() {
    266   driver_->RendererShouldClearPreviewedForm();
    267 }
    268 
    269 void AutofillExternalDelegate::Reset() {
    270   manager_->client()->HideAutofillPopup();
    271 }
    272 
    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 
    282 base::WeakPtr<AutofillExternalDelegate> AutofillExternalDelegate::GetWeakPtr() {
    283   return weak_ptr_factory_.GetWeakPtr();
    284 }
    285 
    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 
    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 
    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 
    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)
    405 void AutofillExternalDelegate::PingRenderer() {
    406   driver_->PingRenderer();
    407 }
    408 #endif  // defined(OS_MACOSX) && !defined(OS_IOS)
    409 
    410 }  // namespace autofill
    411