| OLD | NEW |
| (Empty) |
| 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 "chrome/browser/ui/autofill/autofill_dialog_controller_impl.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <map> | |
| 9 #include <string> | |
| 10 #include <utility> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/bind_helpers.h" | |
| 14 #include "base/i18n/case_conversion.h" | |
| 15 #include "base/i18n/rtl.h" | |
| 16 #include "base/location.h" | |
| 17 #include "base/logging.h" | |
| 18 #include "base/macros.h" | |
| 19 #include "base/rand_util.h" | |
| 20 #include "base/single_thread_task_runner.h" | |
| 21 #include "base/strings/string_number_conversions.h" | |
| 22 #include "base/strings/string_split.h" | |
| 23 #include "base/strings/stringprintf.h" | |
| 24 #include "base/strings/utf_string_conversions.h" | |
| 25 #include "base/thread_task_runner_handle.h" | |
| 26 #include "base/time/time.h" | |
| 27 #include "chrome/browser/autofill/personal_data_manager_factory.h" | |
| 28 #include "chrome/browser/autofill/risk_util.h" | |
| 29 #include "chrome/browser/autofill/validation_rules_storage_factory.h" | |
| 30 #include "chrome/browser/browser_process.h" | |
| 31 #include "chrome/browser/profiles/profile.h" | |
| 32 #include "chrome/browser/ui/autofill/autofill_dialog_common.h" | |
| 33 #include "chrome/browser/ui/autofill/autofill_dialog_i18n_input.h" | |
| 34 #include "chrome/browser/ui/autofill/autofill_dialog_view.h" | |
| 35 #include "chrome/browser/ui/autofill/data_model_wrapper.h" | |
| 36 #include "chrome/browser/ui/browser.h" | |
| 37 #include "chrome/browser/ui/browser_finder.h" | |
| 38 #include "chrome/browser/ui/browser_navigator.h" | |
| 39 #include "chrome/browser/ui/browser_navigator_params.h" | |
| 40 #include "chrome/browser/ui/browser_window.h" | |
| 41 #include "chrome/common/pref_names.h" | |
| 42 #include "chrome/common/url_constants.h" | |
| 43 #include "chrome/grit/chromium_strings.h" | |
| 44 #include "chrome/grit/generated_resources.h" | |
| 45 #include "components/autofill/core/browser/address_i18n.h" | |
| 46 #include "components/autofill/core/browser/autofill_country.h" | |
| 47 #include "components/autofill/core/browser/autofill_data_model.h" | |
| 48 #include "components/autofill/core/browser/autofill_manager.h" | |
| 49 #include "components/autofill/core/browser/autofill_type.h" | |
| 50 #include "components/autofill/core/browser/country_names.h" | |
| 51 #include "components/autofill/core/browser/detail_input.h" | |
| 52 #include "components/autofill/core/browser/field_types.h" | |
| 53 #include "components/autofill/core/browser/personal_data_manager.h" | |
| 54 #include "components/autofill/core/browser/phone_number_i18n.h" | |
| 55 #include "components/autofill/core/browser/server_field_types_util.h" | |
| 56 #include "components/autofill/core/browser/validation.h" | |
| 57 #include "components/autofill/core/common/autofill_pref_names.h" | |
| 58 #include "components/autofill/core/common/form_data.h" | |
| 59 #include "components/pref_registry/pref_registry_syncable.h" | |
| 60 #include "components/prefs/pref_registry_simple.h" | |
| 61 #include "components/prefs/pref_service.h" | |
| 62 #include "components/prefs/scoped_user_pref_update.h" | |
| 63 #include "content/public/browser/browser_thread.h" | |
| 64 #include "content/public/browser/geolocation_provider.h" | |
| 65 #include "content/public/browser/navigation_controller.h" | |
| 66 #include "content/public/browser/navigation_details.h" | |
| 67 #include "content/public/browser/navigation_entry.h" | |
| 68 #include "content/public/browser/notification_service.h" | |
| 69 #include "content/public/browser/notification_types.h" | |
| 70 #include "content/public/browser/render_view_host.h" | |
| 71 #include "content/public/browser/web_contents.h" | |
| 72 #include "content/public/common/url_constants.h" | |
| 73 #include "grit/components_scaled_resources.h" | |
| 74 #include "grit/components_strings.h" | |
| 75 #include "grit/platform_locale_settings.h" | |
| 76 #include "grit/theme_resources.h" | |
| 77 #include "net/cert/cert_status_flags.h" | |
| 78 #include "third_party/libaddressinput/chromium/chrome_metadata_source.h" | |
| 79 #include "third_party/libaddressinput/chromium/chrome_storage_impl.h" | |
| 80 #include "third_party/libaddressinput/messages.h" | |
| 81 #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_da
ta.h" | |
| 82 #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_fi
eld.h" | |
| 83 #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_pr
oblem.h" | |
| 84 #include "third_party/libaddressinput/src/cpp/include/libaddressinput/localizati
on.h" | |
| 85 #include "ui/base/l10n/l10n_util.h" | |
| 86 #include "ui/base/models/combobox_model.h" | |
| 87 #include "ui/base/resource/resource_bundle.h" | |
| 88 #include "ui/gfx/canvas.h" | |
| 89 #include "ui/gfx/geometry/vector2d.h" | |
| 90 #include "ui/gfx/image/image_skia_operations.h" | |
| 91 #include "ui/gfx/skia_util.h" | |
| 92 | |
| 93 using ::i18n::addressinput::AddressData; | |
| 94 using ::i18n::addressinput::AddressField; | |
| 95 using ::i18n::addressinput::AddressProblem; | |
| 96 using ::i18n::addressinput::ADMIN_AREA; | |
| 97 using ::i18n::addressinput::DEPENDENT_LOCALITY; | |
| 98 using ::i18n::addressinput::FieldProblemMap; | |
| 99 using ::i18n::addressinput::Localization; | |
| 100 using ::i18n::addressinput::MISSING_REQUIRED_FIELD; | |
| 101 | |
| 102 namespace autofill { | |
| 103 | |
| 104 namespace { | |
| 105 | |
| 106 const char kAddNewItemKey[] = "add-new-item"; | |
| 107 const char kManageItemsKey[] = "manage-items"; | |
| 108 const char kSameAsBillingKey[] = "same-as-billing"; | |
| 109 | |
| 110 // Keys for the kAutofillDialogAutofillDefault pref dictionary (do not change | |
| 111 // these values). | |
| 112 const char kGuidPrefKey[] = "guid"; | |
| 113 | |
| 114 // This string is stored along with saved addresses and credit cards in the | |
| 115 // WebDB, and hence should not be modified, so that it remains consistent over | |
| 116 // time. | |
| 117 const char kAutofillDialogOrigin[] = "Chrome Autofill dialog"; | |
| 118 | |
| 119 // The number of milliseconds to delay enabling the submit button after showing | |
| 120 // the dialog. This delay prevents users from accidentally clicking the submit | |
| 121 // button on startup. | |
| 122 const int kSubmitButtonDelayMs = 1000; | |
| 123 | |
| 124 // A helper class to make sure an AutofillDialogView knows when a series of | |
| 125 // updates is incoming. | |
| 126 class ScopedViewUpdates { | |
| 127 public: | |
| 128 explicit ScopedViewUpdates(AutofillDialogView* view) : view_(view) { | |
| 129 if (view_) | |
| 130 view_->UpdatesStarted(); | |
| 131 } | |
| 132 | |
| 133 ~ScopedViewUpdates() { | |
| 134 if (view_) | |
| 135 view_->UpdatesFinished(); | |
| 136 } | |
| 137 | |
| 138 private: | |
| 139 AutofillDialogView* view_; | |
| 140 | |
| 141 DISALLOW_COPY_AND_ASSIGN(ScopedViewUpdates); | |
| 142 }; | |
| 143 | |
| 144 base::string16 NullGetInfo(const AutofillType& type) { | |
| 145 return base::string16(); | |
| 146 } | |
| 147 | |
| 148 // Extract |type| from |inputs| using |section| to determine whether the info | |
| 149 // should be billing or shipping specific (for sections with address info). | |
| 150 base::string16 GetInfoFromInputs(const FieldValueMap& inputs, | |
| 151 DialogSection section, | |
| 152 const AutofillType& type) { | |
| 153 ServerFieldType field_type = type.GetStorableType(); | |
| 154 if (section != SECTION_SHIPPING) | |
| 155 field_type = AutofillType::GetEquivalentBillingFieldType(field_type); | |
| 156 | |
| 157 base::string16 info; | |
| 158 FieldValueMap::const_iterator it = inputs.find(field_type); | |
| 159 if (it != inputs.end()) | |
| 160 info = it->second; | |
| 161 | |
| 162 if (!info.empty() && type.html_type() == HTML_TYPE_COUNTRY_CODE) { | |
| 163 info = | |
| 164 base::ASCIIToUTF16(CountryNames::GetInstance()->GetCountryCode(info)); | |
| 165 } | |
| 166 | |
| 167 return info; | |
| 168 } | |
| 169 | |
| 170 // Returns true if |input| should be used to fill a site-requested |field| which | |
| 171 // is notated with a "shipping" tag, for use when the user has decided to use | |
| 172 // the billing address as the shipping address. | |
| 173 bool ServerTypeMatchesShippingField(ServerFieldType type, | |
| 174 const AutofillField& field) { | |
| 175 // Equivalent billing field type is used to support UseBillingAsShipping | |
| 176 // usecase. | |
| 177 return ServerTypeEncompassesFieldType( | |
| 178 type, AutofillType(AutofillType::GetEquivalentBillingFieldType( | |
| 179 field.Type().GetStorableType()))); | |
| 180 } | |
| 181 | |
| 182 // Initializes |form_group| from user-entered data. | |
| 183 void FillFormGroupFromOutputs(const FieldValueMap& detail_outputs, | |
| 184 FormGroup* form_group) { | |
| 185 for (FieldValueMap::const_iterator iter = detail_outputs.begin(); | |
| 186 iter != detail_outputs.end(); ++iter) { | |
| 187 ServerFieldType type = iter->first; | |
| 188 if (!iter->second.empty()) { | |
| 189 form_group->SetInfo(AutofillType(type), | |
| 190 iter->second, | |
| 191 g_browser_process->GetApplicationLocale()); | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 | |
| 196 // Returns a string descriptor for a DialogSection, for use with prefs (do not | |
| 197 // change these values). | |
| 198 std::string SectionToPrefString(DialogSection section) { | |
| 199 switch (section) { | |
| 200 case SECTION_CC: | |
| 201 return "cc"; | |
| 202 | |
| 203 case SECTION_BILLING: | |
| 204 return "billing"; | |
| 205 | |
| 206 case SECTION_SHIPPING: | |
| 207 return "shipping"; | |
| 208 } | |
| 209 | |
| 210 NOTREACHED(); | |
| 211 return std::string(); | |
| 212 } | |
| 213 | |
| 214 gfx::Image CreditCardIconForType(const std::string& credit_card_type) { | |
| 215 const int input_card_idr = CreditCard::IconResourceId(credit_card_type); | |
| 216 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 217 if (input_card_idr == IDR_AUTOFILL_CC_GENERIC) { | |
| 218 // When the credit card type is unknown, no image should be shown. However, | |
| 219 // to simplify the view code on Mac, save space for the credit card image by | |
| 220 // returning a transparent image of the appropriate size. All credit card | |
| 221 // icons are the same size. | |
| 222 return gfx::Image(gfx::ImageSkiaOperations::CreateTransparentImage( | |
| 223 rb.GetImageNamed(IDR_AUTOFILL_CC_GENERIC).AsImageSkia(), 0)); | |
| 224 } | |
| 225 return rb.GetImageNamed(input_card_idr); | |
| 226 } | |
| 227 | |
| 228 gfx::Image CvcIconForCreditCardType(const base::string16& credit_card_type) { | |
| 229 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 230 if (credit_card_type == l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX)) | |
| 231 return rb.GetImageNamed(IDR_CREDIT_CARD_CVC_HINT_AMEX); | |
| 232 | |
| 233 return rb.GetImageNamed(IDR_CREDIT_CARD_CVC_HINT); | |
| 234 } | |
| 235 | |
| 236 ServerFieldType CountryTypeForSection(DialogSection section) { | |
| 237 return section == SECTION_SHIPPING ? ADDRESS_HOME_COUNTRY : | |
| 238 ADDRESS_BILLING_COUNTRY; | |
| 239 } | |
| 240 | |
| 241 ValidityMessage GetPhoneValidityMessage(const base::string16& country_name, | |
| 242 const base::string16& number) { | |
| 243 std::string region = | |
| 244 CountryNames::GetInstance()->GetCountryCode(country_name); | |
| 245 i18n::PhoneObject phone_object(number, region); | |
| 246 ValidityMessage phone_message(base::string16(), true); | |
| 247 | |
| 248 // Check if the phone number is invalid. Allow valid international | |
| 249 // numbers that don't match the address's country only if they have an | |
| 250 // international calling code. | |
| 251 if (!phone_object.IsValidNumber() || | |
| 252 (phone_object.country_code().empty() && | |
| 253 phone_object.region() != region)) { | |
| 254 phone_message.text = l10n_util::GetStringUTF16( | |
| 255 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_PHONE_NUMBER); | |
| 256 } | |
| 257 | |
| 258 return phone_message; | |
| 259 } | |
| 260 | |
| 261 // Constructs |inputs| from template data for a given |dialog_section|. | |
| 262 // |country_country| specifies the country code that the inputs should be built | |
| 263 // for. Sets the |language_code| to be used for address formatting, if | |
| 264 // internationalized address input is enabled. The |language_code| parameter can | |
| 265 // be NULL. | |
| 266 void BuildInputsForSection(DialogSection dialog_section, | |
| 267 const std::string& country_code, | |
| 268 DetailInputs* inputs, | |
| 269 std::string* language_code) { | |
| 270 using l10n_util::GetStringUTF16; | |
| 271 | |
| 272 const DetailInput kCCInputs[] = { | |
| 273 { DetailInput::LONG, | |
| 274 CREDIT_CARD_NUMBER, | |
| 275 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARD_NUMBER) }, | |
| 276 { DetailInput::SHORT, | |
| 277 CREDIT_CARD_EXP_MONTH, | |
| 278 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_EXPIRY_MONTH) }, | |
| 279 { DetailInput::SHORT, | |
| 280 CREDIT_CARD_EXP_4_DIGIT_YEAR, | |
| 281 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_EXPIRY_YEAR) }, | |
| 282 { DetailInput::SHORT_EOL, | |
| 283 CREDIT_CARD_VERIFICATION_CODE, | |
| 284 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC), | |
| 285 1.5 }, | |
| 286 }; | |
| 287 | |
| 288 const DetailInput kBillingPhoneInputs[] = { | |
| 289 { DetailInput::LONG, | |
| 290 PHONE_BILLING_WHOLE_NUMBER, | |
| 291 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER) }, | |
| 292 }; | |
| 293 | |
| 294 const DetailInput kEmailInputs[] = { | |
| 295 { DetailInput::LONG, | |
| 296 EMAIL_ADDRESS, | |
| 297 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_EMAIL) }, | |
| 298 }; | |
| 299 | |
| 300 const DetailInput kShippingPhoneInputs[] = { | |
| 301 { DetailInput::LONG, | |
| 302 PHONE_HOME_WHOLE_NUMBER, | |
| 303 GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER) }, | |
| 304 }; | |
| 305 | |
| 306 switch (dialog_section) { | |
| 307 case SECTION_CC: { | |
| 308 BuildInputs(kCCInputs, arraysize(kCCInputs), inputs); | |
| 309 break; | |
| 310 } | |
| 311 | |
| 312 case SECTION_BILLING: { | |
| 313 i18ninput::BuildAddressInputs(common::ADDRESS_TYPE_BILLING, | |
| 314 country_code, inputs, language_code); | |
| 315 BuildInputs(kBillingPhoneInputs, arraysize(kBillingPhoneInputs), inputs); | |
| 316 BuildInputs(kEmailInputs, arraysize(kEmailInputs), inputs); | |
| 317 break; | |
| 318 } | |
| 319 | |
| 320 case SECTION_SHIPPING: { | |
| 321 i18ninput::BuildAddressInputs(common::ADDRESS_TYPE_SHIPPING, | |
| 322 country_code, inputs, language_code); | |
| 323 BuildInputs(kShippingPhoneInputs, arraysize(kShippingPhoneInputs), | |
| 324 inputs); | |
| 325 break; | |
| 326 } | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 } // namespace | |
| 331 | |
| 332 AutofillDialogViewDelegate::~AutofillDialogViewDelegate() {} | |
| 333 | |
| 334 AutofillDialogControllerImpl::~AutofillDialogControllerImpl() { | |
| 335 if (popup_controller_) | |
| 336 popup_controller_->Hide(); | |
| 337 | |
| 338 AutofillMetrics::LogDialogInitialUserState(initial_user_state_); | |
| 339 } | |
| 340 | |
| 341 // Checks the country code against the values the form structure enumerates. | |
| 342 bool AutofillCountryFilter( | |
| 343 const std::set<base::string16>& form_structure_values, | |
| 344 const std::string& country_code) { | |
| 345 if (!form_structure_values.empty() && | |
| 346 !form_structure_values.count(base::ASCIIToUTF16(country_code))) { | |
| 347 return false; | |
| 348 } | |
| 349 | |
| 350 return true; | |
| 351 } | |
| 352 | |
| 353 // Checks the country code against the values the form structure enumerates and | |
| 354 // against the ones Wallet allows. | |
| 355 bool WalletCountryFilter( | |
| 356 const std::set<base::string16>& form_structure_values, | |
| 357 const std::set<std::string>& wallet_allowed_values, | |
| 358 const std::string& country_code) { | |
| 359 if ((!form_structure_values.empty() && | |
| 360 !form_structure_values.count(base::ASCIIToUTF16(country_code))) || | |
| 361 (!wallet_allowed_values.empty() && | |
| 362 !wallet_allowed_values.count(country_code))) { | |
| 363 return false; | |
| 364 } | |
| 365 | |
| 366 return true; | |
| 367 } | |
| 368 | |
| 369 // static | |
| 370 base::WeakPtr<AutofillDialogControllerImpl> | |
| 371 AutofillDialogControllerImpl::Create( | |
| 372 content::WebContents* contents, | |
| 373 const FormData& form_structure, | |
| 374 const GURL& source_url, | |
| 375 const AutofillClient::ResultCallback& callback) { | |
| 376 // AutofillDialogControllerImpl owns itself. | |
| 377 AutofillDialogControllerImpl* autofill_dialog_controller = | |
| 378 new AutofillDialogControllerImpl(contents, | |
| 379 form_structure, | |
| 380 source_url, | |
| 381 callback); | |
| 382 return autofill_dialog_controller->weak_ptr_factory_.GetWeakPtr(); | |
| 383 } | |
| 384 | |
| 385 // static | |
| 386 void AutofillDialogController::RegisterPrefs(PrefRegistrySimple* registry) { | |
| 387 registry->RegisterListPref(::prefs::kAutofillDialogWalletLocationAcceptance); | |
| 388 } | |
| 389 | |
| 390 // static | |
| 391 void AutofillDialogController::RegisterProfilePrefs( | |
| 392 user_prefs::PrefRegistrySyncable* registry) { | |
| 393 registry->RegisterBooleanPref( | |
| 394 ::prefs::kAutofillDialogPayWithoutWallet, | |
| 395 false, | |
| 396 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 397 registry->RegisterDictionaryPref( | |
| 398 ::prefs::kAutofillDialogAutofillDefault, | |
| 399 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 400 registry->RegisterBooleanPref( | |
| 401 ::prefs::kAutofillDialogSaveData, | |
| 402 true, | |
| 403 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 404 registry->RegisterBooleanPref( | |
| 405 ::prefs::kAutofillDialogWalletShippingSameAsBilling, | |
| 406 false, | |
| 407 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); | |
| 408 } | |
| 409 | |
| 410 // static | |
| 411 base::WeakPtr<AutofillDialogController> AutofillDialogController::Create( | |
| 412 content::WebContents* contents, | |
| 413 const FormData& form_structure, | |
| 414 const GURL& source_url, | |
| 415 const AutofillClient::ResultCallback& callback) { | |
| 416 return AutofillDialogControllerImpl::Create(contents, | |
| 417 form_structure, | |
| 418 source_url, | |
| 419 callback); | |
| 420 } | |
| 421 | |
| 422 void AutofillDialogControllerImpl::Show() { | |
| 423 dialog_shown_timestamp_ = base::Time::Now(); | |
| 424 | |
| 425 // Determine what field types should be included in the dialog. | |
| 426 form_structure_.ParseFieldTypesFromAutocompleteAttributes(); | |
| 427 | |
| 428 // Fail if the author didn't specify autocomplete types. | |
| 429 if (!form_structure_.has_author_specified_types()) { | |
| 430 callback_.Run( | |
| 431 AutofillClient::AutocompleteResultErrorDisabled, | |
| 432 base::ASCIIToUTF16("Form is missing autocomplete attributes."), | |
| 433 NULL); | |
| 434 delete this; | |
| 435 return; | |
| 436 } | |
| 437 | |
| 438 // Fail if the author didn't ask for at least some kind of credit card | |
| 439 // information. | |
| 440 bool has_credit_card_field = false; | |
| 441 for (size_t i = 0; i < form_structure_.field_count(); ++i) { | |
| 442 AutofillType type = form_structure_.field(i)->Type(); | |
| 443 if (type.html_type() != HTML_TYPE_UNSPECIFIED && | |
| 444 type.group() == CREDIT_CARD) { | |
| 445 has_credit_card_field = true; | |
| 446 break; | |
| 447 } | |
| 448 } | |
| 449 | |
| 450 if (!has_credit_card_field) { | |
| 451 callback_.Run(AutofillClient::AutocompleteResultErrorDisabled, | |
| 452 base::ASCIIToUTF16( | |
| 453 "Form is not a payment form (must contain " | |
| 454 "some autocomplete=\"cc-*\" fields). "), | |
| 455 NULL); | |
| 456 delete this; | |
| 457 return; | |
| 458 } | |
| 459 | |
| 460 billing_country_combobox_model_.reset(new CountryComboboxModel()); | |
| 461 billing_country_combobox_model_->SetCountries( | |
| 462 *GetManager(), | |
| 463 base::Bind(AutofillCountryFilter, | |
| 464 form_structure_.PossibleValues(ADDRESS_BILLING_COUNTRY))); | |
| 465 shipping_country_combobox_model_.reset(new CountryComboboxModel()); | |
| 466 acceptable_shipping_countries_ = | |
| 467 form_structure_.PossibleValues(ADDRESS_HOME_COUNTRY); | |
| 468 shipping_country_combobox_model_->SetCountries( | |
| 469 *GetManager(), | |
| 470 base::Bind(AutofillCountryFilter, | |
| 471 base::ConstRef(acceptable_shipping_countries_))); | |
| 472 | |
| 473 // If the form has a country <select> but none of the options are valid, bail. | |
| 474 if (billing_country_combobox_model_->GetItemCount() == 0 || | |
| 475 shipping_country_combobox_model_->GetItemCount() == 0) { | |
| 476 callback_.Run(AutofillClient::AutocompleteResultErrorDisabled, | |
| 477 base::ASCIIToUTF16("No valid/supported country options" | |
| 478 " found."), | |
| 479 NULL); | |
| 480 delete this; | |
| 481 return; | |
| 482 } | |
| 483 | |
| 484 // Log any relevant UI metrics and security exceptions. | |
| 485 AutofillMetrics::LogDialogUiEvent(AutofillMetrics::DIALOG_UI_SHOWN); | |
| 486 | |
| 487 AutofillMetrics::LogDialogSecurityMetric( | |
| 488 AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); | |
| 489 | |
| 490 // The Autofill dialog is shown in response to a message from the renderer and | |
| 491 // as such, it can only be made in the context of the current document. A call | |
| 492 // to GetActiveEntry would return a pending entry, if there was one, which | |
| 493 // would be a security bug. Therefore, we use the last committed URL for the | |
| 494 // access checks. | |
| 495 const GURL& current_url = web_contents()->GetLastCommittedURL(); | |
| 496 invoked_from_same_origin_ = | |
| 497 current_url.GetOrigin() == source_url_.GetOrigin(); | |
| 498 | |
| 499 if (!invoked_from_same_origin_) { | |
| 500 AutofillMetrics::LogDialogSecurityMetric( | |
| 501 AutofillMetrics::SECURITY_METRIC_CROSS_ORIGIN_FRAME); | |
| 502 } | |
| 503 | |
| 504 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 505 DialogSection section = static_cast<DialogSection>(i); | |
| 506 | |
| 507 std::string country_code; | |
| 508 CountryComboboxModel* model = CountryComboboxModelForSection(section); | |
| 509 if (model) | |
| 510 country_code = model->GetDefaultCountryCode(); | |
| 511 | |
| 512 DetailInputs* inputs = MutableRequestedFieldsForSection(section); | |
| 513 BuildInputsForSection( | |
| 514 section, country_code, inputs, | |
| 515 MutableAddressLanguageCodeForSection(section)); | |
| 516 } | |
| 517 | |
| 518 // Test whether we need to show the shipping section. If filling that section | |
| 519 // would be a no-op, don't show it. | |
| 520 cares_about_shipping_ = form_structure_.FillFields( | |
| 521 RequestedTypesForSection(SECTION_SHIPPING), | |
| 522 base::Bind(ServerTypeMatchesField, SECTION_SHIPPING), | |
| 523 base::Bind(NullGetInfo), std::string(), | |
| 524 g_browser_process->GetApplicationLocale()); | |
| 525 | |
| 526 transaction_amount_ = form_structure_.GetUniqueValue( | |
| 527 HTML_TYPE_TRANSACTION_AMOUNT); | |
| 528 transaction_currency_ = form_structure_.GetUniqueValue( | |
| 529 HTML_TYPE_TRANSACTION_CURRENCY); | |
| 530 acceptable_cc_types_ = form_structure_.PossibleValues(CREDIT_CARD_TYPE); | |
| 531 | |
| 532 validator_.reset(new AddressValidator( | |
| 533 std::unique_ptr<::i18n::addressinput::Source>( | |
| 534 new autofill::ChromeMetadataSource(I18N_ADDRESS_VALIDATION_DATA_URL, | |
| 535 profile_->GetRequestContext())), | |
| 536 ValidationRulesStorageFactory::CreateStorage(), this)); | |
| 537 | |
| 538 SuggestionsUpdated(); | |
| 539 SubmitButtonDelayBegin(); | |
| 540 view_.reset(CreateView()); | |
| 541 view_->Show(); | |
| 542 GetManager()->AddObserver(this); | |
| 543 | |
| 544 LogDialogLatencyToShow(); | |
| 545 } | |
| 546 | |
| 547 void AutofillDialogControllerImpl::Hide() { | |
| 548 if (view_) | |
| 549 view_->Hide(); | |
| 550 } | |
| 551 | |
| 552 // TODO(estade): remove. | |
| 553 void AutofillDialogControllerImpl::TabActivated() { | |
| 554 } | |
| 555 | |
| 556 //////////////////////////////////////////////////////////////////////////////// | |
| 557 // AutofillDialogViewDelegate implementation. | |
| 558 | |
| 559 base::string16 AutofillDialogControllerImpl::DialogTitle() const { | |
| 560 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_TITLE); | |
| 561 } | |
| 562 | |
| 563 base::string16 AutofillDialogControllerImpl::EditSuggestionText() const { | |
| 564 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EDIT); | |
| 565 } | |
| 566 | |
| 567 base::string16 AutofillDialogControllerImpl::CancelButtonText() const { | |
| 568 return l10n_util::GetStringUTF16(IDS_CANCEL); | |
| 569 } | |
| 570 | |
| 571 base::string16 AutofillDialogControllerImpl::ConfirmButtonText() const { | |
| 572 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON); | |
| 573 } | |
| 574 | |
| 575 base::string16 AutofillDialogControllerImpl::SaveLocallyText() const { | |
| 576 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX); | |
| 577 } | |
| 578 | |
| 579 base::string16 AutofillDialogControllerImpl::SaveLocallyTooltip() const { | |
| 580 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_TOOLTIP); | |
| 581 } | |
| 582 | |
| 583 bool AutofillDialogControllerImpl::ShouldOfferToSaveInChrome() const { | |
| 584 return IsAutofillEnabled() && !profile_->IsOffTheRecord() && | |
| 585 IsManuallyEditingAnySection(); | |
| 586 } | |
| 587 | |
| 588 bool AutofillDialogControllerImpl::ShouldSaveInChrome() const { | |
| 589 return profile_->GetPrefs()->GetBoolean(::prefs::kAutofillDialogSaveData); | |
| 590 } | |
| 591 | |
| 592 int AutofillDialogControllerImpl::GetDialogButtons() const { | |
| 593 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL; | |
| 594 } | |
| 595 | |
| 596 bool AutofillDialogControllerImpl::IsDialogButtonEnabled( | |
| 597 ui::DialogButton button) const { | |
| 598 if (button == ui::DIALOG_BUTTON_OK) | |
| 599 return !submit_button_delay_timer_.IsRunning(); | |
| 600 | |
| 601 DCHECK_EQ(ui::DIALOG_BUTTON_CANCEL, button); | |
| 602 return true; | |
| 603 } | |
| 604 | |
| 605 bool AutofillDialogControllerImpl::SectionIsActive(DialogSection section) | |
| 606 const { | |
| 607 return FormStructureCaresAboutSection(section); | |
| 608 } | |
| 609 | |
| 610 void AutofillDialogControllerImpl::ResetSectionInput(DialogSection section) { | |
| 611 SetEditingExistingData(section, false); | |
| 612 needs_validation_.erase(section); | |
| 613 | |
| 614 CountryComboboxModel* model = CountryComboboxModelForSection(section); | |
| 615 if (model) { | |
| 616 base::string16 country = model->GetItemAt(model->GetDefaultIndex()); | |
| 617 RebuildInputsForCountry(section, country, false); | |
| 618 } | |
| 619 | |
| 620 DetailInputs* inputs = MutableRequestedFieldsForSection(section); | |
| 621 for (DetailInputs::iterator it = inputs->begin(); | |
| 622 it != inputs->end(); ++it) { | |
| 623 if (it->length != DetailInput::NONE) { | |
| 624 it->initial_value.clear(); | |
| 625 } else if (!it->initial_value.empty() && | |
| 626 (it->type == ADDRESS_BILLING_COUNTRY || | |
| 627 it->type == ADDRESS_HOME_COUNTRY)) { | |
| 628 GetValidator()->LoadRules( | |
| 629 CountryNames::GetInstance()->GetCountryCode(it->initial_value)); | |
| 630 } | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 void AutofillDialogControllerImpl::ShowEditUiIfBadSuggestion( | |
| 635 DialogSection section) { | |
| 636 // |CreateWrapper()| returns an empty wrapper if |IsEditingExistingData()|, so | |
| 637 // get the wrapper before this potentially happens below. | |
| 638 std::unique_ptr<DataModelWrapper> wrapper = CreateWrapper(section); | |
| 639 | |
| 640 // If the chosen item in |model| yields an empty suggestion text, it is | |
| 641 // invalid. In this case, show the edit UI and highlight invalid fields. | |
| 642 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 643 base::string16 unused, unused2; | |
| 644 if (IsASuggestionItemKey(model->GetItemKeyForCheckedItem()) && | |
| 645 !SuggestionTextForSection(section, &unused, &unused2)) { | |
| 646 SetEditingExistingData(section, true); | |
| 647 } | |
| 648 | |
| 649 if (wrapper && IsEditingExistingData(section)) { | |
| 650 base::string16 country = | |
| 651 wrapper->GetInfo(AutofillType(CountryTypeForSection(section))); | |
| 652 if (!country.empty()) { | |
| 653 // There's no user input to restore here as this is only called after | |
| 654 // resetting all section input. | |
| 655 if (RebuildInputsForCountry(section, country, false)) | |
| 656 UpdateSection(section); | |
| 657 } | |
| 658 wrapper->FillInputs(MutableRequestedFieldsForSection(section)); | |
| 659 } | |
| 660 } | |
| 661 | |
| 662 bool AutofillDialogControllerImpl::InputWasEdited(ServerFieldType type, | |
| 663 const base::string16& value) { | |
| 664 if (value.empty()) | |
| 665 return false; | |
| 666 | |
| 667 // If this is a combobox at the default value, don't preserve it. | |
| 668 ui::ComboboxModel* model = ComboboxModelForAutofillType(type); | |
| 669 if (model && model->GetItemAt(model->GetDefaultIndex()) == value) | |
| 670 return false; | |
| 671 | |
| 672 return true; | |
| 673 } | |
| 674 | |
| 675 FieldValueMap AutofillDialogControllerImpl::TakeUserInputSnapshot() { | |
| 676 FieldValueMap snapshot; | |
| 677 if (!view_) | |
| 678 return snapshot; | |
| 679 | |
| 680 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 681 DialogSection section = static_cast<DialogSection>(i); | |
| 682 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 683 if (model->GetItemKeyForCheckedItem() != kAddNewItemKey) | |
| 684 continue; | |
| 685 | |
| 686 FieldValueMap outputs; | |
| 687 view_->GetUserInput(section, &outputs); | |
| 688 // Remove fields that are empty, at their default values, or invalid. | |
| 689 for (FieldValueMap::iterator it = outputs.begin(); it != outputs.end(); | |
| 690 ++it) { | |
| 691 if (InputWasEdited(it->first, it->second) && | |
| 692 InputValidityMessage(section, it->first, it->second).empty()) { | |
| 693 snapshot.insert(std::make_pair(it->first, it->second)); | |
| 694 } | |
| 695 } | |
| 696 } | |
| 697 | |
| 698 return snapshot; | |
| 699 } | |
| 700 | |
| 701 void AutofillDialogControllerImpl::RestoreUserInputFromSnapshot( | |
| 702 const FieldValueMap& snapshot) { | |
| 703 if (snapshot.empty()) | |
| 704 return; | |
| 705 | |
| 706 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 707 DialogSection section = static_cast<DialogSection>(i); | |
| 708 if (!SectionIsActive(section)) | |
| 709 continue; | |
| 710 | |
| 711 DetailInputs* inputs = MutableRequestedFieldsForSection(section); | |
| 712 for (size_t i = 0; i < inputs->size(); ++i) { | |
| 713 DetailInput* input = &(*inputs)[i]; | |
| 714 if (input->length != DetailInput::NONE) { | |
| 715 input->initial_value = | |
| 716 GetInfoFromInputs(snapshot, section, AutofillType(input->type)); | |
| 717 } | |
| 718 if (InputWasEdited(input->type, input->initial_value)) | |
| 719 SuggestionsMenuModelForSection(section)->SetCheckedItem(kAddNewItemKey); | |
| 720 } | |
| 721 } | |
| 722 } | |
| 723 | |
| 724 void AutofillDialogControllerImpl::UpdateSection(DialogSection section) { | |
| 725 if (view_) | |
| 726 view_->UpdateSection(section); | |
| 727 } | |
| 728 | |
| 729 void AutofillDialogControllerImpl::UpdateForErrors() { | |
| 730 if (!view_) | |
| 731 return; | |
| 732 | |
| 733 // Currently, the view should only need to be updated if validating a | |
| 734 // suggestion that's based on existing data. | |
| 735 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 736 if (IsEditingExistingData(static_cast<DialogSection>(i))) { | |
| 737 view_->UpdateForErrors(); | |
| 738 break; | |
| 739 } | |
| 740 } | |
| 741 } | |
| 742 | |
| 743 const DetailInputs& AutofillDialogControllerImpl::RequestedFieldsForSection( | |
| 744 DialogSection section) const { | |
| 745 switch (section) { | |
| 746 case SECTION_CC: | |
| 747 return requested_cc_fields_; | |
| 748 case SECTION_BILLING: | |
| 749 return requested_billing_fields_; | |
| 750 case SECTION_SHIPPING: | |
| 751 return requested_shipping_fields_; | |
| 752 } | |
| 753 | |
| 754 NOTREACHED(); | |
| 755 return requested_billing_fields_; | |
| 756 } | |
| 757 | |
| 758 ui::ComboboxModel* AutofillDialogControllerImpl::ComboboxModelForAutofillType( | |
| 759 ServerFieldType type) { | |
| 760 switch (type) { | |
| 761 case CREDIT_CARD_EXP_MONTH: | |
| 762 return &cc_exp_month_combobox_model_; | |
| 763 | |
| 764 case CREDIT_CARD_EXP_4_DIGIT_YEAR: | |
| 765 return &cc_exp_year_combobox_model_; | |
| 766 | |
| 767 case ADDRESS_BILLING_COUNTRY: | |
| 768 return billing_country_combobox_model_.get(); | |
| 769 | |
| 770 case ADDRESS_HOME_COUNTRY: | |
| 771 return shipping_country_combobox_model_.get(); | |
| 772 | |
| 773 default: | |
| 774 return NULL; | |
| 775 } | |
| 776 } | |
| 777 | |
| 778 ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSection( | |
| 779 DialogSection section) { | |
| 780 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 781 // The shipping section menu is special. It will always show because there is | |
| 782 // a choice between "Use billing" and "enter new". | |
| 783 if (section == SECTION_SHIPPING) | |
| 784 return model; | |
| 785 | |
| 786 // For other sections, only show a menu if there's at least one suggestion. | |
| 787 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 788 if (IsASuggestionItemKey(model->GetItemKeyAt(i))) | |
| 789 return model; | |
| 790 } | |
| 791 | |
| 792 return NULL; | |
| 793 } | |
| 794 | |
| 795 base::string16 AutofillDialogControllerImpl::LabelForSection( | |
| 796 DialogSection section) const { | |
| 797 switch (section) { | |
| 798 case SECTION_CC: | |
| 799 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC); | |
| 800 case SECTION_BILLING: | |
| 801 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_BILLING); | |
| 802 case SECTION_SHIPPING: | |
| 803 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_SHIPPING); | |
| 804 } | |
| 805 NOTREACHED(); | |
| 806 return base::string16(); | |
| 807 } | |
| 808 | |
| 809 SuggestionState AutofillDialogControllerImpl::SuggestionStateForSection( | |
| 810 DialogSection section) { | |
| 811 base::string16 vertically_compact, horizontally_compact; | |
| 812 bool show_suggestion = SuggestionTextForSection(section, | |
| 813 &vertically_compact, | |
| 814 &horizontally_compact); | |
| 815 return SuggestionState(show_suggestion, | |
| 816 vertically_compact, | |
| 817 horizontally_compact, | |
| 818 SuggestionIconForSection(section), | |
| 819 ExtraSuggestionTextForSection(section), | |
| 820 ExtraSuggestionIconForSection(section)); | |
| 821 } | |
| 822 | |
| 823 bool AutofillDialogControllerImpl::SuggestionTextForSection( | |
| 824 DialogSection section, | |
| 825 base::string16* vertically_compact, | |
| 826 base::string16* horizontally_compact) { | |
| 827 // When the user has clicked 'edit' or a suggestion is somehow invalid (e.g. a | |
| 828 // user selects a credit card that has expired), don't show a suggestion (even | |
| 829 // though there is a profile selected in the model). | |
| 830 if (IsEditingExistingData(section)) | |
| 831 return false; | |
| 832 | |
| 833 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 834 std::string item_key = model->GetItemKeyForCheckedItem(); | |
| 835 if (item_key == kSameAsBillingKey) { | |
| 836 *vertically_compact = *horizontally_compact = l10n_util::GetStringUTF16( | |
| 837 IDS_AUTOFILL_DIALOG_USING_BILLING_FOR_SHIPPING); | |
| 838 return true; | |
| 839 } | |
| 840 | |
| 841 if (!IsASuggestionItemKey(item_key)) | |
| 842 return false; | |
| 843 | |
| 844 if (section == SECTION_BILLING || section == SECTION_SHIPPING) { | |
| 845 // Also check if the address is invalid (rules may have loaded since | |
| 846 // the dialog was shown). | |
| 847 if (HasInvalidAddress(*GetManager()->GetProfileByGUID(item_key))) | |
| 848 return false; | |
| 849 } | |
| 850 | |
| 851 std::unique_ptr<DataModelWrapper> wrapper = CreateWrapper(section); | |
| 852 return wrapper->GetDisplayText(vertically_compact, horizontally_compact); | |
| 853 } | |
| 854 | |
| 855 base::string16 AutofillDialogControllerImpl::ExtraSuggestionTextForSection( | |
| 856 DialogSection section) const { | |
| 857 if (section == SECTION_CC) | |
| 858 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC); | |
| 859 | |
| 860 return base::string16(); | |
| 861 } | |
| 862 | |
| 863 std::unique_ptr<DataModelWrapper> AutofillDialogControllerImpl::CreateWrapper( | |
| 864 DialogSection section) { | |
| 865 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 866 std::string item_key = model->GetItemKeyForCheckedItem(); | |
| 867 if (!IsASuggestionItemKey(item_key) || IsManuallyEditingSection(section)) | |
| 868 return std::unique_ptr<DataModelWrapper>(); | |
| 869 | |
| 870 if (section == SECTION_CC) { | |
| 871 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); | |
| 872 DCHECK(card); | |
| 873 return std::unique_ptr<DataModelWrapper>( | |
| 874 new AutofillCreditCardWrapper(card)); | |
| 875 } | |
| 876 | |
| 877 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); | |
| 878 DCHECK(profile); | |
| 879 if (section == SECTION_SHIPPING) { | |
| 880 return std::unique_ptr<DataModelWrapper>( | |
| 881 new AutofillShippingAddressWrapper(profile)); | |
| 882 } | |
| 883 DCHECK_EQ(SECTION_BILLING, section); | |
| 884 return std::unique_ptr<DataModelWrapper>(new AutofillProfileWrapper(profile)); | |
| 885 } | |
| 886 | |
| 887 gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( | |
| 888 DialogSection section) { | |
| 889 std::unique_ptr<DataModelWrapper> model = CreateWrapper(section); | |
| 890 if (!model.get()) | |
| 891 return gfx::Image(); | |
| 892 | |
| 893 return model->GetIcon(); | |
| 894 } | |
| 895 | |
| 896 gfx::Image AutofillDialogControllerImpl::ExtraSuggestionIconForSection( | |
| 897 DialogSection section) { | |
| 898 if (section != SECTION_CC) | |
| 899 return gfx::Image(); | |
| 900 | |
| 901 std::unique_ptr<DataModelWrapper> model = CreateWrapper(section); | |
| 902 if (!model.get()) | |
| 903 return gfx::Image(); | |
| 904 | |
| 905 return CvcIconForCreditCardType( | |
| 906 model->GetInfo(AutofillType(CREDIT_CARD_TYPE))); | |
| 907 } | |
| 908 | |
| 909 FieldIconMap AutofillDialogControllerImpl::IconsForFields( | |
| 910 const FieldValueMap& user_inputs) const { | |
| 911 FieldIconMap result; | |
| 912 base::string16 credit_card_type; | |
| 913 | |
| 914 FieldValueMap::const_iterator credit_card_iter = | |
| 915 user_inputs.find(CREDIT_CARD_NUMBER); | |
| 916 if (credit_card_iter != user_inputs.end()) { | |
| 917 const base::string16& number = credit_card_iter->second; | |
| 918 const std::string type = CreditCard::GetCreditCardType(number); | |
| 919 credit_card_type = CreditCard::TypeForDisplay(type); | |
| 920 result[CREDIT_CARD_NUMBER] = CreditCardIconForType(type); | |
| 921 } | |
| 922 | |
| 923 if (!user_inputs.count(CREDIT_CARD_VERIFICATION_CODE)) | |
| 924 return result; | |
| 925 | |
| 926 result[CREDIT_CARD_VERIFICATION_CODE] = | |
| 927 CvcIconForCreditCardType(credit_card_type); | |
| 928 | |
| 929 return result; | |
| 930 } | |
| 931 | |
| 932 bool AutofillDialogControllerImpl::FieldControlsIcons( | |
| 933 ServerFieldType type) const { | |
| 934 return type == CREDIT_CARD_NUMBER; | |
| 935 } | |
| 936 | |
| 937 base::string16 AutofillDialogControllerImpl::TooltipForField( | |
| 938 ServerFieldType type) const { | |
| 939 if (type == PHONE_HOME_WHOLE_NUMBER || type == PHONE_BILLING_WHOLE_NUMBER) | |
| 940 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_TOOLTIP_PHONE_NUMBER); | |
| 941 | |
| 942 return base::string16(); | |
| 943 } | |
| 944 | |
| 945 // TODO(groby): Add more tests. | |
| 946 base::string16 AutofillDialogControllerImpl::InputValidityMessage( | |
| 947 DialogSection section, | |
| 948 ServerFieldType type, | |
| 949 const base::string16& value) { | |
| 950 AutofillType autofill_type(type); | |
| 951 if (autofill_type.group() == ADDRESS_HOME || | |
| 952 autofill_type.group() == ADDRESS_BILLING) { | |
| 953 return base::string16(); | |
| 954 } | |
| 955 | |
| 956 switch (autofill_type.GetStorableType()) { | |
| 957 case EMAIL_ADDRESS: | |
| 958 if (!value.empty() && !IsValidEmailAddress(value)) { | |
| 959 return l10n_util::GetStringUTF16( | |
| 960 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_EMAIL_ADDRESS); | |
| 961 } | |
| 962 break; | |
| 963 | |
| 964 case CREDIT_CARD_NUMBER: { | |
| 965 if (!value.empty()) { | |
| 966 base::string16 message = CreditCardNumberValidityMessage(value); | |
| 967 if (!message.empty()) | |
| 968 return message; | |
| 969 } | |
| 970 break; | |
| 971 } | |
| 972 | |
| 973 case CREDIT_CARD_EXP_MONTH: | |
| 974 if (!InputWasEdited(CREDIT_CARD_EXP_MONTH, value)) { | |
| 975 return l10n_util::GetStringUTF16( | |
| 976 IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD); | |
| 977 } | |
| 978 break; | |
| 979 | |
| 980 case CREDIT_CARD_EXP_4_DIGIT_YEAR: | |
| 981 if (!InputWasEdited(CREDIT_CARD_EXP_4_DIGIT_YEAR, value)) { | |
| 982 return l10n_util::GetStringUTF16( | |
| 983 IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD); | |
| 984 } | |
| 985 break; | |
| 986 | |
| 987 case CREDIT_CARD_VERIFICATION_CODE: | |
| 988 if (!value.empty() && !autofill::IsValidCreditCardSecurityCode(value)) { | |
| 989 return l10n_util::GetStringUTF16( | |
| 990 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_SECURITY_CODE); | |
| 991 } | |
| 992 break; | |
| 993 | |
| 994 case NAME_FULL: | |
| 995 break; | |
| 996 | |
| 997 case PHONE_HOME_WHOLE_NUMBER: // Used in shipping section. | |
| 998 break; | |
| 999 | |
| 1000 case PHONE_BILLING_WHOLE_NUMBER: // Used in billing section. | |
| 1001 break; | |
| 1002 | |
| 1003 default: | |
| 1004 NOTREACHED(); // Trying to validate unknown field. | |
| 1005 break; | |
| 1006 } | |
| 1007 | |
| 1008 return value.empty() ? l10n_util::GetStringUTF16( | |
| 1009 IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD) : | |
| 1010 base::string16(); | |
| 1011 } | |
| 1012 | |
| 1013 // TODO(groby): Also add tests. | |
| 1014 ValidityMessages AutofillDialogControllerImpl::InputsAreValid( | |
| 1015 DialogSection section, | |
| 1016 const FieldValueMap& inputs) { | |
| 1017 ValidityMessages messages; | |
| 1018 if (inputs.empty()) | |
| 1019 return messages; | |
| 1020 | |
| 1021 AddressValidator::Status status = AddressValidator::SUCCESS; | |
| 1022 if (section != SECTION_CC) { | |
| 1023 AutofillProfile profile; | |
| 1024 FillFormGroupFromOutputs(inputs, &profile); | |
| 1025 std::unique_ptr<AddressData> address_data = | |
| 1026 i18n::CreateAddressDataFromAutofillProfile( | |
| 1027 profile, g_browser_process->GetApplicationLocale()); | |
| 1028 address_data->language_code = AddressLanguageCodeForSection(section); | |
| 1029 | |
| 1030 Localization localization; | |
| 1031 localization.SetGetter(l10n_util::GetStringUTF8); | |
| 1032 FieldProblemMap problems; | |
| 1033 status = GetValidator()->ValidateAddress(*address_data, NULL, &problems); | |
| 1034 bool billing = section != SECTION_SHIPPING; | |
| 1035 | |
| 1036 for (FieldProblemMap::const_iterator iter = problems.begin(); | |
| 1037 iter != problems.end(); ++iter) { | |
| 1038 bool sure = iter->second != MISSING_REQUIRED_FIELD; | |
| 1039 base::string16 text = base::UTF8ToUTF16(localization.GetErrorMessage( | |
| 1040 *address_data, iter->first, iter->second, true, false)); | |
| 1041 messages.Set(i18n::TypeForField(iter->first, billing), | |
| 1042 ValidityMessage(text, sure)); | |
| 1043 } | |
| 1044 } | |
| 1045 | |
| 1046 for (FieldValueMap::const_iterator iter = inputs.begin(); | |
| 1047 iter != inputs.end(); ++iter) { | |
| 1048 const ServerFieldType type = iter->first; | |
| 1049 base::string16 text = InputValidityMessage(section, type, iter->second); | |
| 1050 | |
| 1051 // Skip empty/unchanged fields in edit mode. If the individual field does | |
| 1052 // not have validation errors, assume it to be valid unless later proven | |
| 1053 // otherwise. | |
| 1054 bool sure = InputWasEdited(type, iter->second); | |
| 1055 | |
| 1056 if (sure && status == AddressValidator::RULES_NOT_READY && | |
| 1057 !ComboboxModelForAutofillType(type) && | |
| 1058 (AutofillType(type).group() == ADDRESS_HOME || | |
| 1059 AutofillType(type).group() == ADDRESS_BILLING)) { | |
| 1060 DCHECK(text.empty()); | |
| 1061 text = l10n_util::GetStringUTF16( | |
| 1062 IDS_AUTOFILL_DIALOG_VALIDATION_WAITING_FOR_RULES); | |
| 1063 sure = false; | |
| 1064 needs_validation_.insert(section); | |
| 1065 } | |
| 1066 | |
| 1067 messages.Set(type, ValidityMessage(text, sure)); | |
| 1068 } | |
| 1069 | |
| 1070 // For the convenience of using operator[]. | |
| 1071 FieldValueMap& field_values = const_cast<FieldValueMap&>(inputs); | |
| 1072 // Validate the date formed by month and year field. (Autofill dialog is | |
| 1073 // never supposed to have 2-digit years, so not checked). | |
| 1074 if (field_values.count(CREDIT_CARD_EXP_4_DIGIT_YEAR) && | |
| 1075 field_values.count(CREDIT_CARD_EXP_MONTH) && | |
| 1076 InputWasEdited(CREDIT_CARD_EXP_4_DIGIT_YEAR, | |
| 1077 field_values[CREDIT_CARD_EXP_4_DIGIT_YEAR]) && | |
| 1078 InputWasEdited(CREDIT_CARD_EXP_MONTH, | |
| 1079 field_values[CREDIT_CARD_EXP_MONTH])) { | |
| 1080 ValidityMessage year_message(base::string16(), true); | |
| 1081 ValidityMessage month_message(base::string16(), true); | |
| 1082 if (!autofill::IsValidCreditCardExpirationDate( | |
| 1083 field_values[CREDIT_CARD_EXP_4_DIGIT_YEAR], | |
| 1084 field_values[CREDIT_CARD_EXP_MONTH], base::Time::Now())) { | |
| 1085 // The dialog shows the same error message for the month and year fields. | |
| 1086 year_message.text = l10n_util::GetStringUTF16( | |
| 1087 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_EXPIRATION_DATE); | |
| 1088 month_message.text = l10n_util::GetStringUTF16( | |
| 1089 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_EXPIRATION_DATE); | |
| 1090 } | |
| 1091 messages.Set(CREDIT_CARD_EXP_4_DIGIT_YEAR, year_message); | |
| 1092 messages.Set(CREDIT_CARD_EXP_MONTH, month_message); | |
| 1093 } | |
| 1094 | |
| 1095 // If there is a credit card number and a CVC, validate them together. | |
| 1096 if (field_values.count(CREDIT_CARD_NUMBER) && | |
| 1097 field_values.count(CREDIT_CARD_VERIFICATION_CODE)) { | |
| 1098 ValidityMessage ccv_message(base::string16(), true); | |
| 1099 if (!autofill::IsValidCreditCardSecurityCode( | |
| 1100 field_values[CREDIT_CARD_VERIFICATION_CODE], | |
| 1101 field_values[CREDIT_CARD_NUMBER])) { | |
| 1102 ccv_message.text = l10n_util::GetStringUTF16( | |
| 1103 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_SECURITY_CODE); | |
| 1104 } | |
| 1105 messages.Set(CREDIT_CARD_VERIFICATION_CODE, ccv_message); | |
| 1106 } | |
| 1107 | |
| 1108 // Validate the shipping phone number against the country code of the address. | |
| 1109 if (field_values.count(ADDRESS_HOME_COUNTRY) && | |
| 1110 field_values.count(PHONE_HOME_WHOLE_NUMBER)) { | |
| 1111 messages.Set( | |
| 1112 PHONE_HOME_WHOLE_NUMBER, | |
| 1113 GetPhoneValidityMessage(field_values[ADDRESS_HOME_COUNTRY], | |
| 1114 field_values[PHONE_HOME_WHOLE_NUMBER])); | |
| 1115 } | |
| 1116 | |
| 1117 // Validate the billing phone number against the country code of the address. | |
| 1118 if (field_values.count(ADDRESS_BILLING_COUNTRY) && | |
| 1119 field_values.count(PHONE_BILLING_WHOLE_NUMBER)) { | |
| 1120 messages.Set( | |
| 1121 PHONE_BILLING_WHOLE_NUMBER, | |
| 1122 GetPhoneValidityMessage(field_values[ADDRESS_BILLING_COUNTRY], | |
| 1123 field_values[PHONE_BILLING_WHOLE_NUMBER])); | |
| 1124 } | |
| 1125 | |
| 1126 return messages; | |
| 1127 } | |
| 1128 | |
| 1129 void AutofillDialogControllerImpl::UserEditedOrActivatedInput( | |
| 1130 DialogSection section, | |
| 1131 ServerFieldType type, | |
| 1132 gfx::NativeView parent_view, | |
| 1133 const gfx::Rect& content_bounds, | |
| 1134 const base::string16& field_contents, | |
| 1135 bool was_edit) { | |
| 1136 ScopedViewUpdates updates(view_.get()); | |
| 1137 | |
| 1138 if (type == ADDRESS_BILLING_COUNTRY || type == ADDRESS_HOME_COUNTRY) { | |
| 1139 const FieldValueMap& snapshot = TakeUserInputSnapshot(); | |
| 1140 | |
| 1141 // Clobber the inputs because the view's already been updated. | |
| 1142 RebuildInputsForCountry(section, field_contents, true); | |
| 1143 RestoreUserInputFromSnapshot(snapshot); | |
| 1144 UpdateSection(section); | |
| 1145 } | |
| 1146 | |
| 1147 // The rest of this method applies only to textfields while Autofill is | |
| 1148 // enabled. If a combobox or Autofill is disabled, bail. | |
| 1149 if (ComboboxModelForAutofillType(type) || !IsAutofillEnabled()) | |
| 1150 return; | |
| 1151 | |
| 1152 // If the field is edited down to empty, don't show a popup. | |
| 1153 if (was_edit && field_contents.empty()) { | |
| 1154 HidePopup(); | |
| 1155 return; | |
| 1156 } | |
| 1157 | |
| 1158 // If the user clicks while the popup is already showing, be sure to hide | |
| 1159 // it. | |
| 1160 if (!was_edit && popup_controller_.get()) { | |
| 1161 HidePopup(); | |
| 1162 return; | |
| 1163 } | |
| 1164 | |
| 1165 std::vector<autofill::Suggestion> popup_suggestions; | |
| 1166 popup_suggestion_ids_.clear(); | |
| 1167 if (IsCreditCardType(type)) { | |
| 1168 popup_suggestions = GetManager()->GetCreditCardSuggestions( | |
| 1169 AutofillType(type), field_contents); | |
| 1170 for (const auto& suggestion : popup_suggestions) | |
| 1171 popup_suggestion_ids_.push_back(suggestion.backend_id); | |
| 1172 } else { | |
| 1173 popup_suggestions = GetManager()->GetProfileSuggestions( | |
| 1174 AutofillType(type), | |
| 1175 field_contents, | |
| 1176 false, | |
| 1177 RequestedTypesForSection(section)); | |
| 1178 // Filter out ones we don't want. | |
| 1179 for (int i = 0; i < static_cast<int>(popup_suggestions.size()); i++) { | |
| 1180 const autofill::AutofillProfile* profile = | |
| 1181 GetManager()->GetProfileByGUID(popup_suggestions[i].backend_id); | |
| 1182 if (!profile || !ShouldSuggestProfile(section, *profile)) { | |
| 1183 popup_suggestions.erase(popup_suggestions.begin() + i); | |
| 1184 i--; | |
| 1185 } | |
| 1186 } | |
| 1187 | |
| 1188 // Save the IDs. | |
| 1189 for (const auto& suggestion : popup_suggestions) | |
| 1190 popup_suggestion_ids_.push_back(suggestion.backend_id); | |
| 1191 | |
| 1192 // This will append to the popup_suggestions but not the IDs since there | |
| 1193 // are no backend IDs for the I18N validator suggestions. | |
| 1194 GetI18nValidatorSuggestions(section, type, &popup_suggestions); | |
| 1195 } | |
| 1196 | |
| 1197 if (popup_suggestions.empty()) { | |
| 1198 HidePopup(); | |
| 1199 return; | |
| 1200 } | |
| 1201 | |
| 1202 // |popup_input_type_| must be set before calling |Show()|. | |
| 1203 popup_input_type_ = type; | |
| 1204 popup_section_ = section; | |
| 1205 | |
| 1206 // Use our own 0-based IDs for the items. | |
| 1207 // TODO(estade): do we need separators and control rows like 'Clear | |
| 1208 // Form'? | |
| 1209 for (size_t i = 0; i < popup_suggestions.size(); ++i) { | |
| 1210 popup_suggestions[i].frontend_id = i; | |
| 1211 } | |
| 1212 | |
| 1213 popup_controller_ = AutofillPopupControllerImpl::GetOrCreate( | |
| 1214 popup_controller_, weak_ptr_factory_.GetWeakPtr(), NULL, parent_view, | |
| 1215 gfx::RectF(content_bounds), | |
| 1216 base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT | |
| 1217 : base::i18n::LEFT_TO_RIGHT); | |
| 1218 popup_controller_->Show(popup_suggestions); | |
| 1219 } | |
| 1220 | |
| 1221 void AutofillDialogControllerImpl::FocusMoved() { | |
| 1222 HidePopup(); | |
| 1223 } | |
| 1224 | |
| 1225 bool AutofillDialogControllerImpl::ShouldShowErrorBubble() const { | |
| 1226 return popup_input_type_ == UNKNOWN_TYPE; | |
| 1227 } | |
| 1228 | |
| 1229 void AutofillDialogControllerImpl::ViewClosed() { | |
| 1230 GetManager()->RemoveObserver(this); | |
| 1231 | |
| 1232 delete this; | |
| 1233 } | |
| 1234 | |
| 1235 std::vector<DialogNotification> AutofillDialogControllerImpl:: | |
| 1236 CurrentNotifications() { | |
| 1237 std::vector<DialogNotification> notifications; | |
| 1238 | |
| 1239 if (!invoked_from_same_origin_) { | |
| 1240 notifications.push_back(DialogNotification( | |
| 1241 DialogNotification::SECURITY_WARNING, | |
| 1242 l10n_util::GetStringFUTF16(IDS_AUTOFILL_DIALOG_SITE_WARNING, | |
| 1243 base::UTF8ToUTF16(source_url_.host())))); | |
| 1244 } | |
| 1245 | |
| 1246 return notifications; | |
| 1247 } | |
| 1248 | |
| 1249 void AutofillDialogControllerImpl::LinkClicked(const GURL& url) { | |
| 1250 OpenTabWithUrl(url); | |
| 1251 } | |
| 1252 | |
| 1253 void AutofillDialogControllerImpl::OnCancel() { | |
| 1254 HidePopup(); | |
| 1255 if (!data_was_passed_back_) | |
| 1256 LogOnCancelMetrics(); | |
| 1257 callback_.Run( | |
| 1258 AutofillClient::AutocompleteResultErrorCancel, base::string16(), NULL); | |
| 1259 } | |
| 1260 | |
| 1261 void AutofillDialogControllerImpl::OnAccept() { | |
| 1262 ScopedViewUpdates updates(view_.get()); | |
| 1263 HidePopup(); | |
| 1264 FinishSubmit(); | |
| 1265 } | |
| 1266 | |
| 1267 Profile* AutofillDialogControllerImpl::profile() { | |
| 1268 return profile_; | |
| 1269 } | |
| 1270 | |
| 1271 content::WebContents* AutofillDialogControllerImpl::GetWebContents() { | |
| 1272 return web_contents(); | |
| 1273 } | |
| 1274 | |
| 1275 //////////////////////////////////////////////////////////////////////////////// | |
| 1276 // AutofillPopupDelegate implementation. | |
| 1277 | |
| 1278 void AutofillDialogControllerImpl::OnPopupShown() { | |
| 1279 ScopedViewUpdates update(view_.get()); | |
| 1280 view_->UpdateErrorBubble(); | |
| 1281 | |
| 1282 AutofillMetrics::LogDialogPopupEvent(AutofillMetrics::DIALOG_POPUP_SHOWN); | |
| 1283 } | |
| 1284 | |
| 1285 void AutofillDialogControllerImpl::OnPopupHidden() {} | |
| 1286 | |
| 1287 void AutofillDialogControllerImpl::DidSelectSuggestion( | |
| 1288 const base::string16& value, | |
| 1289 int identifier) { | |
| 1290 // TODO(estade): implement. | |
| 1291 } | |
| 1292 | |
| 1293 void AutofillDialogControllerImpl::DidAcceptSuggestion( | |
| 1294 const base::string16& value, | |
| 1295 int identifier, | |
| 1296 int position) { | |
| 1297 DCHECK_NE(UNKNOWN_TYPE, popup_input_type_); | |
| 1298 // Because |HidePopup()| can be called from |UpdateSection()|, remember the | |
| 1299 // type of the input for later here. | |
| 1300 const ServerFieldType popup_input_type = popup_input_type_; | |
| 1301 | |
| 1302 ScopedViewUpdates updates(view_.get()); | |
| 1303 std::unique_ptr<DataModelWrapper> wrapper; | |
| 1304 | |
| 1305 if (static_cast<size_t>(identifier) < popup_suggestion_ids_.size()) { | |
| 1306 const std::string& guid = popup_suggestion_ids_[identifier]; | |
| 1307 if (IsCreditCardType(popup_input_type)) { | |
| 1308 wrapper.reset(new AutofillCreditCardWrapper( | |
| 1309 GetManager()->GetCreditCardByGUID(guid))); | |
| 1310 } else { | |
| 1311 wrapper.reset(new AutofillProfileWrapper( | |
| 1312 GetManager()->GetProfileByGUID(guid))); | |
| 1313 } | |
| 1314 } else { | |
| 1315 wrapper.reset(new I18nAddressDataWrapper( | |
| 1316 &i18n_validator_suggestions_[ | |
| 1317 identifier - popup_suggestion_ids_.size()])); | |
| 1318 } | |
| 1319 | |
| 1320 // If the user hasn't switched away from the default country and |wrapper|'s | |
| 1321 // country differs from the |view_|'s, rebuild inputs and restore user data. | |
| 1322 const FieldValueMap snapshot = TakeUserInputSnapshot(); | |
| 1323 bool billing_rebuilt = false, shipping_rebuilt = false; | |
| 1324 | |
| 1325 base::string16 billing_country = | |
| 1326 wrapper->GetInfo(AutofillType(ADDRESS_BILLING_COUNTRY)); | |
| 1327 if (popup_section_ == ActiveBillingSection() && | |
| 1328 !snapshot.count(ADDRESS_BILLING_COUNTRY) && | |
| 1329 !billing_country.empty()) { | |
| 1330 billing_rebuilt = RebuildInputsForCountry( | |
| 1331 ActiveBillingSection(), billing_country, false); | |
| 1332 } | |
| 1333 | |
| 1334 base::string16 shipping_country = | |
| 1335 wrapper->GetInfo(AutofillType(ADDRESS_HOME_COUNTRY)); | |
| 1336 if (popup_section_ == SECTION_SHIPPING && | |
| 1337 !snapshot.count(ADDRESS_HOME_COUNTRY) && | |
| 1338 !shipping_country.empty()) { | |
| 1339 shipping_rebuilt = RebuildInputsForCountry( | |
| 1340 SECTION_SHIPPING, shipping_country, false); | |
| 1341 } | |
| 1342 | |
| 1343 if (billing_rebuilt || shipping_rebuilt) { | |
| 1344 RestoreUserInputFromSnapshot(snapshot); | |
| 1345 if (billing_rebuilt) | |
| 1346 UpdateSection(ActiveBillingSection()); | |
| 1347 if (shipping_rebuilt) | |
| 1348 UpdateSection(SECTION_SHIPPING); | |
| 1349 } | |
| 1350 | |
| 1351 DCHECK(SectionIsActive(popup_section_)); | |
| 1352 wrapper->FillInputs(MutableRequestedFieldsForSection(popup_section_)); | |
| 1353 view_->FillSection(popup_section_, popup_input_type); | |
| 1354 | |
| 1355 AutofillMetrics::LogDialogPopupEvent( | |
| 1356 AutofillMetrics::DIALOG_POPUP_FORM_FILLED); | |
| 1357 | |
| 1358 // TODO(estade): not sure why it's necessary to do this explicitly. | |
| 1359 HidePopup(); | |
| 1360 } | |
| 1361 | |
| 1362 bool AutofillDialogControllerImpl::GetDeletionConfirmationText( | |
| 1363 const base::string16& value, | |
| 1364 int identifier, | |
| 1365 base::string16* title, | |
| 1366 base::string16* body) { | |
| 1367 return false; | |
| 1368 } | |
| 1369 | |
| 1370 bool AutofillDialogControllerImpl::RemoveSuggestion(const base::string16& value, | |
| 1371 int identifier) { | |
| 1372 // TODO(estade): implement. | |
| 1373 return false; | |
| 1374 } | |
| 1375 | |
| 1376 void AutofillDialogControllerImpl::ClearPreviewedForm() { | |
| 1377 // TODO(estade): implement. | |
| 1378 } | |
| 1379 | |
| 1380 //////////////////////////////////////////////////////////////////////////////// | |
| 1381 // SuggestionsMenuModelDelegate implementation. | |
| 1382 | |
| 1383 void AutofillDialogControllerImpl::SuggestionItemSelected( | |
| 1384 SuggestionsMenuModel* model, | |
| 1385 size_t index) { | |
| 1386 ScopedViewUpdates updates(view_.get()); | |
| 1387 | |
| 1388 if (model->GetItemKeyAt(index) == kManageItemsKey) { | |
| 1389 GURL url; | |
| 1390 DCHECK(IsAutofillEnabled()); | |
| 1391 GURL settings_url(chrome::kChromeUISettingsURL); | |
| 1392 url = settings_url.Resolve(chrome::kAutofillSubPage); | |
| 1393 OpenTabWithUrl(url); | |
| 1394 return; | |
| 1395 } | |
| 1396 | |
| 1397 model->SetCheckedIndex(index); | |
| 1398 DialogSection section = SectionForSuggestionsMenuModel(*model); | |
| 1399 | |
| 1400 ResetSectionInput(section); | |
| 1401 ShowEditUiIfBadSuggestion(section); | |
| 1402 UpdateSection(section); | |
| 1403 view_->UpdateNotificationArea(); | |
| 1404 UpdateForErrors(); | |
| 1405 | |
| 1406 LogSuggestionItemSelectedMetric(*model); | |
| 1407 } | |
| 1408 | |
| 1409 //////////////////////////////////////////////////////////////////////////////// | |
| 1410 // PersonalDataManagerObserver implementation. | |
| 1411 | |
| 1412 void AutofillDialogControllerImpl::OnPersonalDataChanged() { | |
| 1413 SuggestionsUpdated(); | |
| 1414 } | |
| 1415 | |
| 1416 //////////////////////////////////////////////////////////////////////////////// | |
| 1417 | |
| 1418 bool AutofillDialogControllerImpl::HandleKeyPressEventInInput( | |
| 1419 const content::NativeWebKeyboardEvent& event) { | |
| 1420 if (popup_controller_.get()) | |
| 1421 return popup_controller_->HandleKeyPressEvent(event); | |
| 1422 | |
| 1423 return false; | |
| 1424 } | |
| 1425 | |
| 1426 void AutofillDialogControllerImpl::SubmitButtonDelayBegin() { | |
| 1427 submit_button_delay_timer_.Start( | |
| 1428 FROM_HERE, | |
| 1429 base::TimeDelta::FromMilliseconds(kSubmitButtonDelayMs), | |
| 1430 this, | |
| 1431 &AutofillDialogControllerImpl::OnSubmitButtonDelayEnd); | |
| 1432 } | |
| 1433 | |
| 1434 void AutofillDialogControllerImpl::SubmitButtonDelayEndForTesting() { | |
| 1435 DCHECK(submit_button_delay_timer_.IsRunning()); | |
| 1436 submit_button_delay_timer_.user_task().Run(); | |
| 1437 submit_button_delay_timer_.Stop(); | |
| 1438 } | |
| 1439 | |
| 1440 AutofillDialogControllerImpl::AutofillDialogControllerImpl( | |
| 1441 content::WebContents* contents, | |
| 1442 const FormData& form_structure, | |
| 1443 const GURL& source_url, | |
| 1444 const AutofillClient::ResultCallback& callback) | |
| 1445 : WebContentsObserver(contents), | |
| 1446 profile_(Profile::FromBrowserContext(contents->GetBrowserContext())), | |
| 1447 initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), | |
| 1448 form_structure_(form_structure), | |
| 1449 invoked_from_same_origin_(true), | |
| 1450 source_url_(source_url), | |
| 1451 callback_(callback), | |
| 1452 suggested_cc_(this), | |
| 1453 suggested_billing_(this), | |
| 1454 suggested_shipping_(this), | |
| 1455 cares_about_shipping_(true), | |
| 1456 popup_input_type_(UNKNOWN_TYPE), | |
| 1457 popup_section_(SECTION_MIN), | |
| 1458 data_was_passed_back_(false), | |
| 1459 was_ui_latency_logged_(false), | |
| 1460 weak_ptr_factory_(this) { | |
| 1461 DCHECK(!callback_.is_null()); | |
| 1462 } | |
| 1463 | |
| 1464 AutofillDialogView* AutofillDialogControllerImpl::CreateView() { | |
| 1465 return AutofillDialogView::Create(this); | |
| 1466 } | |
| 1467 | |
| 1468 PersonalDataManager* AutofillDialogControllerImpl::GetManager() const { | |
| 1469 return PersonalDataManagerFactory::GetForProfile(profile_); | |
| 1470 } | |
| 1471 | |
| 1472 AddressValidator* AutofillDialogControllerImpl::GetValidator() { | |
| 1473 return validator_.get(); | |
| 1474 } | |
| 1475 | |
| 1476 void AutofillDialogControllerImpl::OpenTabWithUrl(const GURL& url) { | |
| 1477 chrome::NavigateParams params( | |
| 1478 chrome::FindBrowserWithWebContents(web_contents()), | |
| 1479 url, | |
| 1480 ui::PAGE_TRANSITION_LINK); | |
| 1481 params.disposition = NEW_FOREGROUND_TAB; | |
| 1482 chrome::Navigate(¶ms); | |
| 1483 } | |
| 1484 | |
| 1485 // TODO(estade): remove. | |
| 1486 DialogSection AutofillDialogControllerImpl::ActiveBillingSection() const { | |
| 1487 return SECTION_BILLING; | |
| 1488 } | |
| 1489 | |
| 1490 bool AutofillDialogControllerImpl::IsEditingExistingData( | |
| 1491 DialogSection section) const { | |
| 1492 return section_editing_state_.count(section) > 0; | |
| 1493 } | |
| 1494 | |
| 1495 bool AutofillDialogControllerImpl::IsManuallyEditingSection( | |
| 1496 DialogSection section) const { | |
| 1497 return IsEditingExistingData(section) || | |
| 1498 SuggestionsMenuModelForSection(section)-> | |
| 1499 GetItemKeyForCheckedItem() == kAddNewItemKey; | |
| 1500 } | |
| 1501 | |
| 1502 void AutofillDialogControllerImpl::SuggestionsUpdated() { | |
| 1503 ScopedViewUpdates updates(view_.get()); | |
| 1504 | |
| 1505 const FieldValueMap snapshot = TakeUserInputSnapshot(); | |
| 1506 | |
| 1507 suggested_cc_.Reset(); | |
| 1508 suggested_billing_.Reset(); | |
| 1509 suggested_shipping_.Reset(); | |
| 1510 HidePopup(); | |
| 1511 | |
| 1512 suggested_shipping_.AddKeyedItem( | |
| 1513 kSameAsBillingKey, | |
| 1514 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_USE_BILLING_FOR_SHIPPING)); | |
| 1515 | |
| 1516 shipping_country_combobox_model_->SetCountries( | |
| 1517 *GetManager(), | |
| 1518 base::Bind(AutofillCountryFilter, acceptable_shipping_countries_)); | |
| 1519 | |
| 1520 if (IsAutofillEnabled()) { | |
| 1521 PersonalDataManager* manager = GetManager(); | |
| 1522 const std::vector<CreditCard*>& cards = manager->GetCreditCards(); | |
| 1523 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
| 1524 for (size_t i = 0; i < cards.size(); ++i) { | |
| 1525 if (!i18ninput::CardHasCompleteAndVerifiedData(*cards[i])) | |
| 1526 continue; | |
| 1527 | |
| 1528 suggested_cc_.AddKeyedItemWithIcon( | |
| 1529 cards[i]->guid(), cards[i]->Label(), | |
| 1530 rb.GetImageNamed(CreditCard::IconResourceId(cards[i]->type()))); | |
| 1531 suggested_cc_.SetEnabled( | |
| 1532 cards[i]->guid(), !ShouldDisallowCcType(cards[i]->TypeForDisplay())); | |
| 1533 } | |
| 1534 | |
| 1535 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); | |
| 1536 std::vector<base::string16> labels; | |
| 1537 AutofillProfile::CreateDifferentiatingLabels( | |
| 1538 profiles, g_browser_process->GetApplicationLocale(), &labels); | |
| 1539 DCHECK_EQ(labels.size(), profiles.size()); | |
| 1540 for (size_t i = 0; i < profiles.size(); ++i) { | |
| 1541 const AutofillProfile& profile = *profiles[i]; | |
| 1542 if (!i18ninput::AddressHasCompleteAndVerifiedData( | |
| 1543 profile, g_browser_process->GetApplicationLocale())) { | |
| 1544 continue; | |
| 1545 } | |
| 1546 | |
| 1547 suggested_shipping_.AddKeyedItem(profile.guid(), labels[i]); | |
| 1548 suggested_shipping_.SetEnabled( | |
| 1549 profile.guid(), | |
| 1550 CanAcceptCountry( | |
| 1551 SECTION_SHIPPING, | |
| 1552 base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)))); | |
| 1553 if (!profile.GetRawInfo(EMAIL_ADDRESS).empty() && | |
| 1554 !profile.IsPresentButInvalid(EMAIL_ADDRESS)) { | |
| 1555 suggested_billing_.AddKeyedItem(profile.guid(), labels[i]); | |
| 1556 suggested_billing_.SetEnabled( | |
| 1557 profile.guid(), | |
| 1558 CanAcceptCountry( | |
| 1559 SECTION_BILLING, | |
| 1560 base::UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)))); | |
| 1561 } | |
| 1562 } | |
| 1563 } | |
| 1564 | |
| 1565 suggested_cc_.AddKeyedItem( | |
| 1566 kAddNewItemKey, | |
| 1567 l10n_util::GetStringUTF16(IsAutofillEnabled() | |
| 1568 ? IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD | |
| 1569 : IDS_AUTOFILL_DIALOG_ENTER_CREDIT_CARD)); | |
| 1570 suggested_cc_.AddKeyedItem( | |
| 1571 kManageItemsKey, | |
| 1572 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_CREDIT_CARD)); | |
| 1573 suggested_billing_.AddKeyedItem( | |
| 1574 kAddNewItemKey, | |
| 1575 l10n_util::GetStringUTF16( | |
| 1576 IsAutofillEnabled() ? IDS_AUTOFILL_DIALOG_ADD_BILLING_ADDRESS | |
| 1577 : IDS_AUTOFILL_DIALOG_ENTER_BILLING_DETAILS)); | |
| 1578 suggested_billing_.AddKeyedItem( | |
| 1579 kManageItemsKey, | |
| 1580 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_BILLING_ADDRESS)); | |
| 1581 | |
| 1582 suggested_shipping_.AddKeyedItem( | |
| 1583 kAddNewItemKey, | |
| 1584 l10n_util::GetStringUTF16( | |
| 1585 IsAutofillEnabled() | |
| 1586 ? IDS_AUTOFILL_DIALOG_ADD_SHIPPING_ADDRESS | |
| 1587 : IDS_AUTOFILL_DIALOG_USE_DIFFERENT_SHIPPING_ADDRESS)); | |
| 1588 | |
| 1589 if (IsAutofillEnabled()) { | |
| 1590 suggested_shipping_.AddKeyedItem( | |
| 1591 kManageItemsKey, | |
| 1592 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_SHIPPING_ADDRESS)); | |
| 1593 | |
| 1594 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 1595 DialogSection section = static_cast<DialogSection>(i); | |
| 1596 if (!SectionIsActive(section)) | |
| 1597 continue; | |
| 1598 | |
| 1599 // Set the starting choice for the menu. First set to the default in case | |
| 1600 // the GUID saved in prefs refers to a profile that no longer exists. | |
| 1601 std::string guid; | |
| 1602 GetDefaultAutofillChoice(section, &guid); | |
| 1603 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 1604 model->SetCheckedItem(guid); | |
| 1605 if (GetAutofillChoice(section, &guid)) | |
| 1606 model->SetCheckedItem(guid); | |
| 1607 } | |
| 1608 } | |
| 1609 | |
| 1610 if (view_) | |
| 1611 view_->ModelChanged(); | |
| 1612 | |
| 1613 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 1614 ResetSectionInput(static_cast<DialogSection>(i)); | |
| 1615 } | |
| 1616 | |
| 1617 FieldValueMap::const_iterator billing_it = | |
| 1618 snapshot.find(ADDRESS_BILLING_COUNTRY); | |
| 1619 if (billing_it != snapshot.end()) | |
| 1620 RebuildInputsForCountry(ActiveBillingSection(), billing_it->second, true); | |
| 1621 | |
| 1622 FieldValueMap::const_iterator shipping_it = | |
| 1623 snapshot.find(ADDRESS_HOME_COUNTRY); | |
| 1624 if (shipping_it != snapshot.end()) | |
| 1625 RebuildInputsForCountry(SECTION_SHIPPING, shipping_it->second, true); | |
| 1626 | |
| 1627 RestoreUserInputFromSnapshot(snapshot); | |
| 1628 | |
| 1629 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 1630 DialogSection section = static_cast<DialogSection>(i); | |
| 1631 if (!SectionIsActive(section)) | |
| 1632 continue; | |
| 1633 | |
| 1634 ShowEditUiIfBadSuggestion(section); | |
| 1635 UpdateSection(section); | |
| 1636 } | |
| 1637 | |
| 1638 UpdateForErrors(); | |
| 1639 } | |
| 1640 | |
| 1641 void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( | |
| 1642 DialogSection section, | |
| 1643 const FormStructure::InputFieldComparator& compare) { | |
| 1644 if (!SectionIsActive(section)) | |
| 1645 return; | |
| 1646 | |
| 1647 DetailInputs inputs; | |
| 1648 std::string country_code = CountryCodeForSection(section); | |
| 1649 BuildInputsForSection(section, country_code, &inputs, | |
| 1650 MutableAddressLanguageCodeForSection(section)); | |
| 1651 std::vector<ServerFieldType> types = TypesFromInputs(inputs); | |
| 1652 | |
| 1653 std::unique_ptr<DataModelWrapper> wrapper = CreateWrapper(section); | |
| 1654 if (wrapper) { | |
| 1655 // Only fill in data that is associated with this section. | |
| 1656 wrapper->FillFormStructure(types, compare, &form_structure_); | |
| 1657 | |
| 1658 // CVC needs special-casing because the CreditCard class doesn't store or | |
| 1659 // handle them. This isn't necessary when filling the combined CC and | |
| 1660 // billing section as CVC comes from |full_wallet_| in this case. | |
| 1661 if (section == SECTION_CC) | |
| 1662 SetOutputForFieldsOfType(CREDIT_CARD_VERIFICATION_CODE, view_->GetCvc()); | |
| 1663 | |
| 1664 } else { | |
| 1665 // The user manually input data. If using Autofill, save the info as new or | |
| 1666 // edited data. Always fill local data into |form_structure_|. | |
| 1667 FieldValueMap output; | |
| 1668 view_->GetUserInput(section, &output); | |
| 1669 | |
| 1670 if (section == SECTION_CC) { | |
| 1671 CreditCard card; | |
| 1672 FillFormGroupFromOutputs(output, &card); | |
| 1673 | |
| 1674 // The card holder name comes from the billing address section. | |
| 1675 card.SetRawInfo(CREDIT_CARD_NAME_FULL, | |
| 1676 GetValueFromSection(SECTION_BILLING, NAME_BILLING_FULL)); | |
| 1677 | |
| 1678 AutofillCreditCardWrapper card_wrapper(&card); | |
| 1679 card_wrapper.FillFormStructure(types, compare, &form_structure_); | |
| 1680 | |
| 1681 // Again, CVC needs special-casing. Fill it in directly from |output|. | |
| 1682 SetOutputForFieldsOfType( | |
| 1683 CREDIT_CARD_VERIFICATION_CODE, | |
| 1684 output[CREDIT_CARD_VERIFICATION_CODE]); | |
| 1685 } else { | |
| 1686 AutofillProfile profile; | |
| 1687 FillFormGroupFromOutputs(output, &profile); | |
| 1688 profile.set_language_code(AddressLanguageCodeForSection(section)); | |
| 1689 | |
| 1690 if (ShouldSaveDetailsLocally()) { | |
| 1691 profile.set_origin(RulesAreLoaded(section) ? | |
| 1692 kAutofillDialogOrigin : source_url_.GetOrigin().spec()); | |
| 1693 | |
| 1694 std::string guid = GetManager()->SaveImportedProfile(profile); | |
| 1695 newly_saved_data_model_guids_[section] = guid; | |
| 1696 } | |
| 1697 | |
| 1698 AutofillProfileWrapper profile_wrapper(&profile); | |
| 1699 profile_wrapper.FillFormStructure(types, compare, &form_structure_); | |
| 1700 } | |
| 1701 } | |
| 1702 } | |
| 1703 | |
| 1704 void AutofillDialogControllerImpl::FillOutputForSection(DialogSection section) { | |
| 1705 FillOutputForSectionWithComparator( | |
| 1706 section, base::Bind(ServerTypeMatchesField, section)); | |
| 1707 } | |
| 1708 | |
| 1709 bool AutofillDialogControllerImpl::FormStructureCaresAboutSection( | |
| 1710 DialogSection section) const { | |
| 1711 // For now, only SECTION_SHIPPING may be omitted due to a site not asking for | |
| 1712 // any of the fields. | |
| 1713 if (section == SECTION_SHIPPING) | |
| 1714 return cares_about_shipping_; | |
| 1715 | |
| 1716 return true; | |
| 1717 } | |
| 1718 | |
| 1719 void AutofillDialogControllerImpl::SetOutputForFieldsOfType( | |
| 1720 ServerFieldType type, | |
| 1721 const base::string16& output) { | |
| 1722 for (size_t i = 0; i < form_structure_.field_count(); ++i) { | |
| 1723 AutofillField* field = form_structure_.field(i); | |
| 1724 if (field->Type().GetStorableType() == type) | |
| 1725 field->value = output; | |
| 1726 } | |
| 1727 } | |
| 1728 | |
| 1729 base::string16 AutofillDialogControllerImpl::GetValueFromSection( | |
| 1730 DialogSection section, | |
| 1731 ServerFieldType type) { | |
| 1732 DCHECK(SectionIsActive(section)); | |
| 1733 | |
| 1734 std::unique_ptr<DataModelWrapper> wrapper = CreateWrapper(section); | |
| 1735 if (wrapper) | |
| 1736 return wrapper->GetInfo(AutofillType(type)); | |
| 1737 | |
| 1738 FieldValueMap output; | |
| 1739 view_->GetUserInput(section, &output); | |
| 1740 return output[type]; | |
| 1741 } | |
| 1742 | |
| 1743 bool AutofillDialogControllerImpl::CanAcceptCountry( | |
| 1744 DialogSection section, | |
| 1745 const std::string& country_code) { | |
| 1746 DCHECK_EQ(2U, country_code.size()); | |
| 1747 CountryComboboxModel* model = CountryComboboxModelForSection(section); | |
| 1748 const std::vector<AutofillCountry*>& countries = model->countries(); | |
| 1749 for (size_t i = 0; i < countries.size(); ++i) { | |
| 1750 if (countries[i] && countries[i]->country_code() == country_code) | |
| 1751 return true; | |
| 1752 } | |
| 1753 | |
| 1754 return false; | |
| 1755 } | |
| 1756 | |
| 1757 bool AutofillDialogControllerImpl::ShouldSuggestProfile( | |
| 1758 DialogSection section, | |
| 1759 const AutofillProfile& profile) { | |
| 1760 std::string country_code = | |
| 1761 base::UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)); | |
| 1762 return country_code.empty() || CanAcceptCountry(section, country_code); | |
| 1763 } | |
| 1764 | |
| 1765 SuggestionsMenuModel* AutofillDialogControllerImpl:: | |
| 1766 SuggestionsMenuModelForSection(DialogSection section) { | |
| 1767 switch (section) { | |
| 1768 case SECTION_CC: | |
| 1769 return &suggested_cc_; | |
| 1770 case SECTION_BILLING: | |
| 1771 return &suggested_billing_; | |
| 1772 case SECTION_SHIPPING: | |
| 1773 return &suggested_shipping_; | |
| 1774 } | |
| 1775 | |
| 1776 NOTREACHED(); | |
| 1777 return NULL; | |
| 1778 } | |
| 1779 | |
| 1780 const SuggestionsMenuModel* AutofillDialogControllerImpl:: | |
| 1781 SuggestionsMenuModelForSection(DialogSection section) const { | |
| 1782 return const_cast<AutofillDialogControllerImpl*>(this)-> | |
| 1783 SuggestionsMenuModelForSection(section); | |
| 1784 } | |
| 1785 | |
| 1786 DialogSection AutofillDialogControllerImpl::SectionForSuggestionsMenuModel( | |
| 1787 const SuggestionsMenuModel& model) { | |
| 1788 if (&model == &suggested_cc_) | |
| 1789 return SECTION_CC; | |
| 1790 | |
| 1791 if (&model == &suggested_billing_) | |
| 1792 return SECTION_BILLING; | |
| 1793 | |
| 1794 DCHECK_EQ(&model, &suggested_shipping_); | |
| 1795 return SECTION_SHIPPING; | |
| 1796 } | |
| 1797 | |
| 1798 CountryComboboxModel* AutofillDialogControllerImpl:: | |
| 1799 CountryComboboxModelForSection(DialogSection section) { | |
| 1800 if (section == SECTION_BILLING) | |
| 1801 return billing_country_combobox_model_.get(); | |
| 1802 | |
| 1803 if (section == SECTION_SHIPPING) | |
| 1804 return shipping_country_combobox_model_.get(); | |
| 1805 | |
| 1806 return NULL; | |
| 1807 } | |
| 1808 | |
| 1809 void AutofillDialogControllerImpl::GetI18nValidatorSuggestions( | |
| 1810 DialogSection section, | |
| 1811 ServerFieldType type, | |
| 1812 std::vector<autofill::Suggestion>* popup_suggestions) { | |
| 1813 AddressField focused_field; | |
| 1814 if (!i18n::FieldForType(type, &focused_field)) | |
| 1815 return; | |
| 1816 | |
| 1817 FieldValueMap inputs; | |
| 1818 view_->GetUserInput(section, &inputs); | |
| 1819 | |
| 1820 AutofillProfile profile; | |
| 1821 FillFormGroupFromOutputs(inputs, &profile); | |
| 1822 | |
| 1823 std::unique_ptr<AddressData> user_input = | |
| 1824 i18n::CreateAddressDataFromAutofillProfile( | |
| 1825 profile, g_browser_process->GetApplicationLocale()); | |
| 1826 user_input->language_code = AddressLanguageCodeForSection(section); | |
| 1827 | |
| 1828 static const size_t kSuggestionsLimit = 10; | |
| 1829 AddressValidator::Status status = GetValidator()->GetSuggestions( | |
| 1830 *user_input, focused_field, kSuggestionsLimit, | |
| 1831 &i18n_validator_suggestions_); | |
| 1832 | |
| 1833 if (status != AddressValidator::SUCCESS) | |
| 1834 return; | |
| 1835 | |
| 1836 for (size_t i = 0; i < i18n_validator_suggestions_.size(); ++i) { | |
| 1837 popup_suggestions->push_back(autofill::Suggestion( | |
| 1838 base::UTF8ToUTF16( | |
| 1839 i18n_validator_suggestions_[i].GetFieldValue(focused_field)))); | |
| 1840 | |
| 1841 // Disambiguate the suggestion by showing the smallest administrative | |
| 1842 // region of the suggested address: | |
| 1843 // ADMIN_AREA > LOCALITY > DEPENDENT_LOCALITY | |
| 1844 for (int field = DEPENDENT_LOCALITY; field >= ADMIN_AREA; --field) { | |
| 1845 const std::string& field_value = | |
| 1846 i18n_validator_suggestions_[i].GetFieldValue( | |
| 1847 static_cast<AddressField>(field)); | |
| 1848 if (focused_field != field && !field_value.empty()) { | |
| 1849 popup_suggestions->back().label = base::UTF8ToUTF16(field_value); | |
| 1850 break; | |
| 1851 } | |
| 1852 } | |
| 1853 } | |
| 1854 } | |
| 1855 | |
| 1856 DetailInputs* AutofillDialogControllerImpl::MutableRequestedFieldsForSection( | |
| 1857 DialogSection section) { | |
| 1858 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); | |
| 1859 } | |
| 1860 | |
| 1861 std::string* AutofillDialogControllerImpl::MutableAddressLanguageCodeForSection( | |
| 1862 DialogSection section) { | |
| 1863 switch (section) { | |
| 1864 case SECTION_BILLING: | |
| 1865 return &billing_address_language_code_; | |
| 1866 case SECTION_SHIPPING: | |
| 1867 return &shipping_address_language_code_; | |
| 1868 case SECTION_CC: | |
| 1869 return NULL; | |
| 1870 } | |
| 1871 NOTREACHED(); | |
| 1872 return NULL; | |
| 1873 } | |
| 1874 | |
| 1875 std::string AutofillDialogControllerImpl::AddressLanguageCodeForSection( | |
| 1876 DialogSection section) { | |
| 1877 std::string* language_code = MutableAddressLanguageCodeForSection(section); | |
| 1878 return language_code != NULL ? *language_code : std::string(); | |
| 1879 } | |
| 1880 | |
| 1881 std::vector<ServerFieldType> AutofillDialogControllerImpl:: | |
| 1882 RequestedTypesForSection(DialogSection section) const { | |
| 1883 return TypesFromInputs(RequestedFieldsForSection(section)); | |
| 1884 } | |
| 1885 | |
| 1886 std::string AutofillDialogControllerImpl::CountryCodeForSection( | |
| 1887 DialogSection section) { | |
| 1888 base::string16 country; | |
| 1889 | |
| 1890 std::unique_ptr<DataModelWrapper> wrapper = CreateWrapper(section); | |
| 1891 if (wrapper) { | |
| 1892 country = wrapper->GetInfo(AutofillType(CountryTypeForSection(section))); | |
| 1893 } else { | |
| 1894 FieldValueMap outputs; | |
| 1895 view_->GetUserInput(section, &outputs); | |
| 1896 country = outputs[CountryTypeForSection(section)]; | |
| 1897 } | |
| 1898 | |
| 1899 return CountryNames::GetInstance()->GetCountryCode(country); | |
| 1900 } | |
| 1901 | |
| 1902 bool AutofillDialogControllerImpl::RebuildInputsForCountry( | |
| 1903 DialogSection section, | |
| 1904 const base::string16& country_name, | |
| 1905 bool should_clobber) { | |
| 1906 CountryComboboxModel* model = CountryComboboxModelForSection(section); | |
| 1907 if (!model) | |
| 1908 return false; | |
| 1909 | |
| 1910 std::string country_code = | |
| 1911 CountryNames::GetInstance()->GetCountryCode(country_name); | |
| 1912 DCHECK(CanAcceptCountry(section, country_code)); | |
| 1913 | |
| 1914 if (view_ && !should_clobber) { | |
| 1915 FieldValueMap outputs; | |
| 1916 view_->GetUserInput(section, &outputs); | |
| 1917 | |
| 1918 // If |country_name| is the same as the view, no-op and let the caller know. | |
| 1919 if (outputs[CountryTypeForSection(section)] == country_name) | |
| 1920 return false; | |
| 1921 } | |
| 1922 | |
| 1923 DetailInputs* inputs = MutableRequestedFieldsForSection(section); | |
| 1924 inputs->clear(); | |
| 1925 BuildInputsForSection(section, country_code, inputs, | |
| 1926 MutableAddressLanguageCodeForSection(section)); | |
| 1927 | |
| 1928 if (!country_code.empty()) { | |
| 1929 GetValidator()->LoadRules( | |
| 1930 CountryNames::GetInstance()->GetCountryCode(country_name)); | |
| 1931 } | |
| 1932 | |
| 1933 return true; | |
| 1934 } | |
| 1935 | |
| 1936 void AutofillDialogControllerImpl::HidePopup() { | |
| 1937 if (popup_controller_) | |
| 1938 popup_controller_->Hide(); | |
| 1939 popup_input_type_ = UNKNOWN_TYPE; | |
| 1940 } | |
| 1941 | |
| 1942 void AutofillDialogControllerImpl::SetEditingExistingData( | |
| 1943 DialogSection section, bool editing) { | |
| 1944 if (editing) | |
| 1945 section_editing_state_.insert(section); | |
| 1946 else | |
| 1947 section_editing_state_.erase(section); | |
| 1948 } | |
| 1949 | |
| 1950 bool AutofillDialogControllerImpl::IsASuggestionItemKey( | |
| 1951 const std::string& key) const { | |
| 1952 return !key.empty() && | |
| 1953 key != kAddNewItemKey && | |
| 1954 key != kManageItemsKey && | |
| 1955 key != kSameAsBillingKey; | |
| 1956 } | |
| 1957 | |
| 1958 bool AutofillDialogControllerImpl::IsAutofillEnabled() const { | |
| 1959 return profile_->GetPrefs()->GetBoolean(prefs::kAutofillEnabled); | |
| 1960 } | |
| 1961 | |
| 1962 bool AutofillDialogControllerImpl::IsManuallyEditingAnySection() const { | |
| 1963 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { | |
| 1964 if (IsManuallyEditingSection(static_cast<DialogSection>(section))) | |
| 1965 return true; | |
| 1966 } | |
| 1967 return false; | |
| 1968 } | |
| 1969 | |
| 1970 base::string16 AutofillDialogControllerImpl::CreditCardNumberValidityMessage( | |
| 1971 const base::string16& number) const { | |
| 1972 if (!number.empty() && !autofill::IsValidCreditCardNumber(number)) { | |
| 1973 return l10n_util::GetStringUTF16( | |
| 1974 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_NUMBER); | |
| 1975 } | |
| 1976 | |
| 1977 if (ShouldDisallowCcType( | |
| 1978 CreditCard::TypeForDisplay(CreditCard::GetCreditCardType(number)))) { | |
| 1979 int ids = IDS_AUTOFILL_DIALOG_VALIDATION_UNACCEPTED_GENERIC_CARD; | |
| 1980 const char* const type = CreditCard::GetCreditCardType(number); | |
| 1981 if (type == kAmericanExpressCard) | |
| 1982 ids = IDS_AUTOFILL_DIALOG_VALIDATION_UNACCEPTED_AMEX; | |
| 1983 else if (type == kDiscoverCard) | |
| 1984 ids = IDS_AUTOFILL_DIALOG_VALIDATION_UNACCEPTED_DISCOVER; | |
| 1985 else if (type == kMasterCard) | |
| 1986 ids = IDS_AUTOFILL_DIALOG_VALIDATION_UNACCEPTED_MASTERCARD; | |
| 1987 else if (type == kVisaCard) | |
| 1988 ids = IDS_AUTOFILL_DIALOG_VALIDATION_UNACCEPTED_VISA; | |
| 1989 | |
| 1990 return l10n_util::GetStringUTF16(ids); | |
| 1991 } | |
| 1992 | |
| 1993 // Card number is good and supported. | |
| 1994 return base::string16(); | |
| 1995 } | |
| 1996 | |
| 1997 bool AutofillDialogControllerImpl::AllSectionsAreValid() { | |
| 1998 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { | |
| 1999 if (!SectionIsValid(static_cast<DialogSection>(section))) | |
| 2000 return false; | |
| 2001 } | |
| 2002 return true; | |
| 2003 } | |
| 2004 | |
| 2005 bool AutofillDialogControllerImpl::SectionIsValid( | |
| 2006 DialogSection section) { | |
| 2007 if (!IsManuallyEditingSection(section)) | |
| 2008 return true; | |
| 2009 | |
| 2010 FieldValueMap detail_outputs; | |
| 2011 view_->GetUserInput(section, &detail_outputs); | |
| 2012 return !InputsAreValid(section, detail_outputs).HasSureErrors(); | |
| 2013 } | |
| 2014 | |
| 2015 bool AutofillDialogControllerImpl::RulesAreLoaded(DialogSection section) { | |
| 2016 AddressData address_data; | |
| 2017 address_data.region_code = CountryCodeForSection(section); | |
| 2018 AddressValidator::Status status = GetValidator()->ValidateAddress( | |
| 2019 address_data, NULL, NULL); | |
| 2020 return status == AddressValidator::SUCCESS; | |
| 2021 } | |
| 2022 | |
| 2023 bool AutofillDialogControllerImpl::ShouldDisallowCcType( | |
| 2024 const base::string16& type) const { | |
| 2025 if (acceptable_cc_types_.empty()) | |
| 2026 return false; | |
| 2027 | |
| 2028 if (acceptable_cc_types_.find(base::i18n::ToUpper(type)) == | |
| 2029 acceptable_cc_types_.end()) { | |
| 2030 return true; | |
| 2031 } | |
| 2032 | |
| 2033 return false; | |
| 2034 } | |
| 2035 | |
| 2036 bool AutofillDialogControllerImpl::HasInvalidAddress( | |
| 2037 const AutofillProfile& profile) { | |
| 2038 std::unique_ptr<AddressData> address_data = | |
| 2039 i18n::CreateAddressDataFromAutofillProfile( | |
| 2040 profile, g_browser_process->GetApplicationLocale()); | |
| 2041 | |
| 2042 FieldProblemMap problems; | |
| 2043 GetValidator()->ValidateAddress(*address_data, NULL, &problems); | |
| 2044 return !problems.empty(); | |
| 2045 } | |
| 2046 | |
| 2047 bool AutofillDialogControllerImpl::ShouldUseBillingForShipping() { | |
| 2048 return SectionIsActive(SECTION_SHIPPING) && | |
| 2049 suggested_shipping_.GetItemKeyForCheckedItem() == kSameAsBillingKey; | |
| 2050 } | |
| 2051 | |
| 2052 bool AutofillDialogControllerImpl::ShouldSaveDetailsLocally() { | |
| 2053 // It's possible that the user checked [X] Save details locally before | |
| 2054 // switching payment methods, so only ask the view whether to save details | |
| 2055 // locally if that checkbox is showing (currently if not paying with wallet). | |
| 2056 // Also, if the user isn't editing any sections, there's no data to save | |
| 2057 // locally. | |
| 2058 return ShouldOfferToSaveInChrome() && view_->SaveDetailsLocally(); | |
| 2059 } | |
| 2060 | |
| 2061 void AutofillDialogControllerImpl::FinishSubmit() { | |
| 2062 FillOutputForSection(SECTION_CC); | |
| 2063 FillOutputForSection(SECTION_BILLING); | |
| 2064 | |
| 2065 if (ShouldUseBillingForShipping()) { | |
| 2066 FillOutputForSectionWithComparator( | |
| 2067 SECTION_BILLING, | |
| 2068 base::Bind(ServerTypeMatchesShippingField)); | |
| 2069 FillOutputForSectionWithComparator( | |
| 2070 SECTION_CC, | |
| 2071 base::Bind(ServerTypeMatchesShippingField)); | |
| 2072 } else { | |
| 2073 FillOutputForSection(SECTION_SHIPPING); | |
| 2074 } | |
| 2075 | |
| 2076 if (ShouldOfferToSaveInChrome()) { | |
| 2077 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 2078 DialogSection section = static_cast<DialogSection>(i); | |
| 2079 if (!SectionIsActive(section) || section == SECTION_CC) | |
| 2080 continue; | |
| 2081 | |
| 2082 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 2083 std::string item_key = model->GetItemKeyForCheckedItem(); | |
| 2084 if (IsASuggestionItemKey(item_key) || item_key == kSameAsBillingKey) { | |
| 2085 PersistAutofillChoice(section, item_key); | |
| 2086 } else if (item_key == kAddNewItemKey && ShouldSaveDetailsLocally()) { | |
| 2087 DCHECK(newly_saved_data_model_guids_.count(section)); | |
| 2088 PersistAutofillChoice(section, newly_saved_data_model_guids_[section]); | |
| 2089 } | |
| 2090 } | |
| 2091 | |
| 2092 profile_->GetPrefs()->SetBoolean(::prefs::kAutofillDialogSaveData, | |
| 2093 view_->SaveDetailsLocally()); | |
| 2094 } | |
| 2095 | |
| 2096 LogOnFinishSubmitMetrics(); | |
| 2097 | |
| 2098 // Callback should be called as late as possible. | |
| 2099 callback_.Run(AutofillClient::AutocompleteResultSuccess, | |
| 2100 base::string16(), | |
| 2101 &form_structure_); | |
| 2102 data_was_passed_back_ = true; | |
| 2103 | |
| 2104 // This might delete us. | |
| 2105 Hide(); | |
| 2106 } | |
| 2107 | |
| 2108 void AutofillDialogControllerImpl::OnAddressValidationRulesLoaded( | |
| 2109 const std::string& country_code, | |
| 2110 bool success) { | |
| 2111 // Rules may load instantly (during initialization, before the view is | |
| 2112 // even ready). We'll validate when the view is created. | |
| 2113 if (!view_) | |
| 2114 return; | |
| 2115 | |
| 2116 ScopedViewUpdates updates(view_.get()); | |
| 2117 | |
| 2118 // TODO(dbeam): should we retry on failure? | |
| 2119 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { | |
| 2120 DialogSection section = static_cast<DialogSection>(i); | |
| 2121 if (!SectionIsActive(section) || | |
| 2122 CountryCodeForSection(section) != country_code) { | |
| 2123 continue; | |
| 2124 } | |
| 2125 | |
| 2126 if (IsManuallyEditingSection(section) && needs_validation_.count(section)) { | |
| 2127 view_->ValidateSection(section); | |
| 2128 needs_validation_.erase(section); | |
| 2129 } else if (success) { | |
| 2130 ShowEditUiIfBadSuggestion(section); | |
| 2131 UpdateSection(section); | |
| 2132 } | |
| 2133 } | |
| 2134 } | |
| 2135 | |
| 2136 void AutofillDialogControllerImpl::PersistAutofillChoice( | |
| 2137 DialogSection section, | |
| 2138 const std::string& guid) { | |
| 2139 DCHECK(ShouldOfferToSaveInChrome()); | |
| 2140 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); | |
| 2141 value->SetString(kGuidPrefKey, guid); | |
| 2142 | |
| 2143 DictionaryPrefUpdate updater(profile()->GetPrefs(), | |
| 2144 ::prefs::kAutofillDialogAutofillDefault); | |
| 2145 base::DictionaryValue* autofill_choice = updater.Get(); | |
| 2146 autofill_choice->Set(SectionToPrefString(section), value.release()); | |
| 2147 } | |
| 2148 | |
| 2149 void AutofillDialogControllerImpl::GetDefaultAutofillChoice( | |
| 2150 DialogSection section, | |
| 2151 std::string* guid) { | |
| 2152 DCHECK(IsAutofillEnabled()); | |
| 2153 // The default choice is the first thing in the menu that is a suggestion | |
| 2154 // item. | |
| 2155 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 2156 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 2157 // Try the first suggestion item that is enabled. | |
| 2158 if (IsASuggestionItemKey(model->GetItemKeyAt(i)) && model->IsEnabledAt(i)) { | |
| 2159 *guid = model->GetItemKeyAt(i); | |
| 2160 return; | |
| 2161 // Fall back to the first non-suggestion key. | |
| 2162 } else if (!IsASuggestionItemKey(model->GetItemKeyAt(i)) && guid->empty()) { | |
| 2163 *guid = model->GetItemKeyAt(i); | |
| 2164 } | |
| 2165 } | |
| 2166 } | |
| 2167 | |
| 2168 bool AutofillDialogControllerImpl::GetAutofillChoice(DialogSection section, | |
| 2169 std::string* guid) { | |
| 2170 DCHECK(IsAutofillEnabled()); | |
| 2171 const base::DictionaryValue* choices = profile()->GetPrefs()->GetDictionary( | |
| 2172 ::prefs::kAutofillDialogAutofillDefault); | |
| 2173 if (!choices) | |
| 2174 return false; | |
| 2175 | |
| 2176 const base::DictionaryValue* choice = NULL; | |
| 2177 if (!choices->GetDictionary(SectionToPrefString(section), &choice)) | |
| 2178 return false; | |
| 2179 | |
| 2180 choice->GetString(kGuidPrefKey, guid); | |
| 2181 return true; | |
| 2182 } | |
| 2183 | |
| 2184 void AutofillDialogControllerImpl::LogOnFinishSubmitMetrics() { | |
| 2185 AutofillMetrics::LogDialogUiDuration( | |
| 2186 base::Time::Now() - dialog_shown_timestamp_, | |
| 2187 AutofillMetrics::DIALOG_ACCEPTED); | |
| 2188 | |
| 2189 AutofillMetrics::LogDialogUiEvent(AutofillMetrics::DIALOG_UI_ACCEPTED); | |
| 2190 | |
| 2191 AutofillMetrics::DialogDismissalState dismissal_state; | |
| 2192 if (!IsManuallyEditingAnySection()) { | |
| 2193 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_EXISTING_AUTOFILL_DATA; | |
| 2194 } else if (ShouldSaveDetailsLocally()) { | |
| 2195 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_AUTOFILL; | |
| 2196 } else { | |
| 2197 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_NO_SAVE; | |
| 2198 } | |
| 2199 | |
| 2200 AutofillMetrics::LogDialogDismissalState(dismissal_state); | |
| 2201 } | |
| 2202 | |
| 2203 void AutofillDialogControllerImpl::LogOnCancelMetrics() { | |
| 2204 AutofillMetrics::LogDialogUiEvent(AutofillMetrics::DIALOG_UI_CANCELED); | |
| 2205 | |
| 2206 AutofillMetrics::DialogDismissalState dismissal_state; | |
| 2207 if (!IsManuallyEditingAnySection()) | |
| 2208 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_EDITS; | |
| 2209 else if (AllSectionsAreValid()) | |
| 2210 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_INVALID_FIELDS; | |
| 2211 else | |
| 2212 dismissal_state = AutofillMetrics::DIALOG_CANCELED_WITH_INVALID_FIELDS; | |
| 2213 | |
| 2214 AutofillMetrics::LogDialogDismissalState(dismissal_state); | |
| 2215 | |
| 2216 AutofillMetrics::LogDialogUiDuration( | |
| 2217 base::Time::Now() - dialog_shown_timestamp_, | |
| 2218 AutofillMetrics::DIALOG_CANCELED); | |
| 2219 } | |
| 2220 | |
| 2221 void AutofillDialogControllerImpl::LogSuggestionItemSelectedMetric( | |
| 2222 const SuggestionsMenuModel& model) { | |
| 2223 DialogSection section = SectionForSuggestionsMenuModel(model); | |
| 2224 | |
| 2225 AutofillMetrics::DialogUiEvent dialog_ui_event; | |
| 2226 if (model.GetItemKeyForCheckedItem() == kAddNewItemKey) { | |
| 2227 // Selected to add a new item. | |
| 2228 dialog_ui_event = common::DialogSectionToUiItemAddedEvent(section); | |
| 2229 } else if (IsASuggestionItemKey(model.GetItemKeyForCheckedItem())) { | |
| 2230 // Selected an existing item. | |
| 2231 dialog_ui_event = common::DialogSectionToUiSelectionChangedEvent(section); | |
| 2232 } else { | |
| 2233 // TODO(estade): add logging for "Manage items" or "Use billing for | |
| 2234 // shipping"? | |
| 2235 return; | |
| 2236 } | |
| 2237 | |
| 2238 AutofillMetrics::LogDialogUiEvent(dialog_ui_event); | |
| 2239 } | |
| 2240 | |
| 2241 void AutofillDialogControllerImpl::LogDialogLatencyToShow() { | |
| 2242 if (was_ui_latency_logged_) | |
| 2243 return; | |
| 2244 | |
| 2245 AutofillMetrics::LogDialogLatencyToShow(base::Time::Now() - | |
| 2246 dialog_shown_timestamp_); | |
| 2247 was_ui_latency_logged_ = true; | |
| 2248 } | |
| 2249 | |
| 2250 AutofillMetrics::DialogInitialUserStateMetric | |
| 2251 AutofillDialogControllerImpl::GetInitialUserState() const { | |
| 2252 // Consider a user to be an Autofill user if the user has any credit cards | |
| 2253 // or addresses saved. Check that the item count is greater than 2 because | |
| 2254 // an "empty" menu still has the "add new" menu item and "manage" menu item. | |
| 2255 const bool has_autofill_profiles = | |
| 2256 suggested_cc_.GetItemCount() > 2 || | |
| 2257 suggested_billing_.GetItemCount() > 2; | |
| 2258 | |
| 2259 return has_autofill_profiles | |
| 2260 ? AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_HAS_AUTOFILL | |
| 2261 : AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_NO_AUTOFILL; | |
| 2262 } | |
| 2263 | |
| 2264 void AutofillDialogControllerImpl::OnSubmitButtonDelayEnd() { | |
| 2265 if (!view_) | |
| 2266 return; | |
| 2267 ScopedViewUpdates updates(view_.get()); | |
| 2268 view_->UpdateButtonStrip(); | |
| 2269 } | |
| 2270 | |
| 2271 } // namespace autofill | |
| OLD | NEW |