blob: a97c5ab237716e9e851048862dd3ebac6ac7222b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/autofill/autofill_dialog_controller.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autofill/autofill_country.h"
#include "chrome/browser/autofill/autofill_manager.h"
#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/autofill/autofill_dialog_view.h"
#include "chrome/common/form_data.h"
#include "content/public/browser/web_contents.h"
namespace autofill {
namespace {
// Returns true if |input| should be shown when |field| has been requested.
bool InputTypeMatchesFieldType(const DetailInput& input,
const AutofillField& field) {
// If any credit card expiration info is asked for, show both month and year
// inputs.
if (field.type() == CREDIT_CARD_EXP_4_DIGIT_YEAR ||
field.type() == CREDIT_CARD_EXP_2_DIGIT_YEAR ||
field.type() == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR ||
field.type() == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR ||
field.type() == CREDIT_CARD_EXP_MONTH) {
return input.type == CREDIT_CARD_EXP_4_DIGIT_YEAR ||
input.type == CREDIT_CARD_EXP_MONTH;
}
return input.type == field.type();
}
// Returns true if |input| should be used for a site-requested |field|.
bool DetailInputMatchesField(const DetailInput& input,
const AutofillField& field) {
bool right_section = !input.section_suffix ||
EndsWith(field.section(), input.section_suffix, false);
return InputTypeMatchesFieldType(input, field) && right_section;
}
// Returns true if |input| should be used to fill a site-requested |field| which
// is notated with a "shipping" tag, for use when the user has decided to use
// the billing address as the shipping address.
bool DetailInputMatchesShippingField(const DetailInput& input,
const AutofillField& field) {
if (input.section_suffix &&
std::string(input.section_suffix) == "billing") {
return InputTypeMatchesFieldType(input, field);
}
if (field.type() == NAME_FULL)
return input.type == CREDIT_CARD_NAME;
return DetailInputMatchesField(input, field);
}
// Looks through |input_template| for the types in |requested_data|. Appends
// DetailInput values to |inputs|.
void FilterInputs(const FormStructure& form_structure,
const DetailInput* input_template,
size_t template_size,
DetailInputs* inputs) {
for (size_t i = 0; i < template_size; ++i) {
const DetailInput* input = &input_template[i];
for (size_t j = 0; j < form_structure.field_count(); ++j) {
if (DetailInputMatchesField(*input, *form_structure.field(j))) {
inputs->push_back(*input);
break;
}
}
}
}
// Finds the instance of T which has the most data for filling in the inputs in
// |all_inputs|. T should be a FormGroup type.
template<class T> FormGroup* GetBestDataSource(
const std::vector<T*>& sources,
const DetailInputs* const* all_inputs,
size_t all_inputs_length) {
const std::string app_locale = AutofillCountry::ApplicationLocale();
int best_field_fill_count = 0;
FormGroup* best_source = NULL;
for (size_t i = 0; i < sources.size(); ++i) {
int field_fill_count = 0;
for (size_t j = 0; j < all_inputs_length; ++j) {
const DetailInputs& inputs = *all_inputs[j];
for (size_t k = 0; k < inputs.size(); ++k) {
if (!sources[i]->GetInfo(inputs[k].type, app_locale).empty())
field_fill_count++;
}
}
// TODO(estade): should there be a better tiebreaker?
if (field_fill_count > best_field_fill_count) {
best_field_fill_count = field_fill_count;
best_source = sources[i];
}
}
return best_source;
}
// Uses |group| to fill in the |starting_value| for all inputs in |all_inputs|
// (an out-param).
void FillInputsFromFormGroup(FormGroup* group,
DetailInputs** all_inputs,
size_t all_inputs_length) {
const std::string app_locale = AutofillCountry::ApplicationLocale();
for (size_t i = 0; i < all_inputs_length; ++i) {
DetailInputs& inputs = *all_inputs[i];
for (size_t j = 0; j < inputs.size(); ++j) {
inputs[j].starting_value = group->GetInfo(inputs[j].type, app_locale);
}
}
}
// Initializes |form_group| from user-entered data.
void FillFormGroupFromOutputs(const DetailOutputMap& detail_outputs,
FormGroup* form_group) {
for (DetailOutputMap::const_iterator iter = detail_outputs.begin();
iter != detail_outputs.end(); ++iter) {
if (!iter->second.empty())
form_group->SetRawInfo(iter->first->type, iter->second);
}
}
} // namespace
AutofillDialogController::AutofillDialogController(
content::WebContents* contents,
const FormData& form,
const base::Callback<void(const FormStructure*)>& callback)
: profile_(Profile::FromBrowserContext(contents->GetBrowserContext())),
contents_(contents),
form_structure_(form),
callback_(callback) {
// TODO(estade): |this| should observe PersonalDataManager.
// TODO(estade): remove duplicates from |form|?
}
AutofillDialogController::~AutofillDialogController() {}
void AutofillDialogController::Show() {
bool has_types = false;
bool has_sections = false;
form_structure_.ParseFieldTypesFromAutocompleteAttributes(&has_types,
&has_sections);
// Fail if the author didn't specify autocomplete types.
if (!has_types) {
callback_.Run(NULL);
delete this;
return;
}
int row_id = 0;
const DetailInput kEmailInputs[] = {
{ ++row_id, EMAIL_ADDRESS, "Email address" },
};
const DetailInput kCCInputs[] = {
{ ++row_id, CREDIT_CARD_NUMBER, "Card number" },
{ ++row_id, CREDIT_CARD_EXP_MONTH },
{ row_id, CREDIT_CARD_EXP_4_DIGIT_YEAR },
{ row_id, CREDIT_CARD_VERIFICATION_CODE, "CVC" },
{ ++row_id, CREDIT_CARD_NAME, "Cardholder name" },
};
const DetailInput kBillingInputs[] = {
{ ++row_id, ADDRESS_HOME_LINE1, "Street address", "billing" },
{ ++row_id, ADDRESS_HOME_LINE2, "Street address (optional)", "billing" },
{ ++row_id, ADDRESS_HOME_CITY, "City", "billing" },
{ ++row_id, ADDRESS_HOME_STATE, "State", "billing" },
{ row_id, ADDRESS_HOME_ZIP, "ZIP code", "billing", 0.5 },
};
const DetailInput kShippingInputs[] = {
{ ++row_id, NAME_FULL, "Full name", "shipping" },
{ ++row_id, ADDRESS_HOME_LINE1, "Street address", "shipping" },
{ ++row_id, ADDRESS_HOME_LINE2, "Street address (optional)", "shipping" },
{ ++row_id, ADDRESS_HOME_CITY, "City", "shipping" },
{ ++row_id, ADDRESS_HOME_STATE, "State", "shipping" },
{ row_id, ADDRESS_HOME_ZIP, "ZIP code", "shipping", 0.5 },
};
FilterInputs(form_structure_,
kEmailInputs,
arraysize(kEmailInputs),
&requested_email_fields_);
FilterInputs(form_structure_,
kCCInputs,
arraysize(kCCInputs),
&requested_cc_fields_);
FilterInputs(form_structure_,
kBillingInputs,
arraysize(kBillingInputs),
&requested_billing_fields_);
FilterInputs(form_structure_,
kShippingInputs,
arraysize(kShippingInputs),
&requested_shipping_fields_);
// TODO(estade): make this actually check if it's a first run.
bool first_run = true;
if (first_run)
PopulateInputsWithGuesses();
else
GenerateComboboxModels();
// TODO(estade): don't show the dialog if the site didn't specify the right
// fields. First we must figure out what the "right" fields are.
view_.reset(AutofillDialogView::Create(this));
view_->Show();
}
string16 AutofillDialogController::DialogTitle() const {
// TODO(estade): real strings and l10n.
return string16(ASCIIToUTF16("PaY"));
}
string16 AutofillDialogController::IntroText() const {
// TODO(estade): real strings and l10n.
return string16(
ASCIIToUTF16("random.com has requested the following deets:"));
}
string16 AutofillDialogController::LabelForSection(DialogSection section)
const {
switch (section) {
case SECTION_EMAIL:
// TODO(estade): real strings and l10n.
return ASCIIToUTF16("Email address fixme");
case SECTION_BILLING:
return ASCIIToUTF16("Billing details fixme");
case SECTION_SHIPPING:
return ASCIIToUTF16("Shipping details fixme");
default:
NOTREACHED();
return string16();
}
}
string16 AutofillDialogController::UseBillingForShippingText() const {
// TODO(estade): real strings and l10n.
return ASCIIToUTF16("also ship here");
}
string16 AutofillDialogController::WalletOptionText() const {
// TODO(estade): real strings and l10n.
return string16(ASCIIToUTF16("I love lamp."));
}
bool AutofillDialogController::ShouldShowInput(const DetailInput& input) const {
// TODO(estade): filter fields that aren't part of this autofill request.
return true;
}
string16 AutofillDialogController::CancelButtonText() const {
// TODO(estade): real strings and l10n.
return string16(ASCIIToUTF16("CaNceL"));
}
string16 AutofillDialogController::ConfirmButtonText() const {
// TODO(estade): real strings and l10n.
return string16(ASCIIToUTF16("SuBMiT"));
}
bool AutofillDialogController::ConfirmButtonEnabled() const {
// TODO(estade): implement.
return true;
}
const DetailInputs& AutofillDialogController::RequestedFieldsForSection(
DialogSection section) const {
switch (section) {
case SECTION_EMAIL:
return requested_email_fields_;
case SECTION_CC:
return requested_cc_fields_;
case SECTION_BILLING:
return requested_billing_fields_;
case SECTION_SHIPPING:
return requested_shipping_fields_;
}
NOTREACHED();
return requested_shipping_fields_;
}
ui::ComboboxModel* AutofillDialogController::ComboboxModelForAutofillType(
AutofillFieldType type) {
switch (type) {
case CREDIT_CARD_EXP_MONTH:
return &cc_exp_month_combobox_model_;
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
return &cc_exp_year_combobox_model_;
default:
return NULL;
}
}
ui::ComboboxModel* AutofillDialogController::ComboboxModelForSection(
DialogSection section) {
return SuggestionsModelForSection(section);
}
void AutofillDialogController::ViewClosed(DialogAction action) {
if (action == ACTION_SUBMIT) {
FillOutputForSection(SECTION_EMAIL);
FillOutputForSection(SECTION_CC);
FillOutputForSection(SECTION_BILLING);
if (view_->UseBillingForShipping()) {
FillOutputForSectionWithComparator(
SECTION_BILLING,
base::Bind(DetailInputMatchesShippingField));
FillOutputForSectionWithComparator(
SECTION_CC,
base::Bind(DetailInputMatchesShippingField));
} else {
FillOutputForSection(SECTION_SHIPPING);
}
callback_.Run(&form_structure_);
} else {
callback_.Run(NULL);
}
delete this;
}
void AutofillDialogController::GenerateComboboxModels() {
PersonalDataManager* manager =
PersonalDataManagerFactory::GetForProfile(profile_);
const std::vector<CreditCard*>& cards = manager->credit_cards();
for (size_t i = 0; i < cards.size(); ++i) {
suggested_cc_.AddItem(cards[i]->guid(), cards[i]->Label());
}
suggested_cc_.AddItem("", ASCIIToUTF16("Enter new card"));
const std::vector<AutofillProfile*>& profiles = manager->GetProfiles();
const std::string app_locale = AutofillCountry::ApplicationLocale();
for (size_t i = 0; i < profiles.size(); ++i) {
string16 email = profiles[i]->GetInfo(EMAIL_ADDRESS, app_locale);
if (!email.empty())
suggested_email_.AddItem(profiles[i]->guid(), email);
suggested_billing_.AddItem(profiles[i]->guid(), profiles[i]->Label());
suggested_shipping_.AddItem(profiles[i]->guid(), profiles[i]->Label());
}
suggested_billing_.AddItem("", ASCIIToUTF16("Enter new billing"));
suggested_email_.AddItem("", ASCIIToUTF16("Enter new email"));
suggested_shipping_.AddItem("", ASCIIToUTF16("Enter new shipping"));
}
void AutofillDialogController::PopulateInputsWithGuesses() {
PersonalDataManager* manager =
PersonalDataManagerFactory::GetForProfile(profile_);
DetailInputs* profile_inputs[] = { &requested_email_fields_,
&requested_billing_fields_ };
const std::vector<AutofillProfile*>& profiles = manager->GetProfiles();
FormGroup* best_profile =
GetBestDataSource(profiles, profile_inputs, arraysize(profile_inputs));
if (best_profile) {
FillInputsFromFormGroup(best_profile,
profile_inputs,
arraysize(profile_inputs));
}
DetailInputs* cc_inputs[] = { &requested_cc_fields_ };
const std::vector<CreditCard*>& credit_cards = manager->credit_cards();
FormGroup* best_cc =
GetBestDataSource(credit_cards, cc_inputs, arraysize(cc_inputs));
if (best_cc) {
FillInputsFromFormGroup(best_cc,
cc_inputs,
arraysize(cc_inputs));
}
}
void AutofillDialogController::FillOutputForSectionWithComparator(
DialogSection section, const InputFieldComparator& compare) {
int suggestion_selection = view_->GetSuggestionSelection(section);
SuggestionsComboboxModel* model = SuggestionsModelForSection(section);
if (suggestion_selection < model->GetItemCount() - 1) {
std::string guid = model->GetItemKeyAt(suggestion_selection);
PersonalDataManager* manager =
PersonalDataManagerFactory::GetForProfile(profile_);
FormGroup* form_group = section == SECTION_CC ?
static_cast<FormGroup*>(manager->GetCreditCardByGUID(guid)) :
static_cast<FormGroup*>(manager->GetProfileByGUID(guid));
// TODO(estade): we shouldn't let this happen.
if (!form_group)
return;
FillFormStructureForSection(*form_group, section, compare);
} else {
// The user manually input data.
DetailOutputMap output;
view_->GetUserInput(section, &output);
// Save the info as new or edited data, then fill it into |form_structure_|.
PersonalDataManager* manager =
PersonalDataManagerFactory::GetForProfile(profile_);
if (section == SECTION_CC) {
CreditCard card;
FillFormGroupFromOutputs(output, &card);
manager->SaveImportedCreditCard(card);
FillFormStructureForSection(card, section, compare);
// CVC needs special-casing because the CreditCard class doesn't store
// or handle them. Fill it in directly from |output|.
for (size_t i = 0; i < form_structure_.field_count(); ++i) {
AutofillField* field = form_structure_.field(i);
if (field->type() != CREDIT_CARD_VERIFICATION_CODE)
continue;
for (DetailOutputMap::iterator iter = output.begin();
iter != output.end(); ++iter) {
if (!iter->second.empty() && compare.Run(*iter->first, *field)) {
field->value = iter->second;
break;
}
}
}
} else {
AutofillProfile profile;
FillFormGroupFromOutputs(output, &profile);
manager->SaveImportedProfile(profile);
FillFormStructureForSection(profile, section, compare);
}
}
}
void AutofillDialogController::FillOutputForSection(DialogSection section) {
FillOutputForSectionWithComparator(section,
base::Bind(DetailInputMatchesField));
}
void AutofillDialogController::FillFormStructureForSection(
const FormGroup& form_group,
DialogSection section,
const InputFieldComparator& compare) {
for (size_t i = 0; i < form_structure_.field_count(); ++i) {
AutofillField* field = form_structure_.field(i);
// Only fill in data that is associated with this section.
const DetailInputs& inputs = RequestedFieldsForSection(section);
for (size_t j = 0; j < inputs.size(); ++j) {
if (compare.Run(inputs[j], *field)) {
form_group.FillFormField(*field, 0, field);
break;
}
}
}
}
SuggestionsComboboxModel* AutofillDialogController::SuggestionsModelForSection(
DialogSection section) {
switch (section) {
case SECTION_EMAIL:
return &suggested_email_;
case SECTION_CC:
return &suggested_cc_;
case SECTION_BILLING:
return &suggested_billing_;
case SECTION_SHIPPING:
return &suggested_shipping_;
}
NOTREACHED();
return NULL;
}
} // namespace autofill