autofill_dialog_controller_impl.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 <string> 9 10#include "base/bind.h" 11#include "base/logging.h" 12#include "base/prefs/pref_service.h" 13#include "base/string_number_conversions.h" 14#include "base/string_util.h" 15#include "base/strings/string_split.h" 16#include "base/time.h" 17#include "base/utf_string_conversions.h" 18#include "chrome/browser/autofill/personal_data_manager_factory.h" 19#include "chrome/browser/browser_process.h" 20#include "chrome/browser/extensions/shell_window_registry.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/autofill/autofill_dialog_view.h" 23#include "chrome/browser/ui/autofill/data_model_wrapper.h" 24#include "chrome/browser/ui/base_window.h" 25#include "chrome/browser/ui/browser.h" 26#include "chrome/browser/ui/browser_finder.h" 27#include "chrome/browser/ui/browser_window.h" 28#include "chrome/browser/ui/extensions/native_app_window.h" 29#include "chrome/browser/ui/extensions/shell_window.h" 30#include "chrome/common/chrome_version_info.h" 31#include "chrome/common/pref_names.h" 32#include "components/autofill/browser/autofill_country.h" 33#include "components/autofill/browser/autofill_manager.h" 34#include "components/autofill/browser/autofill_type.h" 35#include "components/autofill/browser/personal_data_manager.h" 36#include "components/autofill/browser/risk/fingerprint.h" 37#include "components/autofill/browser/risk/proto/fingerprint.pb.h" 38#include "components/autofill/browser/validation.h" 39#include "components/autofill/browser/wallet/cart.h" 40#include "components/autofill/browser/wallet/full_wallet.h" 41#include "components/autofill/browser/wallet/instrument.h" 42#include "components/autofill/browser/wallet/wallet_address.h" 43#include "components/autofill/browser/wallet/wallet_items.h" 44#include "components/autofill/browser/wallet/wallet_service_url.h" 45#include "components/autofill/common/form_data.h" 46#include "components/user_prefs/pref_registry_syncable.h" 47#include "content/public/browser/navigation_controller.h" 48#include "content/public/browser/navigation_details.h" 49#include "content/public/browser/navigation_entry.h" 50#include "content/public/browser/notification_service.h" 51#include "content/public/browser/notification_types.h" 52#include "content/public/browser/web_contents.h" 53#include "content/public/browser/web_contents_view.h" 54#include "content/public/common/url_constants.h" 55#include "grit/chromium_strings.h" 56#include "grit/generated_resources.h" 57#include "grit/theme_resources.h" 58#include "grit/webkit_resources.h" 59#include "net/base/cert_status_flags.h" 60#include "ui/base/l10n/l10n_util.h" 61#include "ui/base/resource/resource_bundle.h" 62#include "ui/gfx/canvas.h" 63#include "ui/gfx/color_utils.h" 64#include "ui/gfx/skbitmap_operations.h" 65 66namespace autofill { 67 68namespace { 69 70const bool kPayWithoutWalletDefault = false; 71 72// This is a pseudo-scientifically chosen maximum amount we want a fronting 73// (proxy) card to be able to charge. The current actual max is $2000. Using 74// only $1850 leaves some room for tax and shipping, etc. TODO(dbeam): send a 75// special value to the server to just ask for the maximum so we don't need to 76// hardcode it here (http://crbug.com/180731). TODO(dbeam): also maybe allow 77// users to give us this number via an <input> (http://crbug.com/180733). 78const int kCartMax = 1850; 79const char kCartCurrency[] = "USD"; 80 81// Returns true if |input| should be shown when |field| has been requested. 82bool InputTypeMatchesFieldType(const DetailInput& input, 83 const AutofillField& field) { 84 // If any credit card expiration info is asked for, show both month and year 85 // inputs. 86 if (field.type() == CREDIT_CARD_EXP_4_DIGIT_YEAR || 87 field.type() == CREDIT_CARD_EXP_2_DIGIT_YEAR || 88 field.type() == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR || 89 field.type() == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR || 90 field.type() == CREDIT_CARD_EXP_MONTH) { 91 return input.type == CREDIT_CARD_EXP_4_DIGIT_YEAR || 92 input.type == CREDIT_CARD_EXP_MONTH; 93 } 94 95 if (field.type() == CREDIT_CARD_TYPE) 96 return input.type == CREDIT_CARD_NUMBER; 97 98 return input.type == field.type(); 99} 100 101// Returns true if |input| should be used for a site-requested |field|. 102bool DetailInputMatchesField(const DetailInput& input, 103 const AutofillField& field) { 104 bool right_section = !input.section_suffix || 105 EndsWith(field.section(), input.section_suffix, false); 106 return InputTypeMatchesFieldType(input, field) && right_section; 107} 108 109bool IsCreditCardType(AutofillFieldType type) { 110 return AutofillType(type).group() == AutofillType::CREDIT_CARD; 111} 112 113// Returns true if |input| should be used to fill a site-requested |field| which 114// is notated with a "shipping" tag, for use when the user has decided to use 115// the billing address as the shipping address. 116bool DetailInputMatchesShippingField(const DetailInput& input, 117 const AutofillField& field) { 118 if (input.section_suffix && 119 std::string(input.section_suffix) == "billing") { 120 return InputTypeMatchesFieldType(input, field); 121 } 122 123 if (field.type() == NAME_FULL) 124 return input.type == CREDIT_CARD_NAME; 125 126 return DetailInputMatchesField(input, field); 127} 128 129// Constructs |inputs| from template data. 130void BuildInputs(const DetailInput* input_template, 131 size_t template_size, 132 DetailInputs* inputs) { 133 for (size_t i = 0; i < template_size; ++i) { 134 const DetailInput* input = &input_template[i]; 135 inputs->push_back(*input); 136 } 137} 138 139// Uses |group| to fill in the |autofilled_value| for all inputs in |all_inputs| 140// (an out-param). 141void FillInputFromFormGroup(FormGroup* group, DetailInputs* inputs) { 142 const std::string app_locale = AutofillCountry::ApplicationLocale(); 143 for (size_t j = 0; j < inputs->size(); ++j) { 144 (*inputs)[j].autofilled_value = 145 group->GetInfo((*inputs)[j].type, app_locale); 146 } 147} 148 149// Initializes |form_group| from user-entered data. 150void FillFormGroupFromOutputs(const DetailOutputMap& detail_outputs, 151 FormGroup* form_group) { 152 for (DetailOutputMap::const_iterator iter = detail_outputs.begin(); 153 iter != detail_outputs.end(); ++iter) { 154 if (!iter->second.empty()) 155 form_group->SetRawInfo(iter->first->type, iter->second); 156 } 157} 158 159// Get billing info from |output| and put it into |card|, |cvc|, and |profile|. 160// These outparams are required because |card|/|profile| accept different types 161// of raw info, and CreditCard doesn't save CVCs. 162void GetBillingInfoFromOutputs(const DetailOutputMap& output, 163 CreditCard* card, 164 string16* cvc, 165 AutofillProfile* profile) { 166 for (DetailOutputMap::const_iterator it = output.begin(); 167 it != output.end(); ++it) { 168 string16 trimmed; 169 TrimWhitespace(it->second, TRIM_ALL, &trimmed); 170 171 // Special case CVC as CreditCard just swallows it. 172 if (it->first->type == CREDIT_CARD_VERIFICATION_CODE) { 173 cvc->assign(trimmed); 174 } else { 175 // Copy the credit card name to |profile| in addition to |card| as 176 // wallet::Instrument requires a recipient name for its billing address. 177 if (it->first->type == CREDIT_CARD_NAME) 178 profile->SetRawInfo(NAME_FULL, trimmed); 179 180 if (IsCreditCardType(it->first->type)) 181 card->SetRawInfo(it->first->type, trimmed); 182 else 183 profile->SetRawInfo(it->first->type, trimmed); 184 } 185 } 186} 187 188// Returns the containing window for the given |web_contents|. The containing 189// window might be a browser window for a Chrome tab, or it might be a shell 190// window for a platform app. 191BaseWindow* GetBaseWindowForWebContents( 192 const content::WebContents* web_contents) { 193 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 194 if (browser) 195 return browser->window(); 196 197 gfx::NativeWindow native_window = 198 web_contents->GetView()->GetTopLevelNativeWindow(); 199 ShellWindow* shell_window = 200 extensions::ShellWindowRegistry:: 201 GetShellWindowForNativeWindowAnyProfile(native_window); 202 return shell_window->GetBaseWindow(); 203} 204 205// Extracts the string value of a field with |type| from |output|. This is 206// useful when you only need the value of 1 input from a section of view inputs. 207string16 GetValueForType(const DetailOutputMap& output, 208 AutofillFieldType type) { 209 for (DetailOutputMap::const_iterator it = output.begin(); 210 it != output.end(); ++it) { 211 if (it->first->type == type) 212 return it->second; 213 } 214 NOTREACHED(); 215 return string16(); 216} 217 218} // namespace 219 220AutofillDialogController::~AutofillDialogController() {} 221 222AutofillDialogControllerImpl::AutofillDialogControllerImpl( 223 content::WebContents* contents, 224 const FormData& form, 225 const GURL& source_url, 226 const AutofillMetrics& metric_logger, 227 DialogType dialog_type, 228 const base::Callback<void(const FormStructure*)>& callback) 229 : profile_(Profile::FromBrowserContext(contents->GetBrowserContext())), 230 contents_(contents), 231 form_structure_(form, std::string()), 232 invoked_from_same_origin_(true), 233 source_url_(source_url), 234 ssl_status_(form.ssl_status), 235 callback_(callback), 236 ALLOW_THIS_IN_INITIALIZER_LIST( 237 account_chooser_model_(this, profile_->GetPrefs())), 238 ALLOW_THIS_IN_INITIALIZER_LIST( 239 wallet_client_(profile_->GetRequestContext(), this)), 240 ALLOW_THIS_IN_INITIALIZER_LIST(suggested_email_(this)), 241 ALLOW_THIS_IN_INITIALIZER_LIST(suggested_cc_(this)), 242 ALLOW_THIS_IN_INITIALIZER_LIST(suggested_billing_(this)), 243 ALLOW_THIS_IN_INITIALIZER_LIST(suggested_cc_billing_(this)), 244 ALLOW_THIS_IN_INITIALIZER_LIST(suggested_shipping_(this)), 245 section_showing_popup_(SECTION_BILLING), 246 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), 247 metric_logger_(metric_logger), 248 initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), 249 dialog_type_(dialog_type), 250 did_submit_(false), 251 autocheckout_is_running_(false), 252 had_autocheckout_error_(false) { 253 // TODO(estade): remove duplicates from |form|? 254} 255 256AutofillDialogControllerImpl::~AutofillDialogControllerImpl() { 257 if (popup_controller_) 258 popup_controller_->Hide(); 259 260 metric_logger_.LogDialogInitialUserState(dialog_type_, initial_user_state_); 261} 262 263// static 264void AutofillDialogControllerImpl::RegisterUserPrefs( 265 PrefRegistrySyncable* registry) { 266 registry->RegisterBooleanPref(prefs::kAutofillDialogPayWithoutWallet, 267 kPayWithoutWalletDefault, 268 PrefRegistrySyncable::SYNCABLE_PREF); 269} 270 271void AutofillDialogControllerImpl::Show() { 272 dialog_shown_timestamp_ = base::Time::Now(); 273 274 content::NavigationEntry* entry = contents_->GetController().GetActiveEntry(); 275 const GURL& active_url = entry ? entry->GetURL() : contents_->GetURL(); 276 invoked_from_same_origin_ = active_url.GetOrigin() == source_url_.GetOrigin(); 277 278 // Log any relevant security exceptions. 279 metric_logger_.LogDialogSecurityMetric( 280 dialog_type_, 281 AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); 282 283 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 284 metric_logger_.LogDialogSecurityMetric( 285 dialog_type_, 286 AutofillMetrics::SECURITY_METRIC_CREDIT_CARD_OVER_HTTP); 287 } 288 289 if (!invoked_from_same_origin_) { 290 metric_logger_.LogDialogSecurityMetric( 291 dialog_type_, 292 AutofillMetrics::SECURITY_METRIC_CROSS_ORIGIN_FRAME); 293 } 294 295 // Determine what field types should be included in the dialog. 296 bool has_types = false; 297 bool has_sections = false; 298 form_structure_.ParseFieldTypesFromAutocompleteAttributes(&has_types, 299 &has_sections); 300 // Fail if the author didn't specify autocomplete types. 301 if (!has_types) { 302 callback_.Run(NULL); 303 delete this; 304 return; 305 } 306 307 const DetailInput kEmailInputs[] = { 308 { 1, EMAIL_ADDRESS, IDS_AUTOFILL_DIALOG_PLACEHOLDER_EMAIL }, 309 }; 310 311 const DetailInput kCCInputs[] = { 312 { 2, CREDIT_CARD_NUMBER, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARD_NUMBER }, 313 { 3, CREDIT_CARD_EXP_MONTH }, 314 { 3, CREDIT_CARD_EXP_4_DIGIT_YEAR }, 315 { 3, CREDIT_CARD_VERIFICATION_CODE, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC }, 316 { 4, CREDIT_CARD_NAME, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARDHOLDER_NAME }, 317 }; 318 319 const DetailInput kBillingInputs[] = { 320 { 5, ADDRESS_HOME_LINE1, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1, 321 "billing" }, 322 { 6, ADDRESS_HOME_LINE2, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2, 323 "billing" }, 324 { 7, ADDRESS_HOME_CITY, IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY, 325 "billing" }, 326 // TODO(estade): state placeholder should depend on locale. 327 { 8, ADDRESS_HOME_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE, "billing" }, 328 { 8, ADDRESS_HOME_ZIP, IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE, 329 "billing", 0.5 }, 330 // TODO(estade): this should have a default based on the locale. 331 { 9, ADDRESS_HOME_COUNTRY, 0, "billing" }, 332 { 10, PHONE_HOME_WHOLE_NUMBER, 333 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER, "billing" }, 334 }; 335 336 const DetailInput kShippingInputs[] = { 337 { 11, NAME_FULL, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESSEE_NAME, 338 "shipping" }, 339 { 12, ADDRESS_HOME_LINE1, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1, 340 "shipping" }, 341 { 13, ADDRESS_HOME_LINE2, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2, 342 "shipping" }, 343 { 14, ADDRESS_HOME_CITY, IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY, 344 "shipping" }, 345 { 15, ADDRESS_HOME_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE, "shipping" }, 346 { 15, ADDRESS_HOME_ZIP, IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE, 347 "shipping", 0.5 }, 348 { 16, ADDRESS_HOME_COUNTRY, 0, "shipping" }, 349 { 17, PHONE_HOME_WHOLE_NUMBER, 350 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER, "shipping" }, 351 }; 352 353 BuildInputs(kEmailInputs, 354 arraysize(kEmailInputs), 355 &requested_email_fields_); 356 357 BuildInputs(kCCInputs, 358 arraysize(kCCInputs), 359 &requested_cc_fields_); 360 361 BuildInputs(kBillingInputs, 362 arraysize(kBillingInputs), 363 &requested_billing_fields_); 364 365 BuildInputs(kCCInputs, 366 arraysize(kCCInputs), 367 &requested_cc_billing_fields_); 368 BuildInputs(kBillingInputs, 369 arraysize(kBillingInputs), 370 &requested_cc_billing_fields_); 371 372 BuildInputs(kShippingInputs, 373 arraysize(kShippingInputs), 374 &requested_shipping_fields_); 375 376 GenerateSuggestionsModels(); 377 378 // TODO(estade): don't show the dialog if the site didn't specify the right 379 // fields. First we must figure out what the "right" fields are. 380 view_.reset(CreateView()); 381 view_->Show(); 382 GetManager()->AddObserver(this); 383 384 // Request sugar info after the view is showing to simplify code for now. 385 if (IsPayingWithWallet()) { 386 // TODO(dbeam): Add Risk capabilites once the UI supports risk challenges. 387 GetWalletClient()->GetWalletItems( 388 source_url_, 389 std::vector<wallet::WalletClient::RiskCapability>()); 390 } 391} 392 393void AutofillDialogControllerImpl::Hide() { 394 if (view_) 395 view_->Hide(); 396} 397 398void AutofillDialogControllerImpl::UpdateProgressBar(double value) { 399 view_->UpdateProgressBar(value); 400} 401 402void AutofillDialogControllerImpl::OnAutocheckoutError() { 403 had_autocheckout_error_ = true; 404 autocheckout_is_running_ = false; 405 view_->UpdateNotificationArea(); 406 view_->UpdateButtonStrip(); 407} 408 409//////////////////////////////////////////////////////////////////////////////// 410// AutofillDialogController implementation. 411 412string16 AutofillDialogControllerImpl::DialogTitle() const { 413 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_TITLE); 414} 415 416string16 AutofillDialogControllerImpl::EditSuggestionText() const { 417 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EDIT); 418} 419 420string16 AutofillDialogControllerImpl::UseBillingForShippingText() const { 421 return l10n_util::GetStringUTF16( 422 IDS_AUTOFILL_DIALOG_USE_BILLING_FOR_SHIPPING); 423} 424 425string16 AutofillDialogControllerImpl::CancelButtonText() const { 426 return l10n_util::GetStringUTF16(IDS_CANCEL); 427} 428 429string16 AutofillDialogControllerImpl::ConfirmButtonText() const { 430 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON); 431} 432 433string16 AutofillDialogControllerImpl::CancelSignInText() const { 434 // TODO(abodenha): real strings and l10n. 435 return ASCIIToUTF16("Don't sign in."); 436} 437 438string16 AutofillDialogControllerImpl::SaveLocallyText() const { 439 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX); 440} 441 442string16 AutofillDialogControllerImpl::ProgressBarText() const { 443 return l10n_util::GetStringUTF16( 444 IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_PROGRESS_BAR); 445} 446 447DialogSignedInState AutofillDialogControllerImpl::SignedInState() const { 448 if (!wallet_items_) 449 return REQUIRES_RESPONSE; 450 451 if (wallet_items_->HasRequiredAction(wallet::GAIA_AUTH)) 452 return REQUIRES_SIGN_IN; 453 454 if (wallet_items_->HasRequiredAction(wallet::PASSIVE_GAIA_AUTH)) 455 return REQUIRES_PASSIVE_SIGN_IN; 456 457 return SIGNED_IN; 458} 459 460string16 AutofillDialogControllerImpl::AccountChooserText() const { 461 if (!IsPayingWithWallet()) 462 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PAY_WITHOUT_WALLET); 463 464 // TODO(dbeam): real strings and l10n. 465 if (SignedInState() == SIGNED_IN) 466 return ASCIIToUTF16("user@example.com"); 467 468 // In this case, the account chooser should be showing the signin link. 469 return string16(); 470} 471 472string16 AutofillDialogControllerImpl::SignInLinkText() const { 473 // TODO(estade): real strings and l10n. 474 return ASCIIToUTF16("Sign in to use Google Wallet"); 475} 476 477bool AutofillDialogControllerImpl::ShouldOfferToSaveInChrome() const { 478 return !IsPayingWithWallet(); 479} 480 481bool AutofillDialogControllerImpl::AutocheckoutIsRunning() const { 482 return autocheckout_is_running_; 483} 484 485bool AutofillDialogControllerImpl::HadAutocheckoutError() const { 486 return had_autocheckout_error_; 487} 488 489bool AutofillDialogControllerImpl::IsDialogButtonEnabled( 490 ui::DialogButton button) const { 491 if (button == ui::DIALOG_BUTTON_OK) 492 return !did_submit_; 493 DCHECK_EQ(ui::DIALOG_BUTTON_CANCEL, button); 494 // TODO(ahutter): Make it possible for the user to cancel out of the dialog 495 // while Autocheckout is in progress. 496 return had_autocheckout_error_ || !did_submit_; 497} 498 499bool AutofillDialogControllerImpl::SectionIsActive(DialogSection section) 500 const { 501 if (IsPayingWithWallet()) 502 return section != SECTION_BILLING && section != SECTION_CC; 503 504 return section != SECTION_CC_BILLING; 505} 506 507bool AutofillDialogControllerImpl::HasCompleteWallet() const { 508 return wallet_items_.get() != NULL && 509 !wallet_items_->instruments().empty() && 510 !wallet_items_->addresses().empty(); 511} 512 513const DetailInputs& AutofillDialogControllerImpl::RequestedFieldsForSection( 514 DialogSection section) const { 515 switch (section) { 516 case SECTION_EMAIL: 517 return requested_email_fields_; 518 case SECTION_CC: 519 return requested_cc_fields_; 520 case SECTION_BILLING: 521 return requested_billing_fields_; 522 case SECTION_CC_BILLING: 523 return requested_cc_billing_fields_; 524 case SECTION_SHIPPING: 525 return requested_shipping_fields_; 526 } 527 528 NOTREACHED(); 529 return requested_billing_fields_; 530} 531 532ui::ComboboxModel* AutofillDialogControllerImpl::ComboboxModelForAutofillType( 533 AutofillFieldType type) { 534 switch (type) { 535 case CREDIT_CARD_EXP_MONTH: 536 return &cc_exp_month_combobox_model_; 537 538 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 539 return &cc_exp_year_combobox_model_; 540 541 case ADDRESS_HOME_COUNTRY: 542 return &country_combobox_model_; 543 544 default: 545 return NULL; 546 } 547} 548 549ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSection( 550 DialogSection section) { 551 return SuggestionsMenuModelForSection(section); 552} 553 554ui::MenuModel* AutofillDialogControllerImpl::MenuModelForAccountChooser() { 555 // When paying with wallet, but not signed in, there is no menu, just a 556 // sign in link. 557 if (IsPayingWithWallet() && SignedInState() != SIGNED_IN) 558 return NULL; 559 560 return &account_chooser_model_; 561} 562 563gfx::Image AutofillDialogControllerImpl::AccountChooserImage() { 564 if (!MenuModelForAccountChooser()) { 565 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 566 IDR_WALLET_ICON); 567 } 568 569 gfx::Image icon; 570 account_chooser_model_.GetIconAt(account_chooser_model_.checked_item(), 571 &icon); 572 return icon; 573} 574 575string16 AutofillDialogControllerImpl::LabelForSection(DialogSection section) 576 const { 577 switch (section) { 578 case SECTION_EMAIL: 579 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_EMAIL); 580 case SECTION_CC: 581 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC); 582 case SECTION_BILLING: 583 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_BILLING); 584 case SECTION_CC_BILLING: 585 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC_BILLING); 586 case SECTION_SHIPPING: 587 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_SHIPPING); 588 default: 589 NOTREACHED(); 590 return string16(); 591 } 592} 593 594string16 AutofillDialogControllerImpl::SuggestionTextForSection( 595 DialogSection section) { 596 // When the user has clicked 'edit', don't show a suggestion (even though 597 // there is a profile selected in the model). 598 if (section_editing_state_[section]) 599 return string16(); 600 601 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 602 std::string item_key = model->GetItemKeyForCheckedItem(); 603 if (item_key.empty()) 604 return string16(); 605 606 if (section == SECTION_EMAIL) 607 return model->GetLabelAt(model->checked_item()); 608 609 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 610 return wrapper->GetDisplayText(); 611} 612 613scoped_ptr<DataModelWrapper> AutofillDialogControllerImpl::CreateWrapper( 614 DialogSection section) { 615 if (IsPayingWithWallet() && full_wallet_) { 616 if (section == SECTION_CC_BILLING) { 617 return scoped_ptr<DataModelWrapper>( 618 new FullWalletBillingWrapper(full_wallet_.get())); 619 } 620 if (section == SECTION_SHIPPING) { 621 return scoped_ptr<DataModelWrapper>( 622 new FullWalletShippingWrapper(full_wallet_.get())); 623 } 624 } 625 626 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 627 std::string item_key = model->GetItemKeyForCheckedItem(); 628 if (item_key.empty()) 629 return scoped_ptr<DataModelWrapper>(); 630 631 if (IsPayingWithWallet()) { 632 int index; 633 bool success = base::StringToInt(item_key, &index); 634 DCHECK(success); 635 636 if (section == SECTION_CC_BILLING) { 637 return scoped_ptr<DataModelWrapper>( 638 new WalletInstrumentWrapper(wallet_items_->instruments()[index])); 639 } 640 // TODO(dbeam): should SECTION_EMAIL get here? 641 return scoped_ptr<DataModelWrapper>( 642 new WalletAddressWrapper(wallet_items_->addresses()[index])); 643 } 644 645 if (section == SECTION_CC) { 646 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); 647 DCHECK(card); 648 return scoped_ptr<DataModelWrapper>(new AutofillCreditCardWrapper(card)); 649 } 650 651 // Calculate the variant by looking at how many items come from the same 652 // FormGroup. 653 size_t variant = 0; 654 for (int i = model->checked_item() - 1; i >= 0; --i) { 655 if (model->GetItemKeyAt(i) == item_key) 656 variant++; 657 else 658 break; 659 } 660 661 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); 662 DCHECK(profile); 663 return scoped_ptr<DataModelWrapper>( 664 new AutofillProfileWrapper(profile, variant)); 665} 666 667gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( 668 DialogSection section) { 669 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 670 if (!model.get()) 671 return gfx::Image(); 672 673 return model->GetIcon(); 674} 675 676void AutofillDialogControllerImpl::EditClickedForSection( 677 DialogSection section) { 678 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 679 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 680 model->FillInputs(inputs); 681 section_editing_state_[section] = true; 682 view_->UpdateSection(section); 683} 684 685gfx::Image AutofillDialogControllerImpl::IconForField( 686 AutofillFieldType type, const string16& user_input) const { 687 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 688 if (type == CREDIT_CARD_VERIFICATION_CODE) 689 return rb.GetImageNamed(IDR_CREDIT_CARD_CVC_HINT); 690 691 // For the credit card, we show a few grayscale images, and possibly one 692 // color image if |user_input| is a valid card number. 693 if (type == CREDIT_CARD_NUMBER) { 694 const int card_idrs[] = { 695 IDR_AUTOFILL_CC_VISA, 696 IDR_AUTOFILL_CC_MASTERCARD, 697 IDR_AUTOFILL_CC_AMEX, 698 IDR_AUTOFILL_CC_DISCOVER 699 }; 700 const int number_of_cards = arraysize(card_idrs); 701 // The number of pixels between card icons. 702 const int kCardPadding = 2; 703 704 gfx::ImageSkia some_card = *rb.GetImageSkiaNamed(card_idrs[0]); 705 const int card_width = some_card.width(); 706 gfx::Canvas canvas( 707 gfx::Size((card_width + kCardPadding) * number_of_cards - kCardPadding, 708 some_card.height()), 709 ui::SCALE_FACTOR_100P, 710 true); 711 CreditCard card; 712 card.SetRawInfo(CREDIT_CARD_NUMBER, user_input); 713 714 for (int i = 0; i < number_of_cards; ++i) { 715 int idr = card_idrs[i]; 716 gfx::ImageSkia card_image = *rb.GetImageSkiaNamed(idr); 717 if (card.IconResourceId() != idr) { 718 color_utils::HSL shift = {-1, 0, 0.8}; 719 SkBitmap disabled_bitmap = 720 SkBitmapOperations::CreateHSLShiftedBitmap(*card_image.bitmap(), 721 shift); 722 card_image = gfx::ImageSkia::CreateFrom1xBitmap(disabled_bitmap); 723 } 724 725 canvas.DrawImageInt(card_image, i * (card_width + kCardPadding), 0); 726 } 727 728 gfx::ImageSkia skia(canvas.ExtractImageRep()); 729 return gfx::Image(skia); 730 } 731 732 return gfx::Image(); 733} 734 735bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, 736 const string16& value) { 737 switch (type) { 738 case EMAIL_ADDRESS: 739 return IsValidEmailAddress(value); 740 741 case CREDIT_CARD_NUMBER: 742 return autofill::IsValidCreditCardNumber(value); 743 case CREDIT_CARD_NAME: 744 break; 745 case CREDIT_CARD_EXP_MONTH: 746 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 747 break; 748 case CREDIT_CARD_VERIFICATION_CODE: 749 return autofill::IsValidCreditCardSecurityCode(value); 750 751 case ADDRESS_HOME_LINE1: 752 break; 753 case ADDRESS_HOME_LINE2: 754 return true; // Line 2 is optional - always valid. 755 case ADDRESS_HOME_CITY: 756 case ADDRESS_HOME_STATE: 757 case ADDRESS_HOME_ZIP: 758 case ADDRESS_HOME_COUNTRY: 759 break; 760 761 case NAME_FULL: // Used for shipping. 762 break; 763 764 case PHONE_HOME_WHOLE_NUMBER: // Used in billing section. 765 // TODO(dbeam): validate with libphonenumber. 766 break; 767 768 default: 769 NOTREACHED(); // Trying to validate unknown field. 770 break; 771 } 772 773 return !value.empty(); 774} 775 776std::vector<AutofillFieldType> AutofillDialogControllerImpl::InputsAreValid( 777 const DetailOutputMap& inputs) { 778 std::vector<AutofillFieldType> invalid_fields; 779 std::map<AutofillFieldType, string16> field_values; 780 for (DetailOutputMap::const_iterator iter = inputs.begin(); 781 iter != inputs.end(); ++iter) { 782 field_values[iter->first->type] = iter->second; 783 if (!InputIsValid(iter->first->type, iter->second)) 784 invalid_fields.push_back(iter->first->type); 785 } 786 787 // If the dialog has a CC month, there must 4 digit year. 788 // Validate the date formed by month and appropriate year field. 789 if (field_values.count(CREDIT_CARD_EXP_MONTH)) { 790 DCHECK(field_values.count(CREDIT_CARD_EXP_4_DIGIT_YEAR)); 791 if (!autofill::IsValidCreditCardExpirationDate( 792 field_values[CREDIT_CARD_EXP_4_DIGIT_YEAR], 793 field_values[CREDIT_CARD_EXP_MONTH], 794 base::Time::Now())) { 795 invalid_fields.push_back(CREDIT_CARD_EXP_MONTH); 796 invalid_fields.push_back(CREDIT_CARD_EXP_4_DIGIT_YEAR); 797 } 798 } 799 800 // If there is a credit card number and a CVC, validate them together. 801 if (field_values.count(CREDIT_CARD_NUMBER) && 802 field_values.count(CREDIT_CARD_VERIFICATION_CODE)) { 803 if (!autofill::IsValidCreditCardSecurityCode( 804 field_values[CREDIT_CARD_VERIFICATION_CODE], 805 field_values[CREDIT_CARD_NUMBER])) { 806 invalid_fields.push_back(CREDIT_CARD_VERIFICATION_CODE); 807 } 808 } 809 810 // De-duplicate invalid fields. 811 std::sort(invalid_fields.begin(), invalid_fields.end()); 812 invalid_fields.erase(std::unique( 813 invalid_fields.begin(), invalid_fields.end()), invalid_fields.end()); 814 815 return invalid_fields; 816} 817 818void AutofillDialogControllerImpl::UserEditedOrActivatedInput( 819 const DetailInput* input, 820 DialogSection section, 821 gfx::NativeView parent_view, 822 const gfx::Rect& content_bounds, 823 const string16& field_contents, 824 bool was_edit) { 825 // If the field is edited down to empty, don't show a popup. 826 if (was_edit && field_contents.empty()) { 827 HidePopup(); 828 return; 829 } 830 831 // If the user clicks while the popup is already showing, be sure to hide 832 // it. 833 if (!was_edit && popup_controller_) { 834 HidePopup(); 835 return; 836 } 837 838 std::vector<string16> popup_values, popup_labels, popup_icons; 839 if (section == SECTION_CC) { 840 GetManager()->GetCreditCardSuggestions(input->type, 841 field_contents, 842 &popup_values, 843 &popup_labels, 844 &popup_icons, 845 &popup_guids_); 846 } else { 847 // TODO(estade): add field types from email section? 848 const DetailInputs& inputs = RequestedFieldsForSection(section); 849 std::vector<AutofillFieldType> field_types; 850 field_types.reserve(inputs.size()); 851 for (DetailInputs::const_iterator iter = inputs.begin(); 852 iter != inputs.end(); ++iter) { 853 field_types.push_back(iter->type); 854 } 855 GetManager()->GetProfileSuggestions(input->type, 856 field_contents, 857 false, 858 field_types, 859 &popup_values, 860 &popup_labels, 861 &popup_icons, 862 &popup_guids_); 863 } 864 865 if (popup_values.empty()) 866 return; 867 868 // TODO(estade): do we need separators and control rows like 'Clear 869 // Form'? 870 std::vector<int> popup_ids; 871 for (size_t i = 0; i < popup_guids_.size(); ++i) { 872 popup_ids.push_back(i); 873 } 874 875 popup_controller_ = AutofillPopupControllerImpl::GetOrCreate( 876 popup_controller_, this, parent_view, content_bounds); 877 popup_controller_->Show(popup_values, 878 popup_labels, 879 popup_icons, 880 popup_ids); 881 section_showing_popup_ = section; 882} 883 884void AutofillDialogControllerImpl::FocusMoved() { 885 HidePopup(); 886} 887 888void AutofillDialogControllerImpl::ViewClosed() { 889 GetManager()->RemoveObserver(this); 890 891 if (autocheckout_is_running_ || had_autocheckout_error_) { 892 AutofillMetrics::AutocheckoutCompletionStatus metric = 893 autocheckout_is_running_ ? 894 AutofillMetrics::AUTOCHECKOUT_SUCCEEDED : 895 AutofillMetrics::AUTOCHECKOUT_FAILED; 896 metric_logger_.LogAutocheckoutDuration( 897 base::Time::Now() - autocheckout_started_timestamp_, 898 metric); 899 } 900 901 // On a successful submit, save the payment type for next time. If there 902 // was a Wallet server error, try to pay with Wallet again next time. 903 // Reset the view so that updates to the pref aren't processed. 904 view_.reset(); 905 bool pay_without_wallet = !IsPayingWithWallet() && 906 !account_chooser_model_.had_wallet_error(); 907 profile_->GetPrefs()->SetBoolean(prefs::kAutofillDialogPayWithoutWallet, 908 pay_without_wallet); 909 910 delete this; 911} 912 913std::vector<DialogNotification> 914 AutofillDialogControllerImpl::CurrentNotifications() const { 915 std::vector<DialogNotification> notifications; 916 917 if (IsPayingWithWallet()) { 918 // TODO(dbeam): what about REQUIRES_PASSIVE_SIGN_IN? 919 if (SignedInState() == SIGNED_IN) { 920 // On first run with a complete wallet profile, show a notification 921 // explaining where this data came from. 922 if (IsFirstRun() && HasCompleteWallet()) { 923 notifications.push_back( 924 DialogNotification( 925 DialogNotification::EXPLANATORY_MESSAGE, 926 l10n_util::GetStringUTF16( 927 IDS_AUTOFILL_DIALOG_DETAILS_FROM_WALLET))); 928 } else { 929 notifications.push_back( 930 DialogNotification( 931 DialogNotification::WALLET_PROMO, 932 l10n_util::GetStringUTF16( 933 IDS_AUTOFILL_DIALOG_SAVE_DETAILS_IN_WALLET))); 934 } 935 } else if (IsFirstRun()) { 936 // If the user is not signed in, show an upsell notification on first run. 937 notifications.push_back( 938 DialogNotification( 939 DialogNotification::WALLET_PROMO, 940 l10n_util::GetStringUTF16( 941 IDS_AUTOFILL_DIALOG_SIGN_IN_AND_SAVE_DETAILS))); 942 } 943 } 944 945 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 946 notifications.push_back( 947 DialogNotification( 948 DialogNotification::SECURITY_WARNING, 949 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECURITY_WARNING))); 950 } 951 952 if (!invoked_from_same_origin_) { 953 notifications.push_back( 954 DialogNotification( 955 DialogNotification::SECURITY_WARNING, 956 l10n_util::GetStringFUTF16( 957 IDS_AUTOFILL_DIALOG_SITE_WARNING, 958 UTF8ToUTF16(source_url_.host())))); 959 } 960 961 if (had_autocheckout_error_) { 962 notifications.push_back( 963 DialogNotification( 964 DialogNotification::AUTOCHECKOUT_ERROR, 965 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_ERROR))); 966 } 967 968 if (account_chooser_model_.had_wallet_error()) { 969 // TODO(dbeam): pass along the Wallet error or remove from the translation. 970 // TODO(dbeam): figure out a way to dismiss this error after a while. 971 notifications.push_back(DialogNotification( 972 DialogNotification::WALLET_ERROR, 973 l10n_util::GetStringFUTF16( 974 IDS_AUTOFILL_DIALOG_COMPLETE_WITHOUT_WALLET, 975 ASCIIToUTF16("Oops, [Wallet-Error].")))); 976 } 977 978 return notifications; 979} 980 981void AutofillDialogControllerImpl::StartSignInFlow() { 982 DCHECK(registrar_.IsEmpty()); 983 984 content::Source<content::NavigationController> source(view_->ShowSignIn()); 985 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, source); 986} 987 988void AutofillDialogControllerImpl::EndSignInFlow() { 989 DCHECK(!registrar_.IsEmpty()); 990 registrar_.RemoveAll(); 991 view_->HideSignIn(); 992} 993 994void AutofillDialogControllerImpl::OnCancel() { 995 if (!did_submit_) { 996 metric_logger_.LogDialogUiDuration( 997 base::Time::Now() - dialog_shown_timestamp_, 998 dialog_type_, 999 AutofillMetrics::DIALOG_CANCELED); 1000 } 1001 1002 // If Autocheckout has an error, it's possible that the dialog will be 1003 // submitted to start the flow and then cancelled to close the dialog after 1004 // the error. 1005 if (!callback_.is_null()) { 1006 callback_.Run(NULL); 1007 callback_ = base::Callback<void(const FormStructure*)>(); 1008 } 1009} 1010 1011void AutofillDialogControllerImpl::OnSubmit() { 1012 did_submit_ = true; 1013 metric_logger_.LogDialogUiDuration( 1014 base::Time::Now() - dialog_shown_timestamp_, 1015 dialog_type_, 1016 AutofillMetrics::DIALOG_ACCEPTED); 1017 1018 if (dialog_type_ == DIALOG_TYPE_AUTOCHECKOUT) { 1019 // Stop observing PersonalDataManager to avoid the dialog redrawing while 1020 // in an Autocheckout flow. 1021 GetManager()->RemoveObserver(this); 1022 autocheckout_is_running_ = true; 1023 autocheckout_started_timestamp_ = base::Time::Now(); 1024 view_->UpdateButtonStrip(); 1025 } 1026 1027 if (IsPayingWithWallet()) 1028 SubmitWithWallet(); 1029 else 1030 FinishSubmit(); 1031} 1032 1033Profile* AutofillDialogControllerImpl::profile() { 1034 return profile_; 1035} 1036 1037content::WebContents* AutofillDialogControllerImpl::web_contents() { 1038 return contents_; 1039} 1040 1041//////////////////////////////////////////////////////////////////////////////// 1042// AutofillPopupDelegate implementation. 1043 1044void AutofillDialogControllerImpl::OnPopupShown( 1045 content::KeyboardListener* listener) { 1046 metric_logger_.LogDialogPopupEvent( 1047 dialog_type_, AutofillMetrics::DIALOG_POPUP_SHOWN); 1048} 1049 1050void AutofillDialogControllerImpl::OnPopupHidden( 1051 content::KeyboardListener* listener) {} 1052 1053void AutofillDialogControllerImpl::DidSelectSuggestion(int identifier) { 1054 // TODO(estade): implement. 1055} 1056 1057void AutofillDialogControllerImpl::DidAcceptSuggestion(const string16& value, 1058 int identifier) { 1059 const PersonalDataManager::GUIDPair& pair = popup_guids_[identifier]; 1060 1061 FormGroup* form_group = section_showing_popup_ == SECTION_CC ? 1062 static_cast<FormGroup*>(GetManager()->GetCreditCardByGUID(pair.first)) : 1063 // TODO(estade): need to use the variant, |pair.second|. 1064 static_cast<FormGroup*>(GetManager()->GetProfileByGUID(pair.first)); 1065 DCHECK(form_group); 1066 1067 FillInputFromFormGroup( 1068 form_group, 1069 MutableRequestedFieldsForSection(section_showing_popup_)); 1070 view_->UpdateSection(section_showing_popup_); 1071 1072 metric_logger_.LogDialogPopupEvent( 1073 dialog_type_, AutofillMetrics::DIALOG_POPUP_FORM_FILLED); 1074 1075 // TODO(estade): not sure why it's necessary to do this explicitly. 1076 HidePopup(); 1077} 1078 1079void AutofillDialogControllerImpl::RemoveSuggestion(const string16& value, 1080 int identifier) { 1081 // TODO(estade): implement. 1082} 1083 1084void AutofillDialogControllerImpl::ClearPreviewedForm() { 1085 // TODO(estade): implement. 1086} 1087 1088//////////////////////////////////////////////////////////////////////////////// 1089// content::NotificationObserver implementation. 1090 1091void AutofillDialogControllerImpl::Observe( 1092 int type, 1093 const content::NotificationSource& source, 1094 const content::NotificationDetails& details) { 1095 DCHECK_EQ(type, content::NOTIFICATION_NAV_ENTRY_COMMITTED); 1096 content::LoadCommittedDetails* load_details = 1097 content::Details<content::LoadCommittedDetails>(details).ptr(); 1098 if (wallet::IsSignInContinueUrl(load_details->entry->GetVirtualURL())) { 1099 EndSignInFlow(); 1100 if (IsPayingWithWallet()) { 1101 // TODO(dbeam): Add Risk capabilites once the UI supports risk challenges. 1102 GetWalletClient()->GetWalletItems( 1103 source_url_, 1104 std::vector<wallet::WalletClient::RiskCapability>()); 1105 } 1106 } 1107} 1108 1109//////////////////////////////////////////////////////////////////////////////// 1110// SuggestionsMenuModelDelegate implementation. 1111 1112void AutofillDialogControllerImpl::SuggestionItemSelected( 1113 const SuggestionsMenuModel& model) { 1114 DialogSection section = SectionForSuggestionsMenuModel(model); 1115 section_editing_state_[section] = false; 1116 view_->UpdateSection(section); 1117} 1118 1119//////////////////////////////////////////////////////////////////////////////// 1120// wallet::WalletClientDelegate implementation. 1121 1122const AutofillMetrics& AutofillDialogControllerImpl::GetMetricLogger() const { 1123 return metric_logger_; 1124} 1125 1126DialogType AutofillDialogControllerImpl::GetDialogType() const { 1127 return dialog_type_; 1128} 1129 1130std::string AutofillDialogControllerImpl::GetRiskData() const { 1131 // TODO(dbeam): Implement this. 1132 return "risky business"; 1133} 1134 1135void AutofillDialogControllerImpl::OnDidAcceptLegalDocuments() { 1136} 1137 1138void AutofillDialogControllerImpl::OnDidAuthenticateInstrument(bool success) { 1139 // TODO(dbeam): use the returned full wallet. b/8332329 1140 if (success) 1141 GetFullWallet(); 1142 else 1143 DisableWallet(); 1144} 1145 1146void AutofillDialogControllerImpl::OnDidGetFullWallet( 1147 scoped_ptr<wallet::FullWallet> full_wallet) { 1148 // TODO(dbeam): handle more required actions. 1149 full_wallet_ = full_wallet.Pass(); 1150 1151 if (full_wallet_->HasRequiredAction(wallet::VERIFY_CVV)) 1152 DisableWallet(); 1153 else 1154 FinishSubmit(); 1155} 1156 1157void AutofillDialogControllerImpl::OnDidGetWalletItems( 1158 scoped_ptr<wallet::WalletItems> wallet_items) { 1159 // TODO(dbeam): verify all items support kCartCurrency? 1160 wallet_items_ = wallet_items.Pass(); 1161 GenerateSuggestionsModels(); 1162 view_->ModelChanged(); 1163 view_->UpdateAccountChooser(); 1164 view_->UpdateNotificationArea(); 1165 1166 // On the first successful response, compute the initial user state metric. 1167 if (initial_user_state_ == AutofillMetrics::DIALOG_USER_STATE_UNKNOWN) 1168 initial_user_state_ = GetInitialUserState(); 1169} 1170 1171void AutofillDialogControllerImpl::OnDidSaveAddress( 1172 const std::string& address_id, 1173 const std::vector<wallet::RequiredAction>& required_actions) { 1174 // TODO(dbeam): handle required actions. 1175 active_address_id_ = address_id; 1176 GetFullWallet(); 1177} 1178 1179void AutofillDialogControllerImpl::OnDidSaveInstrument( 1180 const std::string& instrument_id, 1181 const std::vector<wallet::RequiredAction>& required_actions) { 1182 // TODO(dbeam): handle required actions. 1183 active_instrument_id_ = instrument_id; 1184 GetFullWallet(); 1185} 1186 1187void AutofillDialogControllerImpl::OnDidSaveInstrumentAndAddress( 1188 const std::string& instrument_id, 1189 const std::string& address_id, 1190 const std::vector<wallet::RequiredAction>& required_actions) { 1191 // TODO(dbeam): handle required actions. 1192 active_instrument_id_ = instrument_id; 1193 active_address_id_ = address_id; 1194 GetFullWallet(); 1195} 1196 1197void AutofillDialogControllerImpl::OnDidSendAutocheckoutStatus() { 1198 NOTIMPLEMENTED(); 1199} 1200 1201void AutofillDialogControllerImpl::OnDidUpdateAddress( 1202 const std::string& address_id, 1203 const std::vector<wallet::RequiredAction>& required_actions) { 1204 // TODO(dbeam): Handle this callback. 1205 NOTIMPLEMENTED() << " address_id=" << address_id; 1206} 1207 1208void AutofillDialogControllerImpl::OnDidUpdateInstrument( 1209 const std::string& instrument_id, 1210 const std::vector<wallet::RequiredAction>& required_actions) { 1211 // TODO(dbeam): handle required actions. 1212} 1213 1214void AutofillDialogControllerImpl::OnWalletError( 1215 wallet::WalletClient::ErrorType error_type) { 1216 // TODO(dbeam): Do something with |error_type|. 1217 DisableWallet(); 1218} 1219 1220void AutofillDialogControllerImpl::OnMalformedResponse() { 1221 DisableWallet(); 1222} 1223 1224void AutofillDialogControllerImpl::OnNetworkError(int response_code) { 1225 DisableWallet(); 1226} 1227 1228//////////////////////////////////////////////////////////////////////////////// 1229// PersonalDataManagerObserver implementation. 1230 1231void AutofillDialogControllerImpl::OnPersonalDataChanged() { 1232 HidePopup(); 1233 GenerateSuggestionsModels(); 1234 view_->ModelChanged(); 1235} 1236 1237void AutofillDialogControllerImpl::AccountChoiceChanged() { 1238 if (!view_) 1239 return; 1240 1241 GenerateSuggestionsModels(); 1242 view_->ModelChanged(); 1243 view_->UpdateAccountChooser(); 1244 view_->UpdateNotificationArea(); 1245 1246 if (IsPayingWithWallet() && !wallet_items_) { 1247 // TODO(dbeam): Add Risk capabilites once the UI supports risk challenges. 1248 GetWalletClient()->GetWalletItems( 1249 source_url_, 1250 std::vector<wallet::WalletClient::RiskCapability>()); 1251 } 1252} 1253 1254//////////////////////////////////////////////////////////////////////////////// 1255 1256bool AutofillDialogControllerImpl::HandleKeyPressEventInInput( 1257 const content::NativeWebKeyboardEvent& event) { 1258 if (popup_controller_) 1259 return popup_controller_->HandleKeyPressEvent(event); 1260 1261 return false; 1262} 1263 1264bool AutofillDialogControllerImpl::RequestingCreditCardInfo() const { 1265 DCHECK_GT(form_structure_.field_count(), 0U); 1266 1267 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1268 if (IsCreditCardType(form_structure_.field(i)->type())) 1269 return true; 1270 } 1271 1272 return false; 1273} 1274 1275bool AutofillDialogControllerImpl::TransmissionWillBeSecure() const { 1276 return source_url_.SchemeIs(chrome::kHttpsScheme) && 1277 !net::IsCertStatusError(ssl_status_.cert_status) && 1278 !net::IsCertStatusMinorError(ssl_status_.cert_status); 1279} 1280 1281AutofillDialogView* AutofillDialogControllerImpl::CreateView() { 1282 return AutofillDialogView::Create(this); 1283} 1284 1285PersonalDataManager* AutofillDialogControllerImpl::GetManager() { 1286 return PersonalDataManagerFactory::GetForProfile(profile_); 1287} 1288 1289wallet::WalletClient* AutofillDialogControllerImpl::GetWalletClient() { 1290 return &wallet_client_; 1291} 1292 1293bool AutofillDialogControllerImpl::IsPayingWithWallet() const { 1294 return account_chooser_model_.WalletIsSelected(); 1295} 1296 1297void AutofillDialogControllerImpl::DisableWallet() { 1298 account_chooser_model_.SetHadWalletError(); 1299 GetWalletClient()->CancelPendingRequests(); 1300 wallet_items_.reset(); 1301 1302 GenerateSuggestionsModels(); 1303 if (view_) { 1304 view_->ModelChanged(); 1305 view_->UpdateNotificationArea(); 1306 } 1307} 1308 1309bool AutofillDialogControllerImpl::IsFirstRun() const { 1310 PrefService* prefs = profile_->GetPrefs(); 1311 return !prefs->HasPrefPath(prefs::kAutofillDialogPayWithoutWallet); 1312} 1313 1314void AutofillDialogControllerImpl::GenerateSuggestionsModels() { 1315 suggested_email_.Reset(); 1316 suggested_cc_.Reset(); 1317 suggested_billing_.Reset(); 1318 suggested_cc_billing_.Reset(); 1319 suggested_shipping_.Reset(); 1320 1321 if (IsPayingWithWallet()) { 1322 if (wallet_items_.get()) { 1323 // TODO(estade): seems we need to hardcode the email address. 1324 1325 const std::vector<wallet::Address*>& addresses = 1326 wallet_items_->addresses(); 1327 for (size_t i = 0; i < addresses.size(); ++i) { 1328 // TODO(dbeam): respect wallet_items_->default_instrument_id(). 1329 suggested_shipping_.AddKeyedItemWithSublabel( 1330 base::IntToString(i), 1331 addresses[i]->DisplayName(), 1332 addresses[i]->DisplayNameDetail()); 1333 } 1334 1335 const std::vector<wallet::WalletItems::MaskedInstrument*>& instruments = 1336 wallet_items_->instruments(); 1337 for (size_t i = 0; i < instruments.size(); ++i) { 1338 // TODO(dbeam): respect wallet_items_->default_address_id(). 1339 suggested_cc_billing_.AddKeyedItemWithSublabelAndIcon( 1340 base::IntToString(i), 1341 instruments[i]->DisplayName(), 1342 instruments[i]->DisplayNameDetail(), 1343 instruments[i]->CardIcon()); 1344 } 1345 } 1346 1347 suggested_cc_billing_.AddKeyedItem( 1348 std::string(), 1349 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_DETAILS)); 1350 } else { 1351 PersonalDataManager* manager = GetManager(); 1352 const std::vector<CreditCard*>& cards = manager->credit_cards(); 1353 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1354 for (size_t i = 0; i < cards.size(); ++i) { 1355 suggested_cc_.AddKeyedItemWithIcon( 1356 cards[i]->guid(), 1357 cards[i]->Label(), 1358 rb.GetImageNamed(cards[i]->IconResourceId())); 1359 } 1360 1361 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); 1362 const std::string app_locale = AutofillCountry::ApplicationLocale(); 1363 for (size_t i = 0; i < profiles.size(); ++i) { 1364 if (!IsCompleteProfile(*profiles[i])) 1365 continue; 1366 1367 // Add all email addresses. 1368 std::vector<string16> values; 1369 profiles[i]->GetMultiInfo(EMAIL_ADDRESS, app_locale, &values); 1370 for (size_t j = 0; j < values.size(); ++j) { 1371 if (!values[j].empty()) 1372 suggested_email_.AddKeyedItem(profiles[i]->guid(), values[j]); 1373 } 1374 1375 // Don't add variants for addresses: the email variants are handled above, 1376 // name is part of credit card and we'll just ignore phone number 1377 // variants. 1378 suggested_billing_.AddKeyedItem(profiles[i]->guid(), 1379 profiles[i]->Label()); 1380 suggested_shipping_.AddKeyedItem(profiles[i]->guid(), 1381 profiles[i]->Label()); 1382 } 1383 1384 suggested_cc_.AddKeyedItem( 1385 std::string(), 1386 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD)); 1387 suggested_billing_.AddKeyedItem( 1388 std::string(), 1389 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_ADDRESS)); 1390 } 1391 1392 suggested_email_.AddKeyedItem( 1393 std::string(), 1394 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL_ADDRESS)); 1395 suggested_shipping_.AddKeyedItem( 1396 std::string(), 1397 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_SHIPPING_ADDRESS)); 1398} 1399 1400bool AutofillDialogControllerImpl::IsCompleteProfile( 1401 const AutofillProfile& profile) { 1402 const std::string app_locale = AutofillCountry::ApplicationLocale(); 1403 for (size_t i = 0; i < requested_shipping_fields_.size(); ++i) { 1404 AutofillFieldType type = requested_shipping_fields_[i].type; 1405 if (type != ADDRESS_HOME_LINE2 && 1406 profile.GetInfo(type, app_locale).empty()) { 1407 return false; 1408 } 1409 } 1410 1411 return true; 1412} 1413 1414void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( 1415 DialogSection section, 1416 const InputFieldComparator& compare) { 1417 if (!SectionIsActive(section)) 1418 return; 1419 1420 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 1421 if (wrapper) { 1422 // Only fill in data that is associated with this section. 1423 const DetailInputs& inputs = RequestedFieldsForSection(section); 1424 wrapper->FillFormStructure(inputs, compare, &form_structure_); 1425 1426 // CVC needs special-casing because the CreditCard class doesn't store or 1427 // handle them. This isn't necessary when filling the combined CC and 1428 // billing section as CVC comes from |full_wallet_| in this case. 1429 if (section == SECTION_CC) 1430 SetCvcResult(view_->GetCvc()); 1431 } else { 1432 // The user manually input data. If using Autofill, save the info as new or 1433 // edited data. Always fill local data into |form_structure_|. 1434 DetailOutputMap output; 1435 view_->GetUserInput(section, &output); 1436 1437 if (section == SECTION_CC) { 1438 CreditCard card; 1439 FillFormGroupFromOutputs(output, &card); 1440 1441 if (ShouldSaveDetailsLocally()) 1442 GetManager()->SaveImportedCreditCard(card); 1443 1444 FillFormStructureForSection(card, 0, section, compare); 1445 1446 // Again, CVC needs special-casing. Fill it in directly from |output|. 1447 SetCvcResult(GetValueForType(output, CREDIT_CARD_VERIFICATION_CODE)); 1448 } else { 1449 AutofillProfile profile; 1450 FillFormGroupFromOutputs(output, &profile); 1451 1452 // TODO(estade): we should probably edit the existing profile in the 1453 // cases where the input data is based on an existing profile (user 1454 // clicked "Edit" or autofill popup filled in the form). 1455 if (ShouldSaveDetailsLocally()) 1456 GetManager()->SaveImportedProfile(profile); 1457 1458 FillFormStructureForSection(profile, 0, section, compare); 1459 } 1460 } 1461} 1462 1463void AutofillDialogControllerImpl::FillOutputForSection(DialogSection section) { 1464 FillOutputForSectionWithComparator(section, 1465 base::Bind(DetailInputMatchesField)); 1466} 1467 1468void AutofillDialogControllerImpl::FillFormStructureForSection( 1469 const FormGroup& form_group, 1470 size_t variant, 1471 DialogSection section, 1472 const InputFieldComparator& compare) { 1473 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1474 AutofillField* field = form_structure_.field(i); 1475 // Only fill in data that is associated with this section. 1476 const DetailInputs& inputs = RequestedFieldsForSection(section); 1477 for (size_t j = 0; j < inputs.size(); ++j) { 1478 if (compare.Run(inputs[j], *field)) { 1479 form_group.FillFormField(*field, variant, field); 1480 break; 1481 } 1482 } 1483 } 1484} 1485 1486void AutofillDialogControllerImpl::SetCvcResult(const string16& cvc) { 1487 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1488 AutofillField* field = form_structure_.field(i); 1489 if (field->type() == CREDIT_CARD_VERIFICATION_CODE) { 1490 field->value = cvc; 1491 break; 1492 } 1493 } 1494} 1495 1496SuggestionsMenuModel* AutofillDialogControllerImpl:: 1497 SuggestionsMenuModelForSection(DialogSection section) { 1498 switch (section) { 1499 case SECTION_EMAIL: 1500 return &suggested_email_; 1501 case SECTION_CC: 1502 return &suggested_cc_; 1503 case SECTION_BILLING: 1504 return &suggested_billing_; 1505 case SECTION_SHIPPING: 1506 return &suggested_shipping_; 1507 case SECTION_CC_BILLING: 1508 return &suggested_cc_billing_; 1509 } 1510 1511 NOTREACHED(); 1512 return NULL; 1513} 1514 1515DialogSection AutofillDialogControllerImpl::SectionForSuggestionsMenuModel( 1516 const SuggestionsMenuModel& model) { 1517 if (&model == &suggested_email_) 1518 return SECTION_EMAIL; 1519 1520 if (&model == &suggested_cc_) 1521 return SECTION_CC; 1522 1523 if (&model == &suggested_billing_) 1524 return SECTION_BILLING; 1525 1526 if (&model == &suggested_cc_billing_) 1527 return SECTION_CC_BILLING; 1528 1529 DCHECK_EQ(&model, &suggested_shipping_); 1530 return SECTION_SHIPPING; 1531} 1532 1533DetailInputs* AutofillDialogControllerImpl::MutableRequestedFieldsForSection( 1534 DialogSection section) { 1535 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); 1536} 1537 1538void AutofillDialogControllerImpl::HidePopup() { 1539 if (popup_controller_) 1540 popup_controller_->Hide(); 1541} 1542 1543void AutofillDialogControllerImpl::LoadRiskFingerprintData() { 1544 // TODO(dbeam): Add a CHECK or otherwise strong guarantee that the ToS have 1545 // been accepted prior to calling into this method. Also, ensure that the UI 1546 // contains a clear indication to the user as to what data will be collected. 1547 // Until then, this code should not be called. 1548 1549 int64 gaia_id = 0; 1550 bool success = 1551 base::StringToInt64(wallet_items_->obfuscated_gaia_id(), &gaia_id); 1552 DCHECK(success); 1553 1554 gfx::Rect window_bounds = 1555 GetBaseWindowForWebContents(web_contents())->GetBounds(); 1556 1557 PrefService* user_prefs = profile_->GetPrefs(); 1558 std::string charset = user_prefs->GetString(prefs::kDefaultCharset); 1559 std::string accept_languages = user_prefs->GetString(prefs::kAcceptLanguages); 1560 base::Time install_time = base::Time::FromTimeT( 1561 g_browser_process->local_state()->GetInt64(prefs::kInstallDate)); 1562 1563 risk::GetFingerprint( 1564 gaia_id, window_bounds, *web_contents(), 1565 chrome::VersionInfo().Version(), charset, accept_languages, install_time, 1566 base::Bind(&AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData, 1567 weak_ptr_factory_.GetWeakPtr())); 1568} 1569 1570void AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData( 1571 scoped_ptr<risk::Fingerprint> fingerprint) { 1572 NOTIMPLEMENTED(); 1573} 1574 1575bool AutofillDialogControllerImpl::IsManuallyEditingSection( 1576 DialogSection section) { 1577 return section_editing_state_[section] || 1578 SuggestionsMenuModelForSection(section)-> 1579 GetItemKeyForCheckedItem().empty(); 1580} 1581 1582bool AutofillDialogControllerImpl::ShouldUseBillingForShipping() { 1583 // If the user is editing or inputting data, ask the view. 1584 if (IsManuallyEditingSection(SECTION_SHIPPING)) 1585 return view_->UseBillingForShipping(); 1586 1587 // Otherwise, the checkbox should be hidden so its state is irrelevant. 1588 // Always use the shipping suggestion model. 1589 return false; 1590} 1591 1592bool AutofillDialogControllerImpl::ShouldSaveDetailsLocally() { 1593 // It's possible that the user checked [X] Save details locally before 1594 // switching payment methods, so only ask the view whether to save details 1595 // locally if that checkbox is showing (currently if not paying with wallet). 1596 return !IsPayingWithWallet() && view_->SaveDetailsLocally(); 1597} 1598 1599void AutofillDialogControllerImpl::SubmitWithWallet() { 1600 // TODO(dbeam): disallow interacting with the dialog while submitting. 1601 1602 active_instrument_id_.clear(); 1603 active_address_id_.clear(); 1604 1605 if (!section_editing_state_[SECTION_CC_BILLING]) { 1606 SuggestionsMenuModel* billing = 1607 SuggestionsMenuModelForSection(SECTION_CC_BILLING); 1608 if (!billing->GetItemKeyForCheckedItem().empty() && 1609 billing->checked_item() < 1610 static_cast<int>(wallet_items_->instruments().size())) { 1611 const wallet::WalletItems::MaskedInstrument* active_instrument = 1612 wallet_items_->instruments()[billing->checked_item()]; 1613 active_instrument_id_ = active_instrument->object_id(); 1614 1615 // TODO(dbeam): does re-using instrument address IDs work? 1616 if (ShouldUseBillingForShipping()) 1617 active_address_id_ = active_instrument->address().object_id(); 1618 } 1619 } 1620 1621 if (!section_editing_state_[SECTION_SHIPPING] && active_address_id_.empty()) { 1622 SuggestionsMenuModel* shipping = 1623 SuggestionsMenuModelForSection(SECTION_SHIPPING); 1624 if (!shipping->GetItemKeyForCheckedItem().empty() && 1625 shipping->checked_item() < 1626 static_cast<int>(wallet_items_->addresses().size())) { 1627 active_address_id_ = 1628 wallet_items_->addresses()[shipping->checked_item()]->object_id(); 1629 } 1630 } 1631 1632 if (!wallet_items_->legal_documents().empty()) { 1633 std::vector<std::string> doc_ids; 1634 for (size_t i = 0; i < wallet_items_->legal_documents().size(); ++i) { 1635 doc_ids.push_back(wallet_items_->legal_documents()[i]->document_id()); 1636 } 1637 GetWalletClient()->AcceptLegalDocuments( 1638 doc_ids, wallet_items_->google_transaction_id(), source_url_); 1639 } 1640 1641 scoped_ptr<wallet::Instrument> new_instrument; 1642 if (active_instrument_id_.empty()) { 1643 DetailOutputMap output; 1644 view_->GetUserInput(SECTION_CC_BILLING, &output); 1645 1646 CreditCard card; 1647 AutofillProfile profile; 1648 string16 cvc; 1649 GetBillingInfoFromOutputs(output, &card, &cvc, &profile); 1650 1651 new_instrument.reset(new wallet::Instrument(card, cvc, profile)); 1652 } 1653 1654 scoped_ptr<wallet::Address> new_address; 1655 if (active_address_id_.empty()) { 1656 if (ShouldUseBillingForShipping() && new_instrument.get()) { 1657 new_address.reset(new wallet::Address(new_instrument->address())); 1658 } else { 1659 DetailOutputMap output; 1660 view_->GetUserInput(SECTION_SHIPPING, &output); 1661 1662 AutofillProfile profile; 1663 FillFormGroupFromOutputs(output, &profile); 1664 1665 new_address.reset(new wallet::Address(profile)); 1666 } 1667 } 1668 1669 if (new_instrument.get() && new_address.get()) { 1670 GetWalletClient()->SaveInstrumentAndAddress( 1671 *new_instrument, 1672 *new_address, 1673 wallet_items_->obfuscated_gaia_id(), 1674 source_url_); 1675 } else if (new_instrument.get()) { 1676 GetWalletClient()->SaveInstrument(*new_instrument, 1677 wallet_items_->obfuscated_gaia_id(), 1678 source_url_); 1679 } else if (new_address.get()) { 1680 GetWalletClient()->SaveAddress(*new_address, source_url_); 1681 } else { 1682 GetFullWallet(); 1683 } 1684} 1685 1686void AutofillDialogControllerImpl::GetFullWallet() { 1687 DCHECK(did_submit_); 1688 DCHECK(IsPayingWithWallet()); 1689 DCHECK(wallet_items_); 1690 DCHECK(!active_instrument_id_.empty()); 1691 DCHECK(!active_address_id_.empty()); 1692 1693 GetWalletClient()->GetFullWallet(wallet::WalletClient::FullWalletRequest( 1694 active_instrument_id_, 1695 active_address_id_, 1696 source_url_, 1697 wallet::Cart(base::IntToString(kCartMax), kCartCurrency), 1698 wallet_items_->google_transaction_id(), 1699 std::vector<wallet::WalletClient::RiskCapability>())); 1700} 1701 1702void AutofillDialogControllerImpl::FinishSubmit() { 1703 FillOutputForSection(SECTION_EMAIL); 1704 FillOutputForSection(SECTION_CC); 1705 FillOutputForSection(SECTION_BILLING); 1706 FillOutputForSection(SECTION_CC_BILLING); 1707 if (ShouldUseBillingForShipping()) { 1708 FillOutputForSectionWithComparator( 1709 SECTION_BILLING, 1710 base::Bind(DetailInputMatchesShippingField)); 1711 FillOutputForSectionWithComparator( 1712 SECTION_CC, 1713 base::Bind(DetailInputMatchesShippingField)); 1714 } else { 1715 FillOutputForSection(SECTION_SHIPPING); 1716 } 1717 callback_.Run(&form_structure_); 1718 callback_ = base::Callback<void(const FormStructure*)>(); 1719 1720 if (dialog_type_ == DIALOG_TYPE_REQUEST_AUTOCOMPLETE) { 1721 // This may delete us. 1722 Hide(); 1723 } 1724} 1725 1726AutofillMetrics::DialogInitialUserStateMetric 1727 AutofillDialogControllerImpl::GetInitialUserState() const { 1728 // Consider a user to be an Autofill user if the user has any credit cards 1729 // or addresses saved. Check that the item count is greater than 1 because 1730 // an "empty" menu still has the "add new" menu item. 1731 const bool has_autofill_profiles = 1732 suggested_cc_.GetItemCount() > 1 || 1733 suggested_billing_.GetItemCount() > 1; 1734 1735 if (SignedInState() != SIGNED_IN) { 1736 // Not signed in. 1737 return has_autofill_profiles ? 1738 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_HAS_AUTOFILL : 1739 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_NO_AUTOFILL; 1740 } 1741 1742 // Signed in. 1743 if (wallet_items_->instruments().empty()) { 1744 // No Wallet items. 1745 return has_autofill_profiles ? 1746 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_HAS_AUTOFILL : 1747 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_NO_AUTOFILL; 1748 } 1749 1750 // Has Wallet items. 1751 return has_autofill_profiles ? 1752 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_HAS_AUTOFILL : 1753 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_NO_AUTOFILL; 1754} 1755 1756} // namespace autofill 1757