Commit 7d24db75 authored by isherman@chromium.org's avatar isherman@chromium.org

Add metrics to track Autofill "user happiness"

BUG=none
TEST=TBD


Review URL: http://codereview.chromium.org/7747009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@98397 0039d316-1c4b-4281-b951-d872f2087c98
parent fbc66010
......@@ -227,7 +227,11 @@ AutofillManager::AutofillManager(TabContentsWrapper* tab_contents)
disable_download_manager_requests_(false),
metric_logger_(new AutofillMetrics),
has_logged_autofill_enabled_(false),
has_logged_address_suggestions_count_(false) {
has_logged_address_suggestions_count_(false),
did_show_suggestions_(false),
user_did_type_(false),
user_did_autofill_(false),
user_did_edit_autofilled_field_(false) {
DCHECK(tab_contents);
// |personal_data_| is NULL when using TestTabContents.
......@@ -278,12 +282,16 @@ bool AutofillManager::OnMessageReceived(const IPC::Message& message) {
IPC_BEGIN_MESSAGE_MAP(AutofillManager, message)
IPC_MESSAGE_HANDLER(AutofillHostMsg_FormsSeen, OnFormsSeen)
IPC_MESSAGE_HANDLER(AutofillHostMsg_FormSubmitted, OnFormSubmitted)
IPC_MESSAGE_HANDLER(AutofillHostMsg_TextFieldDidChange,
OnTextFieldDidChange)
IPC_MESSAGE_HANDLER(AutofillHostMsg_QueryFormFieldAutofill,
OnQueryFormFieldAutofill)
IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowAutofillDialog,
OnShowAutofillDialog)
IPC_MESSAGE_HANDLER(AutofillHostMsg_FillAutofillFormData,
OnFillAutofillFormData)
IPC_MESSAGE_HANDLER(AutofillHostMsg_DidPreviewAutofillFormData,
OnDidPreviewAutofillFormData)
IPC_MESSAGE_HANDLER(AutofillHostMsg_DidFillAutofillFormData,
OnDidFillAutofillFormData)
IPC_MESSAGE_HANDLER(AutofillHostMsg_DidShowAutofillSuggestions,
......@@ -352,6 +360,31 @@ void AutofillManager::OnFormsSeen(const std::vector<FormData>& forms) {
ParseForms(forms);
}
void AutofillManager::OnTextFieldDidChange(const FormData& form,
const FormField& field) {
FormStructure* form_structure = NULL;
AutofillField* autofill_field = NULL;
if (!FindCachedFormAndField(form, field, &form_structure, &autofill_field))
return;
if (!user_did_type_) {
user_did_type_ = true;
metric_logger_->LogUserHappinessMetric(AutofillMetrics::USER_DID_TYPE);
}
if (autofill_field->is_autofilled) {
autofill_field->is_autofilled = false;
metric_logger_->LogUserHappinessMetric(
AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD);
if (!user_did_edit_autofilled_field_) {
user_did_edit_autofilled_field_ = true;
metric_logger_->LogUserHappinessMetric(
AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD_ONCE);
}
}
}
void AutofillManager::OnQueryFormFieldAutofill(
int query_id,
const webkit_glue::FormData& form,
......@@ -508,6 +541,10 @@ void AutofillManager::OnFillAutofillFormData(int query_id,
AutofillType(field_type).group());
FillCreditCardFormField(*credit_card, field_type, &(*iter));
}
// Mark the cached field as autofilled, so that we can detect when a
// user edits an autofilled field (for metrics).
autofill_field->is_autofilled = true;
break;
}
}
......@@ -559,6 +596,10 @@ void AutofillManager::OnFillAutofillFormData(int query_id,
DCHECK_EQ(AutofillType::CREDIT_CARD, field_group_type);
FillCreditCardFormField(*credit_card, field_type, &result.fields[j]);
}
// Mark the cached field as autofilled, so that we can detect when a user
// edits an autofilled field (for metrics).
form_structure->field(k)->is_autofilled = true;
}
// We found a matching field in the |form_structure|, so on the next
......@@ -583,18 +624,43 @@ void AutofillManager::OnShowAutofillDialog() {
browser->ShowOptionsTab(chrome::kAutofillSubPage);
}
void AutofillManager::OnDidPreviewAutofillFormData() {
NotificationService::current()->Notify(
chrome::NOTIFICATION_AUTOFILL_DID_FILL_FORM_DATA,
Source<RenderViewHost>(tab_contents()->render_view_host()),
NotificationService::NoDetails());
}
void AutofillManager::OnDidFillAutofillFormData() {
NotificationService::current()->Notify(
chrome::NOTIFICATION_AUTOFILL_DID_FILL_FORM_DATA,
Source<RenderViewHost>(tab_contents()->render_view_host()),
NotificationService::NoDetails());
metric_logger_->LogUserHappinessMetric(AutofillMetrics::USER_DID_AUTOFILL);
if (!user_did_autofill_) {
user_did_autofill_ = true;
metric_logger_->LogUserHappinessMetric(
AutofillMetrics::USER_DID_AUTOFILL_ONCE);
}
}
void AutofillManager::OnDidShowAutofillSuggestions() {
void AutofillManager::OnDidShowAutofillSuggestions(bool is_new_popup) {
NotificationService::current()->Notify(
chrome::NOTIFICATION_AUTOFILL_DID_SHOW_SUGGESTIONS,
Source<RenderViewHost>(tab_contents()->render_view_host()),
NotificationService::NoDetails());
if (is_new_popup) {
metric_logger_->LogUserHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN);
if (!did_show_suggestions_) {
did_show_suggestions_ = true;
metric_logger_->LogUserHappinessMetric(
AutofillMetrics::SUGGESTIONS_SHOWN_ONCE);
}
}
}
void AutofillManager::OnLoadedServerPredictions(
......@@ -646,7 +712,7 @@ void AutofillManager::DeterminePossibleFieldTypesForUpload(
// For each field in the |submitted_form|, extract the value. Then for each
// profile or credit card, identify any stored types that match the value.
for (size_t i = 0; i < submitted_form->field_count(); i++) {
const AutofillField* field = submitted_form->field(i);
AutofillField* field = submitted_form->field(i);
string16 value = CollapseWhitespace(field->value, false);
FieldTypeSet matching_types;
for (std::vector<FormGroup*>::const_iterator it = stored_data.begin();
......@@ -657,7 +723,7 @@ void AutofillManager::DeterminePossibleFieldTypesForUpload(
if (matching_types.empty())
matching_types.insert(UNKNOWN_TYPE);
submitted_form->set_possible_types(i, matching_types);
field->set_possible_types(matching_types);
}
}
......@@ -704,6 +770,10 @@ void AutofillManager::Reset() {
form_structures_.reset();
has_logged_autofill_enabled_ = false;
has_logged_address_suggestions_count_ = false;
did_show_suggestions_ = false;
user_did_type_ = false;
user_did_autofill_ = false;
user_did_edit_autofilled_field_ = false;
}
AutofillManager::AutofillManager(TabContentsWrapper* tab_contents,
......@@ -715,7 +785,11 @@ AutofillManager::AutofillManager(TabContentsWrapper* tab_contents,
disable_download_manager_requests_(true),
metric_logger_(new AutofillMetrics),
has_logged_autofill_enabled_(false),
has_logged_address_suggestions_count_(false) {
has_logged_address_suggestions_count_(false),
did_show_suggestions_(false),
user_did_type_(false),
user_did_autofill_(false),
user_did_edit_autofilled_field_(false) {
DCHECK(tab_contents);
}
......@@ -1013,6 +1087,9 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) {
form_structures_.push_back(*iter);
}
if (!form_structures_.empty())
metric_logger_->LogUserHappinessMetric(AutofillMetrics::FORMS_LOADED);
CheckForPopularForms(form_structures_.get(), tab_contents_wrapper_,
tab_contents());
}
......
......@@ -115,6 +115,8 @@ class AutofillManager : public TabContentsObserver,
private:
void OnFormSubmitted(const webkit_glue::FormData& form);
void OnFormsSeen(const std::vector<webkit_glue::FormData>& forms);
void OnTextFieldDidChange(const webkit_glue::FormData& form,
const webkit_glue::FormField& field);
void OnQueryFormFieldAutofill(int query_id,
const webkit_glue::FormData& form,
const webkit_glue::FormField& field);
......@@ -123,8 +125,9 @@ class AutofillManager : public TabContentsObserver,
const webkit_glue::FormField& field,
int unique_id);
void OnShowAutofillDialog();
void OnDidPreviewAutofillFormData();
void OnDidFillAutofillFormData();
void OnDidShowAutofillSuggestions();
void OnDidShowAutofillSuggestions(bool is_new_popup);
// Fills |host| with the RenderViewHost for this tab.
// Returns false if Autofill is disabled or if the host is unavailable.
......@@ -217,12 +220,19 @@ class AutofillManager : public TabContentsObserver,
// For logging UMA metrics. Overridden by metrics tests.
scoped_ptr<const AutofillMetrics> metric_logger_;
// Have we logged whether Autofill is enabled for this page load?
bool has_logged_autofill_enabled_;
// Have we logged an address suggestions count metric for this page?
bool has_logged_address_suggestions_count_;
// Have we shown Autofill suggestions at least once?
bool did_show_suggestions_;
// Has the user manually edited at least one form field among the autofillable
// ones?
bool user_did_type_;
// Has the user autofilled a form on this page?
bool user_did_autofill_;
// Has the user edited a field that was previously autofilled?
bool user_did_edit_autofilled_field_;
// Our copy of the form data.
ScopedVector<FormStructure> form_structures_;
......@@ -264,6 +274,9 @@ class AutofillManager : public TabContentsObserver,
FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, QualityMetricsForFailure);
FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, QualityMetricsWithExperimentId);
FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, SaneMetricsWithCacheMismatch);
FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest,
UserHappinessFormLoadAndSubmission);
FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, UserHappinessFormInteraction);
DISALLOW_COPY_AND_ASSIGN(AutofillManager);
};
......
......@@ -288,6 +288,13 @@ void AutofillMetrics::LogServerQueryMetric(ServerQueryMetric metric) const {
NUM_SERVER_QUERY_METRICS);
}
void AutofillMetrics::LogUserHappinessMetric(UserHappinessMetric metric) const {
DCHECK(metric < NUM_USER_HAPPINESS_METRICS);
UMA_HISTOGRAM_ENUMERATION("Autofill.UserHappiness", metric,
NUM_USER_HAPPINESS_METRICS);
}
void AutofillMetrics::LogIsAutofillEnabledAtStartup(bool enabled) const {
UMA_HISTOGRAM_BOOLEAN("Autofill.IsEnabled.Startup", enabled);
}
......
......@@ -79,6 +79,47 @@ class AutofillMetrics {
NUM_SERVER_QUERY_METRICS
};
// Each of these metrics is logged only for potentially autofillable forms,
// i.e. forms with at least three fields, etc.
// These are used to derive certain "user happiness" metrics. For example, we
// can compute the ratio (USER_DID_EDIT_AUTOFILLED_FIELD / USER_DID_AUTOFILL)
// to see how often users have to correct autofilled data.
enum UserHappinessMetric {
// Loaded a page containing forms.
FORMS_LOADED,
// Submitted a fillable form -- i.e. one with at least three field values
// that match the user's stored Autofill data -- and all matching fields
// were autofilled.
SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL,
// Submitted a fillable form and some (but not all) matching fields were
// autofilled.
SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME,
// Submitted a fillable form and no fields were autofilled.
SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE,
// Submitted a non-fillable form.
SUBMITTED_NON_FILLABLE_FORM,
// User manually filled one of the form fields.
USER_DID_TYPE,
// We showed a popup containing Autofill suggestions.
SUGGESTIONS_SHOWN,
// Same as above, but only logged once per page load.
SUGGESTIONS_SHOWN_ONCE,
// User autofilled at least part of the form.
USER_DID_AUTOFILL,
// Same as above, but only logged once per page load.
USER_DID_AUTOFILL_ONCE,
// User edited a previously autofilled field.
USER_DID_EDIT_AUTOFILLED_FIELD,
// Same as above, but only logged once per page load.
USER_DID_EDIT_AUTOFILLED_FIELD_ONCE,
NUM_USER_HAPPINESS_METRICS
};
// TODO(isherman): Add histograms to measure time elapsed between form load
// form submission, comparing autofilled and non-autofilled forms. So that we
// are measuring apples to apples, restrict just to fillable forms.
AutofillMetrics();
virtual ~AutofillMetrics();
......@@ -101,6 +142,8 @@ class AutofillMetrics {
virtual void LogServerQueryMetric(ServerQueryMetric metric) const;
virtual void LogUserHappinessMetric(UserHappinessMetric metric) const;
// This should be called each time a page containing forms is loaded.
virtual void LogIsAutofillEnabledAtPageLoad(bool enabled) const;
......
......@@ -650,6 +650,9 @@ void FormStructure::LogQualityMetrics(
std::string experiment_id = server_experiment_id();
metric_logger.LogServerExperimentIdForUpload(experiment_id);
size_t num_detected_field_types = 0;
bool did_autofill_all_possible_fields = true;
bool did_autofill_some_possible_fields = false;
for (size_t i = 0; i < field_count(); ++i) {
const AutofillField* field = this->field(i);
metric_logger.LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED,
......@@ -662,6 +665,12 @@ void FormStructure::LogQualityMetrics(
if (field_types.count(EMPTY_TYPE) || field_types.count(UNKNOWN_TYPE))
continue;
++num_detected_field_types;
if (field->is_autofilled)
did_autofill_some_possible_fields = true;
else
did_autofill_all_possible_fields = false;
// Collapse field types that Chrome treats as identical, e.g. home and
// billing address fields.
FieldTypeSet collapsed_field_types;
......@@ -765,16 +774,20 @@ void FormStructure::LogQualityMetrics(
}
}
}
}
void FormStructure::set_possible_types(size_t index,
const FieldTypeSet& types) {
if (index >= fields_.size()) {
NOTREACHED();
return;
if (num_detected_field_types < kRequiredFillableFields) {
metric_logger.LogUserHappinessMetric(
AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM);
} else if (did_autofill_all_possible_fields) {
metric_logger.LogUserHappinessMetric(
AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL);
} else if (did_autofill_some_possible_fields) {
metric_logger.LogUserHappinessMetric(
AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME);
} else {
metric_logger.LogUserHappinessMetric(
AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE);
}
fields_[index]->set_possible_types(types);
}
const AutofillField* FormStructure::field(size_t index) const {
......@@ -786,6 +799,11 @@ const AutofillField* FormStructure::field(size_t index) const {
return fields_[index];
}
AutofillField* FormStructure::field(size_t index) {
return const_cast<AutofillField*>(
static_cast<const FormStructure*>(this)->field(index));
}
size_t FormStructure::field_count() const {
return fields_.size();
}
......
......@@ -109,10 +109,8 @@ class FormStructure {
// set for each field.
void LogQualityMetrics(const AutofillMetrics& metric_logger) const;
// Sets the possible types for the field at |index|.
void set_possible_types(size_t index, const FieldTypeSet& types);
const AutofillField* field(size_t index) const;
AutofillField* field(size_t index);
size_t field_count() const;
// Returns the number of fields that are able to be autofilled.
......
......@@ -1504,7 +1504,7 @@ TEST(FormStructureTest, EncodeUploadRequest) {
ASSERT_EQ(form_structure->field_count(), possible_field_types.size());
for (size_t i = 0; i < form_structure->field_count(); ++i)
form_structure->set_possible_types(i, possible_field_types[i]);
form_structure->field(i)->set_possible_types(possible_field_types[i]);
FieldTypeSet available_field_types;
available_field_types.insert(NAME_FIRST);
......@@ -1564,7 +1564,7 @@ TEST(FormStructureTest, EncodeUploadRequest) {
form_structure.reset(new FormStructure(form));
ASSERT_EQ(form_structure->field_count(), possible_field_types.size());
for (size_t i = 0; i < form_structure->field_count(); ++i)
form_structure->set_possible_types(i, possible_field_types[i]);
form_structure->field(i)->set_possible_types(possible_field_types[i]);
EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false,
&encoded_xml));
......@@ -1604,7 +1604,7 @@ TEST(FormStructureTest, EncodeUploadRequest) {
form_structure.reset(new FormStructure(form));
ASSERT_EQ(form_structure->field_count(), possible_field_types.size());
for (size_t i = 0; i < form_structure->field_count(); ++i)
form_structure->set_possible_types(i, possible_field_types[i]);
form_structure->field(i)->set_possible_types(possible_field_types[i]);
EXPECT_FALSE(form_structure->EncodeUploadRequest(available_field_types, false,
&encoded_xml));
}
......@@ -1635,7 +1635,7 @@ TEST(FormStructureTest, CheckDataPresence) {
FieldTypeSet unknown_type;
unknown_type.insert(UNKNOWN_TYPE);
for (size_t i = 0; i < form_structure.field_count(); ++i)
form_structure.set_possible_types(i, unknown_type);
form_structure.field(i)->set_possible_types(unknown_type);
// No available types.
// datapresent should be "" == trimmmed(0x0000000000000000) ==
......@@ -1919,7 +1919,7 @@ TEST(FormStructureTest, CheckMultipleTypes) {
form_structure.reset(new FormStructure(form));
for (size_t i = 0; i < form_structure->field_count(); ++i)
form_structure->set_possible_types(i, possible_field_types[i]);
form_structure->field(i)->set_possible_types(possible_field_types[i]);
std::string encoded_xml;
// Now we matched both fields singularly.
......@@ -1937,7 +1937,7 @@ TEST(FormStructureTest, CheckMultipleTypes) {
encoded_xml);
// Match third field as both first and last.
possible_field_types[2].insert(NAME_FIRST);
form_structure->set_possible_types(2, possible_field_types[2]);
form_structure->field(2)->set_possible_types(possible_field_types[2]);
EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false,
&encoded_xml));
EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>"
......@@ -1952,8 +1952,7 @@ TEST(FormStructureTest, CheckMultipleTypes) {
"</autofillupload>",
encoded_xml);
possible_field_types[3].insert(ADDRESS_HOME_LINE2);
form_structure->set_possible_types(
form_structure->field_count() - 1,
form_structure->field(form_structure->field_count() - 1)->set_possible_types(
possible_field_types[form_structure->field_count() - 1]);
EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false,
&encoded_xml));
......@@ -1972,8 +1971,7 @@ TEST(FormStructureTest, CheckMultipleTypes) {
possible_field_types[3].clear();
possible_field_types[3].insert(ADDRESS_HOME_LINE1);
possible_field_types[3].insert(COMPANY_NAME);
form_structure->set_possible_types(
form_structure->field_count() - 1,
form_structure->field(form_structure->field_count() - 1)->set_possible_types(
possible_field_types[form_structure->field_count() - 1]);
EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false,
&encoded_xml));
......
......@@ -107,6 +107,11 @@ IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsVisible,
IPC_MESSAGE_ROUTED1(AutofillHostMsg_FormSubmitted,
webkit_glue::FormData /* form */)
// Notification that a form field's value has changed.
IPC_MESSAGE_ROUTED2(AutofillHostMsg_TextFieldDidChange,
webkit_glue::FormData /* the form */,
webkit_glue::FormField /* the form field */)
// Queries the browser for Autofill suggestions for a form input field.
IPC_MESSAGE_ROUTED3(AutofillHostMsg_QueryFormFieldAutofill,
int /* id of this message */,
......@@ -114,7 +119,8 @@ IPC_MESSAGE_ROUTED3(AutofillHostMsg_QueryFormFieldAutofill,
webkit_glue::FormField /* the form field */)
// Sent when the popup with Autofill suggestions for a form is shown.
IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidShowAutofillSuggestions)
IPC_MESSAGE_ROUTED1(AutofillHostMsg_DidShowAutofillSuggestions,
bool /* is this a new popup? */)
// Instructs the browser to fill in the values for a form using Autofill
// profile data.
......@@ -124,7 +130,10 @@ IPC_MESSAGE_ROUTED4(AutofillHostMsg_FillAutofillFormData,
webkit_glue::FormField /* the form field */,
int /* profile unique ID */)
// Sent when a form is previewed or filled with Autofill suggestions.
// Sent when a form is previewed with Autofill suggestions.
IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidPreviewAutofillFormData)
// Sent when a form is filled with Autofill suggestions.
IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidFillAutofillFormData)
// Instructs the browser to remove the specified Autocomplete entry from the
......
......@@ -179,6 +179,7 @@ void AutofillAgent::removeAutocompleteSuggestion(const WebString& name,
void AutofillAgent::textFieldDidEndEditing(const WebInputElement& element) {
password_autofill_manager_->TextFieldDidEndEditing(element);
has_shown_autofill_popup_for_current_edit_ = false;
}
void AutofillAgent::textFieldDidChange(const WebInputElement& element) {
......@@ -197,6 +198,11 @@ void AutofillAgent::TextFieldDidChangeImpl(const WebInputElement& element) {
return;
ShowSuggestions(element, false, true, false);
webkit_glue::FormData form;
webkit_glue::FormField field;
if (FindFormAndFieldForNode(element, &form, &field))
Send(new AutofillHostMsg_TextFieldDidChange(routing_id(), form, field));
}
void AutofillAgent::textFieldDidReceiveKeyDown(const WebInputElement& element,
......@@ -230,6 +236,7 @@ void AutofillAgent::OnSuggestionsReturned(int query_id,
std::vector<int> ids(unique_ids);
int separator_index = -1;
DCHECK_GT(ids.size(), 0U);
if (!autofill_query_element_.isNull() &&
!autofill_query_element_.autoComplete()) {
// If autofill is disabled and we had suggestions, show a warning instead.
......@@ -290,7 +297,10 @@ void AutofillAgent::OnSuggestionsReturned(int query_id,
autofill_query_element_, v, l, i, ids, separator_index);
}
Send(new AutofillHostMsg_DidShowAutofillSuggestions(routing_id()));
Send(new AutofillHostMsg_DidShowAutofillSuggestions(
routing_id(),
has_autofill_item && !has_shown_autofill_popup_for_current_edit_));
has_shown_autofill_popup_for_current_edit_ |= has_autofill_item;
}
void AutofillAgent::OnFormDataFilled(int query_id,
......@@ -301,15 +311,16 @@ void AutofillAgent::OnFormDataFilled(int query_id,
switch (autofill_action_) {
case AUTOFILL_FILL:
form_manager_.FillForm(form, autofill_query_element_);
Send(new AutofillHostMsg_DidFillAutofillFormData(routing_id()));
break;
case AUTOFILL_PREVIEW:
form_manager_.PreviewForm(form, autofill_query_element_);
Send(new AutofillHostMsg_DidPreviewAutofillFormData(routing_id()));
break;
default:
NOTREACHED();
}
autofill_action_ = AUTOFILL_NONE;
Send(new AutofillHostMsg_DidFillAutofillFormData(routing_id()));
}
void AutofillAgent::OnFieldTypePredictionsAvailable(
......
......@@ -163,6 +163,10 @@ class AutofillAgent : public RenderViewObserver,
// The menu index of the "Autofill options..." menu item.
int suggestions_options_index_;
// Have we already shown Autofill suggestions for the field the user is
// currently editing? Used to keep track of state for metrics logging.
bool has_shown_autofill_popup_for_current_edit_;
ScopedRunnableMethodFactory<AutofillAgent> method_factory_;
DISALLOW_COPY_AND_ASSIGN(AutofillAgent);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment