blob: 41ec97e893ed810e6b5f0c6200dc00889ddbeddb [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/core/browser/metrics/quality_metrics.h"
#include <memory>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "components/autofill/core/browser/autofill_data_util.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/field_type_utils.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/filling_product.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/metrics/autofill_metrics_utils.h"
#include "components/autofill/core/browser/metrics/field_filling_stats_and_score_metrics.h"
#include "components/autofill/core/browser/metrics/granular_filling_metrics_utils.h"
#include "components/autofill/core/browser/metrics/placeholder_metrics.h"
#include "components/autofill/core/browser/metrics/shadow_prediction_metrics.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_features.h"
namespace autofill::autofill_metrics {
namespace {
void LogNumericQuantityMetrics(const FormStructure& form) {
for (const std::unique_ptr<AutofillField>& field : form) {
if (field->heuristic_type() != NUMERIC_QUANTITY) {
continue;
}
// For every field that has a heuristics prediction for a
// NUMERIC_QUANTITY, log if there was a colliding server
// prediction and if the NUMERIC_QUANTITY was a false-positive prediction.
// The latter is true when the field was correctly filled. This can
// only be recorded when the feature to grant precedence to
// NUMERIC_QUANTITY predictions is disabled.
bool field_has_non_empty_server_prediction =
field->server_type() != UNKNOWN_TYPE &&
field->server_type() != NO_SERVER_DATA;
// Log if there was a colliding server prediction.
AutofillMetrics::LogNumericQuantityCollidesWithServerPrediction(
field_has_non_empty_server_prediction);
// If there was a collision, log if the NUMERIC_QUANTITY was a false
// positive since the field was correctly filled.
if ((field->is_autofilled() || field->previously_autofilled()) &&
field_has_non_empty_server_prediction &&
!base::FeatureList::IsEnabled(
features::kAutofillGivePrecedenceToNumericQuantities)) {
AutofillMetrics::
LogAcceptedFilledFieldWithNumericQuantityHeuristicPrediction(
!field->previously_autofilled());
}
}
}
void LogPerfectFillingMetric(const FormStructure& form) {
// Denotes whether for a given FillingProduct, the form has a field which was
// last filled with this product (and maybe user/JS edited afterwards).
const base::flat_map<FillingProduct, bool> filling_product_was_used =
base::MakeFlatMap<FillingProduct, bool>(
std::vector<FillingProduct>{FillingProduct::kAddress,
FillingProduct::kCreditCard},
{}, [&form](FillingProduct filling_product) {
return std::make_pair(
filling_product,
base::ranges::any_of(
form, [&filling_product](const auto& field) {
return field->filling_product() == filling_product;
}));
});
// A perfectly filled form is submitted as it was filled from Autofill
// without subsequent changes. This means that in a perfect filling
// scenario, a field is either autofilled, empty, has value at page load or
// has value set by JS.
const bool perfect_filling =
base::ranges::none_of(form, [](const auto& field) {
return field->is_user_edited() && !field->is_autofilled();
});
// The perfect filling metric is only recorded if Autofill was used on at
// least one field. This conditions this metric on Assistance, Readiness and
// Acceptance. Perfect filling is recorded for addresses and credit cards
// separately.
if (filling_product_was_used.at(FillingProduct::kAddress)) {
AutofillMetrics::LogAutofillPerfectFilling(/*is_address=*/true,
perfect_filling);
}
if (filling_product_was_used.at(FillingProduct::kCreditCard)) {
AutofillMetrics::LogAutofillPerfectFilling(/*is_address=*/false,
perfect_filling);
}
}
void LogPreFillMetrics(const FormStructure& form) {
for (const std::unique_ptr<AutofillField>& field : form) {
const FormType form_type_of_field =
FieldTypeGroupToFormType(field->Type().group());
const bool is_address_form_field =
form_type_of_field == FormType::kAddressForm;
const bool credit_card_form_field =
form_type_of_field == FormType::kCreditCardForm;
if (is_address_form_field || credit_card_form_field) {
const std::string_view form_type_name =
FormTypeToStringView(form_type_of_field);
LogPreFilledFieldStatus(form_type_name, field->initial_value_changed(),
field->Type().GetStorableType());
LogPreFilledValueChanged(
form_type_name, field->initial_value_changed(), field->value(),
field->field_log_events(), field->possible_types(),
field->Type().GetStorableType(), field->is_autofilled());
LogPreFilledFieldClassifications(form_type_name,
field->initial_value_changed(),
field->may_use_prefilled_placeholder());
}
}
}
// Logs metrics related to how long it took the user from load/interaction time
// till form submission.
void LogDurationMetrics(const FormStructure& form,
const base::TimeTicks& load_time,
const base::TimeTicks& interaction_time,
const base::TimeTicks& submission_time) {
size_t num_detected_field_types =
base::ranges::count_if(form, &FieldHasMeaningfulPossibleFieldTypes,
&std::unique_ptr<AutofillField>::operator*);
bool form_has_autofilled_fields = base::ranges::any_of(
form, [](const auto& field) { return field->is_autofilled(); });
bool has_observed_one_time_code_field =
base::ranges::any_of(form, [](const auto& field) {
return field->Type().html_type() == HtmlFieldType::kOneTimeCode;
});
if (num_detected_field_types >= kMinRequiredFieldsForHeuristics ||
num_detected_field_types >= kMinRequiredFieldsForQuery) {
// `submission_time` should always be available.
CHECK(!submission_time.is_null());
// The |load_time| might be unset, in the case that the form was
// dynamically added to the DOM.
// Submission should chronologically follow form load, however
// this might not be true in case of a timezone change. Therefore make
// sure to log the elapsed time between submission time and load time only
// if it is positive. Same is applied below.
if (!load_time.is_null() && submission_time >= load_time) {
base::TimeDelta elapsed = submission_time - load_time;
if (form_has_autofilled_fields) {
AutofillMetrics::LogFormFillDurationFromLoadWithAutofill(elapsed);
} else {
AutofillMetrics::LogFormFillDurationFromLoadWithoutAutofill(elapsed);
}
}
// The |interaction_time| might be unset, in the case that the user
// submitted a blank form.
if (!interaction_time.is_null() && submission_time >= interaction_time) {
base::TimeDelta elapsed = submission_time - interaction_time;
AutofillMetrics::LogFormFillDurationFromInteraction(
form.GetFormTypes(), form_has_autofilled_fields, elapsed);
}
}
if (has_observed_one_time_code_field) {
if (!load_time.is_null() && submission_time >= load_time) {
base::TimeDelta elapsed = submission_time - load_time;
AutofillMetrics::LogFormFillDurationFromLoadForOneTimeCode(elapsed);
}
if (!interaction_time.is_null() && submission_time >= interaction_time) {
base::TimeDelta elapsed = submission_time - interaction_time;
AutofillMetrics::LogFormFillDurationFromInteractionForOneTimeCode(
elapsed);
}
}
}
void LogExtractionMetrics(const FormStructure& form) {
for (const std::unique_ptr<AutofillField>& field : form) {
CHECK(!field->possible_types().empty());
if (FieldHasMeaningfulPossibleFieldTypes(*field)) {
base::UmaHistogramEnumeration(
"Autofill.LabelInference.InferredLabelSource.AtSubmission2",
field->label_source());
}
}
}
void LogPredictionMetrics(
const FormStructure& form,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger,
bool observed_submission) {
const AutofillMetrics::QualityMetricType metric_type =
observed_submission ? AutofillMetrics::TYPE_SUBMISSION
: AutofillMetrics::TYPE_NO_SUBMISSION;
for (const std::unique_ptr<AutofillField>& field : form) {
AutofillMetrics::LogHeuristicPredictionQualityMetrics(
form_interactions_ukm_logger, form, *field, metric_type);
AutofillMetrics::LogServerPredictionQualityMetrics(
form_interactions_ukm_logger, form, *field, metric_type);
AutofillMetrics::LogOverallPredictionQualityMetrics(
form_interactions_ukm_logger, form, *field, metric_type);
AutofillMetrics::LogEmailFieldPredictionMetrics(*field);
autofill_metrics::LogShadowPredictionComparison(*field);
}
}
void LogFillingMetrics(
const FormStructure& form,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger,
bool observed_submission) {
const AutofillMetrics::QualityMetricType metric_type =
observed_submission ? AutofillMetrics::TYPE_SUBMISSION
: AutofillMetrics::TYPE_NO_SUBMISSION;
for (const std::unique_ptr<AutofillField>& field : form) {
form_interactions_ukm_logger->LogFieldFillStatus(form, *field, metric_type);
}
if (!observed_submission) {
return;
}
LogPerfectFillingMetric(form);
LogPreFillMetrics(form);
autofill_metrics::LogFieldFillingStatsAndScore(form);
FieldTypeSet autofilled_field_types;
for (const std::unique_ptr<AutofillField>& field : form) {
if (field->is_autofilled() || field->previously_autofilled()) {
AutofillMetrics::LogEditedAutofilledFieldAtSubmission(
form_interactions_ukm_logger, form, *field);
}
if (FieldHasMeaningfulPossibleFieldTypes(*field) &&
field->is_autofilled()) {
autofilled_field_types.insert(field->Type().GetStorableType());
}
}
if (base::Contains(form.GetFormTypes(), FormType::kCreditCardForm)) {
AutofillMetrics::LogCreditCardSeamlessnessAtSubmissionTime(
autofilled_field_types);
}
}
} // namespace
void LogQualityMetrics(
const FormStructure& form_structure,
const base::TimeTicks& load_time,
const base::TimeTicks& interaction_time,
const base::TimeTicks& submission_time,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger,
bool observed_submission) {
// Use the same timestamp on UKM Metrics generated within this method's scope.
AutofillMetrics::UkmTimestampPin timestamp_pin(form_interactions_ukm_logger);
LogPredictionMetrics(form_structure, form_interactions_ukm_logger,
observed_submission);
LogFillingMetrics(form_structure, form_interactions_ukm_logger,
observed_submission);
if (observed_submission) {
LogExtractionMetrics(form_structure);
LogNumericQuantityMetrics(form_structure);
LogDurationMetrics(form_structure, load_time, interaction_time,
submission_time);
}
}
// Log the quality of the heuristics and server predictions for this form
// structure, if autocomplete attributes are present on the fields (since the
// autocomplete attribute takes precedence over other type predictions).
void LogQualityMetricsBasedOnAutocomplete(
const FormStructure& form_structure,
AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
const AutofillMetrics::QualityMetricType metric_type =
AutofillMetrics::TYPE_AUTOCOMPLETE_BASED;
for (const auto& field : form_structure) {
if (field->html_type() != HtmlFieldType::kUnspecified &&
field->html_type() != HtmlFieldType::kUnrecognized) {
AutofillMetrics::LogHeuristicPredictionQualityMetrics(
form_interactions_ukm_logger, form_structure, *field, metric_type);
AutofillMetrics::LogServerPredictionQualityMetrics(
form_interactions_ukm_logger, form_structure, *field, metric_type);
}
}
}
} // namespace autofill::autofill_metrics