blob: ee1c4576afc691f99b0a23782a99563ed5d0926b [file] [log] [blame]
// Copyright 2013 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 "components/autofill/content/browser/autocheckout_manager.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/content/browser/autocheckout_request_manager.h"
#include "components/autofill/content/browser/autocheckout_statistic.h"
#include "components/autofill/content/browser/autocheckout_steps.h"
#include "components/autofill/core/browser/autofill_country.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/common/autofill_messages.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/web_element_descriptor.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_store.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/gfx/rect.h"
#include "url/gurl.h"
using content::RenderViewHost;
using content::WebContents;
namespace autofill {
namespace {
const char kGoogleAccountsUrl[] = "https://accounts.google.com/";
// Build FormFieldData based on the supplied |autocomplete_attribute|. Will
// fill rest of properties with default values.
FormFieldData BuildField(const std::string& autocomplete_attribute) {
FormFieldData field;
field.name = base::string16();
field.value = base::string16();
field.autocomplete_attribute = autocomplete_attribute;
field.form_control_type = "text";
return field;
}
// Build Autocheckout specific form data to be consumed by
// AutofillDialogController to show the Autocheckout specific UI.
FormData BuildAutocheckoutFormData() {
FormData formdata;
formdata.fields.push_back(BuildField("email"));
formdata.fields.push_back(BuildField("cc-name"));
formdata.fields.push_back(BuildField("cc-number"));
formdata.fields.push_back(BuildField("cc-exp-month"));
formdata.fields.push_back(BuildField("cc-exp-year"));
formdata.fields.push_back(BuildField("cc-csc"));
formdata.fields.push_back(BuildField("billing address-line1"));
formdata.fields.push_back(BuildField("billing address-line2"));
formdata.fields.push_back(BuildField("billing locality"));
formdata.fields.push_back(BuildField("billing region"));
formdata.fields.push_back(BuildField("billing country"));
formdata.fields.push_back(BuildField("billing postal-code"));
formdata.fields.push_back(BuildField("billing tel"));
formdata.fields.push_back(BuildField("shipping name"));
formdata.fields.push_back(BuildField("shipping address-line1"));
formdata.fields.push_back(BuildField("shipping address-line2"));
formdata.fields.push_back(BuildField("shipping locality"));
formdata.fields.push_back(BuildField("shipping region"));
formdata.fields.push_back(BuildField("shipping country"));
formdata.fields.push_back(BuildField("shipping postal-code"));
formdata.fields.push_back(BuildField("shipping tel"));
return formdata;
}
AutofillMetrics::AutocheckoutBuyFlowMetric AutocheckoutStatusToUmaMetric(
AutocheckoutStatus status) {
switch (status) {
case SUCCESS:
return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS;
case MISSING_FIELDMAPPING:
return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING;
case MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING:
return AutofillMetrics::
AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING;
case MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING:
return AutofillMetrics::
AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING;
case MISSING_ADVANCE:
return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT;
case CANNOT_PROCEED:
return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED;
case AUTOCHECKOUT_STATUS_NUM_STATUS:
NOTREACHED();
}
NOTREACHED();
return AutofillMetrics::NUM_AUTOCHECKOUT_BUY_FLOW_METRICS;
}
// Callback for retrieving Google Account cookies. |callback| is passed the
// retrieved cookies and posted back to the UI thread. |cookies| is any Google
// Account cookies.
void GetGoogleCookiesCallback(
const base::Callback<void(const std::string&)>& callback,
const std::string& cookies) {
content::BrowserThread::PostTask(content::BrowserThread::UI,
FROM_HERE,
base::Bind(callback, cookies));
}
// Gets Google Account cookies. Must be called on the IO thread.
// |request_context_getter| is a getter for the current request context.
// |callback| is called when retrieving cookies is completed.
void GetGoogleCookies(
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
const base::Callback<void(const std::string&)>& callback) {
net::URLRequestContext* url_request_context =
request_context_getter->GetURLRequestContext();
if (!url_request_context)
return;
net::CookieStore* cookie_store = url_request_context->cookie_store();
base::Callback<void(const std::string&)> cookie_callback = base::Bind(
&GetGoogleCookiesCallback,
callback);
net::CookieOptions cookie_options;
cookie_options.set_include_httponly();
cookie_store->GetCookiesWithOptionsAsync(GURL(kGoogleAccountsUrl),
cookie_options,
cookie_callback);
}
bool IsBillingGroup(FieldTypeGroup group) {
return group == ADDRESS_BILLING ||
group == PHONE_BILLING ||
group == NAME_BILLING;
}
const char kTransactionIdNotSet[] = "transaction id not set";
} // namespace
AutocheckoutManager::AutocheckoutManager(AutofillManager* autofill_manager)
: autofill_manager_(autofill_manager),
metric_logger_(new AutofillMetrics),
should_show_bubble_(true),
is_autocheckout_bubble_showing_(false),
in_autocheckout_flow_(false),
should_preserve_dialog_(false),
google_transaction_id_(kTransactionIdNotSet),
weak_ptr_factory_(this) {}
AutocheckoutManager::~AutocheckoutManager() {
}
void AutocheckoutManager::FillForms() {
// |page_meta_data_| should have been set by OnLoadedPageMetaData.
DCHECK(page_meta_data_);
// Fill the forms on the page with data given by user.
std::vector<FormData> filled_forms;
const std::vector<FormStructure*>& form_structures =
autofill_manager_->GetFormStructures();
for (std::vector<FormStructure*>::const_iterator iter =
form_structures.begin(); iter != form_structures.end(); ++iter) {
FormStructure* form_structure = *iter;
form_structure->set_filled_by_autocheckout(true);
FormData form = form_structure->ToFormData();
DCHECK_EQ(form_structure->field_count(), form.fields.size());
for (size_t i = 0; i < form_structure->field_count(); ++i) {
const AutofillField* field = form_structure->field(i);
SetValue(*field, &form.fields[i]);
}
filled_forms.push_back(form);
}
// Send filled forms along with proceed descriptor to renderer.
RenderViewHost* host =
autofill_manager_->GetWebContents()->GetRenderViewHost();
if (!host)
return;
host->Send(new AutofillMsg_FillFormsAndClick(
host->GetRoutingID(),
filled_forms,
page_meta_data_->click_elements_before_form_fill,
page_meta_data_->click_elements_after_form_fill,
page_meta_data_->proceed_element_descriptor));
// Record time taken for navigating current page.
RecordTimeTaken(page_meta_data_->current_page_number);
}
void AutocheckoutManager::OnAutocheckoutPageCompleted(
AutocheckoutStatus status) {
if (!in_autocheckout_flow_)
return;
DVLOG(2) << "OnAutocheckoutPageCompleted, page_no: "
<< page_meta_data_->current_page_number
<< " status: "
<< status;
DCHECK_NE(MISSING_FIELDMAPPING, status);
SetStepProgressForPage(
page_meta_data_->current_page_number,
(status == SUCCESS) ? AUTOCHECKOUT_STEP_COMPLETED :
AUTOCHECKOUT_STEP_FAILED);
if (page_meta_data_->IsEndOfAutofillableFlow() || status != SUCCESS)
EndAutocheckout(status);
}
void AutocheckoutManager::OnLoadedPageMetaData(
scoped_ptr<AutocheckoutPageMetaData> page_meta_data) {
scoped_ptr<AutocheckoutPageMetaData> old_meta_data = page_meta_data_.Pass();
page_meta_data_ = page_meta_data.Pass();
// If there is no click element in the last page, then it's the real last page
// of the flow, and the dialog will be closed when the page navigates.
// Otherwise, the dialog should be preserved for the page loaded by the click
// element on the last page of the flow.
// Note, |should_preserve_dialog_| has to be computed at this point because
// |in_autocheckout_flow_| may change after |OnLoadedPageMetaData| is called.
should_preserve_dialog_ = in_autocheckout_flow_ ||
(old_meta_data.get() &&
old_meta_data->IsEndOfAutofillableFlow() &&
old_meta_data->proceed_element_descriptor.retrieval_method !=
WebElementDescriptor::NONE);
// Don't log that the bubble could be displayed if the user entered an
// Autocheckout flow and sees the first page of the flow again due to an
// error.
if (IsStartOfAutofillableFlow() && !in_autocheckout_flow_) {
metric_logger_->LogAutocheckoutBubbleMetric(
AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED);
}
// On the first page of an Autocheckout flow, when this function is called the
// user won't have opted into the flow yet.
if (!in_autocheckout_flow_)
return;
AutocheckoutStatus status = SUCCESS;
// Missing Autofill server results.
if (!page_meta_data_.get()) {
status = MISSING_FIELDMAPPING;
} else if (IsStartOfAutofillableFlow()) {
// Not possible unless Autocheckout failed to proceed.
status = CANNOT_PROCEED;
} else if (!page_meta_data_->IsInAutofillableFlow()) {
// Missing Autocheckout meta data in the Autofill server results.
status = MISSING_FIELDMAPPING;
} else if (page_meta_data_->current_page_number <=
old_meta_data->current_page_number) {
// Not possible unless Autocheckout failed to proceed.
status = CANNOT_PROCEED;
}
// Encountered an error during the Autocheckout flow, probably to
// do with a problem on the previous page.
if (status != SUCCESS) {
SetStepProgressForPage(old_meta_data->current_page_number,
AUTOCHECKOUT_STEP_FAILED);
EndAutocheckout(status);
return;
}
SetStepProgressForPage(page_meta_data_->current_page_number,
AUTOCHECKOUT_STEP_STARTED);
FillForms();
}
void AutocheckoutManager::OnFormsSeen() {
should_show_bubble_ = true;
}
bool AutocheckoutManager::ShouldIgnoreAjax() {
return in_autocheckout_flow_ && page_meta_data_->ignore_ajax;
}
void AutocheckoutManager::MaybeShowAutocheckoutBubble(
const GURL& frame_url,
const gfx::RectF& bounding_box) {
if (!should_show_bubble_ ||
is_autocheckout_bubble_showing_ ||
!IsStartOfAutofillableFlow())
return;
base::Callback<void(const std::string&)> callback = base::Bind(
&AutocheckoutManager::ShowAutocheckoutBubble,
weak_ptr_factory_.GetWeakPtr(),
frame_url,
bounding_box);
content::WebContents* web_contents = autofill_manager_->GetWebContents();
if (!web_contents)
return;
content::BrowserContext* browser_context = web_contents->GetBrowserContext();
if(!browser_context)
return;
scoped_refptr<net::URLRequestContextGetter> request_context =
scoped_refptr<net::URLRequestContextGetter>(
browser_context->GetRequestContext());
if (!request_context.get())
return;
base::Closure task = base::Bind(&GetGoogleCookies, request_context, callback);
content::BrowserThread::PostTask(content::BrowserThread::IO,
FROM_HERE,
task);
}
void AutocheckoutManager::ReturnAutocheckoutData(
const FormStructure* result,
const std::string& google_transaction_id) {
if (!result) {
// When user cancels the dialog, |result| is NULL.
// TODO(): add AutocheckoutStatus.USER_CANCELLED, and call
// EndAutocheckout(USER_CANCELLED) instead.
in_autocheckout_flow_ = false;
return;
}
latency_statistics_.clear();
last_step_completion_timestamp_ = base::TimeTicks().Now();
google_transaction_id_ = google_transaction_id;
in_autocheckout_flow_ = true;
should_preserve_dialog_ = true;
metric_logger_->LogAutocheckoutBuyFlowMetric(
AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED);
profile_.reset(new AutofillProfile());
credit_card_.reset(new CreditCard());
billing_address_.reset(new AutofillProfile());
for (size_t i = 0; i < result->field_count(); ++i) {
const AutofillType& type = result->field(i)->Type();
const base::string16& value = result->field(i)->value;
if (type.server_type() == CREDIT_CARD_VERIFICATION_CODE) {
cvv_ = result->field(i)->value;
continue;
}
FieldTypeGroup group = type.group();
if (group == CREDIT_CARD) {
credit_card_->SetRawInfo(type.server_type(), value);
// TODO(dgwallinga): Find a way of cleanly deprecating CREDIT_CARD_NAME.
// code.google.com/p/chromium/issues/detail?id=263498
if (type.server_type() == CREDIT_CARD_NAME)
billing_address_->SetRawInfo(NAME_BILLING_FULL, value);
} else if (type.server_type() == ADDRESS_HOME_COUNTRY) {
profile_->SetInfo(type, value, autofill_manager_->app_locale());
} else if (type.server_type() == ADDRESS_BILLING_COUNTRY) {
billing_address_->SetInfo(type, value, autofill_manager_->app_locale());
} else if (IsBillingGroup(group)) {
billing_address_->SetRawInfo(type.server_type(), value);
} else {
profile_->SetRawInfo(type.server_type(), value);
}
}
// Page types only available in first-page meta data, so save
// them for use later as we navigate.
page_types_ = page_meta_data_->page_types;
SetStepProgressForPage(page_meta_data_->current_page_number,
AUTOCHECKOUT_STEP_STARTED);
FillForms();
}
void AutocheckoutManager::set_metric_logger(
scoped_ptr<AutofillMetrics> metric_logger) {
metric_logger_ = metric_logger.Pass();
}
void AutocheckoutManager::MaybeShowAutocheckoutDialog(
const GURL& frame_url,
AutocheckoutBubbleState state) {
is_autocheckout_bubble_showing_ = false;
// User has taken action on the bubble, don't offer bubble again.
if (state != AUTOCHECKOUT_BUBBLE_IGNORED)
should_show_bubble_ = false;
if (state != AUTOCHECKOUT_BUBBLE_ACCEPTED)
return;
base::Callback<void(const FormStructure*, const std::string&)> callback =
base::Bind(&AutocheckoutManager::ReturnAutocheckoutData,
weak_ptr_factory_.GetWeakPtr());
autofill_manager_->ShowRequestAutocompleteDialog(BuildAutocheckoutFormData(),
frame_url,
DIALOG_TYPE_AUTOCHECKOUT,
callback);
for (std::map<int, std::vector<AutocheckoutStepType> >::const_iterator
it = page_meta_data_->page_types.begin();
it != page_meta_data_->page_types.end(); ++it) {
for (size_t i = 0; i < it->second.size(); ++i) {
autofill_manager_->delegate()->AddAutocheckoutStep(it->second[i]);
}
}
}
void AutocheckoutManager::ShowAutocheckoutBubble(
const GURL& frame_url,
const gfx::RectF& bounding_box,
const std::string& cookies) {
DCHECK(thread_checker_.CalledOnValidThread());
base::Callback<void(AutocheckoutBubbleState)> callback = base::Bind(
&AutocheckoutManager::MaybeShowAutocheckoutDialog,
weak_ptr_factory_.GetWeakPtr(),
frame_url);
autofill_manager_->delegate()->ShowAutocheckoutBubble(
bounding_box,
cookies.find("LSID") != std::string::npos,
callback);
is_autocheckout_bubble_showing_ = true;
}
bool AutocheckoutManager::IsStartOfAutofillableFlow() const {
return page_meta_data_ && page_meta_data_->IsStartOfAutofillableFlow();
}
bool AutocheckoutManager::IsInAutofillableFlow() const {
return page_meta_data_ && page_meta_data_->IsInAutofillableFlow();
}
void AutocheckoutManager::SetValue(const AutofillField& field,
FormFieldData* field_to_fill) {
// No-op if Autofill server doesn't know about the field.
if (field.server_type() == NO_SERVER_DATA)
return;
const AutofillType& type = field.Type();
if (type.server_type() == FIELD_WITH_DEFAULT_VALUE) {
// For a form with radio buttons, like:
// <form>
// <input type="radio" name="sex" value="male">Male<br>
// <input type="radio" name="sex" value="female">Female
// </form>
// If the default value specified at the server is "female", then
// Autofill server responds back with following field mappings
// (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female")
// (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female")
// Note that, the field mapping is repeated twice to respond to both the
// input elements with the same name/signature in the form.
//
// FIELD_WITH_DEFAULT_VALUE can also be used for selects, the correspondent
// example of the radio buttons example above is:
// <SELECT name="sex">
// <OPTION value="female">Female</OPTION>
// <OPTION value="male">Male</OPTION>
// </SELECT>
base::string16 default_value = UTF8ToUTF16(field.default_value());
if (field.is_checkable) {
// Mark the field checked if server says the default value of the field
// to be this field's value.
field_to_fill->is_checked = (field.value == default_value);
} else if (field.form_control_type == "select-one") {
field_to_fill->value = default_value;
} else {
// FIELD_WITH_DEFAULT_VALUE should not be used for other type of fields.
NOTREACHED();
}
return;
}
// Handle verification code directly.
if (type.server_type() == CREDIT_CARD_VERIFICATION_CODE) {
field_to_fill->value = cvv_;
return;
}
if (type.group() == CREDIT_CARD) {
credit_card_->FillFormField(
field, 0, autofill_manager_->app_locale(), field_to_fill);
} else if (IsBillingGroup(type.group())) {
billing_address_->FillFormField(
field, 0, autofill_manager_->app_locale(), field_to_fill);
} else {
profile_->FillFormField(
field, 0, autofill_manager_->app_locale(), field_to_fill);
}
}
void AutocheckoutManager::SendAutocheckoutStatus(AutocheckoutStatus status) {
// To ensure stale data isn't being sent.
DCHECK_NE(kTransactionIdNotSet, google_transaction_id_);
AutocheckoutRequestManager::CreateForBrowserContext(
autofill_manager_->GetWebContents()->GetBrowserContext());
AutocheckoutRequestManager* autocheckout_request_manager =
AutocheckoutRequestManager::FromBrowserContext(
autofill_manager_->GetWebContents()->GetBrowserContext());
// It is assumed that the domain Autocheckout starts on does not change
// during the flow. If this proves to be incorrect, the |source_url| from
// AutofillDialogControllerImpl will need to be provided in its callback in
// addition to the Google transaction id.
autocheckout_request_manager->SendAutocheckoutStatus(
status,
autofill_manager_->GetWebContents()->GetURL(),
latency_statistics_,
google_transaction_id_);
// Log the result of this Autocheckout flow to UMA.
metric_logger_->LogAutocheckoutBuyFlowMetric(
AutocheckoutStatusToUmaMetric(status));
google_transaction_id_ = kTransactionIdNotSet;
}
void AutocheckoutManager::SetStepProgressForPage(
int page_number,
AutocheckoutStepStatus status) {
if (page_types_.count(page_number) == 1) {
for (size_t i = 0; i < page_types_[page_number].size(); ++i) {
autofill_manager_->delegate()->UpdateAutocheckoutStep(
page_types_[page_number][i], status);
}
}
}
void AutocheckoutManager::RecordTimeTaken(int page_number) {
AutocheckoutStatistic statistic;
statistic.page_number = page_number;
if (page_types_.count(page_number) == 1) {
for (size_t i = 0; i < page_types_[page_number].size(); ++i) {
statistic.steps.push_back(page_types_[page_number][i]);
}
}
statistic.time_taken =
base::TimeTicks().Now() - last_step_completion_timestamp_;
latency_statistics_.push_back(statistic);
// Reset timestamp.
last_step_completion_timestamp_ = base::TimeTicks().Now();
}
void AutocheckoutManager::EndAutocheckout(AutocheckoutStatus status) {
DCHECK(in_autocheckout_flow_);
DVLOG(2) << "EndAutocheckout at step: "
<< page_meta_data_->current_page_number
<< " with status: "
<< status;
SendAutocheckoutStatus(status);
if (status == SUCCESS)
autofill_manager_->delegate()->OnAutocheckoutSuccess();
else
autofill_manager_->delegate()->OnAutocheckoutError();
in_autocheckout_flow_ = false;
}
} // namespace autofill