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