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