autofill_dialog_controller_impl.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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 11#include "apps/shell_window.h" 12#include "base/base64.h" 13#include "base/bind.h" 14#include "base/i18n/rtl.h" 15#include "base/logging.h" 16#include "base/prefs/pref_service.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/string_split.h" 19#include "base/strings/string_util.h" 20#include "base/strings/utf_string_conversions.h" 21#include "base/time/time.h" 22#include "chrome/browser/autofill/personal_data_manager_factory.h" 23#include "chrome/browser/browser_process.h" 24#include "chrome/browser/extensions/shell_window_registry.h" 25#include "chrome/browser/prefs/scoped_user_pref_update.h" 26#include "chrome/browser/profiles/profile.h" 27#include "chrome/browser/ui/autofill/autofill_credit_card_bubble_controller.h" 28#include "chrome/browser/ui/autofill/autofill_dialog_view.h" 29#include "chrome/browser/ui/autofill/data_model_wrapper.h" 30#include "chrome/browser/ui/browser.h" 31#include "chrome/browser/ui/browser_finder.h" 32#include "chrome/browser/ui/browser_navigator.h" 33#include "chrome/browser/ui/browser_window.h" 34#include "chrome/browser/ui/extensions/native_app_window.h" 35#include "chrome/common/chrome_version_info.h" 36#include "chrome/common/pref_names.h" 37#include "chrome/common/render_messages.h" 38#include "chrome/common/url_constants.h" 39#include "components/autofill/content/browser/risk/fingerprint.h" 40#include "components/autofill/content/browser/risk/proto/fingerprint.pb.h" 41#include "components/autofill/content/browser/wallet/form_field_error.h" 42#include "components/autofill/content/browser/wallet/full_wallet.h" 43#include "components/autofill/content/browser/wallet/instrument.h" 44#include "components/autofill/content/browser/wallet/wallet_address.h" 45#include "components/autofill/content/browser/wallet/wallet_items.h" 46#include "components/autofill/content/browser/wallet/wallet_service_url.h" 47#include "components/autofill/content/browser/wallet/wallet_signin_helper.h" 48#include "components/autofill/core/browser/autofill_country.h" 49#include "components/autofill/core/browser/autofill_data_model.h" 50#include "components/autofill/core/browser/autofill_manager.h" 51#include "components/autofill/core/browser/autofill_type.h" 52#include "components/autofill/core/browser/personal_data_manager.h" 53#include "components/autofill/core/browser/phone_number_i18n.h" 54#include "components/autofill/core/browser/validation.h" 55#include "components/autofill/core/common/form_data.h" 56#include "components/user_prefs/pref_registry_syncable.h" 57#include "content/public/browser/browser_thread.h" 58#include "content/public/browser/geolocation_provider.h" 59#include "content/public/browser/navigation_controller.h" 60#include "content/public/browser/navigation_details.h" 61#include "content/public/browser/navigation_entry.h" 62#include "content/public/browser/notification_service.h" 63#include "content/public/browser/notification_types.h" 64#include "content/public/browser/render_view_host.h" 65#include "content/public/browser/web_contents.h" 66#include "content/public/browser/web_contents_view.h" 67#include "content/public/common/url_constants.h" 68#include "grit/chromium_strings.h" 69#include "grit/component_strings.h" 70#include "grit/generated_resources.h" 71#include "grit/theme_resources.h" 72#include "grit/webkit_resources.h" 73#include "net/base/registry_controlled_domains/registry_controlled_domain.h" 74#include "net/cert/cert_status_flags.h" 75#include "ui/base/base_window.h" 76#include "ui/base/l10n/l10n_util.h" 77#include "ui/base/models/combobox_model.h" 78#include "ui/base/resource/resource_bundle.h" 79#include "ui/gfx/canvas.h" 80#include "ui/gfx/color_utils.h" 81#include "ui/gfx/skbitmap_operations.h" 82 83namespace autofill { 84 85namespace { 86 87const char kAddNewItemKey[] = "add-new-item"; 88const char kManageItemsKey[] = "manage-items"; 89const char kSameAsBillingKey[] = "same-as-billing"; 90 91// Keys for the kAutofillDialogAutofillDefault pref dictionary (do not change 92// these values). 93const char kGuidPrefKey[] = "guid"; 94const char kVariantPrefKey[] = "variant"; 95 96// This string is stored along with saved addresses and credit cards in the 97// WebDB, and hence should not be modified, so that it remains consistent over 98// time. 99const char kAutofillDialogOrigin[] = "Chrome Autofill dialog"; 100 101// HSL shift to gray out an image. 102const color_utils::HSL kGrayImageShift = {-1, 0, 0.8}; 103 104// Returns true if |card_type| is supported by Wallet. 105bool IsWalletSupportedCard(const std::string& card_type) { 106 return card_type == autofill::kVisaCard || 107 card_type == autofill::kMasterCard || 108 card_type == autofill::kDiscoverCard; 109} 110 111// Returns true if |input| should be shown when |field_type| has been requested. 112bool InputTypeMatchesFieldType(const DetailInput& input, 113 AutofillFieldType field_type) { 114 // If any credit card expiration info is asked for, show both month and year 115 // inputs. 116 if (field_type == CREDIT_CARD_EXP_4_DIGIT_YEAR || 117 field_type == CREDIT_CARD_EXP_2_DIGIT_YEAR || 118 field_type == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR || 119 field_type == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR || 120 field_type == CREDIT_CARD_EXP_MONTH) { 121 return input.type == CREDIT_CARD_EXP_4_DIGIT_YEAR || 122 input.type == CREDIT_CARD_EXP_MONTH; 123 } 124 125 if (field_type == CREDIT_CARD_TYPE) 126 return input.type == CREDIT_CARD_NUMBER; 127 128 return input.type == field_type; 129} 130 131// Returns true if |input| in the given |section| should be used for a 132// site-requested |field|. 133bool DetailInputMatchesField(DialogSection section, 134 const DetailInput& input, 135 const AutofillField& field) { 136 // The credit card name is filled from the billing section's data. 137 if (field.type() == CREDIT_CARD_NAME && 138 (section == SECTION_BILLING || section == SECTION_CC_BILLING)) { 139 return input.type == NAME_FULL; 140 } 141 142 return InputTypeMatchesFieldType(input, field.type()); 143} 144 145bool IsCreditCardType(AutofillFieldType type) { 146 return AutofillType(type).group() == AutofillType::CREDIT_CARD; 147} 148 149// Returns true if |input| should be used to fill a site-requested |field| which 150// is notated with a "shipping" tag, for use when the user has decided to use 151// the billing address as the shipping address. 152bool DetailInputMatchesShippingField(const DetailInput& input, 153 const AutofillField& field) { 154 // Equivalent billing field type is used to support UseBillingAsShipping 155 // usecase. 156 AutofillFieldType field_type = 157 AutofillType::GetEquivalentBillingFieldType(field.type()); 158 159 return InputTypeMatchesFieldType(input, field_type); 160} 161 162// Constructs |inputs| from template data. 163void BuildInputs(const DetailInput* input_template, 164 size_t template_size, 165 DetailInputs* inputs) { 166 for (size_t i = 0; i < template_size; ++i) { 167 const DetailInput* input = &input_template[i]; 168 inputs->push_back(*input); 169 } 170} 171 172// Initializes |form_group| from user-entered data. 173void FillFormGroupFromOutputs(const DetailOutputMap& detail_outputs, 174 FormGroup* form_group) { 175 for (DetailOutputMap::const_iterator iter = detail_outputs.begin(); 176 iter != detail_outputs.end(); ++iter) { 177 if (!iter->second.empty()) { 178 AutofillFieldType type = iter->first->type; 179 if (type == ADDRESS_HOME_COUNTRY || type == ADDRESS_BILLING_COUNTRY) { 180 form_group->SetInfo(type, 181 iter->second, 182 g_browser_process->GetApplicationLocale()); 183 } else { 184 form_group->SetRawInfo(iter->first->type, iter->second); 185 } 186 } 187 } 188} 189 190// Get billing info from |output| and put it into |card|, |cvc|, and |profile|. 191// These outparams are required because |card|/|profile| accept different types 192// of raw info, and CreditCard doesn't save CVCs. 193void GetBillingInfoFromOutputs(const DetailOutputMap& output, 194 CreditCard* card, 195 string16* cvc, 196 AutofillProfile* profile) { 197 for (DetailOutputMap::const_iterator it = output.begin(); 198 it != output.end(); ++it) { 199 string16 trimmed; 200 TrimWhitespace(it->second, TRIM_ALL, &trimmed); 201 202 // Special case CVC as CreditCard just swallows it. 203 if (it->first->type == CREDIT_CARD_VERIFICATION_CODE) { 204 if (cvc) 205 cvc->assign(trimmed); 206 } else if (it->first->type == ADDRESS_HOME_COUNTRY || 207 it->first->type == ADDRESS_BILLING_COUNTRY) { 208 if (profile) { 209 profile->SetInfo(it->first->type, 210 trimmed, 211 g_browser_process->GetApplicationLocale()); 212 } 213 } else { 214 // Copy the credit card name to |profile| in addition to |card| as 215 // wallet::Instrument requires a recipient name for its billing address. 216 if (card && it->first->type == NAME_FULL) 217 card->SetRawInfo(CREDIT_CARD_NAME, trimmed); 218 219 if (IsCreditCardType(it->first->type)) { 220 if (card) 221 card->SetRawInfo(it->first->type, trimmed); 222 } else if (profile) { 223 profile->SetRawInfo(it->first->type, trimmed); 224 } 225 } 226 } 227} 228 229// Returns the containing window for the given |web_contents|. The containing 230// window might be a browser window for a Chrome tab, or it might be a shell 231// window for a platform app. 232ui::BaseWindow* GetBaseWindowForWebContents( 233 const content::WebContents* web_contents) { 234 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 235 if (browser) 236 return browser->window(); 237 238 gfx::NativeWindow native_window = 239 web_contents->GetView()->GetTopLevelNativeWindow(); 240 apps::ShellWindow* shell_window = 241 extensions::ShellWindowRegistry:: 242 GetShellWindowForNativeWindowAnyProfile(native_window); 243 return shell_window->GetBaseWindow(); 244} 245 246// Extracts the string value of a field with |type| from |output|. This is 247// useful when you only need the value of 1 input from a section of view inputs. 248string16 GetValueForType(const DetailOutputMap& output, 249 AutofillFieldType type) { 250 for (DetailOutputMap::const_iterator it = output.begin(); 251 it != output.end(); ++it) { 252 if (it->first->type == type) 253 return it->second; 254 } 255 NOTREACHED(); 256 return string16(); 257} 258 259// Returns a string descriptor for a DialogSection, for use with prefs (do not 260// change these values). 261std::string SectionToPrefString(DialogSection section) { 262 switch (section) { 263 case SECTION_CC: 264 return "cc"; 265 266 case SECTION_BILLING: 267 return "billing"; 268 269 case SECTION_CC_BILLING: 270 // The SECTION_CC_BILLING section isn't active when using Autofill. 271 NOTREACHED(); 272 return std::string(); 273 274 case SECTION_SHIPPING: 275 return "shipping"; 276 277 case SECTION_EMAIL: 278 return "email"; 279 } 280 281 NOTREACHED(); 282 return std::string(); 283} 284 285// Check if a given MaskedInstrument is allowed for the purchase. 286bool IsInstrumentAllowed( 287 const wallet::WalletItems::MaskedInstrument& instrument) { 288 switch (instrument.status()) { 289 case wallet::WalletItems::MaskedInstrument::VALID: 290 case wallet::WalletItems::MaskedInstrument::PENDING: 291 case wallet::WalletItems::MaskedInstrument::EXPIRED: 292 case wallet::WalletItems::MaskedInstrument::BILLING_INCOMPLETE: 293 return true; 294 default: 295 return false; 296 } 297} 298 299// Signals that the user has opted in to geolocation services. Factored out 300// into a separate method because all interaction with the geolocation provider 301// needs to happen on the IO thread, which is not the thread 302// AutofillDialogController lives on. 303void UserDidOptIntoLocationServices() { 304 content::GeolocationProvider::GetInstance()->UserDidOptIntoLocationServices(); 305} 306 307// Returns whether |data_model| is complete, i.e. can fill out all the 308// |requested_fields|, and verified, i.e. not just automatically aggregated. 309// Incomplete or unverifed data will not be displayed in the dropdown menu. 310bool HasCompleteAndVerifiedData(const AutofillDataModel& data_model, 311 const DetailInputs& requested_fields) { 312 if (!data_model.IsVerified()) 313 return false; 314 315 const std::string app_locale = g_browser_process->GetApplicationLocale(); 316 for (size_t i = 0; i < requested_fields.size(); ++i) { 317 AutofillFieldType type = requested_fields[i].type; 318 if (type != ADDRESS_HOME_LINE2 && 319 type != CREDIT_CARD_VERIFICATION_CODE && 320 data_model.GetInfo(type, app_locale).empty()) { 321 return false; 322 } 323 } 324 325 return true; 326} 327 328// Returns true if |profile| has an invalid address, i.e. an invalid state, zip 329// code, or phone number. Otherwise returns false. Profiles with invalid 330// addresses are not suggested in the dropdown menu for billing and shipping 331// addresses. 332bool HasInvalidAddress(const AutofillProfile& profile) { 333 return profile.IsPresentButInvalid(ADDRESS_HOME_STATE) || 334 profile.IsPresentButInvalid(ADDRESS_HOME_ZIP) || 335 profile.IsPresentButInvalid(PHONE_HOME_WHOLE_NUMBER); 336} 337 338// Loops through |addresses_| comparing to |address| ignoring ID. If a match 339// is not found, NULL is returned. 340const wallet::Address* FindDuplicateAddress( 341 const std::vector<wallet::Address*>& addresses, 342 const wallet::Address& address) { 343 for (size_t i = 0; i < addresses.size(); ++i) { 344 if (addresses[i]->EqualsIgnoreID(address)) 345 return addresses[i]; 346 } 347 return NULL; 348} 349 350bool IsCardHolderNameValidForWallet(const string16& name) { 351 base::string16 whitespace_collapsed_name = CollapseWhitespace(name, true); 352 std::vector<base::string16> split_name; 353 base::SplitString(whitespace_collapsed_name, ' ', &split_name); 354 return split_name.size() >= 2; 355} 356 357DialogSection SectionFromLocation(wallet::FormFieldError::Location location) { 358 switch (location) { 359 case wallet::FormFieldError::PAYMENT_INSTRUMENT: 360 case wallet::FormFieldError::LEGAL_ADDRESS: 361 return SECTION_CC_BILLING; 362 363 case wallet::FormFieldError::SHIPPING_ADDRESS: 364 return SECTION_SHIPPING; 365 366 case wallet::FormFieldError::UNKNOWN_LOCATION: 367 NOTREACHED(); 368 return SECTION_MAX; 369 } 370 371 NOTREACHED(); 372 return SECTION_MAX; 373} 374 375base::string16 WalletErrorMessage(wallet::WalletClient::ErrorType error_type) { 376 switch (error_type) { 377 case wallet::WalletClient::BUYER_ACCOUNT_ERROR: 378 return l10n_util::GetStringUTF16(IDS_AUTOFILL_WALLET_BUYER_ACCOUNT_ERROR); 379 380 case wallet::WalletClient::BAD_REQUEST: 381 return l10n_util::GetStringFUTF16( 382 IDS_AUTOFILL_WALLET_UPGRADE_CHROME_ERROR, 383 ASCIIToUTF16("71")); 384 385 case wallet::WalletClient::INVALID_PARAMS: 386 return l10n_util::GetStringFUTF16( 387 IDS_AUTOFILL_WALLET_UPGRADE_CHROME_ERROR, 388 ASCIIToUTF16("42")); 389 390 case wallet::WalletClient::UNSUPPORTED_API_VERSION: 391 return l10n_util::GetStringFUTF16( 392 IDS_AUTOFILL_WALLET_UPGRADE_CHROME_ERROR, 393 ASCIIToUTF16("43")); 394 395 case wallet::WalletClient::SERVICE_UNAVAILABLE: 396 return l10n_util::GetStringUTF16( 397 IDS_AUTOFILL_WALLET_SERVICE_UNAVAILABLE_ERROR); 398 399 case wallet::WalletClient::INTERNAL_ERROR: 400 return l10n_util::GetStringFUTF16(IDS_AUTOFILL_WALLET_UNKNOWN_ERROR, 401 ASCIIToUTF16("62")); 402 403 case wallet::WalletClient::MALFORMED_RESPONSE: 404 return l10n_util::GetStringFUTF16(IDS_AUTOFILL_WALLET_UNKNOWN_ERROR, 405 ASCIIToUTF16("72")); 406 407 case wallet::WalletClient::NETWORK_ERROR: 408 return l10n_util::GetStringFUTF16(IDS_AUTOFILL_WALLET_UNKNOWN_ERROR, 409 ASCIIToUTF16("73")); 410 411 case wallet::WalletClient::UNKNOWN_ERROR: 412 return l10n_util::GetStringFUTF16(IDS_AUTOFILL_WALLET_UNKNOWN_ERROR, 413 ASCIIToUTF16("74")); 414 } 415 416 NOTREACHED(); 417 return base::string16(); 418} 419 420gfx::Image GetGeneratedCardImage(const string16& card_number) { 421 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 422 const gfx::ImageSkia* card = 423 rb.GetImageSkiaNamed(IDR_AUTOFILL_GENERATED_CARD); 424 gfx::Canvas canvas(card->size(), ui::SCALE_FACTOR_100P, false); 425 canvas.DrawImageInt(*card, 0, 0); 426 427#if !defined(OS_ANDROID) 428 gfx::Rect display_rect(gfx::Point(), card->size()); 429 display_rect.Inset(14, 0, 14, 0); 430 // TODO(estade): fallback font for systems that don't have Helvetica? 431 gfx::Font helvetica("Helvetica", 14); 432 gfx::ShadowValues shadows; 433 shadows.push_back(gfx::ShadowValue(gfx::Point(0, 1), 434 0.0, 435 SkColorSetARGB(85, 0, 0, 0))); 436 canvas.DrawStringWithShadows( 437 card_number, 438 helvetica, 439 SK_ColorWHITE, 440 display_rect, 0, 0, shadows); 441#endif 442 443 gfx::ImageSkia skia(canvas.ExtractImageRep()); 444 return gfx::Image(skia); 445} 446 447} // namespace 448 449AutofillDialogController::~AutofillDialogController() {} 450 451AutofillDialogControllerImpl::~AutofillDialogControllerImpl() { 452 if (popup_controller_) 453 popup_controller_->Hide(); 454 455 GetMetricLogger().LogDialogInitialUserState( 456 GetDialogType(), initial_user_state_); 457 458 if (deemphasized_render_view_) { 459 web_contents()->GetRenderViewHost()->Send( 460 new ChromeViewMsg_SetVisuallyDeemphasized( 461 web_contents()->GetRenderViewHost()->GetRoutingID(), false)); 462 } 463} 464 465// static 466base::WeakPtr<AutofillDialogControllerImpl> 467 AutofillDialogControllerImpl::Create( 468 content::WebContents* contents, 469 const FormData& form_structure, 470 const GURL& source_url, 471 const DialogType dialog_type, 472 const base::Callback<void(const FormStructure*, 473 const std::string&)>& callback) { 474 // AutofillDialogControllerImpl owns itself. 475 AutofillDialogControllerImpl* autofill_dialog_controller = 476 new AutofillDialogControllerImpl(contents, 477 form_structure, 478 source_url, 479 dialog_type, 480 callback); 481 return autofill_dialog_controller->weak_ptr_factory_.GetWeakPtr(); 482} 483 484// static 485void AutofillDialogControllerImpl::RegisterProfilePrefs( 486 user_prefs::PrefRegistrySyncable* registry) { 487 registry->RegisterIntegerPref( 488 ::prefs::kAutofillDialogShowCount, 489 0, 490 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 491 registry->RegisterBooleanPref( 492 ::prefs::kAutofillDialogHasPaidWithWallet, 493 false, 494 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 495 registry->RegisterBooleanPref( 496 ::prefs::kAutofillDialogPayWithoutWallet, 497 false, 498 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 499 registry->RegisterDictionaryPref( 500 ::prefs::kAutofillDialogAutofillDefault, 501 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 502} 503 504void AutofillDialogControllerImpl::Show() { 505 dialog_shown_timestamp_ = base::Time::Now(); 506 507 content::NavigationEntry* entry = contents_->GetController().GetActiveEntry(); 508 const GURL& active_url = entry ? entry->GetURL() : contents_->GetURL(); 509 invoked_from_same_origin_ = active_url.GetOrigin() == source_url_.GetOrigin(); 510 511 // Log any relevant UI metrics and security exceptions. 512 GetMetricLogger().LogDialogUiEvent( 513 GetDialogType(), AutofillMetrics::DIALOG_UI_SHOWN); 514 515 GetMetricLogger().LogDialogSecurityMetric( 516 GetDialogType(), AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); 517 518 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 519 GetMetricLogger().LogDialogSecurityMetric( 520 GetDialogType(), 521 AutofillMetrics::SECURITY_METRIC_CREDIT_CARD_OVER_HTTP); 522 } 523 524 if (!invoked_from_same_origin_) { 525 GetMetricLogger().LogDialogSecurityMetric( 526 GetDialogType(), 527 AutofillMetrics::SECURITY_METRIC_CROSS_ORIGIN_FRAME); 528 } 529 530 // Determine what field types should be included in the dialog. 531 bool has_types = false; 532 bool has_sections = false; 533 form_structure_.ParseFieldTypesFromAutocompleteAttributes( 534 FormStructure::PARSE_FOR_AUTOFILL_DIALOG, &has_types, &has_sections); 535 536 // Fail if the author didn't specify autocomplete types. 537 if (!has_types) { 538 callback_.Run(NULL, std::string()); 539 delete this; 540 return; 541 } 542 543 const DetailInput kEmailInputs[] = { 544 { 1, EMAIL_ADDRESS, IDS_AUTOFILL_DIALOG_PLACEHOLDER_EMAIL }, 545 }; 546 547 const DetailInput kCCInputs[] = { 548 { 2, CREDIT_CARD_NUMBER, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARD_NUMBER }, 549 { 3, CREDIT_CARD_EXP_MONTH }, 550 { 3, CREDIT_CARD_EXP_4_DIGIT_YEAR }, 551 { 3, CREDIT_CARD_VERIFICATION_CODE, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC, 552 1.5 }, 553 }; 554 555 const DetailInput kBillingInputs[] = { 556 { 4, NAME_FULL, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARDHOLDER_NAME }, 557 { 5, ADDRESS_BILLING_LINE1, 558 IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1 }, 559 { 6, ADDRESS_BILLING_LINE2, 560 IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2 }, 561 { 7, ADDRESS_BILLING_CITY, 562 IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY }, 563 // TODO(estade): state placeholder should depend on locale. 564 { 8, ADDRESS_BILLING_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE }, 565 { 8, ADDRESS_BILLING_ZIP, 566 IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE }, 567 // TODO(estade): this should have a default based on the locale. 568 { 9, ADDRESS_BILLING_COUNTRY, 0 }, 569 { 10, PHONE_BILLING_WHOLE_NUMBER, 570 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER }, 571 }; 572 573 const DetailInput kShippingInputs[] = { 574 { 11, NAME_FULL, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESSEE_NAME }, 575 { 12, ADDRESS_HOME_LINE1, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1 }, 576 { 13, ADDRESS_HOME_LINE2, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2 }, 577 { 14, ADDRESS_HOME_CITY, IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY }, 578 { 15, ADDRESS_HOME_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE }, 579 { 15, ADDRESS_HOME_ZIP, IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE }, 580 { 16, ADDRESS_HOME_COUNTRY, 0 }, 581 { 17, PHONE_HOME_WHOLE_NUMBER, 582 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER }, 583 }; 584 585 BuildInputs(kEmailInputs, 586 arraysize(kEmailInputs), 587 &requested_email_fields_); 588 589 BuildInputs(kCCInputs, 590 arraysize(kCCInputs), 591 &requested_cc_fields_); 592 593 BuildInputs(kBillingInputs, 594 arraysize(kBillingInputs), 595 &requested_billing_fields_); 596 597 BuildInputs(kCCInputs, 598 arraysize(kCCInputs), 599 &requested_cc_billing_fields_); 600 BuildInputs(kBillingInputs, 601 arraysize(kBillingInputs), 602 &requested_cc_billing_fields_); 603 604 BuildInputs(kShippingInputs, 605 arraysize(kShippingInputs), 606 &requested_shipping_fields_); 607 608 // Test whether we need to show the shipping section. If filling that section 609 // would be a no-op, don't show it. 610 const DetailInputs& inputs = RequestedFieldsForSection(SECTION_SHIPPING); 611 EmptyDataModelWrapper empty_wrapper; 612 cares_about_shipping_ = empty_wrapper.FillFormStructure( 613 inputs, 614 base::Bind(DetailInputMatchesField, SECTION_SHIPPING), 615 &form_structure_); 616 617 SuggestionsUpdated(); 618 619 int show_count = 620 profile_->GetPrefs()->GetInteger(::prefs::kAutofillDialogShowCount); 621 profile_->GetPrefs()->SetInteger(::prefs::kAutofillDialogShowCount, 622 show_count + 1); 623 624 // TODO(estade): don't show the dialog if the site didn't specify the right 625 // fields. First we must figure out what the "right" fields are. 626 view_.reset(CreateView()); 627 view_->Show(); 628 GetManager()->AddObserver(this); 629 630 // Try to see if the user is already signed-in. If signed-in, fetch the user's 631 // Wallet data. Otherwise, see if the user could be signed in passively. 632 // TODO(aruslan): UMA metrics for sign-in. 633 signin_helper_.reset(new wallet::WalletSigninHelper( 634 this, profile_->GetRequestContext())); 635 signin_helper_->StartWalletCookieValueFetch(); 636 637 if (!account_chooser_model_.WalletIsSelected()) 638 LogDialogLatencyToShow(); 639} 640 641void AutofillDialogControllerImpl::Hide() { 642 if (view_) 643 view_->Hide(); 644} 645 646void AutofillDialogControllerImpl::OnAutocheckoutError() { 647 DCHECK_EQ(AUTOCHECKOUT_IN_PROGRESS, autocheckout_state_); 648 GetMetricLogger().LogAutocheckoutDuration( 649 base::Time::Now() - autocheckout_started_timestamp_, 650 AutofillMetrics::AUTOCHECKOUT_FAILED); 651 SetAutocheckoutState(AUTOCHECKOUT_ERROR); 652 autocheckout_started_timestamp_ = base::Time(); 653} 654 655void AutofillDialogControllerImpl::OnAutocheckoutSuccess() { 656 DCHECK_EQ(AUTOCHECKOUT_IN_PROGRESS, autocheckout_state_); 657 GetMetricLogger().LogAutocheckoutDuration( 658 base::Time::Now() - autocheckout_started_timestamp_, 659 AutofillMetrics::AUTOCHECKOUT_SUCCEEDED); 660 SetAutocheckoutState(AUTOCHECKOUT_SUCCESS); 661 autocheckout_started_timestamp_ = base::Time(); 662} 663 664 665TestableAutofillDialogView* AutofillDialogControllerImpl::GetTestableView() { 666 return view_ ? view_->GetTestableView() : NULL; 667} 668 669void AutofillDialogControllerImpl::AddAutocheckoutStep( 670 AutocheckoutStepType step_type) { 671 for (size_t i = 0; i < steps_.size(); ++i) { 672 if (steps_[i].type() == step_type) 673 return; 674 } 675 steps_.push_back( 676 DialogAutocheckoutStep(step_type, AUTOCHECKOUT_STEP_UNSTARTED)); 677} 678 679void AutofillDialogControllerImpl::UpdateAutocheckoutStep( 680 AutocheckoutStepType step_type, 681 AutocheckoutStepStatus step_status) { 682 int total_steps = 0; 683 int completed_steps = 0; 684 for (size_t i = 0; i < steps_.size(); ++i) { 685 ++total_steps; 686 if (steps_[i].status() == AUTOCHECKOUT_STEP_COMPLETED) 687 ++completed_steps; 688 if (steps_[i].type() == step_type && steps_[i].status() != step_status) 689 steps_[i] = DialogAutocheckoutStep(step_type, step_status); 690 } 691 if (view_) { 692 view_->UpdateAutocheckoutStepsArea(); 693 view_->UpdateProgressBar(1.0 * completed_steps / total_steps); 694 } 695} 696 697std::vector<DialogAutocheckoutStep> 698 AutofillDialogControllerImpl::CurrentAutocheckoutSteps() const { 699 if (autocheckout_state_ != AUTOCHECKOUT_NOT_STARTED) 700 return steps_; 701 702 std::vector<DialogAutocheckoutStep> empty_steps; 703 return empty_steps; 704} 705 706//////////////////////////////////////////////////////////////////////////////// 707// AutofillDialogController implementation. 708 709string16 AutofillDialogControllerImpl::DialogTitle() const { 710 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_TITLE); 711} 712 713string16 AutofillDialogControllerImpl::EditSuggestionText() const { 714 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EDIT); 715} 716 717string16 AutofillDialogControllerImpl::CancelButtonText() const { 718 return l10n_util::GetStringUTF16(IDS_CANCEL); 719} 720 721string16 AutofillDialogControllerImpl::ConfirmButtonText() const { 722 if (autocheckout_state_ == AUTOCHECKOUT_ERROR) 723 return l10n_util::GetStringUTF16(IDS_OK); 724 if (autocheckout_state_ == AUTOCHECKOUT_SUCCESS) 725 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_CONTINUE_BUTTON); 726 727 return l10n_util::GetStringUTF16(IsSubmitPausedOn(wallet::VERIFY_CVV) ? 728 IDS_AUTOFILL_DIALOG_VERIFY_BUTTON : IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON); 729} 730 731string16 AutofillDialogControllerImpl::SaveLocallyText() const { 732 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX); 733} 734 735string16 AutofillDialogControllerImpl::LegalDocumentsText() { 736 if (!IsPayingWithWallet() || autocheckout_state_ != AUTOCHECKOUT_NOT_STARTED) 737 return string16(); 738 739 EnsureLegalDocumentsText(); 740 return legal_documents_text_; 741} 742 743DialogSignedInState AutofillDialogControllerImpl::SignedInState() const { 744 if (account_chooser_model_.HadWalletError()) 745 return SIGN_IN_DISABLED; 746 747 if (signin_helper_ || !wallet_items_) 748 return REQUIRES_RESPONSE; 749 750 if (wallet_items_->HasRequiredAction(wallet::GAIA_AUTH)) 751 return REQUIRES_SIGN_IN; 752 753 if (wallet_items_->HasRequiredAction(wallet::PASSIVE_GAIA_AUTH)) 754 return REQUIRES_PASSIVE_SIGN_IN; 755 756 return SIGNED_IN; 757} 758 759bool AutofillDialogControllerImpl::ShouldShowSpinner() const { 760 return account_chooser_model_.WalletIsSelected() && 761 SignedInState() == REQUIRES_RESPONSE; 762} 763 764string16 AutofillDialogControllerImpl::AccountChooserText() const { 765 // TODO(aruslan): this should be l10n "Not using Google Wallet". 766 if (!account_chooser_model_.WalletIsSelected()) 767 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PAY_WITHOUT_WALLET); 768 769 if (SignedInState() == SIGNED_IN) 770 return account_chooser_model_.active_wallet_account_name(); 771 772 // In this case, the account chooser should be showing the signin link. 773 return string16(); 774} 775 776string16 AutofillDialogControllerImpl::SignInLinkText() const { 777 return l10n_util::GetStringUTF16( 778 signin_registrar_.IsEmpty() ? IDS_AUTOFILL_DIALOG_SIGN_IN : 779 IDS_AUTOFILL_DIALOG_PAY_WITHOUT_WALLET); 780} 781 782bool AutofillDialogControllerImpl::ShouldOfferToSaveInChrome() const { 783 return !IsPayingWithWallet() && 784 !profile_->IsOffTheRecord() && 785 IsManuallyEditingAnySection() && 786 ShouldShowDetailArea(); 787} 788 789int AutofillDialogControllerImpl::GetDialogButtons() const { 790 if (autocheckout_state_ == AUTOCHECKOUT_IN_PROGRESS) 791 return ui::DIALOG_BUTTON_CANCEL; 792 if (autocheckout_state_ == AUTOCHECKOUT_NOT_STARTED) 793 return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL; 794 return ui::DIALOG_BUTTON_OK; 795} 796 797bool AutofillDialogControllerImpl::IsDialogButtonEnabled( 798 ui::DialogButton button) const { 799 if (button == ui::DIALOG_BUTTON_OK) { 800 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) 801 return true; 802 803 if (ShouldShowSpinner()) 804 return false; 805 806 if (is_submitting_) { 807 return autocheckout_state_ == AUTOCHECKOUT_SUCCESS || 808 autocheckout_state_ == AUTOCHECKOUT_ERROR; 809 } 810 811 return true; 812 } 813 814 DCHECK_EQ(ui::DIALOG_BUTTON_CANCEL, button); 815 return !is_submitting_ || IsSubmitPausedOn(wallet::VERIFY_CVV); 816} 817 818DialogOverlayState AutofillDialogControllerImpl::GetDialogOverlay() const { 819 bool show_wallet_interstitial = IsPayingWithWallet() && is_submitting_ && 820 !IsSubmitPausedOn(wallet::VERIFY_CVV) && 821 GetDialogType() == DIALOG_TYPE_REQUEST_AUTOCOMPLETE; 822 if (!show_wallet_interstitial) 823 return DialogOverlayState(); 824 825 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 826 DialogOverlayState state; 827 828 state.strings.push_back(DialogOverlayString()); 829 DialogOverlayString& string = state.strings.back(); 830#if !defined(OS_ANDROID) 831 // gfx::Font isn't implemented on Android; DeriveFont() causes a null deref. 832 string.font = rb.GetFont(ui::ResourceBundle::BaseFont).DeriveFont(4); 833#endif 834 835 // First-run, post-submit, Wallet expository page. 836 if (full_wallet_ && full_wallet_->required_actions().empty()) { 837 string16 cc_number = full_wallet_->GetInfo(CREDIT_CARD_NUMBER); 838 DCHECK_EQ(16U, cc_number.size()); 839 state.image = GetGeneratedCardImage( 840 ASCIIToUTF16("xxxx xxxx xxxx ") + 841 cc_number.substr(cc_number.size() - 4)); 842 string.text = l10n_util::GetStringUTF16( 843 IDS_AUTOFILL_DIALOG_CARD_GENERATION_DONE); 844 string.text_color = SK_ColorBLACK; 845 string.alignment = gfx::ALIGN_CENTER; 846 847 state.strings.push_back(DialogOverlayString()); 848 DialogOverlayString& subtext = state.strings.back(); 849 subtext.font = rb.GetFont(ui::ResourceBundle::BaseFont); 850 subtext.text_color = SkColorSetRGB(102, 102, 102); 851 subtext.text = l10n_util::GetStringUTF16( 852 IDS_AUTOFILL_DIALOG_CARD_GENERATION_EXPLANATION); 853 subtext.alignment = gfx::ALIGN_CENTER; 854 855 state.button_text = l10n_util::GetStringUTF16( 856 IDS_AUTOFILL_DIALOG_CARD_GENERATION_OK_BUTTON); 857 } else { 858 // TODO(estade): fix this (animation?). 859 state.image = 860 GetGeneratedCardImage(ASCIIToUTF16("xxxx xxxx xx...")); 861 862 // "Submitting" waiting page. 863 string.text = l10n_util::GetStringUTF16( 864 IDS_AUTOFILL_DIALOG_CARD_GENERATION_IN_PROGRESS); 865 string.text_color = SK_ColorBLACK; 866 string.alignment = gfx::ALIGN_CENTER; 867 } 868 869 return state; 870} 871 872const std::vector<ui::Range>& AutofillDialogControllerImpl:: 873 LegalDocumentLinks() { 874 EnsureLegalDocumentsText(); 875 return legal_document_link_ranges_; 876} 877 878bool AutofillDialogControllerImpl::SectionIsActive(DialogSection section) 879 const { 880 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) 881 return section == SECTION_CC_BILLING; 882 883 if (!FormStructureCaresAboutSection(section)) 884 return false; 885 886 if (IsPayingWithWallet()) 887 return section == SECTION_CC_BILLING || section == SECTION_SHIPPING; 888 889 return section != SECTION_CC_BILLING; 890} 891 892bool AutofillDialogControllerImpl::HasCompleteWallet() const { 893 return wallet_items_.get() != NULL && 894 !wallet_items_->instruments().empty() && 895 !wallet_items_->addresses().empty(); 896} 897 898bool AutofillDialogControllerImpl::IsSubmitPausedOn( 899 wallet::RequiredAction required_action) const { 900 return full_wallet_ && full_wallet_->HasRequiredAction(required_action); 901} 902 903void AutofillDialogControllerImpl::GetWalletItems() { 904 wallet_items_.reset(); 905 // The "Loading..." page should be showing now, which should cause the 906 // account chooser to hide. 907 view_->UpdateAccountChooser(); 908 GetWalletClient()->GetWalletItems(source_url_); 909} 910 911void AutofillDialogControllerImpl::HideSignIn() { 912 signin_registrar_.RemoveAll(); 913 view_->HideSignIn(); 914 view_->UpdateAccountChooser(); 915} 916 917void AutofillDialogControllerImpl::SignedInStateUpdated() { 918 switch (SignedInState()) { 919 case SIGNED_IN: 920 // Start fetching the user name if we don't know it yet. 921 if (account_chooser_model_.active_wallet_account_name().empty()) { 922 signin_helper_.reset(new wallet::WalletSigninHelper( 923 this, profile_->GetRequestContext())); 924 signin_helper_->StartUserNameFetch(); 925 } else { 926 LogDialogLatencyToShow(); 927 } 928 break; 929 930 case REQUIRES_SIGN_IN: 931 case SIGN_IN_DISABLED: 932 // Switch to the local account and refresh the dialog. 933 OnWalletSigninError(); 934 break; 935 936 case REQUIRES_PASSIVE_SIGN_IN: 937 // Attempt to passively sign in the user. 938 DCHECK(!signin_helper_); 939 account_chooser_model_.ClearActiveWalletAccountName(); 940 signin_helper_.reset(new wallet::WalletSigninHelper( 941 this, 942 profile_->GetRequestContext())); 943 signin_helper_->StartPassiveSignin(); 944 break; 945 946 case REQUIRES_RESPONSE: 947 break; 948 } 949} 950 951void AutofillDialogControllerImpl::OnWalletOrSigninUpdate() { 952 SignedInStateUpdated(); 953 SuggestionsUpdated(); 954 UpdateAccountChooserView(); 955 956 if (view_) 957 view_->UpdateButtonStrip(); 958 959 // On the first successful response, compute the initial user state metric. 960 if (initial_user_state_ == AutofillMetrics::DIALOG_USER_STATE_UNKNOWN) 961 initial_user_state_ = GetInitialUserState(); 962} 963 964void AutofillDialogControllerImpl::OnWalletFormFieldError( 965 const std::vector<wallet::FormFieldError>& form_field_errors) { 966 if (form_field_errors.empty()) 967 return; 968 969 for (std::vector<wallet::FormFieldError>::const_iterator it = 970 form_field_errors.begin(); 971 it != form_field_errors.end(); ++it) { 972 if (it->error_type() == wallet::FormFieldError::UNKNOWN_ERROR || 973 it->GetAutofillType() == MAX_VALID_FIELD_TYPE || 974 it->location() == wallet::FormFieldError::UNKNOWN_LOCATION) { 975 wallet_server_validation_recoverable_ = false; 976 break; 977 } 978 DialogSection section = SectionFromLocation(it->location()); 979 wallet_errors_[section][it->GetAutofillType()] = 980 std::make_pair(it->GetErrorMessage(), 981 GetValueFromSection(section, it->GetAutofillType())); 982 } 983 984 // Unrecoverable validation errors. 985 if (!wallet_server_validation_recoverable_) 986 DisableWallet(wallet::WalletClient::UNKNOWN_ERROR); 987 988 UpdateForErrors(); 989} 990 991void AutofillDialogControllerImpl::EnsureLegalDocumentsText() { 992 if (!wallet_items_ || wallet_items_->legal_documents().empty()) 993 return; 994 995 // The text has already been constructed, no need to recompute. 996 if (!legal_documents_text_.empty()) 997 return; 998 999 const std::vector<wallet::WalletItems::LegalDocument*>& documents = 1000 wallet_items_->legal_documents(); 1001 DCHECK_LE(documents.size(), 3U); 1002 DCHECK_GE(documents.size(), 2U); 1003 const bool new_user = wallet_items_->HasRequiredAction(wallet::SETUP_WALLET); 1004 1005 const string16 privacy_policy_display_name = 1006 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK); 1007 string16 text; 1008 if (documents.size() == 2U) { 1009 text = l10n_util::GetStringFUTF16( 1010 new_user ? IDS_AUTOFILL_DIALOG_LEGAL_LINKS_NEW_2 : 1011 IDS_AUTOFILL_DIALOG_LEGAL_LINKS_UPDATED_2, 1012 documents[0]->display_name(), 1013 documents[1]->display_name()); 1014 } else { 1015 text = l10n_util::GetStringFUTF16( 1016 new_user ? IDS_AUTOFILL_DIALOG_LEGAL_LINKS_NEW_3 : 1017 IDS_AUTOFILL_DIALOG_LEGAL_LINKS_UPDATED_3, 1018 documents[0]->display_name(), 1019 documents[1]->display_name(), 1020 documents[2]->display_name()); 1021 } 1022 1023 legal_document_link_ranges_.clear(); 1024 for (size_t i = 0; i < documents.size(); ++i) { 1025 size_t link_start = text.find(documents[i]->display_name()); 1026 legal_document_link_ranges_.push_back(ui::Range( 1027 link_start, link_start + documents[i]->display_name().size())); 1028 } 1029 legal_documents_text_ = text; 1030} 1031 1032void AutofillDialogControllerImpl::ResetSectionInput(DialogSection section) { 1033 SetEditingExistingData(section, false); 1034 1035 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 1036 for (DetailInputs::iterator it = inputs->begin(); it != inputs->end(); ++it) { 1037 it->initial_value.clear(); 1038 } 1039} 1040 1041void AutofillDialogControllerImpl::ShowEditUiIfBadSuggestion( 1042 DialogSection section) { 1043 // |CreateWrapper()| returns an empty wrapper if |IsEditingExistingData()|, so 1044 // get the wrapper before this potentially happens below. 1045 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 1046 1047 // If the chosen item in |model| yields an empty suggestion text, it is 1048 // invalid. In this case, show the edit UI and highlight invalid fields. 1049 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1050 if (IsASuggestionItemKey(model->GetItemKeyForCheckedItem()) && 1051 SuggestionTextForSection(section).empty()) { 1052 SetEditingExistingData(section, true); 1053 } 1054 1055 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 1056 if (wrapper && IsEditingExistingData(section)) 1057 wrapper->FillInputs(inputs); 1058 1059 for (DetailInputs::iterator it = inputs->begin(); it != inputs->end(); ++it) { 1060 it->editable = InputIsEditable(*it, section); 1061 } 1062} 1063 1064bool AutofillDialogControllerImpl::InputWasEdited(const DetailInput& input, 1065 const base::string16& value) { 1066 if (value.empty()) 1067 return false; 1068 1069 // If this is a combobox at the default value, don't preserve. 1070 ui::ComboboxModel* model = ComboboxModelForAutofillType(input.type); 1071 if (model && model->GetItemAt(model->GetDefaultIndex()) == value) 1072 return false; 1073 1074 return true; 1075} 1076 1077DetailOutputMap AutofillDialogControllerImpl::TakeUserInputSnapshot() { 1078 DetailOutputMap snapshot; 1079 if (!view_) 1080 return snapshot; 1081 1082 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 1083 DialogSection section = static_cast<DialogSection>(i); 1084 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1085 if (model->GetItemKeyForCheckedItem() != kAddNewItemKey) 1086 continue; 1087 1088 DetailOutputMap outputs; 1089 view_->GetUserInput(section, &outputs); 1090 // Remove fields that are empty, at their default values, or invalid. 1091 for (DetailOutputMap::iterator it = outputs.begin(); it != outputs.end(); 1092 ++it) { 1093 if (InputWasEdited(*it->first, it->second) && 1094 InputValidityMessage(section, it->first->type, it->second).empty()) { 1095 snapshot.insert(std::make_pair(it->first, it->second)); 1096 } 1097 } 1098 } 1099 1100 return snapshot; 1101} 1102 1103void AutofillDialogControllerImpl::RestoreUserInputFromSnapshot( 1104 const DetailOutputMap& snapshot) { 1105 if (snapshot.empty()) 1106 return; 1107 1108 DetailOutputWrapper wrapper(snapshot); 1109 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 1110 DialogSection section = static_cast<DialogSection>(i); 1111 if (!SectionIsActive(section)) 1112 continue; 1113 1114 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 1115 wrapper.FillInputs(inputs); 1116 1117 for (size_t i = 0; i < inputs->size(); ++i) { 1118 if (InputWasEdited((*inputs)[i], (*inputs)[i].initial_value)) { 1119 SuggestionsMenuModelForSection(section)->SetCheckedItem(kAddNewItemKey); 1120 break; 1121 } 1122 } 1123 } 1124} 1125 1126void AutofillDialogControllerImpl::UpdateSection(DialogSection section) { 1127 if (view_) 1128 view_->UpdateSection(section); 1129} 1130 1131void AutofillDialogControllerImpl::UpdateForErrors() { 1132 if (!view_) 1133 return; 1134 1135 // Currently, the view should only need to be updated if there are 1136 // |wallet_errors_| or validating a suggestion that's based on existing data. 1137 bool should_update = !wallet_errors_.empty(); 1138 if (!should_update) { 1139 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 1140 if (IsEditingExistingData(static_cast<DialogSection>(i))) { 1141 should_update = true; 1142 break; 1143 } 1144 } 1145 } 1146 1147 if (should_update) 1148 view_->UpdateForErrors(); 1149} 1150 1151const DetailInputs& AutofillDialogControllerImpl::RequestedFieldsForSection( 1152 DialogSection section) const { 1153 switch (section) { 1154 case SECTION_EMAIL: 1155 return requested_email_fields_; 1156 case SECTION_CC: 1157 return requested_cc_fields_; 1158 case SECTION_BILLING: 1159 return requested_billing_fields_; 1160 case SECTION_CC_BILLING: 1161 return requested_cc_billing_fields_; 1162 case SECTION_SHIPPING: 1163 return requested_shipping_fields_; 1164 } 1165 1166 NOTREACHED(); 1167 return requested_billing_fields_; 1168} 1169 1170ui::ComboboxModel* AutofillDialogControllerImpl::ComboboxModelForAutofillType( 1171 AutofillFieldType type) { 1172 switch (AutofillType::GetEquivalentFieldType(type)) { 1173 case CREDIT_CARD_EXP_MONTH: 1174 return &cc_exp_month_combobox_model_; 1175 1176 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 1177 return &cc_exp_year_combobox_model_; 1178 1179 case ADDRESS_HOME_COUNTRY: 1180 return &country_combobox_model_; 1181 1182 default: 1183 return NULL; 1184 } 1185} 1186 1187ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSection( 1188 DialogSection section) { 1189 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1190 // The shipping section menu is special. It will always show because there is 1191 // a choice between "Use billing" and "enter new". 1192 if (section == SECTION_SHIPPING) 1193 return model; 1194 1195 // For other sections, only show a menu if there's at least one suggestion. 1196 for (int i = 0; i < model->GetItemCount(); ++i) { 1197 if (IsASuggestionItemKey(model->GetItemKeyAt(i))) 1198 return model; 1199 } 1200 1201 return NULL; 1202} 1203 1204#if defined(OS_ANDROID) 1205ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSectionHack( 1206 DialogSection section) { 1207 return SuggestionsMenuModelForSection(section); 1208} 1209#endif 1210 1211ui::MenuModel* AutofillDialogControllerImpl::MenuModelForAccountChooser() { 1212 // If there were unrecoverable Wallet errors, or if there are choices other 1213 // than "Pay without the wallet", show the full menu. 1214 if (account_chooser_model_.HadWalletError() || 1215 account_chooser_model_.HasAccountsToChoose()) { 1216 return &account_chooser_model_; 1217 } 1218 1219 // Otherwise, there is no menu, just a sign in link. 1220 return NULL; 1221} 1222 1223gfx::Image AutofillDialogControllerImpl::AccountChooserImage() { 1224 if (!MenuModelForAccountChooser()) { 1225 if (signin_registrar_.IsEmpty()) { 1226 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 1227 IDR_WALLET_ICON); 1228 } 1229 1230 return gfx::Image(); 1231 } 1232 1233 gfx::Image icon; 1234 account_chooser_model_.GetIconAt( 1235 account_chooser_model_.GetIndexOfCommandId( 1236 account_chooser_model_.checked_item()), 1237 &icon); 1238 return icon; 1239} 1240 1241bool AutofillDialogControllerImpl::ShouldShowDetailArea() const { 1242 // Hide the detail area when Autocheckout is running or there was an error (as 1243 // there's nothing they can do after an error but cancel). 1244 return autocheckout_state_ == AUTOCHECKOUT_NOT_STARTED; 1245} 1246 1247bool AutofillDialogControllerImpl::ShouldShowProgressBar() const { 1248 // Show the progress bar while Autocheckout is running but hide it on errors, 1249 // as there's no use leaving it up if the flow has failed. 1250 return autocheckout_state_ == AUTOCHECKOUT_IN_PROGRESS; 1251} 1252 1253string16 AutofillDialogControllerImpl::LabelForSection(DialogSection section) 1254 const { 1255 switch (section) { 1256 case SECTION_EMAIL: 1257 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_EMAIL); 1258 case SECTION_CC: 1259 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC); 1260 case SECTION_BILLING: 1261 case SECTION_CC_BILLING: 1262 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_BILLING); 1263 case SECTION_SHIPPING: 1264 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_SHIPPING); 1265 default: 1266 NOTREACHED(); 1267 return string16(); 1268 } 1269} 1270 1271SuggestionState AutofillDialogControllerImpl::SuggestionStateForSection( 1272 DialogSection section) { 1273 return SuggestionState(SuggestionTextForSection(section), 1274 SuggestionTextStyleForSection(section), 1275 SuggestionIconForSection(section), 1276 ExtraSuggestionTextForSection(section), 1277 ExtraSuggestionIconForSection(section)); 1278} 1279 1280string16 AutofillDialogControllerImpl::SuggestionTextForSection( 1281 DialogSection section) { 1282 string16 action_text = RequiredActionTextForSection(section); 1283 if (!action_text.empty()) 1284 return action_text; 1285 1286 // When the user has clicked 'edit' or a suggestion is somehow invalid (e.g. a 1287 // user selects a credit card that has expired), don't show a suggestion (even 1288 // though there is a profile selected in the model). 1289 if (IsEditingExistingData(section)) 1290 return string16(); 1291 1292 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1293 std::string item_key = model->GetItemKeyForCheckedItem(); 1294 if (item_key == kSameAsBillingKey) { 1295 return l10n_util::GetStringUTF16( 1296 IDS_AUTOFILL_DIALOG_USING_BILLING_FOR_SHIPPING); 1297 } 1298 1299 if (!IsASuggestionItemKey(item_key)) 1300 return string16(); 1301 1302 if (section == SECTION_EMAIL) 1303 return model->GetLabelAt(model->checked_item()); 1304 1305 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 1306 return wrapper->GetDisplayText(); 1307} 1308 1309gfx::Font::FontStyle 1310 AutofillDialogControllerImpl::SuggestionTextStyleForSection( 1311 DialogSection section) const { 1312 const SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1313 if (model->GetItemKeyForCheckedItem() == kSameAsBillingKey) 1314 return gfx::Font::ITALIC; 1315 1316 return gfx::Font::NORMAL; 1317} 1318 1319string16 AutofillDialogControllerImpl::RequiredActionTextForSection( 1320 DialogSection section) const { 1321 if (section == SECTION_CC_BILLING && IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1322 const wallet::WalletItems::MaskedInstrument* current_instrument = 1323 wallet_items_->GetInstrumentById(active_instrument_id_); 1324 if (current_instrument) 1325 return current_instrument->TypeAndLastFourDigits(); 1326 1327 DetailOutputMap output; 1328 view_->GetUserInput(section, &output); 1329 CreditCard card; 1330 GetBillingInfoFromOutputs(output, &card, NULL, NULL); 1331 return card.TypeAndLastFourDigits(); 1332 } 1333 1334 return string16(); 1335} 1336 1337string16 AutofillDialogControllerImpl::ExtraSuggestionTextForSection( 1338 DialogSection section) const { 1339 if (section == SECTION_CC || 1340 (section == SECTION_CC_BILLING && IsSubmitPausedOn(wallet::VERIFY_CVV))) { 1341 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC); 1342 } 1343 1344 return string16(); 1345} 1346 1347const wallet::WalletItems::MaskedInstrument* AutofillDialogControllerImpl:: 1348 ActiveInstrument() const { 1349 if (!IsPayingWithWallet()) 1350 return NULL; 1351 1352 const SuggestionsMenuModel* model = 1353 SuggestionsMenuModelForSection(SECTION_CC_BILLING); 1354 const std::string item_key = model->GetItemKeyForCheckedItem(); 1355 if (!IsASuggestionItemKey(item_key)) 1356 return NULL; 1357 1358 int index; 1359 if (!base::StringToInt(item_key, &index) || index < 0 || 1360 static_cast<size_t>(index) >= wallet_items_->instruments().size()) { 1361 NOTREACHED(); 1362 return NULL; 1363 } 1364 1365 return wallet_items_->instruments()[index]; 1366} 1367 1368const wallet::Address* AutofillDialogControllerImpl:: 1369 ActiveShippingAddress() const { 1370 if (!IsPayingWithWallet()) 1371 return NULL; 1372 1373 const SuggestionsMenuModel* model = 1374 SuggestionsMenuModelForSection(SECTION_SHIPPING); 1375 const std::string item_key = model->GetItemKeyForCheckedItem(); 1376 if (!IsASuggestionItemKey(item_key)) 1377 return NULL; 1378 1379 int index; 1380 if (!base::StringToInt(item_key, &index) || index < 0 || 1381 static_cast<size_t>(index) >= wallet_items_->addresses().size()) { 1382 NOTREACHED(); 1383 return NULL; 1384 } 1385 1386 return wallet_items_->addresses()[index]; 1387} 1388 1389scoped_ptr<DataModelWrapper> AutofillDialogControllerImpl::CreateWrapper( 1390 DialogSection section) { 1391 if (IsPayingWithWallet() && full_wallet_ && 1392 full_wallet_->required_actions().empty()) { 1393 if (section == SECTION_CC_BILLING) { 1394 return scoped_ptr<DataModelWrapper>( 1395 new FullWalletBillingWrapper(full_wallet_.get())); 1396 } 1397 if (section == SECTION_SHIPPING) { 1398 return scoped_ptr<DataModelWrapper>( 1399 new FullWalletShippingWrapper(full_wallet_.get())); 1400 } 1401 } 1402 1403 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 1404 std::string item_key = model->GetItemKeyForCheckedItem(); 1405 if (!IsASuggestionItemKey(item_key) || IsManuallyEditingSection(section)) 1406 return scoped_ptr<DataModelWrapper>(); 1407 1408 if (IsPayingWithWallet()) { 1409 if (section == SECTION_CC_BILLING) { 1410 return scoped_ptr<DataModelWrapper>( 1411 new WalletInstrumentWrapper(ActiveInstrument())); 1412 } 1413 1414 if (section == SECTION_SHIPPING) { 1415 return scoped_ptr<DataModelWrapper>( 1416 new WalletAddressWrapper(ActiveShippingAddress())); 1417 } 1418 1419 return scoped_ptr<DataModelWrapper>(); 1420 } 1421 1422 if (section == SECTION_CC) { 1423 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); 1424 DCHECK(card); 1425 return scoped_ptr<DataModelWrapper>(new AutofillCreditCardWrapper(card)); 1426 } 1427 1428 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); 1429 DCHECK(profile); 1430 size_t variant = GetSelectedVariantForModel(*model); 1431 return scoped_ptr<DataModelWrapper>( 1432 new AutofillProfileWrapper(profile, variant)); 1433} 1434 1435gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( 1436 DialogSection section) { 1437 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 1438 if (!model.get()) 1439 return gfx::Image(); 1440 1441 return model->GetIcon(); 1442} 1443 1444gfx::Image AutofillDialogControllerImpl::ExtraSuggestionIconForSection( 1445 DialogSection section) const { 1446 if (section == SECTION_CC || section == SECTION_CC_BILLING) 1447 return IconForField(CREDIT_CARD_VERIFICATION_CODE, string16()); 1448 1449 return gfx::Image(); 1450} 1451 1452void AutofillDialogControllerImpl::EditClickedForSection( 1453 DialogSection section) { 1454 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 1455 SetEditingExistingData(section, true); 1456 1457 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 1458 for (DetailInputs::iterator it = inputs->begin(); it != inputs->end(); ++it) { 1459 it->editable = InputIsEditable(*it, section); 1460 } 1461 model->FillInputs(inputs); 1462 1463 UpdateSection(section); 1464 1465 GetMetricLogger().LogDialogUiEvent( 1466 GetDialogType(), DialogSectionToUiEditEvent(section)); 1467} 1468 1469void AutofillDialogControllerImpl::EditCancelledForSection( 1470 DialogSection section) { 1471 ResetSectionInput(section); 1472 UpdateSection(section); 1473} 1474 1475gfx::Image AutofillDialogControllerImpl::IconForField( 1476 AutofillFieldType type, const string16& user_input) const { 1477 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1478 if (type == CREDIT_CARD_VERIFICATION_CODE) 1479 return rb.GetImageNamed(IDR_CREDIT_CARD_CVC_HINT); 1480 1481 // For the credit card, we show a few grayscale images, and possibly one 1482 // color image if |user_input| is a valid card number. 1483 if (type == CREDIT_CARD_NUMBER) { 1484 const int card_idrs[] = { 1485 IDR_AUTOFILL_CC_VISA, 1486 IDR_AUTOFILL_CC_MASTERCARD, 1487 IDR_AUTOFILL_CC_AMEX, 1488 IDR_AUTOFILL_CC_DISCOVER 1489 }; 1490 const int number_of_cards = arraysize(card_idrs); 1491 // The number of pixels between card icons. 1492 const int kCardPadding = 2; 1493 1494 gfx::ImageSkia some_card = *rb.GetImageSkiaNamed(card_idrs[0]); 1495 const int card_width = some_card.width(); 1496 gfx::Canvas canvas( 1497 gfx::Size((card_width + kCardPadding) * number_of_cards - kCardPadding, 1498 some_card.height()), 1499 ui::SCALE_FACTOR_100P, 1500 false); 1501 1502 const int input_card_idr = CreditCard::IconResourceId( 1503 CreditCard::GetCreditCardType(user_input)); 1504 for (int i = 0; i < number_of_cards; ++i) { 1505 int idr = card_idrs[i]; 1506 gfx::ImageSkia card_image = *rb.GetImageSkiaNamed(idr); 1507 if (input_card_idr != idr) { 1508 SkBitmap disabled_bitmap = 1509 SkBitmapOperations::CreateHSLShiftedBitmap(*card_image.bitmap(), 1510 kGrayImageShift); 1511 card_image = gfx::ImageSkia::CreateFrom1xBitmap(disabled_bitmap); 1512 } 1513 1514 canvas.DrawImageInt(card_image, i * (card_width + kCardPadding), 0); 1515 } 1516 1517 gfx::ImageSkia skia(canvas.ExtractImageRep()); 1518 return gfx::Image(skia); 1519 } 1520 1521 return gfx::Image(); 1522} 1523 1524// TODO(estade): Replace all the error messages here with more helpful and 1525// translateable ones. TODO(groby): Also add tests. 1526string16 AutofillDialogControllerImpl::InputValidityMessage( 1527 DialogSection section, 1528 AutofillFieldType type, 1529 const string16& value) { 1530 // If the field is edited, clear any Wallet errors. 1531 if (IsPayingWithWallet()) { 1532 WalletValidationErrors::iterator it = wallet_errors_.find(section); 1533 if (it != wallet_errors_.end()) { 1534 TypeErrorInputMap::const_iterator iter = it->second.find(type); 1535 if (iter != it->second.end()) { 1536 if (iter->second.second == value) 1537 return iter->second.first; 1538 it->second.erase(type); 1539 } 1540 } 1541 } 1542 1543 switch (AutofillType::GetEquivalentFieldType(type)) { 1544 case EMAIL_ADDRESS: 1545 if (!value.empty() && !IsValidEmailAddress(value)) { 1546 return l10n_util::GetStringUTF16( 1547 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_EMAIL_ADDRESS); 1548 } 1549 break; 1550 1551 case CREDIT_CARD_NUMBER: { 1552 if (!value.empty()) { 1553 base::string16 message = CreditCardNumberValidityMessage(value); 1554 if (!message.empty()) 1555 return message; 1556 } 1557 break; 1558 } 1559 1560 case CREDIT_CARD_EXP_MONTH: 1561 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 1562 break; 1563 1564 case CREDIT_CARD_VERIFICATION_CODE: 1565 if (!value.empty() && !autofill::IsValidCreditCardSecurityCode(value)) { 1566 return l10n_util::GetStringUTF16( 1567 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_SECURITY_CODE); 1568 } 1569 break; 1570 1571 case ADDRESS_HOME_LINE1: 1572 break; 1573 1574 case ADDRESS_HOME_LINE2: 1575 return base::string16(); // Line 2 is optional - always valid. 1576 1577 case ADDRESS_HOME_CITY: 1578 case ADDRESS_HOME_COUNTRY: 1579 break; 1580 1581 case ADDRESS_HOME_STATE: 1582 if (!value.empty() && !autofill::IsValidState(value)) { 1583 return l10n_util::GetStringUTF16( 1584 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_REGION); 1585 } 1586 break; 1587 1588 case ADDRESS_HOME_ZIP: 1589 if (!value.empty() && !autofill::IsValidZip(value)) { 1590 return l10n_util::GetStringUTF16( 1591 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_ZIP_CODE); 1592 } 1593 break; 1594 1595 case NAME_FULL: 1596 // Wallet requires a first and last billing name. 1597 if (section == SECTION_CC_BILLING && !value.empty() && 1598 !IsCardHolderNameValidForWallet(value)) { 1599 DCHECK(IsPayingWithWallet()); 1600 return l10n_util::GetStringUTF16( 1601 IDS_AUTOFILL_DIALOG_VALIDATION_WALLET_REQUIRES_TWO_NAMES); 1602 } 1603 break; 1604 1605 case PHONE_HOME_WHOLE_NUMBER: // Used in shipping section. 1606 break; 1607 1608 case PHONE_BILLING_WHOLE_NUMBER: // Used in billing section. 1609 break; 1610 1611 default: 1612 NOTREACHED(); // Trying to validate unknown field. 1613 break; 1614 } 1615 1616 return value.empty() ? 1617 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_VALIDATION_MISSING_VALUE) : 1618 base::string16(); 1619} 1620 1621// TODO(estade): Replace all the error messages here with more helpful and 1622// translateable ones. TODO(groby): Also add tests. 1623ValidityData AutofillDialogControllerImpl::InputsAreValid( 1624 DialogSection section, 1625 const DetailOutputMap& inputs, 1626 ValidationType validation_type) { 1627 ValidityData invalid_messages; 1628 std::map<AutofillFieldType, string16> field_values; 1629 for (DetailOutputMap::const_iterator iter = inputs.begin(); 1630 iter != inputs.end(); ++iter) { 1631 // Skip empty fields in edit mode. 1632 if (validation_type == VALIDATE_EDIT && iter->second.empty()) 1633 continue; 1634 1635 const AutofillFieldType type = iter->first->type; 1636 string16 message = InputValidityMessage(section, type, iter->second); 1637 if (!message.empty()) 1638 invalid_messages[type] = message; 1639 else 1640 field_values[type] = iter->second; 1641 } 1642 1643 // Validate the date formed by month and year field. (Autofill dialog is 1644 // never supposed to have 2-digit years, so not checked). 1645 if (field_values.count(CREDIT_CARD_EXP_4_DIGIT_YEAR) && 1646 field_values.count(CREDIT_CARD_EXP_MONTH) && 1647 !IsCreditCardExpirationValid(field_values[CREDIT_CARD_EXP_4_DIGIT_YEAR], 1648 field_values[CREDIT_CARD_EXP_MONTH])) { 1649 // The dialog shows the same error message for the month and year fields. 1650 invalid_messages[CREDIT_CARD_EXP_4_DIGIT_YEAR] = l10n_util::GetStringUTF16( 1651 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_EXPIRATION_DATE); 1652 invalid_messages[CREDIT_CARD_EXP_MONTH] = l10n_util::GetStringUTF16( 1653 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_EXPIRATION_DATE); 1654 } 1655 1656 // If there is a credit card number and a CVC, validate them together. 1657 if (field_values.count(CREDIT_CARD_NUMBER) && 1658 field_values.count(CREDIT_CARD_VERIFICATION_CODE) && 1659 !invalid_messages.count(CREDIT_CARD_NUMBER) && 1660 !autofill::IsValidCreditCardSecurityCode( 1661 field_values[CREDIT_CARD_VERIFICATION_CODE], 1662 field_values[CREDIT_CARD_NUMBER])) { 1663 invalid_messages[CREDIT_CARD_VERIFICATION_CODE] = 1664 l10n_util::GetStringUTF16( 1665 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_SECURITY_CODE); 1666 } 1667 1668 // Validate the shipping phone number against the country code of the address. 1669 if (field_values.count(ADDRESS_HOME_COUNTRY) && 1670 field_values.count(PHONE_HOME_WHOLE_NUMBER)) { 1671 i18n::PhoneObject phone_object( 1672 field_values[PHONE_HOME_WHOLE_NUMBER], 1673 AutofillCountry::GetCountryCode( 1674 field_values[ADDRESS_HOME_COUNTRY], 1675 g_browser_process->GetApplicationLocale())); 1676 if (!phone_object.IsValidNumber()) { 1677 invalid_messages[PHONE_HOME_WHOLE_NUMBER] = l10n_util::GetStringUTF16( 1678 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_PHONE_NUMBER); 1679 } 1680 } 1681 1682 // Validate the billing phone number against the country code of the address. 1683 if (field_values.count(ADDRESS_BILLING_COUNTRY) && 1684 field_values.count(PHONE_BILLING_WHOLE_NUMBER)) { 1685 i18n::PhoneObject phone_object( 1686 field_values[PHONE_BILLING_WHOLE_NUMBER], 1687 AutofillCountry::GetCountryCode( 1688 field_values[ADDRESS_BILLING_COUNTRY], 1689 g_browser_process->GetApplicationLocale())); 1690 if (!phone_object.IsValidNumber()) { 1691 invalid_messages[PHONE_BILLING_WHOLE_NUMBER] = l10n_util::GetStringUTF16( 1692 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_PHONE_NUMBER); 1693 } 1694 } 1695 1696 return invalid_messages; 1697} 1698 1699void AutofillDialogControllerImpl::UserEditedOrActivatedInput( 1700 DialogSection section, 1701 const DetailInput* input, 1702 gfx::NativeView parent_view, 1703 const gfx::Rect& content_bounds, 1704 const string16& field_contents, 1705 bool was_edit) { 1706 // If the field is edited down to empty, don't show a popup. 1707 if (was_edit && field_contents.empty()) { 1708 HidePopup(); 1709 return; 1710 } 1711 1712 // If the user clicks while the popup is already showing, be sure to hide 1713 // it. 1714 if (!was_edit && popup_controller_.get()) { 1715 HidePopup(); 1716 return; 1717 } 1718 1719 std::vector<string16> popup_values, popup_labels, popup_icons; 1720 if (IsCreditCardType(input->type)) { 1721 GetManager()->GetCreditCardSuggestions(input->type, 1722 field_contents, 1723 &popup_values, 1724 &popup_labels, 1725 &popup_icons, 1726 &popup_guids_); 1727 } else { 1728 std::vector<AutofillFieldType> field_types; 1729 field_types.push_back(EMAIL_ADDRESS); 1730 for (DetailInputs::const_iterator iter = requested_shipping_fields_.begin(); 1731 iter != requested_shipping_fields_.end(); ++iter) { 1732 field_types.push_back(iter->type); 1733 } 1734 GetManager()->GetProfileSuggestions(input->type, 1735 field_contents, 1736 false, 1737 field_types, 1738 &popup_values, 1739 &popup_labels, 1740 &popup_icons, 1741 &popup_guids_); 1742 } 1743 1744 if (popup_values.empty()) { 1745 HidePopup(); 1746 return; 1747 } 1748 1749 // TODO(estade): do we need separators and control rows like 'Clear 1750 // Form'? 1751 std::vector<int> popup_ids; 1752 for (size_t i = 0; i < popup_guids_.size(); ++i) { 1753 popup_ids.push_back(i); 1754 } 1755 1756 popup_controller_ = AutofillPopupControllerImpl::GetOrCreate( 1757 popup_controller_, 1758 weak_ptr_factory_.GetWeakPtr(), 1759 parent_view, 1760 content_bounds, 1761 base::i18n::IsRTL() ? 1762 base::i18n::RIGHT_TO_LEFT : base::i18n::LEFT_TO_RIGHT); 1763 popup_controller_->Show(popup_values, 1764 popup_labels, 1765 popup_icons, 1766 popup_ids); 1767 input_showing_popup_ = input; 1768} 1769 1770void AutofillDialogControllerImpl::FocusMoved() { 1771 HidePopup(); 1772} 1773 1774gfx::Image AutofillDialogControllerImpl::SplashPageImage() const { 1775 // Only show the splash page the first few times the dialog is opened. 1776 int show_count = 1777 profile_->GetPrefs()->GetInteger(::prefs::kAutofillDialogShowCount); 1778 if (show_count <= 4) { 1779 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 1780 IDR_PRODUCT_LOGO_NAME_48); 1781 } 1782 1783 return gfx::Image(); 1784} 1785 1786void AutofillDialogControllerImpl::ViewClosed() { 1787 GetManager()->RemoveObserver(this); 1788 1789 // TODO(ahutter): Once a user can cancel Autocheckout mid-flow, log that 1790 // metric here. 1791 1792 // Called from here rather than in ~AutofillDialogControllerImpl as this 1793 // relies on virtual methods that change to their base class in the dtor. 1794 MaybeShowCreditCardBubble(); 1795 1796 delete this; 1797} 1798 1799std::vector<DialogNotification> AutofillDialogControllerImpl:: 1800 CurrentNotifications() { 1801 std::vector<DialogNotification> notifications; 1802 1803 if (IsPayingWithWallet() && !wallet::IsUsingProd()) { 1804 notifications.push_back(DialogNotification( 1805 DialogNotification::DEVELOPER_WARNING, 1806 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_NOT_PROD_WARNING))); 1807 } 1808 1809 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 1810 notifications.push_back(DialogNotification( 1811 DialogNotification::SECURITY_WARNING, 1812 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECURITY_WARNING))); 1813 } 1814 1815 if (!invoked_from_same_origin_) { 1816 notifications.push_back(DialogNotification( 1817 DialogNotification::SECURITY_WARNING, 1818 l10n_util::GetStringFUTF16(IDS_AUTOFILL_DIALOG_SITE_WARNING, 1819 UTF8ToUTF16(source_url_.host())))); 1820 } 1821 1822 if (account_chooser_model_.HadWalletError()) { 1823 // TODO(dbeam): figure out a way to dismiss this error after a while. 1824 notifications.push_back(DialogNotification( 1825 DialogNotification::WALLET_ERROR, 1826 l10n_util::GetStringFUTF16( 1827 IDS_AUTOFILL_DIALOG_COMPLETE_WITHOUT_WALLET, 1828 account_chooser_model_.wallet_error_message()))); 1829 } 1830 1831 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1832 notifications.push_back(DialogNotification( 1833 DialogNotification::REQUIRED_ACTION, 1834 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_VERIFY_CVV))); 1835 } 1836 1837 if (autocheckout_state_ == AUTOCHECKOUT_ERROR) { 1838 notifications.push_back(DialogNotification( 1839 DialogNotification::AUTOCHECKOUT_ERROR, 1840 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_ERROR))); 1841 } 1842 1843 if (autocheckout_state_ == AUTOCHECKOUT_SUCCESS) { 1844 notifications.push_back(DialogNotification( 1845 DialogNotification::AUTOCHECKOUT_SUCCESS, 1846 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_SUCCESS))); 1847 } 1848 1849 if (!wallet_server_validation_recoverable_) { 1850 notifications.push_back(DialogNotification( 1851 DialogNotification::REQUIRED_ACTION, 1852 l10n_util::GetStringUTF16( 1853 IDS_AUTOFILL_DIALOG_FAILED_TO_SAVE_WALLET_DATA))); 1854 } 1855 1856 if (choose_another_instrument_or_address_) { 1857 notifications.push_back(DialogNotification( 1858 DialogNotification::REQUIRED_ACTION, 1859 l10n_util::GetStringUTF16( 1860 IDS_AUTOFILL_DIALOG_CHOOSE_DIFFERENT_WALLET_INSTRUMENT))); 1861 } 1862 1863 if (should_show_wallet_promo_ && ShouldShowDetailArea() && 1864 notifications.empty()) { 1865 if (IsPayingWithWallet() && HasCompleteWallet()) { 1866 notifications.push_back(DialogNotification( 1867 DialogNotification::EXPLANATORY_MESSAGE, 1868 l10n_util::GetStringUTF16( 1869 IDS_AUTOFILL_DIALOG_DETAILS_FROM_WALLET))); 1870 } else if ((IsPayingWithWallet() && !HasCompleteWallet()) || 1871 has_shown_wallet_usage_confirmation_) { 1872 DialogNotification notification( 1873 DialogNotification::WALLET_USAGE_CONFIRMATION, 1874 l10n_util::GetStringUTF16( 1875 IDS_AUTOFILL_DIALOG_SAVE_DETAILS_IN_WALLET)); 1876 notification.set_checked(account_chooser_model_.WalletIsSelected()); 1877 notification.set_interactive(!is_submitting_); 1878 notifications.push_back(notification); 1879 has_shown_wallet_usage_confirmation_ = true; 1880 } 1881 } 1882 1883 return notifications; 1884} 1885 1886void AutofillDialogControllerImpl::SignInLinkClicked() { 1887 if (signin_registrar_.IsEmpty()) { 1888 // Start sign in. 1889 DCHECK(!IsPayingWithWallet()); 1890 1891 content::Source<content::NavigationController> source(view_->ShowSignIn()); 1892 signin_registrar_.Add( 1893 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, source); 1894 view_->UpdateAccountChooser(); 1895 1896 GetMetricLogger().LogDialogUiEvent( 1897 GetDialogType(), AutofillMetrics::DIALOG_UI_SIGNIN_SHOWN); 1898 } else { 1899 HideSignIn(); 1900 } 1901} 1902 1903void AutofillDialogControllerImpl::NotificationCheckboxStateChanged( 1904 DialogNotification::Type type, bool checked) { 1905 if (type == DialogNotification::WALLET_USAGE_CONFIRMATION) { 1906 if (checked) 1907 account_chooser_model_.SelectActiveWalletAccount(); 1908 else 1909 account_chooser_model_.SelectUseAutofill(); 1910 } 1911} 1912 1913void AutofillDialogControllerImpl::LegalDocumentLinkClicked( 1914 const ui::Range& range) { 1915 for (size_t i = 0; i < legal_document_link_ranges_.size(); ++i) { 1916 if (legal_document_link_ranges_[i] == range) { 1917 OpenTabWithUrl(wallet_items_->legal_documents()[i]->url()); 1918 return; 1919 } 1920 } 1921 1922 NOTREACHED(); 1923} 1924 1925void AutofillDialogControllerImpl::OverlayButtonPressed() { 1926 DCHECK(is_submitting_); 1927 DCHECK(full_wallet_); 1928 profile_->GetPrefs()->SetBoolean(::prefs::kAutofillDialogHasPaidWithWallet, 1929 true); 1930 FinishSubmit(); 1931} 1932 1933bool AutofillDialogControllerImpl::OnCancel() { 1934 HidePopup(); 1935 if (autocheckout_state_ == AUTOCHECKOUT_NOT_STARTED && !is_submitting_) 1936 LogOnCancelMetrics(); 1937 if (autocheckout_state_ == AUTOCHECKOUT_IN_PROGRESS) { 1938 GetMetricLogger().LogAutocheckoutDuration( 1939 base::Time::Now() - autocheckout_started_timestamp_, 1940 AutofillMetrics::AUTOCHECKOUT_CANCELLED); 1941 } 1942 callback_.Run(NULL, std::string()); 1943 return true; 1944} 1945 1946bool AutofillDialogControllerImpl::OnAccept() { 1947 // If autocheckout has already started, the only thing left to do is to 1948 // close the dialog. 1949 if (autocheckout_state_ != AUTOCHECKOUT_NOT_STARTED) 1950 return true; 1951 1952 choose_another_instrument_or_address_ = false; 1953 wallet_server_validation_recoverable_ = true; 1954 HidePopup(); 1955 if (IsPayingWithWallet()) { 1956 bool has_proxy_card_step = false; 1957 for (size_t i = 0; i < steps_.size(); ++i) { 1958 if (steps_[i].type() == AUTOCHECKOUT_STEP_PROXY_CARD) { 1959 has_proxy_card_step = true; 1960 break; 1961 } 1962 } 1963 if (!has_proxy_card_step) { 1964 steps_.insert(steps_.begin(), 1965 DialogAutocheckoutStep(AUTOCHECKOUT_STEP_PROXY_CARD, 1966 AUTOCHECKOUT_STEP_UNSTARTED)); 1967 } 1968 } 1969 1970 if (GetDialogType() == DIALOG_TYPE_AUTOCHECKOUT) 1971 DeemphasizeRenderView(); 1972 1973 SetIsSubmitting(true); 1974 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1975 DCHECK(!active_instrument_id_.empty()); 1976 GetWalletClient()->AuthenticateInstrument( 1977 active_instrument_id_, 1978 UTF16ToUTF8(view_->GetCvc())); 1979 } else if (IsPayingWithWallet()) { 1980 // TODO(dbeam): disallow interacting with the dialog while submitting. 1981 // http://crbug.com/230932 1982 AcceptLegalDocuments(); 1983 } else { 1984 FinishSubmit(); 1985 } 1986 1987 return false; 1988} 1989 1990Profile* AutofillDialogControllerImpl::profile() { 1991 return profile_; 1992} 1993 1994content::WebContents* AutofillDialogControllerImpl::web_contents() { 1995 return contents_; 1996} 1997 1998//////////////////////////////////////////////////////////////////////////////// 1999// AutofillPopupDelegate implementation. 2000 2001void AutofillDialogControllerImpl::OnPopupShown( 2002 content::KeyboardListener* listener) { 2003 GetMetricLogger().LogDialogPopupEvent( 2004 GetDialogType(), AutofillMetrics::DIALOG_POPUP_SHOWN); 2005} 2006 2007void AutofillDialogControllerImpl::OnPopupHidden( 2008 content::KeyboardListener* listener) {} 2009 2010void AutofillDialogControllerImpl::DidSelectSuggestion(int identifier) { 2011 // TODO(estade): implement. 2012} 2013 2014void AutofillDialogControllerImpl::DidAcceptSuggestion(const string16& value, 2015 int identifier) { 2016 const PersonalDataManager::GUIDPair& pair = popup_guids_[identifier]; 2017 2018 scoped_ptr<DataModelWrapper> wrapper; 2019 if (IsCreditCardType(input_showing_popup_->type)) { 2020 wrapper.reset(new AutofillCreditCardWrapper( 2021 GetManager()->GetCreditCardByGUID(pair.first))); 2022 } else { 2023 wrapper.reset(new AutofillProfileWrapper( 2024 GetManager()->GetProfileByGUID(pair.first), pair.second)); 2025 } 2026 2027 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 2028 DialogSection section = static_cast<DialogSection>(i); 2029 wrapper->FillInputs(MutableRequestedFieldsForSection(section)); 2030 view_->FillSection(section, *input_showing_popup_); 2031 } 2032 2033 GetMetricLogger().LogDialogPopupEvent( 2034 GetDialogType(), AutofillMetrics::DIALOG_POPUP_FORM_FILLED); 2035 2036 // TODO(estade): not sure why it's necessary to do this explicitly. 2037 HidePopup(); 2038} 2039 2040void AutofillDialogControllerImpl::RemoveSuggestion(const string16& value, 2041 int identifier) { 2042 // TODO(estade): implement. 2043} 2044 2045void AutofillDialogControllerImpl::ClearPreviewedForm() { 2046 // TODO(estade): implement. 2047} 2048 2049//////////////////////////////////////////////////////////////////////////////// 2050// content::NotificationObserver implementation. 2051 2052void AutofillDialogControllerImpl::Observe( 2053 int type, 2054 const content::NotificationSource& source, 2055 const content::NotificationDetails& details) { 2056 DCHECK_EQ(type, content::NOTIFICATION_NAV_ENTRY_COMMITTED); 2057 content::LoadCommittedDetails* load_details = 2058 content::Details<content::LoadCommittedDetails>(details).ptr(); 2059 if (wallet::IsSignInContinueUrl(load_details->entry->GetVirtualURL())) { 2060 should_show_wallet_promo_ = false; 2061 account_chooser_model_.SelectActiveWalletAccount(); 2062 signin_helper_.reset(new wallet::WalletSigninHelper( 2063 this, profile_->GetRequestContext())); 2064 signin_helper_->StartWalletCookieValueFetch(); 2065 HideSignIn(); 2066 } 2067} 2068 2069//////////////////////////////////////////////////////////////////////////////// 2070// content::WebContentsObserver implementation. 2071 2072void AutofillDialogControllerImpl::DidNavigateMainFrame( 2073 const content::LoadCommittedDetails& details, 2074 const content::FrameNavigateParams& params) { 2075 // Close view if necessary. 2076 if (!net::registry_controlled_domains::SameDomainOrHost( 2077 details.previous_url, details.entry->GetURL(), 2078 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) { 2079 Hide(); 2080 } 2081} 2082 2083//////////////////////////////////////////////////////////////////////////////// 2084// SuggestionsMenuModelDelegate implementation. 2085 2086void AutofillDialogControllerImpl::SuggestionItemSelected( 2087 SuggestionsMenuModel* model, 2088 size_t index) { 2089 if (model->GetItemKeyAt(index) == kManageItemsKey) { 2090 GURL url; 2091 if (!IsPayingWithWallet()) { 2092 GURL settings_url(chrome::kChromeUISettingsURL); 2093 url = settings_url.Resolve(chrome::kAutofillSubPage); 2094 } else { 2095 url = SectionForSuggestionsMenuModel(*model) == SECTION_SHIPPING ? 2096 wallet::GetManageAddressesUrl() : wallet::GetManageInstrumentsUrl(); 2097 } 2098 2099 OpenTabWithUrl(url); 2100 return; 2101 } 2102 2103 model->SetCheckedIndex(index); 2104 DialogSection section = SectionForSuggestionsMenuModel(*model); 2105 ResetSectionInput(section); 2106 ShowEditUiIfBadSuggestion(section); 2107 UpdateSection(section); 2108 UpdateForErrors(); 2109 2110 LogSuggestionItemSelectedMetric(*model); 2111} 2112 2113//////////////////////////////////////////////////////////////////////////////// 2114// wallet::WalletClientDelegate implementation. 2115 2116const AutofillMetrics& AutofillDialogControllerImpl::GetMetricLogger() const { 2117 return metric_logger_; 2118} 2119 2120DialogType AutofillDialogControllerImpl::GetDialogType() const { 2121 return dialog_type_; 2122} 2123 2124std::string AutofillDialogControllerImpl::GetRiskData() const { 2125 DCHECK(!risk_data_.empty()); 2126 return risk_data_; 2127} 2128 2129std::string AutofillDialogControllerImpl::GetWalletCookieValue() const { 2130 return wallet_cookie_value_; 2131} 2132 2133void AutofillDialogControllerImpl::OnDidAcceptLegalDocuments() { 2134 DCHECK(is_submitting_ && IsPayingWithWallet()); 2135 has_accepted_legal_documents_ = true; 2136 LoadRiskFingerprintData(); 2137} 2138 2139void AutofillDialogControllerImpl::OnDidAuthenticateInstrument(bool success) { 2140 DCHECK(is_submitting_ && IsPayingWithWallet()); 2141 2142 // TODO(dbeam): use the returned full wallet. b/8332329 2143 if (success) { 2144 GetFullWallet(); 2145 } else { 2146 DisableWallet(wallet::WalletClient::UNKNOWN_ERROR); 2147 SuggestionsUpdated(); 2148 } 2149} 2150 2151void AutofillDialogControllerImpl::OnDidGetFullWallet( 2152 scoped_ptr<wallet::FullWallet> full_wallet) { 2153 DCHECK(is_submitting_ && IsPayingWithWallet()); 2154 2155 full_wallet_ = full_wallet.Pass(); 2156 2157 if (full_wallet_->required_actions().empty()) { 2158 UpdateAutocheckoutStep(AUTOCHECKOUT_STEP_PROXY_CARD, 2159 AUTOCHECKOUT_STEP_COMPLETED); 2160 FinishSubmit(); 2161 return; 2162 } 2163 2164 SetAutocheckoutState(AUTOCHECKOUT_NOT_STARTED); 2165 2166 switch (full_wallet_->required_actions()[0]) { 2167 case wallet::CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS: 2168 choose_another_instrument_or_address_ = true; 2169 SetIsSubmitting(false); 2170 GetWalletItems(); 2171 view_->UpdateNotificationArea(); 2172 view_->UpdateButtonStrip(); 2173 break; 2174 2175 case wallet::VERIFY_CVV: 2176 SuggestionsUpdated(); 2177 break; 2178 2179 default: 2180 DisableWallet(wallet::WalletClient::UNKNOWN_ERROR); 2181 break; 2182 } 2183} 2184 2185void AutofillDialogControllerImpl::OnPassiveSigninSuccess( 2186 const std::string& username) { 2187 const string16 username16 = UTF8ToUTF16(username); 2188 signin_helper_->StartWalletCookieValueFetch(); 2189 account_chooser_model_.SetActiveWalletAccountName(username16); 2190} 2191 2192void AutofillDialogControllerImpl::OnUserNameFetchSuccess( 2193 const std::string& username) { 2194 const string16 username16 = UTF8ToUTF16(username); 2195 signin_helper_.reset(); 2196 account_chooser_model_.SetActiveWalletAccountName(username16); 2197 OnWalletOrSigninUpdate(); 2198} 2199 2200void AutofillDialogControllerImpl::OnPassiveSigninFailure( 2201 const GoogleServiceAuthError& error) { 2202 // TODO(aruslan): report an error. 2203 LOG(ERROR) << "failed to passively sign in: " << error.ToString(); 2204 OnWalletSigninError(); 2205} 2206 2207void AutofillDialogControllerImpl::OnUserNameFetchFailure( 2208 const GoogleServiceAuthError& error) { 2209 // TODO(aruslan): report an error. 2210 LOG(ERROR) << "failed to fetch the user account name: " << error.ToString(); 2211 OnWalletSigninError(); 2212} 2213 2214void AutofillDialogControllerImpl::OnDidFetchWalletCookieValue( 2215 const std::string& cookie_value) { 2216 wallet_cookie_value_ = cookie_value; 2217 signin_helper_.reset(); 2218 GetWalletItems(); 2219} 2220 2221void AutofillDialogControllerImpl::OnDidGetWalletItems( 2222 scoped_ptr<wallet::WalletItems> wallet_items) { 2223 legal_documents_text_.clear(); 2224 legal_document_link_ranges_.clear(); 2225 has_accepted_legal_documents_ = false; 2226 2227 wallet_items_ = wallet_items.Pass(); 2228 OnWalletOrSigninUpdate(); 2229} 2230 2231void AutofillDialogControllerImpl::OnDidSaveToWallet( 2232 const std::string& instrument_id, 2233 const std::string& address_id, 2234 const std::vector<wallet::RequiredAction>& required_actions, 2235 const std::vector<wallet::FormFieldError>& form_field_errors) { 2236 DCHECK(is_submitting_ && IsPayingWithWallet()); 2237 2238 if (required_actions.empty()) { 2239 if (!address_id.empty()) 2240 active_address_id_ = address_id; 2241 if (!instrument_id.empty()) 2242 active_instrument_id_ = instrument_id; 2243 GetFullWallet(); 2244 } else { 2245 OnWalletFormFieldError(form_field_errors); 2246 HandleSaveOrUpdateRequiredActions(required_actions); 2247 } 2248} 2249 2250void AutofillDialogControllerImpl::OnWalletError( 2251 wallet::WalletClient::ErrorType error_type) { 2252 DisableWallet(error_type); 2253} 2254 2255//////////////////////////////////////////////////////////////////////////////// 2256// PersonalDataManagerObserver implementation. 2257 2258void AutofillDialogControllerImpl::OnPersonalDataChanged() { 2259 if (is_submitting_) 2260 return; 2261 2262 SuggestionsUpdated(); 2263} 2264 2265//////////////////////////////////////////////////////////////////////////////// 2266// AccountChooserModelDelegate implementation. 2267 2268void AutofillDialogControllerImpl::AccountChoiceChanged() { 2269 if (is_submitting_) 2270 GetWalletClient()->CancelRequests(); 2271 2272 SetIsSubmitting(false); 2273 2274 SuggestionsUpdated(); 2275 UpdateAccountChooserView(); 2276} 2277 2278void AutofillDialogControllerImpl::UpdateAccountChooserView() { 2279 if (view_) { 2280 view_->UpdateAccountChooser(); 2281 view_->UpdateNotificationArea(); 2282 } 2283} 2284 2285//////////////////////////////////////////////////////////////////////////////// 2286 2287bool AutofillDialogControllerImpl::HandleKeyPressEventInInput( 2288 const content::NativeWebKeyboardEvent& event) { 2289 if (popup_controller_.get()) 2290 return popup_controller_->HandleKeyPressEvent(event); 2291 2292 return false; 2293} 2294 2295bool AutofillDialogControllerImpl::RequestingCreditCardInfo() const { 2296 DCHECK_GT(form_structure_.field_count(), 0U); 2297 2298 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 2299 if (IsCreditCardType(form_structure_.field(i)->type())) 2300 return true; 2301 } 2302 2303 return false; 2304} 2305 2306bool AutofillDialogControllerImpl::TransmissionWillBeSecure() const { 2307 return source_url_.SchemeIs(chrome::kHttpsScheme); 2308} 2309 2310AutofillDialogControllerImpl::AutofillDialogControllerImpl( 2311 content::WebContents* contents, 2312 const FormData& form_structure, 2313 const GURL& source_url, 2314 const DialogType dialog_type, 2315 const base::Callback<void(const FormStructure*, 2316 const std::string&)>& callback) 2317 : WebContentsObserver(contents), 2318 profile_(Profile::FromBrowserContext(contents->GetBrowserContext())), 2319 contents_(contents), 2320 initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), 2321 dialog_type_(dialog_type), 2322 form_structure_(form_structure, std::string()), 2323 invoked_from_same_origin_(true), 2324 source_url_(source_url), 2325 callback_(callback), 2326 account_chooser_model_(this, profile_->GetPrefs(), metric_logger_, 2327 dialog_type), 2328 wallet_client_(profile_->GetRequestContext(), this), 2329 suggested_email_(this), 2330 suggested_cc_(this), 2331 suggested_billing_(this), 2332 suggested_cc_billing_(this), 2333 suggested_shipping_(this), 2334 cares_about_shipping_(true), 2335 input_showing_popup_(NULL), 2336 weak_ptr_factory_(this), 2337 should_show_wallet_promo_(!profile_->GetPrefs()->GetBoolean( 2338 ::prefs::kAutofillDialogHasPaidWithWallet)), 2339 has_shown_wallet_usage_confirmation_(false), 2340 has_accepted_legal_documents_(false), 2341 is_submitting_(false), 2342 choose_another_instrument_or_address_(false), 2343 wallet_server_validation_recoverable_(true), 2344 autocheckout_state_(AUTOCHECKOUT_NOT_STARTED), 2345 was_ui_latency_logged_(false), 2346 deemphasized_render_view_(false) { 2347 // TODO(estade): remove duplicates from |form_structure|? 2348 DCHECK(!callback_.is_null()); 2349} 2350 2351AutofillDialogView* AutofillDialogControllerImpl::CreateView() { 2352 return AutofillDialogView::Create(this); 2353} 2354 2355PersonalDataManager* AutofillDialogControllerImpl::GetManager() { 2356 return PersonalDataManagerFactory::GetForProfile(profile_); 2357} 2358 2359wallet::WalletClient* AutofillDialogControllerImpl::GetWalletClient() { 2360 return &wallet_client_; 2361} 2362 2363bool AutofillDialogControllerImpl::IsPayingWithWallet() const { 2364 return account_chooser_model_.WalletIsSelected() && 2365 SignedInState() == SIGNED_IN; 2366} 2367 2368void AutofillDialogControllerImpl::LoadRiskFingerprintData() { 2369 risk_data_.clear(); 2370 2371 uint64 obfuscated_gaia_id = 0; 2372 bool success = base::StringToUint64(wallet_items_->obfuscated_gaia_id(), 2373 &obfuscated_gaia_id); 2374 DCHECK(success); 2375 2376 gfx::Rect window_bounds; 2377#if !defined(OS_ANDROID) 2378 window_bounds = GetBaseWindowForWebContents(web_contents())->GetBounds(); 2379#else 2380 // TODO(dbeam): figure out the correct browser window size to pass along for 2381 // android. 2382#endif 2383 2384 PrefService* user_prefs = profile_->GetPrefs(); 2385 std::string charset = user_prefs->GetString(::prefs::kDefaultCharset); 2386 std::string accept_languages = 2387 user_prefs->GetString(::prefs::kAcceptLanguages); 2388 base::Time install_time = base::Time::FromTimeT( 2389 g_browser_process->local_state()->GetInt64(::prefs::kInstallDate)); 2390 2391 risk::GetFingerprint( 2392 obfuscated_gaia_id, window_bounds, *web_contents(), 2393 chrome::VersionInfo().Version(), charset, accept_languages, install_time, 2394 dialog_type_, g_browser_process->GetApplicationLocale(), 2395 base::Bind(&AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData, 2396 weak_ptr_factory_.GetWeakPtr())); 2397} 2398 2399void AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData( 2400 scoped_ptr<risk::Fingerprint> fingerprint) { 2401 DCHECK(AreLegalDocumentsCurrent()); 2402 2403 std::string proto_data; 2404 fingerprint->SerializeToString(&proto_data); 2405 bool success = base::Base64Encode(proto_data, &risk_data_); 2406 DCHECK(success); 2407 2408 SubmitWithWallet(); 2409} 2410 2411void AutofillDialogControllerImpl::OpenTabWithUrl(const GURL& url) { 2412#if !defined(OS_ANDROID) 2413 chrome::NavigateParams params( 2414 chrome::FindBrowserWithWebContents(web_contents()), 2415 url, 2416 content::PAGE_TRANSITION_AUTO_BOOKMARK); 2417 params.disposition = NEW_FOREGROUND_TAB; 2418 chrome::Navigate(¶ms); 2419#else 2420 // TODO(estade): use TabModelList? 2421#endif 2422} 2423 2424bool AutofillDialogControllerImpl::IsEditingExistingData( 2425 DialogSection section) const { 2426 return section_editing_state_.count(section) > 0; 2427} 2428 2429bool AutofillDialogControllerImpl::IsManuallyEditingSection( 2430 DialogSection section) const { 2431 return IsEditingExistingData(section) || 2432 SuggestionsMenuModelForSection(section)-> 2433 GetItemKeyForCheckedItem() == kAddNewItemKey; 2434} 2435 2436void AutofillDialogControllerImpl::OnWalletSigninError() { 2437 signin_helper_.reset(); 2438 account_chooser_model_.SetHadWalletSigninError(); 2439 GetWalletClient()->CancelRequests(); 2440 LogDialogLatencyToShow(); 2441} 2442 2443void AutofillDialogControllerImpl::DisableWallet( 2444 wallet::WalletClient::ErrorType error_type) { 2445 signin_helper_.reset(); 2446 wallet_items_.reset(); 2447 wallet_errors_.clear(); 2448 GetWalletClient()->CancelRequests(); 2449 SetAutocheckoutState(AUTOCHECKOUT_NOT_STARTED); 2450 for (std::vector<DialogAutocheckoutStep>::iterator it = steps_.begin(); 2451 it != steps_.end(); ++it) { 2452 if (it->type() == AUTOCHECKOUT_STEP_PROXY_CARD) { 2453 steps_.erase(it); 2454 break; 2455 } 2456 } 2457 SetIsSubmitting(false); 2458 account_chooser_model_.SetHadWalletError(WalletErrorMessage(error_type)); 2459} 2460 2461void AutofillDialogControllerImpl::SuggestionsUpdated() { 2462 const DetailOutputMap snapshot = TakeUserInputSnapshot(); 2463 2464 suggested_email_.Reset(); 2465 suggested_cc_.Reset(); 2466 suggested_billing_.Reset(); 2467 suggested_cc_billing_.Reset(); 2468 suggested_shipping_.Reset(); 2469 HidePopup(); 2470 2471 suggested_shipping_.AddKeyedItem( 2472 kSameAsBillingKey, 2473 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_USE_BILLING_FOR_SHIPPING)); 2474 2475 if (IsPayingWithWallet()) { 2476 if (!account_chooser_model_.active_wallet_account_name().empty()) { 2477 suggested_email_.AddKeyedItem( 2478 base::IntToString(0), 2479 account_chooser_model_.active_wallet_account_name()); 2480 } 2481 2482 const std::vector<wallet::Address*>& addresses = 2483 wallet_items_->addresses(); 2484 for (size_t i = 0; i < addresses.size(); ++i) { 2485 std::string key = base::IntToString(i); 2486 suggested_shipping_.AddKeyedItemWithSublabel( 2487 key, 2488 addresses[i]->DisplayName(), 2489 addresses[i]->DisplayNameDetail()); 2490 2491 if (addresses[i]->object_id() == wallet_items_->default_address_id()) 2492 suggested_shipping_.SetCheckedItem(key); 2493 } 2494 2495 if (!IsSubmitPausedOn(wallet::VERIFY_CVV)) { 2496 const std::vector<wallet::WalletItems::MaskedInstrument*>& instruments = 2497 wallet_items_->instruments(); 2498 std::string first_active_instrument_key; 2499 std::string default_instrument_key; 2500 for (size_t i = 0; i < instruments.size(); ++i) { 2501 bool allowed = IsInstrumentAllowed(*instruments[i]); 2502 gfx::Image icon = instruments[i]->CardIcon(); 2503 if (!allowed && !icon.IsEmpty()) { 2504 // Create a grayed disabled icon. 2505 SkBitmap disabled_bitmap = SkBitmapOperations::CreateHSLShiftedBitmap( 2506 *icon.ToSkBitmap(), kGrayImageShift); 2507 icon = gfx::Image( 2508 gfx::ImageSkia::CreateFrom1xBitmap(disabled_bitmap)); 2509 } 2510 std::string key = base::IntToString(i); 2511 suggested_cc_billing_.AddKeyedItemWithSublabelAndIcon( 2512 key, 2513 instruments[i]->DisplayName(), 2514 instruments[i]->DisplayNameDetail(), 2515 icon); 2516 suggested_cc_billing_.SetEnabled(key, allowed); 2517 2518 if (allowed) { 2519 if (first_active_instrument_key.empty()) 2520 first_active_instrument_key = key; 2521 if (instruments[i]->object_id() == 2522 wallet_items_->default_instrument_id()) { 2523 default_instrument_key = key; 2524 } 2525 } 2526 } 2527 2528 // TODO(estade): this should have a URL sublabel. 2529 suggested_cc_billing_.AddKeyedItem( 2530 kAddNewItemKey, 2531 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_DETAILS)); 2532 if (!wallet_items_->HasRequiredAction(wallet::SETUP_WALLET)) { 2533 suggested_cc_billing_.AddKeyedItemWithSublabel( 2534 kManageItemsKey, 2535 l10n_util::GetStringUTF16( 2536 IDS_AUTOFILL_DIALOG_MANAGE_BILLING_DETAILS), 2537 UTF8ToUTF16(wallet::GetManageInstrumentsUrl().host())); 2538 } 2539 2540 // Determine which instrument item should be selected. 2541 if (!default_instrument_key.empty()) 2542 suggested_cc_billing_.SetCheckedItem(default_instrument_key); 2543 else if (!first_active_instrument_key.empty()) 2544 suggested_cc_billing_.SetCheckedItem(first_active_instrument_key); 2545 else 2546 suggested_cc_billing_.SetCheckedItem(kAddNewItemKey); 2547 } 2548 } else { 2549 PersonalDataManager* manager = GetManager(); 2550 const std::vector<CreditCard*>& cards = manager->GetCreditCards(); 2551 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 2552 for (size_t i = 0; i < cards.size(); ++i) { 2553 if (!HasCompleteAndVerifiedData(*cards[i], requested_cc_fields_)) 2554 continue; 2555 2556 suggested_cc_.AddKeyedItemWithIcon( 2557 cards[i]->guid(), 2558 cards[i]->Label(), 2559 rb.GetImageNamed(CreditCard::IconResourceId(cards[i]->type()))); 2560 } 2561 2562 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); 2563 const std::string app_locale = g_browser_process->GetApplicationLocale(); 2564 for (size_t i = 0; i < profiles.size(); ++i) { 2565 if (!HasCompleteAndVerifiedData(*profiles[i], 2566 requested_shipping_fields_) || 2567 HasInvalidAddress(*profiles[i])) { 2568 continue; 2569 } 2570 2571 // Add all email addresses. 2572 std::vector<string16> values; 2573 profiles[i]->GetMultiInfo(EMAIL_ADDRESS, app_locale, &values); 2574 for (size_t j = 0; j < values.size(); ++j) { 2575 if (IsValidEmailAddress(values[j])) 2576 suggested_email_.AddKeyedItem(profiles[i]->guid(), values[j]); 2577 } 2578 2579 // Don't add variants for addresses: the email variants are handled above, 2580 // name is part of credit card and we'll just ignore phone number 2581 // variants. 2582 suggested_billing_.AddKeyedItem(profiles[i]->guid(), 2583 profiles[i]->Label()); 2584 suggested_shipping_.AddKeyedItem(profiles[i]->guid(), 2585 profiles[i]->Label()); 2586 } 2587 2588 suggested_cc_.AddKeyedItem( 2589 kAddNewItemKey, 2590 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD)); 2591 suggested_cc_.AddKeyedItem( 2592 kManageItemsKey, 2593 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_CREDIT_CARD)); 2594 suggested_billing_.AddKeyedItem( 2595 kAddNewItemKey, 2596 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_ADDRESS)); 2597 suggested_billing_.AddKeyedItem( 2598 kManageItemsKey, 2599 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_BILLING_ADDRESS)); 2600 } 2601 2602 suggested_email_.AddKeyedItem( 2603 kAddNewItemKey, 2604 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL_ADDRESS)); 2605 if (!IsPayingWithWallet()) { 2606 suggested_email_.AddKeyedItem( 2607 kManageItemsKey, 2608 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_EMAIL_ADDRESS)); 2609 } 2610 2611 suggested_shipping_.AddKeyedItem( 2612 kAddNewItemKey, 2613 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_SHIPPING_ADDRESS)); 2614 if (!IsPayingWithWallet()) { 2615 suggested_shipping_.AddKeyedItem( 2616 kManageItemsKey, 2617 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_SHIPPING_ADDRESS)); 2618 } else if (!wallet_items_->HasRequiredAction(wallet::SETUP_WALLET)) { 2619 suggested_shipping_.AddKeyedItemWithSublabel( 2620 kManageItemsKey, 2621 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_SHIPPING_ADDRESS), 2622 UTF8ToUTF16(wallet::GetManageAddressesUrl().host())); 2623 } 2624 2625 if (!IsPayingWithWallet()) { 2626 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 2627 DialogSection section = static_cast<DialogSection>(i); 2628 if (!SectionIsActive(section)) 2629 continue; 2630 2631 // Set the starting choice for the menu. First set to the default in case 2632 // the GUID saved in prefs refers to a profile that no longer exists. 2633 std::string guid; 2634 int variant; 2635 GetDefaultAutofillChoice(section, &guid, &variant); 2636 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 2637 model->SetCheckedItemNthWithKey(guid, variant + 1); 2638 if (GetAutofillChoice(section, &guid, &variant)) 2639 model->SetCheckedItemNthWithKey(guid, variant + 1); 2640 } 2641 } 2642 2643 if (view_) 2644 view_->ModelChanged(); 2645 2646 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 2647 ResetSectionInput(static_cast<DialogSection>(i)); 2648 } 2649 2650 RestoreUserInputFromSnapshot(snapshot); 2651 2652 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 2653 DialogSection section = static_cast<DialogSection>(i); 2654 ShowEditUiIfBadSuggestion(section); 2655 UpdateSection(section); 2656 } 2657 2658 UpdateForErrors(); 2659} 2660 2661void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( 2662 DialogSection section, 2663 const InputFieldComparator& compare) { 2664 const DetailInputs& inputs = RequestedFieldsForSection(section); 2665 2666 // Email is hidden while using Wallet, special case it. 2667 if (section == SECTION_EMAIL && IsPayingWithWallet()) { 2668 AutofillProfile profile; 2669 profile.SetRawInfo(EMAIL_ADDRESS, 2670 account_chooser_model_.active_wallet_account_name()); 2671 AutofillProfileWrapper profile_wrapper(&profile, 0); 2672 profile_wrapper.FillFormStructure(inputs, compare, &form_structure_); 2673 return; 2674 } 2675 2676 if (!SectionIsActive(section)) 2677 return; 2678 2679 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 2680 if (wrapper) { 2681 // Only fill in data that is associated with this section. 2682 const DetailInputs& inputs = RequestedFieldsForSection(section); 2683 wrapper->FillFormStructure(inputs, compare, &form_structure_); 2684 2685 // CVC needs special-casing because the CreditCard class doesn't store or 2686 // handle them. This isn't necessary when filling the combined CC and 2687 // billing section as CVC comes from |full_wallet_| in this case. 2688 if (section == SECTION_CC) 2689 SetCvcResult(view_->GetCvc()); 2690 } else { 2691 // The user manually input data. If using Autofill, save the info as new or 2692 // edited data. Always fill local data into |form_structure_|. 2693 DetailOutputMap output; 2694 view_->GetUserInput(section, &output); 2695 2696 if (section == SECTION_CC) { 2697 CreditCard card; 2698 card.set_origin(kAutofillDialogOrigin); 2699 FillFormGroupFromOutputs(output, &card); 2700 2701 // The card holder name comes from the billing address section. 2702 card.SetRawInfo(CREDIT_CARD_NAME, 2703 GetValueFromSection(SECTION_BILLING, NAME_FULL)); 2704 2705 if (ShouldSaveDetailsLocally()) { 2706 GetManager()->SaveImportedCreditCard(card); 2707 DCHECK(!profile()->IsOffTheRecord()); 2708 newly_saved_card_.reset(new CreditCard(card)); 2709 } 2710 2711 AutofillCreditCardWrapper card_wrapper(&card); 2712 card_wrapper.FillFormStructure(inputs, compare, &form_structure_); 2713 2714 // Again, CVC needs special-casing. Fill it in directly from |output|. 2715 SetCvcResult(GetValueForType(output, CREDIT_CARD_VERIFICATION_CODE)); 2716 } else { 2717 AutofillProfile profile; 2718 profile.set_origin(kAutofillDialogOrigin); 2719 FillFormGroupFromOutputs(output, &profile); 2720 2721 // For billing, the email address comes from the separate email section. 2722 if (section == SECTION_BILLING) { 2723 profile.SetRawInfo(EMAIL_ADDRESS, 2724 GetValueFromSection(SECTION_EMAIL, EMAIL_ADDRESS)); 2725 } 2726 2727 if (ShouldSaveDetailsLocally()) 2728 SaveProfileGleanedFromSection(profile, section); 2729 2730 AutofillProfileWrapper profile_wrapper(&profile, 0); 2731 profile_wrapper.FillFormStructure(inputs, compare, &form_structure_); 2732 } 2733 } 2734} 2735 2736void AutofillDialogControllerImpl::FillOutputForSection(DialogSection section) { 2737 FillOutputForSectionWithComparator( 2738 section, base::Bind(DetailInputMatchesField, section)); 2739} 2740 2741bool AutofillDialogControllerImpl::FormStructureCaresAboutSection( 2742 DialogSection section) const { 2743 // For now, only SECTION_SHIPPING may be omitted due to a site not asking for 2744 // any of the fields. 2745 // TODO(estade): remove !IsPayingWithWallet() check once WalletClient support 2746 // is added. http://crbug.com/243514 2747 if (section == SECTION_SHIPPING && !IsPayingWithWallet()) 2748 return cares_about_shipping_; 2749 2750 return true; 2751} 2752 2753void AutofillDialogControllerImpl::SetCvcResult(const string16& cvc) { 2754 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 2755 AutofillField* field = form_structure_.field(i); 2756 if (field->type() == CREDIT_CARD_VERIFICATION_CODE) { 2757 field->value = cvc; 2758 break; 2759 } 2760 } 2761} 2762 2763string16 AutofillDialogControllerImpl::GetValueFromSection( 2764 DialogSection section, 2765 AutofillFieldType type) { 2766 DCHECK(SectionIsActive(section)); 2767 2768 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 2769 if (wrapper) 2770 return wrapper->GetInfo(type); 2771 2772 DetailOutputMap output; 2773 view_->GetUserInput(section, &output); 2774 for (DetailOutputMap::iterator iter = output.begin(); iter != output.end(); 2775 ++iter) { 2776 if (iter->first->type == type) 2777 return iter->second; 2778 } 2779 2780 return string16(); 2781} 2782 2783void AutofillDialogControllerImpl::SaveProfileGleanedFromSection( 2784 const AutofillProfile& profile, 2785 DialogSection section) { 2786 if (section == SECTION_EMAIL) { 2787 // Save the email address to the existing (suggested) billing profile. If 2788 // there is no existing profile, the newly created one will pick up this 2789 // email, so in that case do nothing. 2790 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(SECTION_BILLING); 2791 if (wrapper) { 2792 std::string item_key = SuggestionsMenuModelForSection(SECTION_BILLING)-> 2793 GetItemKeyForCheckedItem(); 2794 AutofillProfile* billing_profile = 2795 GetManager()->GetProfileByGUID(item_key); 2796 billing_profile->OverwriteWithOrAddTo( 2797 profile, 2798 g_browser_process->GetApplicationLocale()); 2799 } 2800 } else { 2801 GetManager()->SaveImportedProfile(profile); 2802 } 2803} 2804 2805SuggestionsMenuModel* AutofillDialogControllerImpl:: 2806 SuggestionsMenuModelForSection(DialogSection section) { 2807 switch (section) { 2808 case SECTION_EMAIL: 2809 return &suggested_email_; 2810 case SECTION_CC: 2811 return &suggested_cc_; 2812 case SECTION_BILLING: 2813 return &suggested_billing_; 2814 case SECTION_SHIPPING: 2815 return &suggested_shipping_; 2816 case SECTION_CC_BILLING: 2817 return &suggested_cc_billing_; 2818 } 2819 2820 NOTREACHED(); 2821 return NULL; 2822} 2823 2824const SuggestionsMenuModel* AutofillDialogControllerImpl:: 2825 SuggestionsMenuModelForSection(DialogSection section) const { 2826 return const_cast<AutofillDialogControllerImpl*>(this)-> 2827 SuggestionsMenuModelForSection(section); 2828} 2829 2830DialogSection AutofillDialogControllerImpl::SectionForSuggestionsMenuModel( 2831 const SuggestionsMenuModel& model) { 2832 if (&model == &suggested_email_) 2833 return SECTION_EMAIL; 2834 2835 if (&model == &suggested_cc_) 2836 return SECTION_CC; 2837 2838 if (&model == &suggested_billing_) 2839 return SECTION_BILLING; 2840 2841 if (&model == &suggested_cc_billing_) 2842 return SECTION_CC_BILLING; 2843 2844 DCHECK_EQ(&model, &suggested_shipping_); 2845 return SECTION_SHIPPING; 2846} 2847 2848DetailInputs* AutofillDialogControllerImpl::MutableRequestedFieldsForSection( 2849 DialogSection section) { 2850 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); 2851} 2852 2853void AutofillDialogControllerImpl::HidePopup() { 2854 if (popup_controller_.get()) 2855 popup_controller_->Hide(); 2856 input_showing_popup_ = NULL; 2857} 2858 2859void AutofillDialogControllerImpl::SetEditingExistingData( 2860 DialogSection section, bool editing) { 2861 if (editing) 2862 section_editing_state_.insert(section); 2863 else 2864 section_editing_state_.erase(section); 2865} 2866 2867bool AutofillDialogControllerImpl::IsASuggestionItemKey( 2868 const std::string& key) const { 2869 return !key.empty() && 2870 key != kAddNewItemKey && 2871 key != kManageItemsKey && 2872 key != kSameAsBillingKey; 2873} 2874 2875bool AutofillDialogControllerImpl::IsManuallyEditingAnySection() const { 2876 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { 2877 if (IsManuallyEditingSection(static_cast<DialogSection>(section))) 2878 return true; 2879 } 2880 return false; 2881} 2882 2883base::string16 AutofillDialogControllerImpl::CreditCardNumberValidityMessage( 2884 const base::string16& number) const { 2885 if (!number.empty() && !autofill::IsValidCreditCardNumber(number)) { 2886 return l10n_util::GetStringUTF16( 2887 IDS_AUTOFILL_DIALOG_VALIDATION_INVALID_CREDIT_CARD_NUMBER); 2888 } 2889 2890 // Wallet only accepts MasterCard, Visa and Discover. No AMEX. 2891 if (IsPayingWithWallet() && 2892 !IsWalletSupportedCard(CreditCard::GetCreditCardType(number))) { 2893 return l10n_util::GetStringUTF16( 2894 IDS_AUTOFILL_DIALOG_VALIDATION_CREDIT_CARD_NOT_SUPPORTED_BY_WALLET); 2895 } 2896 2897 // Card number is good and supported. 2898 return base::string16(); 2899} 2900 2901bool AutofillDialogControllerImpl::InputIsEditable( 2902 const DetailInput& input, 2903 DialogSection section) const { 2904 if (input.type != CREDIT_CARD_NUMBER || !IsPayingWithWallet()) 2905 return true; 2906 2907 if (IsEditingExistingData(section)) 2908 return false; 2909 2910 return true; 2911} 2912 2913bool AutofillDialogControllerImpl::AllSectionsAreValid() { 2914 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { 2915 if (!SectionIsValid(static_cast<DialogSection>(section))) 2916 return false; 2917 } 2918 return true; 2919} 2920 2921bool AutofillDialogControllerImpl::SectionIsValid( 2922 DialogSection section) { 2923 if (!IsManuallyEditingSection(section)) 2924 return true; 2925 2926 DetailOutputMap detail_outputs; 2927 view_->GetUserInput(section, &detail_outputs); 2928 return InputsAreValid(section, detail_outputs, VALIDATE_EDIT).empty(); 2929} 2930 2931bool AutofillDialogControllerImpl::IsCreditCardExpirationValid( 2932 const base::string16& year, 2933 const base::string16& month) const { 2934 // If the expiration is in the past as per the local clock, it's invalid. 2935 base::Time now = base::Time::Now(); 2936 if (!autofill::IsValidCreditCardExpirationDate(year, month, now)) 2937 return false; 2938 2939 if (IsPayingWithWallet() && IsEditingExistingData(SECTION_CC_BILLING)) { 2940 const wallet::WalletItems::MaskedInstrument* instrument = 2941 ActiveInstrument(); 2942 const std::string& locale = g_browser_process->GetApplicationLocale(); 2943 int month_int; 2944 if (base::StringToInt(month, &month_int) && 2945 instrument->status() == 2946 wallet::WalletItems::MaskedInstrument::EXPIRED && 2947 year == instrument->GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, locale) && 2948 month_int == instrument->expiration_month()) { 2949 // Otherwise, if the user is editing an instrument that's deemed expired 2950 // by the Online Wallet server, mark it invalid on selection. 2951 return false; 2952 } 2953 } 2954 2955 return true; 2956} 2957 2958bool AutofillDialogControllerImpl::ShouldUseBillingForShipping() { 2959 return SectionIsActive(SECTION_SHIPPING) && 2960 suggested_shipping_.GetItemKeyForCheckedItem() == kSameAsBillingKey; 2961} 2962 2963bool AutofillDialogControllerImpl::ShouldSaveDetailsLocally() { 2964 // It's possible that the user checked [X] Save details locally before 2965 // switching payment methods, so only ask the view whether to save details 2966 // locally if that checkbox is showing (currently if not paying with wallet). 2967 // Also, if the user isn't editing any sections, there's no data to save 2968 // locally. 2969 return ShouldOfferToSaveInChrome() && view_->SaveDetailsLocally(); 2970} 2971 2972void AutofillDialogControllerImpl::SetIsSubmitting(bool submitting) { 2973 is_submitting_ = submitting; 2974 2975 if (!submitting) 2976 full_wallet_.reset(); 2977 2978 if (view_) { 2979 view_->UpdateButtonStrip(); 2980 view_->UpdateNotificationArea(); 2981 } 2982} 2983 2984bool AutofillDialogControllerImpl::AreLegalDocumentsCurrent() const { 2985 return has_accepted_legal_documents_ || 2986 (wallet_items_ && wallet_items_->legal_documents().empty()); 2987} 2988 2989void AutofillDialogControllerImpl::AcceptLegalDocuments() { 2990 content::BrowserThread::PostTask( 2991 content::BrowserThread::IO, FROM_HERE, 2992 base::Bind(&UserDidOptIntoLocationServices)); 2993 2994 GetWalletClient()->AcceptLegalDocuments( 2995 wallet_items_->legal_documents(), 2996 wallet_items_->google_transaction_id(), 2997 source_url_); 2998 2999 if (AreLegalDocumentsCurrent()) 3000 LoadRiskFingerprintData(); 3001} 3002 3003void AutofillDialogControllerImpl::SubmitWithWallet() { 3004 active_instrument_id_.clear(); 3005 active_address_id_.clear(); 3006 full_wallet_.reset(); 3007 3008 const wallet::WalletItems::MaskedInstrument* active_instrument = 3009 ActiveInstrument(); 3010 if (!IsManuallyEditingSection(SECTION_CC_BILLING)) { 3011 active_instrument_id_ = active_instrument->object_id(); 3012 DCHECK(!active_instrument_id_.empty()); 3013 } 3014 3015 const wallet::Address* active_address = ActiveShippingAddress(); 3016 if (!IsManuallyEditingSection(SECTION_SHIPPING) && 3017 !ShouldUseBillingForShipping()) { 3018 active_address_id_ = active_address->object_id(); 3019 DCHECK(!active_address_id_.empty()); 3020 } 3021 3022 if (GetDialogType() == DIALOG_TYPE_AUTOCHECKOUT) { 3023 DCHECK_EQ(AUTOCHECKOUT_NOT_STARTED, autocheckout_state_); 3024 SetAutocheckoutState(AUTOCHECKOUT_IN_PROGRESS); 3025 } 3026 3027 scoped_ptr<wallet::Instrument> inputted_instrument = 3028 CreateTransientInstrument(); 3029 if (inputted_instrument && IsEditingExistingData(SECTION_CC_BILLING)) { 3030 inputted_instrument->set_object_id(active_instrument->object_id()); 3031 DCHECK(!inputted_instrument->object_id().empty()); 3032 } 3033 3034 scoped_ptr<wallet::Address> inputted_address; 3035 if (active_address_id_.empty()) { 3036 if (ShouldUseBillingForShipping()) { 3037 const wallet::Address& address = inputted_instrument ? 3038 *inputted_instrument->address() : active_instrument->address(); 3039 // Try to find an exact matched shipping address and use it for shipping, 3040 // otherwise save it as a new shipping address. http://crbug.com/225442 3041 const wallet::Address* duplicated_address = 3042 FindDuplicateAddress(wallet_items_->addresses(), address); 3043 if (duplicated_address) { 3044 active_address_id_ = duplicated_address->object_id(); 3045 DCHECK(!active_address_id_.empty()); 3046 } else { 3047 inputted_address.reset(new wallet::Address(address)); 3048 DCHECK(inputted_address->object_id().empty()); 3049 } 3050 } else { 3051 inputted_address = CreateTransientAddress(); 3052 if (IsEditingExistingData(SECTION_SHIPPING)) { 3053 inputted_address->set_object_id(active_address->object_id()); 3054 DCHECK(!inputted_address->object_id().empty()); 3055 } 3056 } 3057 } 3058 3059 // If there's neither an address nor instrument to save, |GetFullWallet()| 3060 // is called when the risk fingerprint is loaded. 3061 if (!active_instrument_id_.empty() && !active_address_id_.empty()) { 3062 GetFullWallet(); 3063 return; 3064 } 3065 3066 GetWalletClient()->SaveToWallet(inputted_instrument.Pass(), 3067 inputted_address.Pass(), 3068 source_url_); 3069} 3070 3071scoped_ptr<wallet::Instrument> AutofillDialogControllerImpl:: 3072 CreateTransientInstrument() { 3073 if (!active_instrument_id_.empty()) 3074 return scoped_ptr<wallet::Instrument>(); 3075 3076 DetailOutputMap output; 3077 view_->GetUserInput(SECTION_CC_BILLING, &output); 3078 3079 CreditCard card; 3080 AutofillProfile profile; 3081 string16 cvc; 3082 GetBillingInfoFromOutputs(output, &card, &cvc, &profile); 3083 3084 return scoped_ptr<wallet::Instrument>( 3085 new wallet::Instrument(card, cvc, profile)); 3086} 3087 3088scoped_ptr<wallet::Address>AutofillDialogControllerImpl:: 3089 CreateTransientAddress() { 3090 // If not using billing for shipping, just scrape the view. 3091 DetailOutputMap output; 3092 view_->GetUserInput(SECTION_SHIPPING, &output); 3093 3094 AutofillProfile profile; 3095 FillFormGroupFromOutputs(output, &profile); 3096 3097 return scoped_ptr<wallet::Address>(new wallet::Address(profile)); 3098} 3099 3100void AutofillDialogControllerImpl::GetFullWallet() { 3101 DCHECK(is_submitting_); 3102 DCHECK(IsPayingWithWallet()); 3103 DCHECK(wallet_items_); 3104 DCHECK(!active_instrument_id_.empty()); 3105 DCHECK(!active_address_id_.empty()); 3106 3107 std::vector<wallet::WalletClient::RiskCapability> capabilities; 3108 capabilities.push_back(wallet::WalletClient::VERIFY_CVC); 3109 3110 UpdateAutocheckoutStep(AUTOCHECKOUT_STEP_PROXY_CARD, 3111 AUTOCHECKOUT_STEP_STARTED); 3112 3113 GetWalletClient()->GetFullWallet(wallet::WalletClient::FullWalletRequest( 3114 active_instrument_id_, 3115 active_address_id_, 3116 source_url_, 3117 wallet_items_->google_transaction_id(), 3118 capabilities)); 3119} 3120 3121void AutofillDialogControllerImpl::HandleSaveOrUpdateRequiredActions( 3122 const std::vector<wallet::RequiredAction>& required_actions) { 3123 DCHECK(!required_actions.empty()); 3124 3125 // TODO(ahutter): Invesitigate if we need to support more generic actions on 3126 // this call such as GAIA_AUTH. See crbug.com/243457. 3127 for (std::vector<wallet::RequiredAction>::const_iterator iter = 3128 required_actions.begin(); 3129 iter != required_actions.end(); ++iter) { 3130 if (*iter != wallet::INVALID_FORM_FIELD) { 3131 // TODO(dbeam): handle this more gracefully. 3132 DisableWallet(wallet::WalletClient::UNKNOWN_ERROR); 3133 } 3134 } 3135 SetAutocheckoutState(AUTOCHECKOUT_NOT_STARTED); 3136 SetIsSubmitting(false); 3137} 3138 3139void AutofillDialogControllerImpl::FinishSubmit() { 3140 if (IsPayingWithWallet() && 3141 !profile_->GetPrefs()->GetBoolean( 3142 ::prefs::kAutofillDialogHasPaidWithWallet)) { 3143 if (GetDialogType() == DIALOG_TYPE_REQUEST_AUTOCOMPLETE) { 3144 // To get past this point, the view must call back OverlayButtonPressed. 3145#if defined(TOOLKIT_VIEWS) 3146 view_->UpdateButtonStrip(); 3147#else 3148 // TODO(estade): implement overlays on other platforms. 3149 OverlayButtonPressed(); 3150#endif 3151 return; 3152 } else { 3153 profile_->GetPrefs()->SetBoolean( 3154 ::prefs::kAutofillDialogHasPaidWithWallet, true); 3155 } 3156 } 3157 3158 FillOutputForSection(SECTION_EMAIL); 3159 FillOutputForSection(SECTION_CC); 3160 FillOutputForSection(SECTION_BILLING); 3161 FillOutputForSection(SECTION_CC_BILLING); 3162 3163 if (ShouldUseBillingForShipping()) { 3164 FillOutputForSectionWithComparator( 3165 SECTION_BILLING, 3166 base::Bind(DetailInputMatchesShippingField)); 3167 FillOutputForSectionWithComparator( 3168 SECTION_CC, 3169 base::Bind(DetailInputMatchesShippingField)); 3170 FillOutputForSectionWithComparator( 3171 SECTION_CC_BILLING, 3172 base::Bind(DetailInputMatchesShippingField)); 3173 } else { 3174 FillOutputForSection(SECTION_SHIPPING); 3175 } 3176 3177 if (!IsPayingWithWallet()) { 3178 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 3179 DialogSection section = static_cast<DialogSection>(i); 3180 if (!SectionIsActive(section)) 3181 continue; 3182 3183 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 3184 std::string item_key = model->GetItemKeyForCheckedItem(); 3185 if (IsASuggestionItemKey(item_key) || item_key == kSameAsBillingKey) { 3186 int variant = GetSelectedVariantForModel(*model); 3187 PersistAutofillChoice(section, item_key, variant); 3188 } 3189 } 3190 } 3191 3192 // On a successful submit, if the user manually selected "pay without wallet", 3193 // stop trying to pay with Wallet on future runs of the dialog. On the other 3194 // hand, if there was an error that prevented the user from having the choice 3195 // of using Wallet, leave the pref alone. 3196 if (!account_chooser_model_.HadWalletError() && 3197 account_chooser_model_.HasAccountsToChoose()) { 3198 profile_->GetPrefs()->SetBoolean( 3199 ::prefs::kAutofillDialogPayWithoutWallet, 3200 !account_chooser_model_.WalletIsSelected()); 3201 } 3202 3203 if (GetDialogType() == DIALOG_TYPE_AUTOCHECKOUT) { 3204 // Stop observing PersonalDataManager to avoid the dialog redrawing while 3205 // in an Autocheckout flow. 3206 GetManager()->RemoveObserver(this); 3207 autocheckout_started_timestamp_ = base::Time::Now(); 3208 SetAutocheckoutState(AUTOCHECKOUT_IN_PROGRESS); 3209 } 3210 3211 LogOnFinishSubmitMetrics(); 3212 3213 // Callback should be called as late as possible. 3214 callback_.Run(&form_structure_, !wallet_items_ ? std::string() : 3215 wallet_items_->google_transaction_id()); 3216 3217 // This might delete us. 3218 if (GetDialogType() == DIALOG_TYPE_REQUEST_AUTOCOMPLETE) 3219 Hide(); 3220} 3221 3222void AutofillDialogControllerImpl::PersistAutofillChoice( 3223 DialogSection section, 3224 const std::string& guid, 3225 int variant) { 3226 DCHECK(!IsPayingWithWallet()); 3227 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue()); 3228 value->SetString(kGuidPrefKey, guid); 3229 value->SetInteger(kVariantPrefKey, variant); 3230 3231 DictionaryPrefUpdate updater(profile()->GetPrefs(), 3232 ::prefs::kAutofillDialogAutofillDefault); 3233 base::DictionaryValue* autofill_choice = updater.Get(); 3234 autofill_choice->Set(SectionToPrefString(section), value.release()); 3235} 3236 3237void AutofillDialogControllerImpl::GetDefaultAutofillChoice( 3238 DialogSection section, 3239 std::string* guid, 3240 int* variant) { 3241 DCHECK(!IsPayingWithWallet()); 3242 // The default choice is the first thing in the menu that is a suggestion 3243 // item. 3244 *variant = 0; 3245 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 3246 for (int i = 0; i < model->GetItemCount(); ++i) { 3247 if (IsASuggestionItemKey(model->GetItemKeyAt(i))) { 3248 *guid = model->GetItemKeyAt(i); 3249 break; 3250 } 3251 } 3252} 3253 3254bool AutofillDialogControllerImpl::GetAutofillChoice(DialogSection section, 3255 std::string* guid, 3256 int* variant) { 3257 DCHECK(!IsPayingWithWallet()); 3258 const base::DictionaryValue* choices = profile()->GetPrefs()->GetDictionary( 3259 ::prefs::kAutofillDialogAutofillDefault); 3260 if (!choices) 3261 return false; 3262 3263 const base::DictionaryValue* choice = NULL; 3264 if (!choices->GetDictionary(SectionToPrefString(section), &choice)) 3265 return false; 3266 3267 choice->GetString(kGuidPrefKey, guid); 3268 choice->GetInteger(kVariantPrefKey, variant); 3269 return true; 3270} 3271 3272size_t AutofillDialogControllerImpl::GetSelectedVariantForModel( 3273 const SuggestionsMenuModel& model) { 3274 size_t variant = 0; 3275 // Calculate the variant by looking at how many items come from the same 3276 // data model. 3277 for (int i = model.checked_item() - 1; i >= 0; --i) { 3278 if (model.GetItemKeyAt(i) == model.GetItemKeyForCheckedItem()) 3279 variant++; 3280 else 3281 break; 3282 } 3283 return variant; 3284} 3285 3286void AutofillDialogControllerImpl::LogOnFinishSubmitMetrics() { 3287 GetMetricLogger().LogDialogUiDuration( 3288 base::Time::Now() - dialog_shown_timestamp_, 3289 GetDialogType(), 3290 AutofillMetrics::DIALOG_ACCEPTED); 3291 3292 GetMetricLogger().LogDialogUiEvent( 3293 GetDialogType(), AutofillMetrics::DIALOG_UI_ACCEPTED); 3294 3295 AutofillMetrics::DialogDismissalState dismissal_state; 3296 if (!IsManuallyEditingAnySection()) 3297 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_EXISTING_DATA; 3298 else if (IsPayingWithWallet()) 3299 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_WALLET; 3300 else if (ShouldSaveDetailsLocally()) 3301 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_AUTOFILL; 3302 else 3303 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_NO_SAVE; 3304 3305 GetMetricLogger().LogDialogDismissalState(GetDialogType(), dismissal_state); 3306} 3307 3308void AutofillDialogControllerImpl::LogOnCancelMetrics() { 3309 GetMetricLogger().LogDialogUiEvent( 3310 GetDialogType(), AutofillMetrics::DIALOG_UI_CANCELED); 3311 3312 AutofillMetrics::DialogDismissalState dismissal_state; 3313 if (!signin_registrar_.IsEmpty()) 3314 dismissal_state = AutofillMetrics::DIALOG_CANCELED_DURING_SIGNIN; 3315 else if (!IsManuallyEditingAnySection()) 3316 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_EDITS; 3317 else if (AllSectionsAreValid()) 3318 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_INVALID_FIELDS; 3319 else 3320 dismissal_state = AutofillMetrics::DIALOG_CANCELED_WITH_INVALID_FIELDS; 3321 3322 GetMetricLogger().LogDialogDismissalState(GetDialogType(), dismissal_state); 3323 3324 GetMetricLogger().LogDialogUiDuration( 3325 base::Time::Now() - dialog_shown_timestamp_, 3326 GetDialogType(), 3327 AutofillMetrics::DIALOG_CANCELED); 3328} 3329 3330void AutofillDialogControllerImpl::LogSuggestionItemSelectedMetric( 3331 const SuggestionsMenuModel& model) { 3332 DialogSection section = SectionForSuggestionsMenuModel(model); 3333 3334 AutofillMetrics::DialogUiEvent dialog_ui_event; 3335 if (model.GetItemKeyForCheckedItem() == kAddNewItemKey) { 3336 // Selected to add a new item. 3337 dialog_ui_event = DialogSectionToUiItemAddedEvent(section); 3338 } else if (IsASuggestionItemKey(model.GetItemKeyForCheckedItem())) { 3339 // Selected an existing item. 3340 dialog_ui_event = DialogSectionToUiSelectionChangedEvent(section); 3341 } else { 3342 // TODO(estade): add logging for "Manage items" or "Use billing for 3343 // shipping"? 3344 return; 3345 } 3346 3347 GetMetricLogger().LogDialogUiEvent(GetDialogType(), dialog_ui_event); 3348} 3349 3350void AutofillDialogControllerImpl::LogDialogLatencyToShow() { 3351 if (was_ui_latency_logged_) 3352 return; 3353 3354 GetMetricLogger().LogDialogLatencyToShow( 3355 GetDialogType(), 3356 base::Time::Now() - dialog_shown_timestamp_); 3357 was_ui_latency_logged_ = true; 3358} 3359 3360void AutofillDialogControllerImpl::SetAutocheckoutState( 3361 AutocheckoutState autocheckout_state) { 3362 if (autocheckout_state_ == autocheckout_state) 3363 return; 3364 3365 autocheckout_state_ = autocheckout_state; 3366 if (view_) { 3367 view_->UpdateDetailArea(); 3368 view_->UpdateButtonStrip(); 3369 view_->UpdateAutocheckoutStepsArea(); 3370 view_->UpdateNotificationArea(); 3371 } 3372} 3373 3374void AutofillDialogControllerImpl::DeemphasizeRenderView() { 3375 web_contents()->GetRenderViewHost()->Send( 3376 new ChromeViewMsg_SetVisuallyDeemphasized( 3377 web_contents()->GetRenderViewHost()->GetRoutingID(), true)); 3378 deemphasized_render_view_ = true; 3379} 3380 3381AutofillMetrics::DialogInitialUserStateMetric 3382 AutofillDialogControllerImpl::GetInitialUserState() const { 3383 // Consider a user to be an Autofill user if the user has any credit cards 3384 // or addresses saved. Check that the item count is greater than 2 because 3385 // an "empty" menu still has the "add new" menu item and "manage" menu item. 3386 const bool has_autofill_profiles = 3387 suggested_cc_.GetItemCount() > 2 || 3388 suggested_billing_.GetItemCount() > 2; 3389 3390 if (SignedInState() != SIGNED_IN) { 3391 // Not signed in. 3392 return has_autofill_profiles ? 3393 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_HAS_AUTOFILL : 3394 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_NO_AUTOFILL; 3395 } 3396 3397 // Signed in. 3398 if (wallet_items_->instruments().empty()) { 3399 // No Wallet items. 3400 return has_autofill_profiles ? 3401 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_HAS_AUTOFILL : 3402 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_NO_AUTOFILL; 3403 } 3404 3405 // Has Wallet items. 3406 return has_autofill_profiles ? 3407 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_HAS_AUTOFILL : 3408 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_NO_AUTOFILL; 3409} 3410 3411void AutofillDialogControllerImpl::MaybeShowCreditCardBubble() { 3412 if (newly_saved_card_) { 3413 AutofillCreditCardBubbleController::ShowNewCardSavedBubble( 3414 web_contents(), newly_saved_card_->TypeAndLastFourDigits()); 3415 return; 3416 } 3417 3418 if (!full_wallet_ || !full_wallet_->billing_address() || 3419 !AutofillCreditCardBubbleController::ShouldShowGeneratedCardBubble( 3420 profile())) { 3421 // If this run of the dialog didn't result in a valid |full_wallet_| or the 3422 // generated card bubble shouldn't be shown now, don't show it again. 3423 return; 3424 } 3425 3426 base::string16 backing_last_four; 3427 if (ActiveInstrument()) { 3428 backing_last_four = ActiveInstrument()->TypeAndLastFourDigits(); 3429 } else { 3430 DetailOutputMap output; 3431 view_->GetUserInput(SECTION_CC_BILLING, &output); 3432 CreditCard card; 3433 GetBillingInfoFromOutputs(output, &card, NULL, NULL); 3434 backing_last_four = card.TypeAndLastFourDigits(); 3435 } 3436 AutofillCreditCardBubbleController::ShowGeneratedCardBubble( 3437 web_contents(), backing_last_four, full_wallet_->TypeAndLastFourDigits()); 3438} 3439 3440} // namespace autofill 3441