| // Copyright 2013 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/autofill_metrics.h" |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/strcat.h" |
| #include "base/time/time.h" |
| #include "base/types/cxx23_to_underlying.h" |
| #include "components/autofill/core/browser/autofill_data_util.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/autofill/core/browser/data_model/autofill_offer_data.h" |
| #include "components/autofill/core/browser/data_model/credit_card.h" |
| #include "components/autofill/core/browser/field_type_utils.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/form_types.h" |
| #include "components/autofill/core/browser/metrics/autofill_metrics_utils.h" |
| #include "components/autofill/core/browser/metrics/form_events/form_event_logger_base.h" |
| #include "components/autofill/core/browser/payments/card_unmask_challenge_option.h" |
| #include "components/autofill/core/browser/validation.h" |
| #include "components/autofill/core/common/autofill_clock.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_prefs.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_interactions_flow.h" |
| #include "components/language/core/browser/language_usage_metrics.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| |
| namespace autofill { |
| |
| using mojom::SubmissionSource; |
| |
| namespace { |
| |
| // Exponential bucket spacing for UKM event data. |
| constexpr double kAutofillEventDataBucketSpacing = 2.0; |
| |
| using autofill_metrics::FormGroupFillingStats; |
| |
| // Translates structured name types into simple names that are used for |
| // naming histograms. |
| constexpr auto kStructuredNameTypeToNameMap = |
| base::MakeFixedFlatMap<FieldType, std::string_view>( |
| {{NAME_FULL, "Full"}, |
| {NAME_FIRST, "First"}, |
| {NAME_MIDDLE, "Middle"}, |
| {NAME_LAST, "Last"}, |
| {NAME_LAST_FIRST, "FirstLast"}, |
| {NAME_LAST_SECOND, "SecondLast"}}); |
| |
| // Translates structured address types into simple names that are used for |
| // naming histograms. |
| constexpr auto kStructuredAddressTypeToNameMap = |
| base::MakeFixedFlatMap<FieldType, std::string_view>( |
| {{ADDRESS_HOME_STREET_ADDRESS, "StreetAddress"}, |
| {ADDRESS_HOME_STREET_NAME, "StreetName"}, |
| {ADDRESS_HOME_HOUSE_NUMBER, "HouseNumber"}, |
| {ADDRESS_HOME_FLOOR, "FloorNumber"}, |
| {ADDRESS_HOME_APT_NUM, "ApartmentNumber"}, |
| {ADDRESS_HOME_SUBPREMISE, "SubPremise"}}); |
| |
| // Don't change the enum values because they are recorded in metrics. |
| enum FieldTypeGroupForMetrics { |
| GROUP_AMBIGUOUS = 0, |
| GROUP_NAME = 1, |
| GROUP_COMPANY = 2, |
| GROUP_ADDRESS_LINE_1 = 3, |
| GROUP_ADDRESS_LINE_2 = 4, |
| GROUP_ADDRESS_CITY = 5, |
| GROUP_ADDRESS_STATE = 6, |
| GROUP_ADDRESS_ZIP = 7, |
| GROUP_ADDRESS_COUNTRY = 8, |
| GROUP_ADDRESS_HOME_STREET_NAME = 9, |
| GROUP_ADDRESS_HOME_HOUSE_NUMBER = 10, |
| GROUP_ADDRESS_HOME_SUBPREMISE = 11, |
| GROUP_PHONE = 12, |
| GROUP_FAX = 13, // Deprecated. |
| GROUP_EMAIL = 14, |
| GROUP_CREDIT_CARD_NAME = 15, |
| GROUP_CREDIT_CARD_NUMBER = 16, |
| GROUP_CREDIT_CARD_DATE = 17, |
| GROUP_CREDIT_CARD_TYPE = 18, |
| GROUP_PASSWORD = 19, |
| GROUP_ADDRESS_LINE_3 = 20, |
| GROUP_USERNAME = 21, |
| GROUP_STREET_ADDRESS = 22, |
| GROUP_CREDIT_CARD_VERIFICATION = 23, |
| GROUP_UNFILLABLE = 24, |
| GROUP_ADDRESS_HOME_APT_NUM = 25, |
| GROUP_ADDRESS_HOME_SORTING_CODE = 26, |
| GROUP_ADDRESS_HOME_DEPENDENT_LOCALITY = 27, |
| GROUP_ADDRESS_HOME_OTHER_SUBUNIT = 28, |
| GROUP_ADDRESS_HOME_ADDRESS = 29, |
| GROUP_ADDRESS_HOME_ADDRESS_WITH_NAME = 30, |
| GROUP_ADDRESS_HOME_FLOOR = 31, |
| GROUP_UNKNOWN_TYPE = 32, |
| GROUP_BIRTHDATE = 33, // Deprecated |
| GROUP_IBAN = 34, |
| GROUP_ADDRESS_HOME_LANDMARK = 35, |
| GROUP_ADDRESS_HOME_BETWEEN_STREETS = 36, |
| GROUP_ADDRESS_HOME_ADMIN_LEVEL2 = 37, |
| GROUP_ADDRESS_HOME_STREET_LOCATION = 38, |
| GROUP_ADDRESS_HOME_OVERFLOW = 39, |
| GROUP_DELIVERY_INSTRUCTIONS = 40, |
| GROUP_ADDRESS_HOME_OVERFLOW_AND_LANDMARK = 41, |
| GROUP_ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK = 42, |
| GROUP_ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY = 43, |
| GROUP_ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK = 44, |
| GROUP_ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK = 45, |
| GROUP_ADDRESS_HOME_HOUSE_NUMBER_AND_APT = 46, |
| // Note: if adding an enum value here, run |
| // tools/metrics/histograms/update_autofill_enums.py |
| NUM_FIELD_TYPE_GROUPS_FOR_METRICS |
| }; |
| |
| } // namespace |
| |
| // First, translates |field_type| to the corresponding logical |group| from |
| // |FieldTypeGroupForMetrics|. Then, interpolates this with the given |metric|, |
| // which should be in the range [0, |num_possible_metrics|). |
| // Returns the interpolated index. |
| // |
| // The interpolation maps the pair (|group|, |metric|) to a single index, so |
| // that all the indices for a given group are adjacent. In particular, with |
| // the groups {AMBIGUOUS, NAME, ...} combining with the metrics |
| // {UNKNOWN, MATCH, MISMATCH}, we create this set of mapped indices: |
| // { |
| // AMBIGUOUS+UNKNOWN, |
| // AMBIGUOUS+MATCH, |
| // AMBIGUOUS+MISMATCH, |
| // NAME+UNKNOWN, |
| // NAME+MATCH, |
| // NAME+MISMATCH, |
| // ... |
| // }. |
| // |
| // Clients must ensure that |field_type| is one of the types Chrome supports |
| // natively, e.g. |field_type| must not be a billing address. |
| // NOTE: This is defined outside of the anonymous namespace so that it can be |
| // accessed from the unit test file. It is not exposed in the header file, |
| // however, because it is not intended for consumption outside of the metrics |
| // implementation. |
| int GetFieldTypeGroupPredictionQualityMetric( |
| FieldType field_type, |
| AutofillMetrics::FieldTypeQualityMetric metric) { |
| DCHECK_LT(metric, AutofillMetrics::NUM_FIELD_TYPE_QUALITY_METRICS); |
| |
| FieldTypeGroupForMetrics group = GROUP_AMBIGUOUS; |
| switch (GroupTypeOfFieldType(field_type)) { |
| case FieldTypeGroup::kNoGroup: |
| group = GROUP_AMBIGUOUS; |
| break; |
| |
| case FieldTypeGroup::kName: |
| group = GROUP_NAME; |
| break; |
| |
| case FieldTypeGroup::kCompany: |
| group = GROUP_COMPANY; |
| break; |
| |
| case FieldTypeGroup::kIban: |
| group = GROUP_IBAN; |
| break; |
| |
| case FieldTypeGroup::kAddress: |
| switch (field_type) { |
| case ADDRESS_HOME_LINE1: |
| group = GROUP_ADDRESS_LINE_1; |
| break; |
| case ADDRESS_HOME_LINE2: |
| group = GROUP_ADDRESS_LINE_2; |
| break; |
| case ADDRESS_HOME_LINE3: |
| group = GROUP_ADDRESS_LINE_3; |
| break; |
| case ADDRESS_HOME_APT: |
| case ADDRESS_HOME_APT_NUM: |
| case ADDRESS_HOME_APT_TYPE: |
| group = GROUP_ADDRESS_HOME_APT_NUM; |
| break; |
| case ADDRESS_HOME_HOUSE_NUMBER_AND_APT: |
| group = GROUP_ADDRESS_HOME_HOUSE_NUMBER_AND_APT; |
| break; |
| case ADDRESS_HOME_STREET_ADDRESS: |
| group = GROUP_STREET_ADDRESS; |
| break; |
| case ADDRESS_HOME_CITY: |
| group = GROUP_ADDRESS_CITY; |
| break; |
| case ADDRESS_HOME_STATE: |
| group = GROUP_ADDRESS_STATE; |
| break; |
| case ADDRESS_HOME_ZIP: |
| group = GROUP_ADDRESS_ZIP; |
| break; |
| case ADDRESS_HOME_COUNTRY: |
| group = GROUP_ADDRESS_COUNTRY; |
| break; |
| case ADDRESS_HOME_STREET_NAME: |
| group = GROUP_ADDRESS_HOME_STREET_NAME; |
| break; |
| case ADDRESS_HOME_SORTING_CODE: |
| group = GROUP_ADDRESS_HOME_SORTING_CODE; |
| break; |
| case ADDRESS_HOME_DEPENDENT_LOCALITY: |
| group = GROUP_ADDRESS_HOME_DEPENDENT_LOCALITY; |
| break; |
| case ADDRESS_HOME_HOUSE_NUMBER: |
| group = GROUP_ADDRESS_HOME_HOUSE_NUMBER; |
| break; |
| case ADDRESS_HOME_SUBPREMISE: |
| group = GROUP_ADDRESS_HOME_SUBPREMISE; |
| break; |
| case ADDRESS_HOME_OTHER_SUBUNIT: |
| group = GROUP_ADDRESS_HOME_OTHER_SUBUNIT; |
| break; |
| case ADDRESS_HOME_ADDRESS: |
| group = GROUP_ADDRESS_HOME_ADDRESS; |
| break; |
| case ADDRESS_HOME_ADDRESS_WITH_NAME: |
| group = GROUP_ADDRESS_HOME_ADDRESS_WITH_NAME; |
| break; |
| case ADDRESS_HOME_FLOOR: |
| group = GROUP_ADDRESS_HOME_FLOOR; |
| break; |
| case ADDRESS_HOME_LANDMARK: |
| group = GROUP_ADDRESS_HOME_LANDMARK; |
| break; |
| case ADDRESS_HOME_BETWEEN_STREETS: |
| case ADDRESS_HOME_BETWEEN_STREETS_1: |
| case ADDRESS_HOME_BETWEEN_STREETS_2: |
| group = GROUP_ADDRESS_HOME_BETWEEN_STREETS; |
| break; |
| case ADDRESS_HOME_ADMIN_LEVEL2: |
| group = GROUP_ADDRESS_HOME_ADMIN_LEVEL2; |
| break; |
| case ADDRESS_HOME_OVERFLOW: |
| group = GROUP_ADDRESS_HOME_OVERFLOW; |
| break; |
| case ADDRESS_HOME_OVERFLOW_AND_LANDMARK: |
| group = GROUP_ADDRESS_HOME_OVERFLOW_AND_LANDMARK; |
| break; |
| case ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK: |
| group = GROUP_ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK; |
| break; |
| case ADDRESS_HOME_STREET_LOCATION: |
| group = GROUP_ADDRESS_HOME_STREET_LOCATION; |
| break; |
| case ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY: |
| group = GROUP_ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY; |
| break; |
| case ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK: |
| group = GROUP_ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK; |
| break; |
| case ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK: |
| group = GROUP_ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK; |
| break; |
| case DELIVERY_INSTRUCTIONS: |
| group = GROUP_DELIVERY_INSTRUCTIONS; |
| break; |
| case UNKNOWN_TYPE: |
| group = GROUP_UNKNOWN_TYPE; |
| break; |
| case NO_SERVER_DATA: |
| case EMPTY_TYPE: |
| case NAME_FIRST: |
| case NAME_MIDDLE: |
| case NAME_LAST: |
| case NAME_MIDDLE_INITIAL: |
| case NAME_FULL: |
| case NAME_SUFFIX: |
| case EMAIL_ADDRESS: |
| case PHONE_HOME_NUMBER: |
| case PHONE_HOME_NUMBER_PREFIX: |
| case PHONE_HOME_NUMBER_SUFFIX: |
| case PHONE_HOME_CITY_CODE: |
| case PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX: |
| case PHONE_HOME_COUNTRY_CODE: |
| case PHONE_HOME_CITY_AND_NUMBER: |
| case PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX: |
| case PHONE_HOME_WHOLE_NUMBER: |
| case CREDIT_CARD_NAME_FULL: |
| case CREDIT_CARD_NUMBER: |
| case CREDIT_CARD_EXP_MONTH: |
| case CREDIT_CARD_EXP_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_4_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: |
| case CREDIT_CARD_TYPE: |
| case CREDIT_CARD_VERIFICATION_CODE: |
| case COMPANY_NAME: |
| case FIELD_WITH_DEFAULT_VALUE: |
| case MERCHANT_EMAIL_SIGNUP: |
| case MERCHANT_PROMO_CODE: |
| case PASSWORD: |
| case ACCOUNT_CREATION_PASSWORD: |
| case NOT_ACCOUNT_CREATION_PASSWORD: |
| case USERNAME: |
| case USERNAME_AND_EMAIL_ADDRESS: |
| case NEW_PASSWORD: |
| case PROBABLY_NEW_PASSWORD: |
| case NOT_NEW_PASSWORD: |
| case CREDIT_CARD_NAME_FIRST: |
| case CREDIT_CARD_NAME_LAST: |
| case PHONE_HOME_EXTENSION: |
| case CONFIRMATION_PASSWORD: |
| case AMBIGUOUS_TYPE: |
| case SEARCH_TERM: |
| case PRICE: |
| case NUMERIC_QUANTITY: |
| case NOT_PASSWORD: |
| case SINGLE_USERNAME: |
| case NOT_USERNAME: |
| case ONE_TIME_CODE: |
| case NAME_LAST_FIRST: |
| case NAME_LAST_CONJUNCTION: |
| case NAME_LAST_SECOND: |
| case NAME_HONORIFIC_PREFIX: |
| case IBAN_VALUE: |
| case MAX_VALID_FIELD_TYPE: |
| case CREDIT_CARD_STANDALONE_VERIFICATION_CODE: |
| case SINGLE_USERNAME_FORGOT_PASSWORD: |
| case SINGLE_USERNAME_WITH_INTERMEDIATE_VALUES: |
| NOTREACHED() << field_type << " type is not in that group."; |
| group = GROUP_AMBIGUOUS; |
| break; |
| } |
| break; |
| |
| case FieldTypeGroup::kEmail: |
| group = GROUP_EMAIL; |
| break; |
| |
| case FieldTypeGroup::kPhone: |
| group = GROUP_PHONE; |
| break; |
| |
| case FieldTypeGroup::kCreditCard: |
| switch (field_type) { |
| case CREDIT_CARD_NAME_FULL: |
| case CREDIT_CARD_NAME_FIRST: |
| case CREDIT_CARD_NAME_LAST: |
| group = GROUP_CREDIT_CARD_NAME; |
| break; |
| case CREDIT_CARD_NUMBER: |
| group = GROUP_CREDIT_CARD_NUMBER; |
| break; |
| case CREDIT_CARD_TYPE: |
| group = GROUP_CREDIT_CARD_TYPE; |
| break; |
| case CREDIT_CARD_EXP_MONTH: |
| case CREDIT_CARD_EXP_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_4_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: |
| case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: |
| group = GROUP_CREDIT_CARD_DATE; |
| break; |
| case CREDIT_CARD_VERIFICATION_CODE: |
| case CREDIT_CARD_STANDALONE_VERIFICATION_CODE: |
| group = GROUP_CREDIT_CARD_VERIFICATION; |
| break; |
| default: |
| NOTREACHED() << field_type << " has no group assigned (ambiguous)"; |
| group = GROUP_AMBIGUOUS; |
| break; |
| } |
| break; |
| |
| case FieldTypeGroup::kPasswordField: |
| group = GROUP_PASSWORD; |
| break; |
| |
| case FieldTypeGroup::kUsernameField: |
| group = GROUP_USERNAME; |
| break; |
| |
| case FieldTypeGroup::kUnfillable: |
| group = GROUP_UNFILLABLE; |
| break; |
| |
| case FieldTypeGroup::kTransaction: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Use bits 8-15 for the group and bits 0-7 for the metric. |
| static_assert(AutofillMetrics::NUM_FIELD_TYPE_QUALITY_METRICS <= UINT8_MAX, |
| "maximum field type quality metric must fit into 8 bits"); |
| static_assert(NUM_FIELD_TYPE_GROUPS_FOR_METRICS <= UINT8_MAX, |
| "number of field type groups must fit into 8 bits"); |
| return (group << 8) | metric; |
| } |
| |
| // This function encodes the integer value of a |FieldType| and the |
| // metric value of an |AutofilledFieldUserEditingStatus| into a 16 bit integer. |
| // The lower four bits are used to encode the editing status and the higher |
| // 12 bits are used to encode the field type. |
| int GetFieldTypeUserEditStatusMetric( |
| FieldType server_type, |
| AutofillMetrics::AutofilledFieldUserEditingStatusMetric metric) { |
| static_assert(FieldType::MAX_VALID_FIELD_TYPE <= (UINT16_MAX >> 4), |
| "Autofill::ServerTypes value needs more than 12 bits."); |
| |
| static_assert( |
| static_cast<int>( |
| AutofillMetrics::AutofilledFieldUserEditingStatusMetric::kMaxValue) <= |
| (UINT16_MAX >> 12), |
| "AutofillMetrics::AutofilledFieldUserEditingStatusMetric value needs " |
| "more than 4 bits"); |
| |
| return (server_type << 4) | static_cast<int>(metric); |
| } |
| |
| namespace { |
| |
| const char* GetQualityMetricPredictionSource( |
| AutofillMetrics::QualityMetricPredictionSource source) { |
| switch (source) { |
| default: |
| case AutofillMetrics::PREDICTION_SOURCE_UNKNOWN: |
| NOTREACHED(); |
| return "Unknown"; |
| |
| case AutofillMetrics::PREDICTION_SOURCE_HEURISTIC: |
| return "Heuristic"; |
| case AutofillMetrics::PREDICTION_SOURCE_SERVER: |
| return "Server"; |
| case AutofillMetrics::PREDICTION_SOURCE_OVERALL: |
| return "Overall"; |
| } |
| } |
| |
| const char* GetQualityMetricTypeSuffix( |
| AutofillMetrics::QualityMetricType metric_type) { |
| switch (metric_type) { |
| default: |
| NOTREACHED(); |
| [[fallthrough]]; |
| case AutofillMetrics::TYPE_SUBMISSION: |
| return ""; |
| case AutofillMetrics::TYPE_NO_SUBMISSION: |
| return ".NoSubmission"; |
| case AutofillMetrics::TYPE_AUTOCOMPLETE_BASED: |
| return ".BasedOnAutocomplete"; |
| } |
| } |
| |
| // Given a set of |possible_types| for a field, select the best type to use as |
| // the "actual" field type when calculating metrics. If the |predicted_type| is |
| // among the |possible_types] then use that as the best type (i.e., the |
| // prediction is deemed to have been correct). |
| FieldType GetActualFieldType(const FieldTypeSet& possible_types, |
| FieldType predicted_type) { |
| DCHECK_NE(possible_types.size(), 0u); |
| |
| if (possible_types.count(EMPTY_TYPE)) { |
| DCHECK_EQ(possible_types.size(), 1u); |
| return EMPTY_TYPE; |
| } |
| |
| if (possible_types.count(UNKNOWN_TYPE)) { |
| DCHECK_EQ(possible_types.size(), 1u); |
| return UNKNOWN_TYPE; |
| } |
| |
| if (possible_types.count(predicted_type)) |
| return predicted_type; |
| |
| // Collapse field types that Chrome treats as identical, e.g. home and |
| // billing address fields. |
| FieldTypeSet collapsed_field_types; |
| for (auto type : possible_types) { |
| DCHECK_NE(type, EMPTY_TYPE); |
| DCHECK_NE(type, UNKNOWN_TYPE); |
| |
| // A phone number that's only missing its country code is (for metrics |
| // purposes) the same as the whole phone number. |
| if (type == PHONE_HOME_CITY_AND_NUMBER) |
| collapsed_field_types.insert(PHONE_HOME_WHOLE_NUMBER); |
| else |
| collapsed_field_types.insert(AutofillType(type).GetStorableType()); |
| } |
| |
| // Capture the field's type, if it is unambiguous. |
| FieldType actual_type = AMBIGUOUS_TYPE; |
| if (collapsed_field_types.size() == 1) |
| actual_type = *collapsed_field_types.begin(); |
| |
| DVLOG(2) << "Inferred Type: " << FieldTypeToStringView(actual_type); |
| return actual_type; |
| } |
| |
| // Check if the value of |field| is same as one of the previously autofilled |
| // values. This indicates a bad rationalization if |field| has |
| // only_fill_when_focused set to true. |
| bool DuplicatedFilling(const FormStructure& form, const AutofillField& field) { |
| for (const auto& form_field : form) { |
| if (field.value() == form_field->value() && form_field->is_autofilled()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void LogPredictionQualityMetricsForFieldsOnlyFilledWhenFocused( |
| const std::string& aggregate_histogram, |
| const std::string& type_specific_histogram, |
| const std::string& rationalization_quality_histogram, |
| FieldType predicted_type, |
| FieldType actual_type, |
| bool is_empty, |
| bool is_ambiguous, |
| bool log_rationalization_metrics, |
| const FormStructure& form, |
| const AutofillField& field) { |
| // If it is filled with values unknown, it is a true negative. |
| if (actual_type == UNKNOWN_TYPE) { |
| // Only log aggregate true negative; do not log type specific metrics |
| // for UNKNOWN/EMPTY. |
| DVLOG(2) << "TRUE NEGATIVE"; |
| base::UmaHistogramSparse( |
| aggregate_histogram, |
| (is_empty ? AutofillMetrics::TRUE_NEGATIVE_EMPTY |
| : (is_ambiguous ? AutofillMetrics::TRUE_NEGATIVE_AMBIGUOUS |
| : AutofillMetrics::TRUE_NEGATIVE_UNKNOWN))); |
| if (log_rationalization_metrics) { |
| base::UmaHistogramSparse( |
| rationalization_quality_histogram, |
| (is_empty ? AutofillMetrics::RATIONALIZATION_GOOD |
| : AutofillMetrics::RATIONALIZATION_OK)); |
| } |
| return; |
| } |
| |
| // If it is filled with same type as predicted, it is a true positive. We |
| // also log an RATIONALIZATION_BAD by checking if the filled value is filled |
| // already in previous fields, this means autofill could have filled it |
| // automatically if there has been no rationalization. |
| if (predicted_type == actual_type) { |
| DVLOG(2) << "TRUE POSITIVE"; |
| base::UmaHistogramSparse(aggregate_histogram, |
| AutofillMetrics::TRUE_POSITIVE); |
| base::UmaHistogramSparse(type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| actual_type, AutofillMetrics::TRUE_POSITIVE)); |
| if (log_rationalization_metrics) { |
| bool duplicated_filling = DuplicatedFilling(form, field); |
| base::UmaHistogramSparse( |
| rationalization_quality_histogram, |
| (duplicated_filling ? AutofillMetrics::RATIONALIZATION_BAD |
| : AutofillMetrics::RATIONALIZATION_OK)); |
| } |
| return; |
| } |
| |
| DVLOG(2) << "MISMATCH"; |
| // Here the prediction is wrong, but user has to provide some value still. |
| // This should be a false negative. |
| base::UmaHistogramSparse(aggregate_histogram, |
| AutofillMetrics::FALSE_NEGATIVE_MISMATCH); |
| // Log FALSE_NEGATIVE_MISMATCH for predicted type if it did predicted |
| // something but actual type is different. |
| if (predicted_type != UNKNOWN_TYPE) |
| base::UmaHistogramSparse( |
| type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| predicted_type, AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); |
| if (log_rationalization_metrics) { |
| // Logging RATIONALIZATION_OK despite of type mismatch here because autofill |
| // would have got it wrong with or without rationalization. Rationalization |
| // here does not help, neither does it do any harm. |
| base::UmaHistogramSparse(rationalization_quality_histogram, |
| AutofillMetrics::RATIONALIZATION_OK); |
| } |
| return; |
| } |
| |
| void LogPredictionQualityMetricsForCommonFields( |
| const std::string& aggregate_histogram, |
| const std::string& type_specific_histogram, |
| FieldType predicted_type, |
| FieldType actual_type, |
| bool is_empty, |
| bool is_ambiguous) { |
| // If the predicted and actual types match then it's either a true positive |
| // or a true negative (if they are both unknown). Do not log type specific |
| // true negatives (instead log a true positive for the "Ambiguous" type). |
| if (predicted_type == actual_type) { |
| if (actual_type == UNKNOWN_TYPE) { |
| // Only log aggregate true negative; do not log type specific metrics |
| // for UNKNOWN/EMPTY. |
| DVLOG(2) << "TRUE NEGATIVE"; |
| base::UmaHistogramSparse( |
| aggregate_histogram, |
| (is_empty ? AutofillMetrics::TRUE_NEGATIVE_EMPTY |
| : (is_ambiguous ? AutofillMetrics::TRUE_NEGATIVE_AMBIGUOUS |
| : AutofillMetrics::TRUE_NEGATIVE_UNKNOWN))); |
| return; |
| } |
| |
| DVLOG(2) << "TRUE POSITIVE"; |
| // Log both aggregate and type specific true positive if we correctly |
| // predict that type with which the field was filled. |
| base::UmaHistogramSparse(aggregate_histogram, |
| AutofillMetrics::TRUE_POSITIVE); |
| base::UmaHistogramSparse(type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| actual_type, AutofillMetrics::TRUE_POSITIVE)); |
| return; |
| } |
| |
| // Note: At this point predicted_type != actual type |
| // If actual type is UNKNOWN_TYPE then the prediction is a false positive. |
| // Further specialize the type of false positive by whether the field was |
| // empty or contained an unknown value. |
| if (actual_type == UNKNOWN_TYPE) { |
| DVLOG(2) << "FALSE POSITIVE"; |
| auto metric = |
| (is_empty ? AutofillMetrics::FALSE_POSITIVE_EMPTY |
| : (is_ambiguous ? AutofillMetrics::FALSE_POSITIVE_AMBIGUOUS |
| : AutofillMetrics::FALSE_POSITIVE_UNKNOWN)); |
| base::UmaHistogramSparse(aggregate_histogram, metric); |
| base::UmaHistogramSparse( |
| type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric(predicted_type, metric)); |
| return; |
| } |
| |
| // Note: At this point predicted_type != actual type, actual_type != UNKNOWN. |
| // If predicted type is UNKNOWN_TYPE then the prediction is a false negative |
| // unknown. |
| if (predicted_type == UNKNOWN_TYPE) { |
| DVLOG(2) << "FALSE NEGATIVE"; |
| base::UmaHistogramSparse(aggregate_histogram, |
| AutofillMetrics::FALSE_NEGATIVE_UNKNOWN); |
| base::UmaHistogramSparse( |
| type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| actual_type, AutofillMetrics::FALSE_NEGATIVE_UNKNOWN)); |
| return; |
| } |
| |
| DVLOG(2) << "MISMATCH"; |
| |
| // Note: At this point predicted_type != actual type, actual_type != UNKNOWN, |
| // predicted_type != UNKNOWN. |
| // This is a mismatch. From the reference of the actual type, this is a false |
| // negative (it was T, but predicted U). From the reference of the prediction, |
| // this is a false positive (predicted it was T, but it was U). |
| base::UmaHistogramSparse(aggregate_histogram, |
| AutofillMetrics::FALSE_NEGATIVE_MISMATCH); |
| base::UmaHistogramSparse( |
| type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| actual_type, AutofillMetrics::FALSE_NEGATIVE_MISMATCH)); |
| base::UmaHistogramSparse( |
| type_specific_histogram, |
| GetFieldTypeGroupPredictionQualityMetric( |
| predicted_type, AutofillMetrics::FALSE_POSITIVE_MISMATCH)); |
| } |
| |
| // Logs field type prediction quality metrics. The primary histogram name is |
| // constructed based on |prediction_source| The field-specific histogram names |
| // also incorporates the possible and predicted types for |field|. A suffix may |
| // be appended to the metric name, depending on |metric_type|. |
| void LogPredictionQualityMetrics( |
| AutofillMetrics::QualityMetricPredictionSource prediction_source, |
| FieldType predicted_type, |
| AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger, |
| const FormStructure& form, |
| const AutofillField& field, |
| AutofillMetrics::QualityMetricType metric_type, |
| bool log_rationalization_metrics) { |
| // Generate histogram names. |
| const char* source = GetQualityMetricPredictionSource(prediction_source); |
| const char* suffix = GetQualityMetricTypeSuffix(metric_type); |
| std::string raw_data_histogram = |
| base::JoinString({"Autofill.FieldPrediction.", source, suffix}, ""); |
| std::string aggregate_histogram = base::JoinString( |
| {"Autofill.FieldPredictionQuality.Aggregate.", source, suffix}, ""); |
| std::string type_specific_histogram = base::JoinString( |
| {"Autofill.FieldPredictionQuality.ByFieldType.", source, suffix}, ""); |
| std::string rationalization_quality_histogram = base::JoinString( |
| {"Autofill.RationalizationQuality.PhoneNumber", suffix}, ""); |
| |
| const FieldTypeSet& possible_types = |
| metric_type == AutofillMetrics::TYPE_AUTOCOMPLETE_BASED |
| ? FieldTypeSet{AutofillType(field.html_type()).GetStorableType()} |
| : field.possible_types(); |
| |
| // Get the best type classification we can for the field. |
| FieldType actual_type = GetActualFieldType(possible_types, predicted_type); |
| |
| DVLOG(2) << "Predicted: " << FieldTypeToStringView(predicted_type) << "; " |
| << "Actual: " << FieldTypeToStringView(actual_type); |
| |
| DCHECK_LE(predicted_type, UINT16_MAX); |
| DCHECK_LE(actual_type, UINT16_MAX); |
| base::UmaHistogramSparse(raw_data_histogram, |
| (predicted_type << 16) | actual_type); |
| |
| form_interactions_ukm_logger->LogFieldType( |
| form.form_parsed_timestamp(), form.form_signature(), |
| field.GetFieldSignature(), prediction_source, metric_type, predicted_type, |
| actual_type); |
| |
| // NO_SERVER_DATA is the equivalent of predicting UNKNOWN. |
| if (predicted_type == NO_SERVER_DATA) |
| predicted_type = UNKNOWN_TYPE; |
| |
| // The actual type being EMPTY_TYPE is the same as UNKNOWN_TYPE for comparison |
| // purposes, but remember whether or not it was empty for more precise logging |
| // later. |
| bool is_empty = (actual_type == EMPTY_TYPE); |
| bool is_ambiguous = (actual_type == AMBIGUOUS_TYPE); |
| if (is_empty || is_ambiguous) |
| actual_type = UNKNOWN_TYPE; |
| |
| // Log metrics for a field that is |only_fill_when_focused|==true. Basically |
| // autofill might have a field prediction but it also thinks it should not |
| // be filled automatically unless user focused on the field. This requires |
| // different metrics logging than normal fields. |
| if (field.only_fill_when_focused()) { |
| LogPredictionQualityMetricsForFieldsOnlyFilledWhenFocused( |
| aggregate_histogram, type_specific_histogram, |
| rationalization_quality_histogram, predicted_type, actual_type, |
| is_empty, is_ambiguous, log_rationalization_metrics, form, field); |
| return; |
| } |
| |
| LogPredictionQualityMetricsForCommonFields( |
| aggregate_histogram, type_specific_histogram, predicted_type, actual_type, |
| is_empty, is_ambiguous); |
| } |
| |
| } // namespace |
| |
| const int kMaxBucketsCount = 50; |
| |
| // static |
| void AutofillMetrics::LogSubmittedCardStateMetric( |
| SubmittedCardStateMetric metric) { |
| DCHECK_LT(metric, NUM_SUBMITTED_CARD_STATE_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.SubmittedCardState", metric, |
| NUM_SUBMITTED_CARD_STATE_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogSubmittedServerCardExpirationStatusMetric( |
| SubmittedServerCardExpirationStatusMetric metric) { |
| DCHECK_LT(metric, NUM_SUBMITTED_SERVER_CARD_EXPIRATION_STATUS_METRICS); |
| UMA_HISTOGRAM_ENUMERATION( |
| "Autofill.SubmittedServerCardExpirationStatus", metric, |
| NUM_SUBMITTED_SERVER_CARD_EXPIRATION_STATUS_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogUploadOfferedCardOriginMetric( |
| UploadOfferedCardOriginMetric metric) { |
| DCHECK_LT(metric, NUM_UPLOAD_OFFERED_CARD_ORIGIN_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.UploadOfferedCardOrigin", metric, |
| NUM_UPLOAD_OFFERED_CARD_ORIGIN_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogUploadAcceptedCardOriginMetric( |
| UploadAcceptedCardOriginMetric metric) { |
| DCHECK_LT(metric, NUM_UPLOAD_ACCEPTED_CARD_ORIGIN_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.UploadAcceptedCardOrigin", metric, |
| NUM_UPLOAD_ACCEPTED_CARD_ORIGIN_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogCardUnmaskAuthenticationSelectionDialogResultMetric( |
| CardUnmaskAuthenticationSelectionDialogResultMetric metric) { |
| DCHECK_LE(metric, |
| CardUnmaskAuthenticationSelectionDialogResultMetric::kMaxValue); |
| base::UmaHistogramEnumeration( |
| "Autofill.CardUnmaskAuthenticationSelectionDialog.Result", metric); |
| } |
| |
| // static |
| void AutofillMetrics::LogCardUnmaskAuthenticationSelectionDialogShown( |
| size_t number_of_challenge_options) { |
| static_assert(static_cast<int>(CardUnmaskChallengeOptionType::kMaxValue) < |
| 10); |
| DCHECK_GE(number_of_challenge_options, 0U); |
| // We are using an exact linear histogram, with a max of 10. This is a |
| // reasonable max so that the histogram is not sparse, as we do not foresee |
| // ever having more than 10 challenge options at the same time on a dialog to |
| // authenticate a virtual card. |
| base::UmaHistogramExactLinear( |
| "Autofill.CardUnmaskAuthenticationSelectionDialog.Shown2", |
| number_of_challenge_options, /*exclusive_max=*/10); |
| } |
| |
| // static |
| void AutofillMetrics::LogCreditCardInfoBarMetric( |
| InfoBarMetric metric, |
| bool is_uploading, |
| AutofillClient::SaveCreditCardOptions options) { |
| DCHECK_LT(metric, NUM_INFO_BAR_METRICS); |
| |
| std::string destination = is_uploading ? ".Server" : ".Local"; |
| base::UmaHistogramEnumeration("Autofill.CreditCardInfoBar" + destination, |
| metric, NUM_INFO_BAR_METRICS); |
| if (options.should_request_name_from_user) { |
| base::UmaHistogramEnumeration("Autofill.CreditCardInfoBar" + destination + |
| ".RequestingCardholderName", |
| metric, NUM_INFO_BAR_METRICS); |
| } |
| |
| if (options.should_request_expiration_date_from_user) { |
| base::UmaHistogramEnumeration("Autofill.CreditCardInfoBar" + destination + |
| ".RequestingExpirationDate", |
| metric, NUM_INFO_BAR_METRICS); |
| } |
| |
| if (options.has_multiple_legal_lines) { |
| base::UmaHistogramEnumeration( |
| "Autofill.CreditCardInfoBar" + destination + ".WithMultipleLegalLines", |
| metric, NUM_INFO_BAR_METRICS); |
| } |
| |
| if (options.has_same_last_four_as_server_card_but_different_expiration_date) { |
| base::UmaHistogramEnumeration("Autofill.CreditCardInfoBar" + destination + |
| ".WithSameLastFourButDifferentExpiration", |
| metric, NUM_INFO_BAR_METRICS); |
| } |
| |
| if (options.card_save_type == |
| AutofillClient::CardSaveType::kCardSaveWithCvc) { |
| base::UmaHistogramEnumeration(base::StrCat({"Autofill.CreditCardInfoBar", |
| destination, ".SavingWithCvc"}), |
| metric, NUM_INFO_BAR_METRICS); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogCreditCardFillingInfoBarMetric(InfoBarMetric metric) { |
| DCHECK_LT(metric, NUM_INFO_BAR_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardFillingInfoBar", metric, |
| NUM_INFO_BAR_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogScanCreditCardPromptMetric( |
| ScanCreditCardPromptMetric metric) { |
| DCHECK_LT(metric, NUM_SCAN_CREDIT_CARD_PROMPT_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.ScanCreditCardPrompt", metric, |
| NUM_SCAN_CREDIT_CARD_PROMPT_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogScanCreditCardCompleted( |
| const base::TimeDelta& duration, |
| bool completed) { |
| std::string suffix = completed ? "Completed" : "Cancelled"; |
| base::UmaHistogramLongTimes("Autofill.ScanCreditCard.Duration_" + suffix, |
| duration); |
| UMA_HISTOGRAM_BOOLEAN("Autofill.ScanCreditCard.Completed", completed); |
| } |
| |
| // static |
| void AutofillMetrics::LogProgressDialogResultMetric( |
| bool is_canceled_by_user, |
| AutofillProgressDialogType autofill_progress_dialog_type) { |
| base::UmaHistogramBoolean(base::StrCat({"Autofill.ProgressDialog.", |
| GetDialogTypeStringForLogging( |
| autofill_progress_dialog_type), |
| ".Result"}), |
| is_canceled_by_user); |
| } |
| |
| void AutofillMetrics::LogProgressDialogShown( |
| AutofillProgressDialogType autofill_progress_dialog_type) { |
| base::UmaHistogramBoolean(base::StrCat({"Autofill.ProgressDialog.", |
| GetDialogTypeStringForLogging( |
| autofill_progress_dialog_type), |
| ".Shown"}), |
| true); |
| } |
| |
| std::string_view AutofillMetrics::GetDialogTypeStringForLogging( |
| AutofillProgressDialogType autofill_progress_dialog_type) { |
| switch (autofill_progress_dialog_type) { |
| case AutofillProgressDialogType::kAndroidFIDOProgressDialog: |
| return "AndroidFIDO"; |
| case AutofillProgressDialogType::kVirtualCardUnmaskProgressDialog: |
| return "VirtualCardUnmask"; |
| case AutofillProgressDialogType::kServerCardUnmaskProgressDialog: |
| return "ServerCardUnmask"; |
| case AutofillProgressDialogType::kServerIbanUnmaskProgressDialog: |
| return "ServerIbanUnmask"; |
| default: |
| NOTREACHED_NORETURN(); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogUnmaskPromptEvent(UnmaskPromptEvent event, |
| bool has_valid_nickname, |
| CreditCard::RecordType card_type) { |
| base::UmaHistogramEnumeration("Autofill.UnmaskPrompt" + |
| GetHistogramStringForCardType(card_type) + |
| ".Events", |
| event, NUM_UNMASK_PROMPT_EVENTS); |
| if (has_valid_nickname) { |
| base::UmaHistogramEnumeration("Autofill.UnmaskPrompt.Events.WithNickname", |
| event, NUM_UNMASK_PROMPT_EVENTS); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogCardholderNameFixFlowPromptEvent( |
| CardholderNameFixFlowPromptEvent event) { |
| UMA_HISTOGRAM_ENUMERATION("Autofill.CardholderNameFixFlowPrompt.Events", |
| event, NUM_CARDHOLDER_NAME_FIXFLOW_PROMPT_EVENTS); |
| } |
| |
| // static |
| void AutofillMetrics::LogExpirationDateFixFlowPromptEvent( |
| ExpirationDateFixFlowPromptEvent event) { |
| UMA_HISTOGRAM_ENUMERATION("Autofill.ExpirationDateFixFlowPrompt.Events", |
| event); |
| } |
| |
| // static |
| void AutofillMetrics::LogExpirationDateFixFlowPromptShown() { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.ExpirationDateFixFlowPromptShown", true); |
| } |
| |
| // static |
| void AutofillMetrics::LogUnmaskPromptEventDuration( |
| const base::TimeDelta& duration, |
| UnmaskPromptEvent close_event, |
| bool has_valid_nickname) { |
| std::string suffix; |
| switch (close_event) { |
| case UNMASK_PROMPT_CLOSED_NO_ATTEMPTS: |
| suffix = ".NoAttempts"; |
| break; |
| case UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_RETRIABLE_FAILURE: |
| case UNMASK_PROMPT_CLOSED_FAILED_TO_UNMASK_NON_RETRIABLE_FAILURE: |
| suffix = ".Failure"; |
| break; |
| case UNMASK_PROMPT_CLOSED_ABANDON_UNMASKING: |
| suffix = ".AbandonUnmasking"; |
| break; |
| case UNMASK_PROMPT_UNMASKED_CARD_FIRST_ATTEMPT: |
| case UNMASK_PROMPT_UNMASKED_CARD_AFTER_FAILED_ATTEMPTS: |
| suffix = ".Success"; |
| break; |
| default: |
| NOTREACHED(); |
| return; |
| } |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.Duration", duration); |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.Duration" + suffix, |
| duration); |
| |
| if (has_valid_nickname) { |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.Duration.WithNickname", |
| duration); |
| base::UmaHistogramLongTimes( |
| "Autofill.UnmaskPrompt.Duration" + suffix + ".WithNickname", duration); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogTimeBeforeAbandonUnmasking( |
| const base::TimeDelta& duration, |
| bool has_valid_nickname) { |
| base::UmaHistogramLongTimes( |
| "Autofill.UnmaskPrompt.TimeBeforeAbandonUnmasking", duration); |
| if (has_valid_nickname) { |
| base::UmaHistogramLongTimes( |
| "Autofill.UnmaskPrompt.TimeBeforeAbandonUnmasking.WithNickname", |
| duration); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogRealPanResult( |
| AutofillClient::PaymentsRpcResult result, |
| AutofillClient::PaymentsRpcCardType card_type) { |
| PaymentsRpcResult metric_result; |
| switch (result) { |
| case AutofillClient::PaymentsRpcResult::kSuccess: |
| metric_result = PAYMENTS_RESULT_SUCCESS; |
| break; |
| case AutofillClient::PaymentsRpcResult::kTryAgainFailure: |
| metric_result = PAYMENTS_RESULT_TRY_AGAIN_FAILURE; |
| break; |
| case AutofillClient::PaymentsRpcResult::kPermanentFailure: |
| metric_result = PAYMENTS_RESULT_PERMANENT_FAILURE; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNetworkError: |
| metric_result = PAYMENTS_RESULT_NETWORK_ERROR; |
| break; |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalTryAgainFailure: |
| DCHECK_EQ(card_type, AutofillClient::PaymentsRpcCardType::kVirtualCard); |
| metric_result = PAYMENTS_RESULT_VCN_RETRIEVAL_TRY_AGAIN_FAILURE; |
| break; |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalPermanentFailure: |
| DCHECK_EQ(card_type, AutofillClient::PaymentsRpcCardType::kVirtualCard); |
| metric_result = PAYMENTS_RESULT_VCN_RETRIEVAL_PERMANENT_FAILURE; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNone: |
| NOTREACHED(); |
| return; |
| } |
| |
| std::string card_type_suffix; |
| switch (card_type) { |
| case AutofillClient::PaymentsRpcCardType::kServerCard: |
| card_type_suffix = "ServerCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kVirtualCard: |
| card_type_suffix = "VirtualCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kUnknown: |
| NOTREACHED(); |
| return; |
| } |
| |
| base::UmaHistogramEnumeration("Autofill.UnmaskPrompt.GetRealPanResult", |
| metric_result); |
| |
| base::UmaHistogramEnumeration( |
| "Autofill.UnmaskPrompt.GetRealPanResult." + card_type_suffix, |
| metric_result); |
| } |
| |
| // static |
| void AutofillMetrics::LogRealPanDuration( |
| const base::TimeDelta& duration, |
| AutofillClient::PaymentsRpcResult result, |
| AutofillClient::PaymentsRpcCardType card_type) { |
| std::string result_suffix; |
| std::string card_type_suffix; |
| |
| switch (card_type) { |
| case AutofillClient::PaymentsRpcCardType::kServerCard: |
| card_type_suffix = "ServerCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kVirtualCard: |
| card_type_suffix = "VirtualCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kUnknown: |
| // Unknown card types imply UnmaskCardRequest::ParseResponse() was never |
| // called, due to bad internet connection or otherwise. Log anyway so that |
| // we have a rough idea of the magnitude of this problem. |
| card_type_suffix = "UnknownCard"; |
| break; |
| } |
| |
| switch (result) { |
| case AutofillClient::PaymentsRpcResult::kSuccess: |
| result_suffix = "Success"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kPermanentFailure: |
| result_suffix = "Failure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalPermanentFailure: |
| DCHECK_EQ(card_type, AutofillClient::PaymentsRpcCardType::kVirtualCard); |
| result_suffix = "VcnRetrievalFailure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNetworkError: |
| result_suffix = "NetworkError"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNone: |
| NOTREACHED(); |
| return; |
| } |
| |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.GetRealPanDuration", |
| duration); |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.GetRealPanDuration." + |
| card_type_suffix + "." + result_suffix, |
| duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogUnmaskingDuration( |
| const base::TimeDelta& duration, |
| AutofillClient::PaymentsRpcResult result, |
| AutofillClient::PaymentsRpcCardType card_type) { |
| std::string result_suffix; |
| std::string card_type_suffix; |
| |
| switch (card_type) { |
| case AutofillClient::PaymentsRpcCardType::kServerCard: |
| card_type_suffix = "ServerCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kVirtualCard: |
| card_type_suffix = "VirtualCard"; |
| break; |
| case AutofillClient::PaymentsRpcCardType::kUnknown: |
| NOTREACHED(); |
| return; |
| } |
| |
| switch (result) { |
| case AutofillClient::PaymentsRpcResult::kSuccess: |
| result_suffix = "Success"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kPermanentFailure: |
| result_suffix = "Failure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalPermanentFailure: |
| DCHECK_EQ(card_type, AutofillClient::PaymentsRpcCardType::kVirtualCard); |
| result_suffix = "VcnRetrievalFailure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNetworkError: |
| result_suffix = "NetworkError"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNone: |
| NOTREACHED(); |
| return; |
| } |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.UnmaskingDuration", |
| duration); |
| base::UmaHistogramLongTimes("Autofill.UnmaskPrompt.UnmaskingDuration." + |
| card_type_suffix + "." + result_suffix, |
| duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogDeveloperEngagementMetric( |
| DeveloperEngagementMetric metric) { |
| DCHECK_LT(metric, NUM_DEVELOPER_ENGAGEMENT_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.DeveloperEngagement", metric, |
| NUM_DEVELOPER_ENGAGEMENT_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogHeuristicPredictionQualityMetrics( |
| FormInteractionsUkmLogger* form_interactions_ukm_logger, |
| const FormStructure& form, |
| const AutofillField& field, |
| QualityMetricType metric_type) { |
| LogPredictionQualityMetrics( |
| PREDICTION_SOURCE_HEURISTIC, |
| AutofillType(field.heuristic_type()).GetStorableType(), |
| form_interactions_ukm_logger, form, field, metric_type, |
| false /*log_rationalization_metrics*/); |
| } |
| |
| // static |
| void AutofillMetrics::LogServerPredictionQualityMetrics( |
| FormInteractionsUkmLogger* form_interactions_ukm_logger, |
| const FormStructure& form, |
| const AutofillField& field, |
| QualityMetricType metric_type) { |
| LogPredictionQualityMetrics( |
| PREDICTION_SOURCE_SERVER, |
| AutofillType(field.server_type()).GetStorableType(), |
| form_interactions_ukm_logger, form, field, metric_type, |
| false /*log_rationalization_metrics*/); |
| } |
| |
| // static |
| void AutofillMetrics::LogOverallPredictionQualityMetrics( |
| FormInteractionsUkmLogger* form_interactions_ukm_logger, |
| const FormStructure& form, |
| const AutofillField& field, |
| QualityMetricType metric_type) { |
| LogPredictionQualityMetrics( |
| PREDICTION_SOURCE_OVERALL, field.Type().GetStorableType(), |
| form_interactions_ukm_logger, form, field, metric_type, |
| true /*log_rationalization_metrics*/); |
| } |
| |
| void AutofillMetrics::LogEmailFieldPredictionMetrics( |
| const AutofillField& field) { |
| // If the field has no value, there is no need to record any of the metrics. |
| if (field.value().empty()) { |
| return; |
| } |
| |
| bool is_valid_email = IsValidEmailAddress(field.value()); |
| bool is_email_prediction = field.Type().GetStorableType() == EMAIL_ADDRESS; |
| |
| if (is_email_prediction) { |
| EmailPredictionConfusionMatrix prediction_precision = |
| is_valid_email ? EmailPredictionConfusionMatrix::kTruePositive |
| : EmailPredictionConfusionMatrix::kFalsePositive; |
| base::UmaHistogramEnumeration( |
| "Autofill.EmailPredictionCorrectness.Precision", prediction_precision); |
| } |
| |
| if (is_valid_email) { |
| EmailPredictionConfusionMatrix prediction_recall = |
| is_email_prediction ? EmailPredictionConfusionMatrix::kTruePositive |
| : EmailPredictionConfusionMatrix::kFalseNegative; |
| base::UmaHistogramEnumeration("Autofill.EmailPredictionCorrectness.Recall", |
| prediction_recall); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogEditedAutofilledFieldAtSubmission( |
| FormInteractionsUkmLogger* form_interactions_ukm_logger, |
| const FormStructure& form, |
| const AutofillField& field) { |
| const std::string aggregate_histogram = |
| "Autofill.EditedAutofilledFieldAtSubmission2.Aggregate"; |
| const std::string type_specific_histogram = |
| "Autofill.EditedAutofilledFieldAtSubmission2.ByFieldType"; |
| |
| AutofilledFieldUserEditingStatusMetric editing_metric = |
| field.previously_autofilled() |
| ? AutofilledFieldUserEditingStatusMetric::AUTOFILLED_FIELD_WAS_EDITED |
| : AutofilledFieldUserEditingStatusMetric:: |
| AUTOFILLED_FIELD_WAS_NOT_EDITED; |
| |
| // Record the aggregated UMA statistics. |
| base::UmaHistogramEnumeration(aggregate_histogram, editing_metric); |
| |
| // Record the type specific UMA statistics. |
| base::UmaHistogramSparse(type_specific_histogram, |
| GetFieldTypeUserEditStatusMetric( |
| field.Type().GetStorableType(), editing_metric)); |
| |
| // Record the UMA statistics spliced by the autocomplete attribute value. |
| FormType form_type = FieldTypeGroupToFormType(field.Type().group()); |
| if (form_type == FormType::kAddressForm || |
| form_type == FormType::kCreditCardForm) { |
| bool autocomplete_off = field.autocomplete_attribute() == "off"; |
| const std::string autocomplete_histogram = base::StrCat( |
| {"Autofill.Autocomplete.", autocomplete_off ? "Off" : "NotOff", |
| ".EditedAutofilledFieldAtSubmission2.", |
| form_type == FormType::kAddressForm ? "Address" : "CreditCard"}); |
| base::UmaHistogramEnumeration(autocomplete_histogram, editing_metric); |
| } |
| |
| // If the field was edited, record the event to UKM. |
| if (editing_metric == |
| AutofilledFieldUserEditingStatusMetric::AUTOFILLED_FIELD_WAS_EDITED) { |
| form_interactions_ukm_logger->LogEditedAutofilledFieldAtSubmission(form, |
| field); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogServerQueryMetric(ServerQueryMetric metric) { |
| DCHECK_LT(metric, NUM_SERVER_QUERY_METRICS); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.ServerQueryResponse", metric, |
| NUM_SERVER_QUERY_METRICS); |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDurationFromLoadWithAutofill( |
| const base::TimeDelta& duration) { |
| LogFormFillDuration("Autofill.FillDuration.FromLoad.WithAutofill", duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDurationFromLoadWithoutAutofill( |
| const base::TimeDelta& duration) { |
| LogFormFillDuration("Autofill.FillDuration.FromLoad.WithoutAutofill", |
| duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDurationFromLoadForOneTimeCode( |
| const base::TimeDelta& duration) { |
| LogFormFillDuration("Autofill.WebOTP.OneTimeCode.FillDuration.FromLoad", |
| duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDurationFromInteraction( |
| const DenseSet<FormType>& form_types, |
| bool used_autofill, |
| const base::TimeDelta& duration) { |
| std::string parent_metric; |
| if (used_autofill) { |
| parent_metric = "Autofill.FillDuration.FromInteraction.WithAutofill"; |
| } else { |
| parent_metric = "Autofill.FillDuration.FromInteraction.WithoutAutofill"; |
| } |
| LogFormFillDuration(parent_metric, duration); |
| if (base::Contains(form_types, FormType::kCreditCardForm)) { |
| LogFormFillDuration(parent_metric + ".CreditCard", duration); |
| } |
| if (base::Contains(form_types, FormType::kAddressForm)) { |
| LogFormFillDuration(parent_metric + ".Address", duration); |
| } |
| if (base::Contains(form_types, FormType::kPasswordForm)) { |
| LogFormFillDuration(parent_metric + ".Password", duration); |
| } |
| if (base::Contains(form_types, FormType::kUnknownFormType)) { |
| LogFormFillDuration(parent_metric + ".Unknown", duration); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDurationFromInteractionForOneTimeCode( |
| const base::TimeDelta& duration) { |
| LogFormFillDuration( |
| "Autofill.WebOTP.OneTimeCode.FillDuration.FromInteraction", duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogFormFillDuration(const std::string& metric, |
| const base::TimeDelta& duration) { |
| base::UmaHistogramCustomTimes(metric, duration, base::Milliseconds(100), |
| base::Minutes(10), 50); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillEnabledAtStartup(bool enabled) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.IsEnabled.Startup", enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillProfileEnabledAtStartup(bool enabled) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.Address.IsEnabled.Startup", enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillCreditCardEnabledAtStartup(bool enabled) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.CreditCard.IsEnabled.Startup", enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillEnabledAtPageLoad( |
| bool enabled, |
| PaymentsSigninState sync_state) { |
| std::string name("Autofill.IsEnabled.PageLoad"); |
| UMA_HISTOGRAM_BOOLEAN(name, enabled); |
| base::UmaHistogramBoolean(name + GetMetricsSyncStateSuffix(sync_state), |
| enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillProfileEnabledAtPageLoad( |
| bool enabled, |
| PaymentsSigninState sync_state) { |
| std::string name("Autofill.Address.IsEnabled.PageLoad"); |
| UMA_HISTOGRAM_BOOLEAN(name, enabled); |
| base::UmaHistogramBoolean(name + GetMetricsSyncStateSuffix(sync_state), |
| enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsAutofillCreditCardEnabledAtPageLoad( |
| bool enabled, |
| PaymentsSigninState sync_state) { |
| std::string name("Autofill.CreditCard.IsEnabled.PageLoad"); |
| UMA_HISTOGRAM_BOOLEAN(name, enabled); |
| base::UmaHistogramBoolean(name + GetMetricsSyncStateSuffix(sync_state), |
| enabled); |
| } |
| |
| // static |
| void AutofillMetrics::LogStoredCreditCardMetrics( |
| const std::vector<std::unique_ptr<CreditCard>>& local_cards, |
| const std::vector<std::unique_ptr<CreditCard>>& server_cards, |
| size_t server_card_count_with_card_art_image, |
| base::TimeDelta disused_data_threshold) { |
| size_t num_local_cards = 0; |
| size_t num_local_cards_with_nickname = 0; |
| size_t num_local_cards_with_invalid_number = 0; |
| size_t num_masked_cards = 0; |
| size_t num_masked_cards_with_nickname = 0; |
| size_t num_unmasked_cards = 0; |
| size_t num_disused_local_cards = 0; |
| size_t num_disused_masked_cards = 0; |
| size_t num_disused_unmasked_cards = 0; |
| |
| // Concatenate the local and server cards into one big collection of raw |
| // CreditCard pointers. |
| std::vector<const CreditCard*> credit_cards; |
| credit_cards.reserve(local_cards.size() + server_cards.size()); |
| for (const auto* collection : {&local_cards, &server_cards}) { |
| for (const auto& card : *collection) { |
| credit_cards.push_back(card.get()); |
| } |
| } |
| |
| // Iterate over all of the cards and gather metrics. |
| const base::Time now = AutofillClock::Now(); |
| for (const CreditCard* card : credit_cards) { |
| const base::TimeDelta time_since_last_use = now - card->use_date(); |
| const int days_since_last_use = time_since_last_use.InDays(); |
| const int disused_delta = |
| (time_since_last_use > disused_data_threshold) ? 1 : 0; |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.DaysSinceLastUse.StoredCreditCard", |
| days_since_last_use); |
| switch (card->record_type()) { |
| case CreditCard::RecordType::kLocalCard: |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.DaysSinceLastUse.StoredCreditCard.Local", |
| days_since_last_use); |
| num_local_cards += 1; |
| num_disused_local_cards += disused_delta; |
| if (card->HasNonEmptyValidNickname()) |
| num_local_cards_with_nickname += 1; |
| if (!card->HasValidCardNumber()) { |
| num_local_cards_with_invalid_number += 1; |
| } |
| break; |
| case CreditCard::RecordType::kMaskedServerCard: |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.DaysSinceLastUse.StoredCreditCard.Server", |
| days_since_last_use); |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.DaysSinceLastUse.StoredCreditCard.Server.Masked", |
| days_since_last_use); |
| num_masked_cards += 1; |
| num_disused_masked_cards += disused_delta; |
| if (card->HasNonEmptyValidNickname()) |
| num_masked_cards_with_nickname += 1; |
| break; |
| case CreditCard::RecordType::kFullServerCard: |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.DaysSinceLastUse.StoredCreditCard.Server", |
| days_since_last_use); |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.DaysSinceLastUse.StoredCreditCard.Server.Unmasked", |
| days_since_last_use); |
| num_unmasked_cards += 1; |
| num_disused_unmasked_cards += disused_delta; |
| break; |
| case CreditCard::RecordType::kVirtualCard: |
| // This card type is not persisted in Chrome. |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // Calculate some summary info. |
| const size_t num_server_cards = num_masked_cards + num_unmasked_cards; |
| const size_t num_cards = num_local_cards + num_server_cards; |
| const size_t num_disused_server_cards = |
| num_disused_masked_cards + num_disused_unmasked_cards; |
| const size_t num_disused_cards = |
| num_disused_local_cards + num_disused_server_cards; |
| |
| // Log the overall counts. |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount", num_cards); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Local", |
| num_local_cards); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Local.WithNickname", |
| num_local_cards_with_nickname); |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.StoredCreditCardCount.Local.WithInvalidCardNumber", |
| num_local_cards_with_invalid_number); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server", |
| num_server_cards); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server.Masked", |
| num_masked_cards); |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.StoredCreditCardCount.Server.Masked.WithNickname", |
| num_masked_cards_with_nickname); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardCount.Server.Unmasked", |
| num_unmasked_cards); |
| |
| // For card types held by the user, log how many are disused. |
| if (num_cards) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardDisusedCount", |
| num_disused_cards); |
| } |
| if (num_local_cards) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardDisusedCount.Local", |
| num_disused_local_cards); |
| } |
| if (num_server_cards) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.StoredCreditCardDisusedCount.Server", |
| num_disused_server_cards); |
| } |
| if (num_masked_cards) { |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.StoredCreditCardDisusedCount.Server.Masked", |
| num_disused_masked_cards); |
| } |
| if (num_unmasked_cards) { |
| UMA_HISTOGRAM_COUNTS_1000( |
| "Autofill.StoredCreditCardDisusedCount.Server.Unmasked", |
| num_disused_unmasked_cards); |
| } |
| |
| // Log the number of server cards that are enrolled with virtual cards. |
| size_t virtual_card_enabled_card_count = base::ranges::count_if( |
| server_cards, [](const std::unique_ptr<CreditCard>& card) { |
| return card->virtual_card_enrollment_state() == |
| CreditCard::VirtualCardEnrollmentState::kEnrolled; |
| }); |
| base::UmaHistogramCounts1000( |
| "Autofill.StoredCreditCardCount.Server.WithVirtualCardMetadata", |
| virtual_card_enabled_card_count); |
| |
| // Log the number of server cards that have valid customized art images. |
| base::UmaHistogramCounts1000( |
| "Autofill.StoredCreditCardCount.Server.WithCardArtImage", |
| server_card_count_with_card_art_image); |
| } |
| |
| // static |
| void AutofillMetrics::LogNumberOfCreditCardsSuppressedForDisuse( |
| size_t num_cards) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.CreditCardsSuppressedForDisuse", |
| num_cards); |
| } |
| |
| // static |
| void AutofillMetrics::LogNumberOfCreditCardsDeletedForDisuse(size_t num_cards) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.CreditCardsDeletedForDisuse", num_cards); |
| } |
| |
| // static |
| void AutofillMetrics::LogNumberOfProfilesAtAutofillableFormSubmission( |
| size_t num_profiles) { |
| UMA_HISTOGRAM_COUNTS_1M( |
| "Autofill.StoredProfileCountAtAutofillableFormSubmission", num_profiles); |
| } |
| |
| // static |
| void AutofillMetrics::LogNumberOfAddressesSuppressedForDisuse( |
| size_t num_profiles) { |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.AddressesSuppressedForDisuse", |
| num_profiles); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutofillSuggestionHidingReason( |
| SuggestionHidingReason reason) { |
| base::UmaHistogramEnumeration("Autofill.PopupHidingReason", reason); |
| } |
| |
| void AutofillMetrics::LogSectioningMetrics( |
| const base::flat_map<Section, size_t>& fields_per_section) { |
| constexpr std::string_view kBaseHistogramName = "Autofill.Sectioning."; |
| UMA_HISTOGRAM_COUNTS_100( |
| base::StrCat({kBaseHistogramName, "NumberOfSections"}), |
| fields_per_section.size()); |
| for (auto& [_, section_size] : fields_per_section) { |
| UMA_HISTOGRAM_COUNTS_100( |
| base::StrCat({kBaseHistogramName, "FieldsPerSection"}), section_size); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogServerResponseHasDataForForm(bool has_data) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.ServerResponseHasDataForForm", has_data); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutofillPerfectFilling(bool is_address, |
| bool perfect_filling) { |
| if (is_address) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.PerfectFilling.Addresses", perfect_filling); |
| } else { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.PerfectFilling.CreditCards", |
| perfect_filling); |
| } |
| } |
| |
| AutofillMetrics::CreditCardSeamlessness::CreditCardSeamlessness( |
| const FieldTypeSet& filled_types) |
| : name_(filled_types.contains(CREDIT_CARD_NAME_FULL) || |
| (filled_types.contains(CREDIT_CARD_NAME_FIRST) && |
| filled_types.contains(CREDIT_CARD_NAME_LAST))), |
| number_(filled_types.contains(CREDIT_CARD_NUMBER)), |
| exp_(filled_types.contains(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR) || |
| filled_types.contains(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR) || |
| (filled_types.contains(CREDIT_CARD_EXP_MONTH) && |
| (filled_types.contains(CREDIT_CARD_EXP_2_DIGIT_YEAR) || |
| filled_types.contains(CREDIT_CARD_EXP_4_DIGIT_YEAR)))), |
| cvc_(filled_types.contains(CREDIT_CARD_VERIFICATION_CODE)) {} |
| |
| AutofillMetrics::CreditCardSeamlessness::Metric |
| AutofillMetrics::CreditCardSeamlessness::QualitativeMetric() const { |
| DCHECK(is_valid()); |
| if (name_ && number_ && exp_ && cvc_) { |
| return Metric::kFullFill; |
| } else if (!name_ && number_ && exp_ && cvc_) { |
| return Metric::kOptionalNameMissing; |
| } else if (name_ && number_ && exp_ && !cvc_) { |
| return Metric::kOptionalCvcMissing; |
| } else if (!name_ && number_ && exp_ && !cvc_) { |
| return Metric::kOptionalNameAndCvcMissing; |
| } else if (name_ && number_ && !exp_ && cvc_) { |
| return Metric::kFullFillButExpDateMissing; |
| } else { |
| return Metric::kPartialFill; |
| } |
| } |
| |
| autofill_metrics::FormEvent |
| AutofillMetrics::CreditCardSeamlessness::QualitativeFillableFormEvent() const { |
| DCHECK(is_valid()); |
| switch (QualitativeMetric()) { |
| case Metric::kFullFill: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_FULL_FILL; |
| case Metric::kOptionalNameMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_OPTIONAL_NAME_MISSING; |
| case Metric::kFullFillButExpDateMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_FULL_FILL_BUT_EXPDATE_MISSING; |
| case Metric::kOptionalNameAndCvcMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_OPTIONAL_NAME_AND_CVC_MISSING; |
| case Metric::kOptionalCvcMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_OPTIONAL_CVC_MISSING; |
| case Metric::kPartialFill: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_PARTIAL_FILL; |
| } |
| NOTREACHED(); |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILLABLE_PARTIAL_FILL; |
| } |
| |
| autofill_metrics::FormEvent |
| AutofillMetrics::CreditCardSeamlessness::QualitativeFillFormEvent() const { |
| DCHECK(is_valid()); |
| switch (QualitativeMetric()) { |
| case Metric::kFullFill: |
| return autofill_metrics::FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_FULL_FILL; |
| case Metric::kOptionalNameMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_OPTIONAL_NAME_MISSING; |
| case Metric::kFullFillButExpDateMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_FULL_FILL_BUT_EXPDATE_MISSING; |
| case Metric::kOptionalNameAndCvcMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_OPTIONAL_NAME_AND_CVC_MISSING; |
| case Metric::kOptionalCvcMissing: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_OPTIONAL_CVC_MISSING; |
| case Metric::kPartialFill: |
| return autofill_metrics:: |
| FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_PARTIAL_FILL; |
| } |
| NOTREACHED(); |
| return autofill_metrics::FORM_EVENT_CREDIT_CARD_SEAMLESS_FILL_PARTIAL_FILL; |
| } |
| |
| uint8_t AutofillMetrics::CreditCardSeamlessness::BitmaskMetric() const { |
| DCHECK(is_valid()); |
| uint32_t bitmask = (name_ << 3) | (number_ << 2) | (exp_ << 1) | (cvc_ << 0); |
| DCHECK_GE(bitmask, 1u); |
| DCHECK_LE(bitmask, BitmaskExclusiveMax()); |
| return bitmask; |
| } |
| |
| // static |
| void AutofillMetrics::LogCreditCardSeamlessnessAtFillTime( |
| const LogCreditCardSeamlessnessParam& p) { |
| auto GetSeamlessness = [&p](bool only_newly_filled_fields, |
| bool only_after_security_policy, |
| bool only_visible_fields) { |
| FieldTypeSet autofilled_types; |
| for (const auto& field : *p.form) { |
| FieldGlobalId id = field->global_id(); |
| if (only_newly_filled_fields && !p.newly_filled_fields->contains(id)) |
| continue; |
| if (only_after_security_policy && !p.safe_fields->contains(id)) |
| continue; |
| if (only_visible_fields && !field->is_visible()) { |
| continue; |
| } |
| autofilled_types.insert(field->Type().GetStorableType()); |
| } |
| return CreditCardSeamlessness(autofilled_types); |
| }; |
| |
| auto RecordUma = [](std::string_view infix, CreditCardSeamlessness s) { |
| std::string prefix = base::StrCat({"Autofill.CreditCard.Seamless", infix}); |
| base::UmaHistogramEnumeration(prefix, s.QualitativeMetric()); |
| base::UmaHistogramExactLinear(prefix + ".Bitmask", s.BitmaskMetric(), |
| s.BitmaskExclusiveMax()); |
| }; |
| |
| if (auto s = GetSeamlessness(false, false, false)) { |
| RecordUma("Fillable.AtFillTimeBeforeSecurityPolicy", s); |
| p.builder->SetFillable_BeforeSecurity_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFillable_BeforeSecurity_Qualitative( |
| s.QualitativeMetricAsInt()); |
| p.event_logger->Log(s.QualitativeFillableFormEvent(), *p.form); |
| } |
| if (auto s = GetSeamlessness(false, true, false)) { |
| RecordUma("Fillable.AtFillTimeAfterSecurityPolicy", s); |
| p.builder->SetFillable_AfterSecurity_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFillable_AfterSecurity_Qualitative( |
| s.QualitativeMetricAsInt()); |
| } |
| if (auto s = GetSeamlessness(true, false, false)) { |
| RecordUma("Fills.AtFillTimeBeforeSecurityPolicy", s); |
| p.builder->SetFilled_BeforeSecurity_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFilled_BeforeSecurity_Qualitative(s.QualitativeMetricAsInt()); |
| } |
| if (auto s = GetSeamlessness(true, true, false)) { |
| RecordUma("Fills.AtFillTimeAfterSecurityPolicy", s); |
| p.builder->SetFilled_AfterSecurity_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFilled_AfterSecurity_Qualitative(s.QualitativeMetricAsInt()); |
| p.event_logger->Log(s.QualitativeFillFormEvent(), *p.form); |
| } |
| if (auto s = GetSeamlessness(false, false, true)) { |
| RecordUma("Fillable.AtFillTimeBeforeSecurityPolicy.Visible", s); |
| p.builder->SetFillable_BeforeSecurity_Visible_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFillable_BeforeSecurity_Visible_Qualitative( |
| s.QualitativeMetricAsInt()); |
| } |
| if (auto s = GetSeamlessness(false, true, true)) { |
| RecordUma("Fillable.AtFillTimeAfterSecurityPolicy.Visible", s); |
| p.builder->SetFillable_AfterSecurity_Visible_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFillable_AfterSecurity_Visible_Qualitative( |
| s.QualitativeMetricAsInt()); |
| } |
| if (auto s = GetSeamlessness(true, false, true)) { |
| RecordUma("Fills.AtFillTimeBeforeSecurityPolicy.Visible", s); |
| p.builder->SetFilled_BeforeSecurity_Visible_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFilled_BeforeSecurity_Visible_Qualitative( |
| s.QualitativeMetricAsInt()); |
| } |
| if (auto s = GetSeamlessness(true, true, true)) { |
| RecordUma("Fills.AtFillTimeAfterSecurityPolicy.Visible", s); |
| p.builder->SetFilled_AfterSecurity_Visible_Bitmask(s.BitmaskMetric()); |
| p.builder->SetFilled_AfterSecurity_Visible_Qualitative( |
| s.QualitativeMetricAsInt()); |
| } |
| |
| // In a multi-frame form, a cross-origin field is filled only if |
| // shared-autofill is enabled in the field's frame. Here, we log whether |
| // shared-autofill did or would improve the fill seamlessness. |
| // |
| // This is referring to the actual fill, not the hypothetical scenarios |
| // assuming that the card on file is complete or that there's no security |
| // policy. |
| // |
| // See FormForest::GetRendererFormsOfBrowserForm() for details when a field |
| // requires shared-autofill in order to be autofilled. |
| // |
| // Shared-autofill is a policy-controlled feature. As such, a parent frame |
| // can enable it in a child frame with in the iframe's "allow" attribute: |
| // <iframe allow="shared-autofill">. Whether it's enabled in the main frame is |
| // controller by an HTTP header; by default, it is. |
| auto RequiresSharedAutofill = [&](const AutofillField& field) { |
| auto IsSensitiveFieldType = [](FieldType field_type) { |
| switch (field_type) { |
| case CREDIT_CARD_TYPE: |
| case CREDIT_CARD_NAME_FULL: |
| case CREDIT_CARD_NAME_FIRST: |
| case CREDIT_CARD_NAME_LAST: |
| return false; |
| default: |
| return true; |
| } |
| }; |
| const url::Origin& main_origin = p.form->main_frame_origin(); |
| const url::Origin& triggered_origin = p.field->origin(); |
| return field.origin() != triggered_origin && |
| (field.origin() != main_origin || |
| IsSensitiveFieldType(field.Type().GetStorableType())) && |
| triggered_origin == main_origin; |
| }; |
| |
| bool some_field_needs_shared_autofill = false; |
| bool some_field_has_shared_autofill = false; |
| for (const auto& field : *p.form) { |
| if (RequiresSharedAutofill(*field) && |
| p.newly_filled_fields->contains(field->global_id())) { |
| if (!p.safe_fields->contains(field->global_id())) { |
| some_field_needs_shared_autofill = true; |
| } else { |
| some_field_has_shared_autofill = true; |
| } |
| } |
| } |
| |
| enum SharedAutofillMetric : uint64_t { |
| kSharedAutofillIsIrrelevant = 0, |
| kSharedAutofillWouldHelp = 1, |
| kSharedAutofillDidHelp = 2, |
| }; |
| if (some_field_needs_shared_autofill) { |
| p.builder->SetSharedAutofill(kSharedAutofillWouldHelp); |
| p.event_logger->Log( |
| autofill_metrics::FORM_EVENT_CREDIT_CARD_MISSING_SHARED_AUTOFILL, |
| *p.form); |
| } else if (some_field_has_shared_autofill) { |
| p.builder->SetSharedAutofill(kSharedAutofillDidHelp); |
| } else { |
| p.builder->SetSharedAutofill(kSharedAutofillIsIrrelevant); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogCreditCardSeamlessnessAtSubmissionTime( |
| const FieldTypeSet& autofilled_types) { |
| CreditCardSeamlessness seamlessness(autofilled_types); |
| if (seamlessness.is_valid()) { |
| base::UmaHistogramExactLinear( |
| "Autofill.CreditCard.SeamlessFills.AtSubmissionTime.Bitmask", |
| seamlessness.BitmaskMetric(), seamlessness.BitmaskExclusiveMax()); |
| base::UmaHistogramEnumeration( |
| "Autofill.CreditCard.SeamlessFills.AtSubmissionTime", |
| seamlessness.QualitativeMetric()); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogParseFormTiming(const base::TimeDelta& duration) { |
| UMA_HISTOGRAM_TIMES("Autofill.Timing.ParseForm", duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogIsQueriedCreditCardFormSecure(bool is_secure) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.QueriedCreditCardFormIsSecure", is_secure); |
| } |
| |
| // static |
| void AutofillMetrics::LogShowedHttpNotSecureExplanation() { |
| base::RecordAction( |
| base::UserMetricsAction("Autofill_ShowedHttpNotSecureExplanation")); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutocompleteDaysSinceLastUse(size_t days) { |
| UMA_HISTOGRAM_COUNTS_1000("Autocomplete.DaysSinceLastUse", days); |
| } |
| |
| // static |
| void AutofillMetrics::OnAutocompleteSuggestionsShown() { |
| AutofillMetrics::LogAutocompleteEvent( |
| AutocompleteEvent::AUTOCOMPLETE_SUGGESTIONS_SHOWN); |
| } |
| |
| // static |
| void AutofillMetrics::OnAutocompleteSuggestionDeleted( |
| SingleEntryRemovalMethod removal_method) { |
| AutofillMetrics::LogAutocompleteEvent( |
| AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED); |
| base::UmaHistogramEnumeration( |
| "Autofill.Autocomplete.SingleEntryRemovalMethod", removal_method); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutocompleteEvent(AutocompleteEvent event) { |
| DCHECK_LT(event, AutocompleteEvent::NUM_AUTOCOMPLETE_EVENTS); |
| base::UmaHistogramEnumeration("Autocomplete.Events2", event, |
| NUM_AUTOCOMPLETE_EVENTS); |
| } |
| |
| // static |
| const char* AutofillMetrics::SubmissionSourceToUploadEventMetric( |
| SubmissionSource source) { |
| switch (source) { |
| case SubmissionSource::NONE: |
| return "Autofill.UploadEvent.None"; |
| case SubmissionSource::SAME_DOCUMENT_NAVIGATION: |
| return "Autofill.UploadEvent.SameDocumentNavigation"; |
| case SubmissionSource::XHR_SUCCEEDED: |
| return "Autofill.UploadEvent.XhrSucceeded"; |
| case SubmissionSource::FRAME_DETACHED: |
| return "Autofill.UploadEvent.FrameDetached"; |
| case SubmissionSource::PROBABLY_FORM_SUBMITTED: |
| return "Autofill.UploadEvent.ProbablyFormSubmitted"; |
| case SubmissionSource::FORM_SUBMISSION: |
| return "Autofill.UploadEvent.FormSubmission"; |
| case SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL: |
| return "Autofill.UploadEvent.DomMutationAfterAutofill"; |
| } |
| // Unit tests exercise this path, so do not put NOTREACHED() here. |
| return "Autofill.UploadEvent.Unknown"; |
| } |
| |
| // static |
| void AutofillMetrics::LogUploadEvent(SubmissionSource submission_source, |
| bool was_sent) { |
| UMA_HISTOGRAM_BOOLEAN("Autofill.UploadEvent", was_sent); |
| base::UmaHistogramEnumeration( |
| SubmissionSourceToUploadEventMetric(submission_source), |
| was_sent ? UploadEventStatus::kSent : UploadEventStatus::kNotSent); |
| } |
| |
| // static |
| void AutofillMetrics::LogDeveloperEngagementUkm( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| const GURL& url, |
| bool is_for_credit_card, |
| DenseSet<FormType> form_types, |
| int developer_engagement_metrics, |
| FormSignature form_signature) { |
| DCHECK(developer_engagement_metrics); |
| DCHECK_LT(developer_engagement_metrics, |
| 1 << NUM_DEVELOPER_ENGAGEMENT_METRICS); |
| if (!url.is_valid()) |
| return; |
| |
| ukm::builders::Autofill_DeveloperEngagement(source_id) |
| .SetDeveloperEngagement(developer_engagement_metrics) |
| .SetIsForCreditCard(is_for_credit_card) |
| .SetFormTypes(FormTypesToBitVector(form_types)) |
| .SetFormSignature(HashFormSignature(form_signature)) |
| .Record(ukm_recorder); |
| } |
| |
| AutofillMetrics::FormInteractionsUkmLogger::FormInteractionsUkmLogger( |
| AutofillClient* autofill_client, |
| ukm::UkmRecorder* ukm_recorder) |
| : autofill_client_(autofill_client), ukm_recorder_(ukm_recorder) {} |
| |
| ukm::builders::Autofill_CreditCardFill |
| AutofillMetrics::FormInteractionsUkmLogger::CreateCreditCardFillBuilder() { |
| return ukm::builders::Autofill_CreditCardFill(GetSourceId()); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::Record( |
| ukm::builders::Autofill_CreditCardFill&& builder) { |
| if (CanLog()) |
| builder.Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::OnFormsParsed( |
| const ukm::SourceId source_id) { |
| if (!CanLog()) |
| return; |
| |
| source_id_ = source_id; |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogInteractedWithForm( |
| bool is_for_credit_card, |
| size_t local_record_type_count, |
| size_t server_record_type_count, |
| FormSignature form_signature) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_InteractedWithForm(GetSourceId()) |
| .SetIsForCreditCard(is_for_credit_card) |
| .SetLocalRecordTypeCount(local_record_type_count) |
| .SetServerRecordTypeCount(server_record_type_count) |
| .SetFormSignature(HashFormSignature(form_signature)) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogSuggestionsShown( |
| const FormStructure& form, |
| const AutofillField& field, |
| const base::TimeTicks& form_parsed_timestamp, |
| bool off_the_record) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_SuggestionsShown(GetSourceId()) |
| .SetHeuristicType(static_cast<int>(field.heuristic_type())) |
| .SetHtmlFieldType(static_cast<int>(field.html_type())) |
| .SetServerType(static_cast<int>(field.server_type())) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form_parsed_timestamp)) |
| .Record(ukm_recorder_); |
| |
| base::UmaHistogramBoolean("Autofill.SuggestionShown.OffTheRecord", |
| off_the_record); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogDidFillSuggestion( |
| const FormStructure& form, |
| const AutofillField& field, |
| std::optional<CreditCard::RecordType> record_type) { |
| if (!CanLog()) |
| return; |
| |
| auto metric = ukm::builders::Autofill_SuggestionFilled(GetSourceId()); |
| if (record_type) { |
| metric.SetRecordType(base::to_underlying(*record_type)); |
| } |
| metric.SetIsForCreditCard(record_type.has_value()) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form.form_parsed_timestamp())) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger:: |
| LogEditedAutofilledFieldAtSubmission(const FormStructure& form, |
| const AutofillField& field) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_EditedAutofilledFieldAtSubmission(GetSourceId()) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetOverallType(static_cast<int64_t>(field.Type().GetStorableType())) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogTextFieldDidChange( |
| const FormStructure& form, |
| const AutofillField& field) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_TextFieldDidChange(GetSourceId()) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetFieldTypeGroup(static_cast<int>(field.Type().group())) |
| .SetHeuristicType(static_cast<int>(field.heuristic_type())) |
| .SetServerType(static_cast<int>(field.server_type())) |
| .SetHtmlFieldType(static_cast<int>(field.html_type())) |
| .SetHtmlFieldMode(static_cast<int>(field.html_mode())) |
| .SetIsAutofilled(field.is_autofilled()) |
| .SetIsEmpty(field.IsEmpty()) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form.form_parsed_timestamp())) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogFieldFillStatus( |
| const FormStructure& form, |
| const AutofillField& field, |
| QualityMetricType metric_type) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_FieldFillStatus(GetSourceId()) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form.form_parsed_timestamp())) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetValidationEvent(static_cast<int64_t>(metric_type)) |
| .SetIsAutofilled(static_cast<int64_t>(field.is_autofilled())) |
| .SetWasPreviouslyAutofilled( |
| static_cast<int64_t>(field.previously_autofilled())) |
| .Record(ukm_recorder_); |
| } |
| |
| // TODO(szhangcs): Take FormStructure and AutofillField and extract |
| // FormSignature and TimeTicks inside the function. |
| void AutofillMetrics::FormInteractionsUkmLogger::LogFieldType( |
| const base::TimeTicks& form_parsed_timestamp, |
| FormSignature form_signature, |
| FieldSignature field_signature, |
| QualityMetricPredictionSource prediction_source, |
| QualityMetricType metric_type, |
| FieldType predicted_type, |
| FieldType actual_type) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_FieldTypeValidation(GetSourceId()) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form_parsed_timestamp)) |
| .SetFormSignature(HashFormSignature(form_signature)) |
| .SetFieldSignature(HashFieldSignature(field_signature)) |
| .SetValidationEvent(static_cast<int64_t>(metric_type)) |
| .SetPredictionSource(static_cast<int64_t>(prediction_source)) |
| .SetPredictedType(static_cast<int64_t>(predicted_type)) |
| .SetActualType(static_cast<int64_t>(actual_type)) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger:: |
| LogAutofillFieldInfoAtFormRemove( |
| const FormStructure& form, |
| const AutofillField& field, |
| AutofillMetrics::AutocompleteState autocomplete_state) { |
| if (!CanLog()) { |
| return; |
| } |
| |
| const std::vector<AutofillField::FieldLogEventType>& field_log_events = |
| field.field_log_events(); |
| if (field_log_events.empty()) { |
| return; |
| } |
| |
| // Set the fields with autofill information according to Autofill2.FieldInfo |
| // UKM schema: |
| // https://docs.google.com/document/d/1ZH0JbL6bES3cD4KqZWsGR6n8I-rhnkx6no6nQOgYq5w/ |
| OptionalBoolean was_focused = OptionalBoolean::kFalse; |
| OptionalBoolean suggestion_was_available = OptionalBoolean::kUndefined; |
| OptionalBoolean suggestion_was_shown = OptionalBoolean::kUndefined; |
| OptionalBoolean suggestion_was_accepted = OptionalBoolean::kUndefined; |
| |
| // Records whether this field was autofilled before checking the iframe |
| // security policy. |
| OptionalBoolean was_autofilled_before_security_policy = |
| OptionalBoolean::kUndefined; |
| OptionalBoolean had_value_before_filling = OptionalBoolean::kUndefined; |
| DenseSet<FieldFillingSkipReason> autofill_skipped_status; |
| size_t autofill_count = 0; |
| |
| OptionalBoolean user_typed_into_field = OptionalBoolean::kFalse; |
| OptionalBoolean filled_value_was_modified = OptionalBoolean::kUndefined; |
| OptionalBoolean had_typed_or_filled_value_at_submission = |
| OptionalBoolean::kUndefined; |
| OptionalBoolean had_value_after_filling = OptionalBoolean::kUndefined; |
| OptionalBoolean has_value_after_typing = OptionalBoolean::kUndefined; |
| |
| // Records whether filling was ever prevented because of the cross iframe |
| // autofill security policy that applies to credit cards. |
| OptionalBoolean filling_prevented_by_iframe_security_policy = |
| OptionalBoolean::kUndefined; |
| // Records whether this field was actually safely autofilled after checking |
| // the iframe security policy. |
| OptionalBoolean was_autofilled_after_security_policy = |
| OptionalBoolean::kUndefined; |
| |
| // TODO(crbug.com/40225658): Add a metric in |FieldInfo| UKM event to indicate |
| // whether the user had any data available for the respective field type. |
| |
| // If multiple fields have the same signature, this indicates the position |
| // within this set of fields. This allows us to understand problems related |
| // to duplicated field signatures. |
| size_t rank_in_field_signature_group = 0; |
| |
| // Field types from local heuristics prediction. |
| // The field type from the active local heuristic pattern. |
| FieldType heuristic_type = UNKNOWN_TYPE; |
| // The type of the field predicted from patterns whose stability is above |
| // suspicion. |
| FieldType heuristic_legacy_type = UNKNOWN_TYPE; |
| // The type of the field predicted from the source of local heuristics on |
| // the client, which uses patterns applied for most users. |
| FieldType heuristic_default_type = UNKNOWN_TYPE; |
| // The type of the field predicted from the heuristics that uses experimental |
| // patterns. |
| FieldType heuristic_experimental_type = UNKNOWN_TYPE; |
| // The type of the field predicted from the heuristics that uses patterns |
| // only for non-user-visible metrics, one step before experimental. |
| FieldType heuristic_next_gen_type = UNKNOWN_TYPE; |
| |
| // Field types from Autocomplete attribute. |
| // Information of the HTML autocomplete attribute, see |
| // components/autofill/core/common/mojom/autofill_types.mojom. |
| HtmlFieldMode html_mode = HtmlFieldMode::kNone; |
| HtmlFieldType html_type = HtmlFieldType::kUnrecognized; |
| |
| // The field type predicted by the Autofill crowdsourced server from |
| // majority voting. |
| std::optional<FieldType> server_type1 = std::nullopt; |
| FieldPrediction::Source prediction_source1 = |
| FieldPrediction::SOURCE_UNSPECIFIED; |
| std::optional<FieldType> server_type2 = std::nullopt; |
| FieldPrediction::Source prediction_source2 = |
| FieldPrediction::SOURCE_UNSPECIFIED; |
| // This is an annotation for server predicted field types which indicates |
| // that a manual override defines the server type. |
| bool server_type_is_override = false; |
| |
| // The final field type from the list of |autofill::FieldType| that we |
| // choose after rationalization, which is used to determine |
| // the autofill suggestion when the user triggers autofilling. |
| FieldType overall_type = NO_SERVER_DATA; |
| // The sections are mapped to consecutive natural numbers starting at 1, |
| // numbered according to the ordering of their first fields. |
| size_t section_id = 0; |
| bool type_changed_by_rationalization = false; |
| |
| bool had_heuristic_type = false; |
| bool had_html_type = false; |
| bool had_server_type = false; |
| bool had_rationalization_event = false; |
| |
| DenseSet<AutofillMetrics::AutofillStatus> autofill_status_vector; |
| auto SetStatusVector = [&autofill_status_vector]( |
| AutofillMetrics::AutofillStatus status, |
| bool value) { |
| DCHECK(!autofill_status_vector.contains(status)); |
| if (value) { |
| autofill_status_vector.insert(status); |
| } |
| }; |
| |
| for (const auto& log_event : field_log_events) { |
| static_assert(absl::variant_size<AutofillField::FieldLogEventType>() == 9, |
| "When adding new variants check that this function does not " |
| "need to be updated."); |
| if (auto* event = |
| absl::get_if<AskForValuesToFillFieldLogEvent>(&log_event)) { |
| was_focused = OptionalBoolean::kTrue; |
| suggestion_was_available |= event->has_suggestion; |
| suggestion_was_shown |= event->suggestion_is_shown; |
| if (suggestion_was_shown == OptionalBoolean::kTrue && |
| suggestion_was_accepted == OptionalBoolean::kUndefined) { |
| // Initialize suggestion_was_accepted to a defined value when the first |
| // time the suggestion is shown. |
| suggestion_was_accepted = OptionalBoolean::kFalse; |
| } |
| } |
| |
| if (auto* event = absl::get_if<TriggerFillFieldLogEvent>(&log_event)) { |
| // Ignore events which are not address or credit card fill events. |
| if (event->data_type != FillDataType::kAutofillProfile && |
| event->data_type != FillDataType::kCreditCard) { |
| continue; |
| } |
| suggestion_was_accepted = OptionalBoolean::kTrue; |
| } |
| |
| if (auto* event = absl::get_if<FillFieldLogEvent>(&log_event)) { |
| was_autofilled_before_security_policy |= |
| event->was_autofilled_before_security_policy; |
| had_value_before_filling |= event->had_value_before_filling; |
| autofill_skipped_status.insert(event->autofill_skipped_status); |
| had_value_after_filling = event->had_value_after_filling; |
| if (was_autofilled_before_security_policy == OptionalBoolean::kTrue && |
| filled_value_was_modified == OptionalBoolean::kUndefined) { |
| // Initialize filled_value_was_modified to a defined value when the |
| // field is filled for the first time. |
| filled_value_was_modified = OptionalBoolean::kFalse; |
| } |
| |
| filling_prevented_by_iframe_security_policy |= |
| OptionalBoolean(event->filling_prevented_by_iframe_security_policy == |
| OptionalBoolean::kTrue); |
| was_autofilled_after_security_policy |= |
| OptionalBoolean(event->filling_prevented_by_iframe_security_policy == |
| OptionalBoolean::kFalse); |
| ++autofill_count; |
| } |
| |
| if (auto* event = absl::get_if<TypingFieldLogEvent>(&log_event)) { |
| user_typed_into_field = OptionalBoolean::kTrue; |
| if (was_autofilled_after_security_policy == OptionalBoolean::kTrue) { |
| filled_value_was_modified = OptionalBoolean::kTrue; |
| } |
| has_value_after_typing = event->has_value_after_typing; |
| } |
| |
| if (auto* event = |
| absl::get_if<HeuristicPredictionFieldLogEvent>(&log_event)) { |
| #if BUILDFLAG(USE_INTERNAL_AUTOFILL_PATTERNS) |
| switch (event->pattern_source) { |
| case PatternSource::kLegacy: |
| heuristic_legacy_type = event->field_type; |
| break; |
| case PatternSource::kDefault: |
| heuristic_default_type = event->field_type; |
| break; |
| case PatternSource::kExperimental: |
| heuristic_experimental_type = event->field_type; |
| break; |
| case PatternSource::kNextGen: |
| heuristic_next_gen_type = event->field_type; |
| break; |
| } |
| #else |
| switch (event->pattern_source) { |
| case PatternSource::kLegacy: |
| heuristic_legacy_type = event->field_type; |
| break; |
| } |
| #endif |
| |
| if (event->is_active_pattern_source) { |
| heuristic_type = event->field_type; |
| } |
| rank_in_field_signature_group = event->rank_in_field_signature_group; |
| had_heuristic_type = true; |
| } |
| |
| if (auto* event = |
| absl::get_if<AutocompleteAttributeFieldLogEvent>(&log_event)) { |
| html_type = event->html_type; |
| html_mode = event->html_mode; |
| rank_in_field_signature_group = event->rank_in_field_signature_group; |
| had_html_type = true; |
| } |
| |
| if (auto* event = absl::get_if<ServerPredictionFieldLogEvent>(&log_event)) { |
| server_type1 = event->server_type1; |
| prediction_source1 = event->prediction_source1; |
| server_type2 = event->server_type2; |
| prediction_source2 = event->prediction_source2; |
| server_type_is_override = event->server_type_prediction_is_override; |
| rank_in_field_signature_group = event->rank_in_field_signature_group; |
| had_server_type = true; |
| } |
| |
| if (auto* event = absl::get_if<RationalizationFieldLogEvent>(&log_event)) { |
| overall_type = event->field_type; |
| section_id = event->section_id; |
| type_changed_by_rationalization = event->type_changed; |
| had_rationalization_event = true; |
| } |
| } |
| |
| if (had_value_after_filling != OptionalBoolean::kUndefined || |
| has_value_after_typing != OptionalBoolean::kUndefined) { |
| had_typed_or_filled_value_at_submission = |
| ToOptionalBoolean(had_value_after_filling == OptionalBoolean::kTrue || |
| has_value_after_typing == OptionalBoolean::kTrue); |
| } |
| |
| ukm::builders::Autofill2_FieldInfo builder(GetSourceId()); |
| builder |
| .SetFormSessionIdentifier( |
| AutofillMetrics::FormGlobalIdToHash64Bit(form.global_id())) |
| .SetFieldSessionIdentifier( |
| AutofillMetrics::FieldGlobalIdToHash64Bit(field.global_id())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetFormControlType2(base::to_underlying(field.form_control_type())) |
| .SetAutocompleteState(base::to_underlying(autocomplete_state)) |
| .SetFieldLogEventCount(field_log_events.size()); |
| |
| SetStatusVector(AutofillStatus::kIsFocusable, field.IsFocusable()); |
| SetStatusVector(AutofillStatus::kUserTypedIntoField, |
| OptionalBooleanToBool(user_typed_into_field)); |
| SetStatusVector(AutofillStatus::kWasFocused, |
| OptionalBooleanToBool(was_focused)); |
| SetStatusVector(AutofillStatus::kIsInSubFrame, |
| form.ToFormData().host_frame != field.host_frame()); |
| |
| if (filling_prevented_by_iframe_security_policy != |
| OptionalBoolean::kUndefined) { |
| SetStatusVector( |
| AutofillStatus::kFillingPreventedByIframeSecurityPolicy, |
| OptionalBooleanToBool(filling_prevented_by_iframe_security_policy)); |
| } |
| |
| if (was_focused == OptionalBoolean::kTrue) { |
| SetStatusVector(AutofillStatus::kSuggestionWasAvailable, |
| OptionalBooleanToBool(suggestion_was_available)); |
| SetStatusVector(AutofillStatus::kSuggestionWasShown, |
| OptionalBooleanToBool(suggestion_was_shown)); |
| } |
| |
| if (suggestion_was_shown == OptionalBoolean::kTrue) { |
| SetStatusVector(AutofillStatus::kSuggestionWasAccepted, |
| OptionalBooleanToBool(suggestion_was_accepted)); |
| } |
| |
| SetStatusVector(AutofillStatus::kWasAutofillTriggered, autofill_count > 0); |
| if (autofill_count > 0) { |
| SetStatusVector( |
| AutofillStatus::kWasAutofilledBeforeSecurityPolicy, |
| OptionalBooleanToBool(was_autofilled_before_security_policy)); |
| SetStatusVector(AutofillStatus::kHadValueBeforeFilling, |
| OptionalBooleanToBool(had_value_before_filling)); |
| SetStatusVector(AutofillStatus::kWasRefill, autofill_count > 1); |
| if (was_autofilled_after_security_policy != OptionalBoolean::kUndefined) { |
| SetStatusVector( |
| AutofillStatus::kWasAutofilledAfterSecurityPolicy, |
| OptionalBooleanToBool(was_autofilled_after_security_policy)); |
| } |
| |
| static_assert(autofill_skipped_status.data().size() == 1); |
| builder.SetAutofillSkippedStatus(autofill_skipped_status.data()[0]); |
| } |
| |
| if (filled_value_was_modified != OptionalBoolean::kUndefined) { |
| SetStatusVector(AutofillStatus::kFilledValueWasModified, |
| OptionalBooleanToBool(filled_value_was_modified)); |
| } |
| |
| if (had_typed_or_filled_value_at_submission != OptionalBoolean::kUndefined) { |
| SetStatusVector( |
| AutofillStatus::kHadTypedOrFilledValueAtSubmission, |
| OptionalBooleanToBool(had_typed_or_filled_value_at_submission)); |
| } |
| |
| if (had_heuristic_type) { |
| builder.SetHeuristicType(heuristic_type) |
| .SetHeuristicTypeLegacy(heuristic_legacy_type) |
| .SetHeuristicTypeDefault(heuristic_default_type) |
| .SetHeuristicTypeExperimental(heuristic_experimental_type) |
| .SetHeuristicTypeNextGen(heuristic_next_gen_type); |
| } |
| |
| if (had_html_type) { |
| builder.SetHtmlFieldType(base::to_underlying(html_type)) |
| .SetHtmlFieldMode(base::to_underlying(html_mode)); |
| } |
| |
| if (had_server_type) { |
| int64_t server_type1_value = server_type1.has_value() |
| ? server_type1.value() |
| : /*SERVER_RESPONSE_PENDING*/ 161; |
| int64_t server_type2_value = server_type2.has_value() |
| ? server_type2.value() |
| : /*SERVER_RESPONSE_PENDING*/ 161; |
| builder.SetServerType1(server_type1_value) |
| .SetServerPredictionSource1(prediction_source1) |
| .SetServerType2(server_type2_value) |
| .SetServerPredictionSource2(prediction_source2) |
| .SetServerTypeIsOverride(server_type_is_override); |
| } |
| |
| if (had_rationalization_event) { |
| builder.SetOverallType(overall_type) |
| .SetSectionId(section_id) |
| .SetTypeChangedByRationalization(type_changed_by_rationalization); |
| } |
| |
| if (rank_in_field_signature_group) { |
| builder.SetRankInFieldSignatureGroup(rank_in_field_signature_group); |
| } |
| |
| // Serialize the DenseSet of the autofill status into int64_t. |
| static_assert(autofill_status_vector.data().size() == 1U); |
| builder.SetAutofillStatusVector(autofill_status_vector.data()[0]); |
| |
| builder.Record(ukm_recorder_); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutofillFieldInfoAfterSubmission( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| const FormStructure& form, |
| const base::TimeTicks& form_submitted_timestamp) { |
| for (const auto& field : form) { |
| // The possible field submitted types determined by comparing the submitted |
| // value in the field with the data stored in the Autofill server. We will |
| // have at most three possible field submitted types. |
| FieldType submitted_type1 = UNKNOWN_TYPE; |
| |
| ukm::builders::Autofill2_FieldInfoAfterSubmission builder(source_id); |
| builder |
| .SetFormSessionIdentifier( |
| AutofillMetrics::FormGlobalIdToHash64Bit(form.global_id())) |
| .SetFieldSessionIdentifier( |
| AutofillMetrics::FieldGlobalIdToHash64Bit(field->global_id())); |
| |
| const FieldTypeSet& type_set = field->possible_types(); |
| if (!type_set.empty()) { |
| auto type = type_set.begin(); |
| submitted_type1 = *type; |
| if (type_set.size() >= 2) { |
| builder.SetSubmittedType2(*++type); |
| } |
| if (type_set.size() >= 3) { |
| builder.SetSubmittedType3(*++type); |
| } |
| } |
| |
| // TODO(crbug.com/40225658): Modify the enum object of SubmissionSource by |
| // assigning values (= 0, = 1, ...) and adding a comment to not change it. |
| builder.SetSubmittedType1(submitted_type1) |
| .SetSubmissionSource(static_cast<int>(form.submission_source())) |
| .SetMillisecondsFromFormParsedUntilSubmission( |
| ukm::GetSemanticBucketMinForDurationTiming( |
| (form_submitted_timestamp - form.form_parsed_timestamp()) |
| .InMilliseconds())) |
| .Record(ukm_recorder); |
| } |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger:: |
| LogAutofillFormSummaryAtFormRemove( |
| const FormStructure& form_structure, |
| FormEventSet form_events, |
| const base::TimeTicks& initial_interaction_timestamp, |
| const base::TimeTicks& form_submitted_timestamp) { |
| if (!CanLog()) { |
| return; |
| } |
| |
| static_assert(form_events.data().size() == 2U, |
| "If you add a new form event, you need to create a new " |
| "AutofillFormEvents metric in Autofill2.FormSummary"); |
| ukm::builders::Autofill2_FormSummary builder(GetSourceId()); |
| builder |
| .SetFormSessionIdentifier( |
| AutofillMetrics::FormGlobalIdToHash64Bit(form_structure.global_id())) |
| .SetFormSignature(HashFormSignature(form_structure.form_signature())) |
| .SetAutofillFormEvents(form_events.data()[0]) |
| .SetAutofillFormEvents2(form_events.data()[1]) |
| .SetWasSubmitted(!form_submitted_timestamp.is_null()) |
| .SetSampleRate(1); |
| |
| if (!form_submitted_timestamp.is_null() && |
| !form_structure.form_parsed_timestamp().is_null() && |
| form_submitted_timestamp > form_structure.form_parsed_timestamp()) { |
| builder.SetMillisecondsFromFormParsedUntilSubmission( |
| ukm::GetSemanticBucketMinForDurationTiming( |
| (form_submitted_timestamp - form_structure.form_parsed_timestamp()) |
| .InMilliseconds())); |
| } |
| |
| if (!form_submitted_timestamp.is_null() && |
| !initial_interaction_timestamp.is_null() && |
| form_submitted_timestamp > initial_interaction_timestamp) { |
| builder.SetMillisecondsFromFirstInteratctionUntilSubmission( |
| ukm::GetSemanticBucketMinForDurationTiming( |
| (form_submitted_timestamp - initial_interaction_timestamp) |
| .InMilliseconds())); |
| } |
| builder.Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger:: |
| LogHiddenRepresentationalFieldSkipDecision(const FormStructure& form, |
| const AutofillField& field, |
| bool is_skipped) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_HiddenRepresentationalFieldSkipDecision(GetSourceId()) |
| .SetFormSignature(HashFormSignature(form.form_signature())) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetFieldTypeGroup(static_cast<int>(field.Type().group())) |
| .SetFieldOverallType(static_cast<int>(field.Type().GetStorableType())) |
| .SetHeuristicType(static_cast<int>(field.heuristic_type())) |
| .SetServerType(static_cast<int>(field.server_type())) |
| .SetHtmlFieldType(static_cast<int>(field.html_type())) |
| .SetHtmlFieldMode(static_cast<int>(field.html_mode())) |
| .SetIsSkipped(is_skipped) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger:: |
| LogRepeatedServerTypePredictionRationalized( |
| const FormSignature form_signature, |
| const AutofillField& field, |
| FieldType old_type) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_RepeatedServerTypePredictionRationalized( |
| GetSourceId()) |
| .SetFormSignature(HashFormSignature(form_signature)) |
| .SetFieldSignature(HashFieldSignature(field.GetFieldSignature())) |
| .SetFieldTypeGroup(static_cast<int>(field.Type().group())) |
| .SetFieldNewOverallType(static_cast<int>(field.Type().GetStorableType())) |
| .SetHeuristicType(static_cast<int>(field.heuristic_type())) |
| .SetHtmlFieldType(static_cast<int>(field.html_type())) |
| .SetHtmlFieldMode(static_cast<int>(field.html_mode())) |
| .SetServerType(static_cast<int>(field.server_type())) |
| .SetFieldOldOverallType(static_cast<int>(old_type)) |
| .Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogSectioningHash( |
| FormSignature form_signature, |
| uint32_t sectioning_signature) { |
| ukm::builders::Autofill_Sectioning(GetSourceId()) |
| .SetFormSignature(HashFormSignature(form_signature)) |
| .SetSectioningSignature(sectioning_signature % 1024) |
| .Record(ukm_recorder_); |
| } |
| |
| ukm::SourceId AutofillMetrics::FormInteractionsUkmLogger::GetSourceId() { |
| if (!source_id_.has_value()) { |
| source_id_ = autofill_client_->GetUkmSourceId(); |
| } |
| return *source_id_; |
| } |
| |
| int64_t AutofillMetrics::FormTypesToBitVector( |
| const DenseSet<FormType>& form_types) { |
| int64_t form_type_bv = 0; |
| for (FormType form_type : form_types) { |
| DCHECK_LT(static_cast<int64_t>(form_type), 63); |
| form_type_bv |= 1LL << static_cast<int64_t>(form_type); |
| } |
| return form_type_bv; |
| } |
| |
| void AutofillMetrics::LogServerCardLinkClicked(PaymentsSigninState sync_state) { |
| base::UmaHistogramEnumeration("Autofill.ServerCardLinkClicked", sync_state); |
| } |
| |
| // static |
| const char* AutofillMetrics::GetMetricsSyncStateSuffix( |
| PaymentsSigninState sync_state) { |
| switch (sync_state) { |
| case PaymentsSigninState::kSignedOut: |
| return ".SignedOut"; |
| case PaymentsSigninState::kSignedIn: |
| return ".SignedIn"; |
| case PaymentsSigninState::kSignedInAndWalletSyncTransportEnabled: |
| return ".SignedInAndWalletSyncTransportEnabled"; |
| case PaymentsSigninState::kSignedInAndSyncFeatureEnabled: |
| return ".SignedInAndSyncFeatureEnabled"; |
| case PaymentsSigninState::kSyncPaused: |
| return ".SyncPaused"; |
| case PaymentsSigninState::kUnknown: |
| return ".Unknown"; |
| } |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogKeyMetrics( |
| const DenseSet<FormType>& form_types, |
| bool data_to_fill_available, |
| bool suggestions_shown, |
| bool edited_autofilled_field, |
| bool suggestion_filled, |
| const FormInteractionCounts& form_interaction_counts, |
| const FormInteractionsFlowId& flow_id, |
| std::optional<int64_t> fast_checkout_run_id) { |
| if (!CanLog()) |
| return; |
| |
| ukm::builders::Autofill_KeyMetrics builder(GetSourceId()); |
| builder.SetFillingReadiness(data_to_fill_available) |
| .SetFillingAssistance(suggestion_filled) |
| .SetFormTypes(FormTypesToBitVector(form_types)) |
| .SetAutofillFills(form_interaction_counts.autofill_fills) |
| .SetFormElementUserModifications( |
| form_interaction_counts.form_element_user_modifications) |
| .SetFlowId(flow_id.value()); |
| if (fast_checkout_run_id) { |
| builder.SetFastCheckoutRunId(fast_checkout_run_id.value()); |
| } |
| if (suggestions_shown) |
| builder.SetFillingAcceptance(suggestion_filled); |
| |
| if (suggestion_filled) |
| builder.SetFillingCorrectness(!edited_autofilled_field); |
| |
| builder.Record(ukm_recorder_); |
| } |
| |
| void AutofillMetrics::FormInteractionsUkmLogger::LogFormEvent( |
| autofill_metrics::FormEvent form_event, |
| const DenseSet<FormType>& form_types, |
| const base::TimeTicks& form_parsed_timestamp) { |
| if (!CanLog()) |
| return; |
| |
| if (form_parsed_timestamp.is_null()) |
| return; |
| |
| ukm::builders::Autofill_FormEvent builder(GetSourceId()); |
| builder.SetAutofillFormEvent(static_cast<int>(form_event)) |
| .SetFormTypes(FormTypesToBitVector(form_types)) |
| .SetMillisecondsSinceFormParsed( |
| MillisecondsSinceFormParsed(form_parsed_timestamp)) |
| .Record(ukm_recorder_); |
| } |
| |
| bool AutofillMetrics::FormInteractionsUkmLogger::CanLog() const { |
| return ukm_recorder_ != nullptr; |
| } |
| |
| int64_t AutofillMetrics::FormInteractionsUkmLogger::MillisecondsSinceFormParsed( |
| const base::TimeTicks& form_parsed_timestamp) const { |
| DCHECK(!form_parsed_timestamp.is_null()); |
| // Use the pinned timestamp as the current time if it's set. |
| base::TimeTicks now = |
| pinned_timestamp_.is_null() ? base::TimeTicks::Now() : pinned_timestamp_; |
| |
| return ukm::GetExponentialBucketMin( |
| (now - form_parsed_timestamp).InMilliseconds(), |
| kAutofillEventDataBucketSpacing); |
| } |
| |
| AutofillMetrics::UkmTimestampPin::UkmTimestampPin( |
| FormInteractionsUkmLogger* logger) |
| : logger_(logger) { |
| DCHECK(logger_); |
| DCHECK(!logger_->has_pinned_timestamp()); |
| logger_->set_pinned_timestamp(base::TimeTicks::Now()); |
| } |
| |
| AutofillMetrics::UkmTimestampPin::~UkmTimestampPin() { |
| DCHECK(logger_->has_pinned_timestamp()); |
| logger_->set_pinned_timestamp(base::TimeTicks()); |
| } |
| |
| // static |
| void AutofillMetrics::LogFieldParsingPageTranslationStatusMetric(bool metric) { |
| base::UmaHistogramBoolean("Autofill.ParsedFieldTypesWasPageTranslated", |
| metric); |
| } |
| |
| // static |
| void AutofillMetrics::LogFieldParsingTranslatedFormLanguageMetric( |
| std::string_view locale) { |
| base::UmaHistogramSparse( |
| "Autofill.ParsedFieldTypesUsingTranslatedPageLanguage", |
| language::LanguageUsageMetrics::ToLanguageCodeHash(locale)); |
| } |
| |
| // static |
| void AutofillMetrics::LogWebOTPPhoneCollectionMetricStateUkm( |
| ukm::UkmRecorder* recorder, |
| ukm::SourceId source_id, |
| uint32_t phone_collection_metric_state) { |
| // UKM recording is not supported for WebViews. |
| if (!recorder || source_id == ukm::kInvalidSourceId) |
| return; |
| |
| ukm::builders::WebOTPImpact builder(source_id); |
| builder.SetPhoneCollection(phone_collection_metric_state); |
| builder.Record(recorder); |
| } |
| |
| void AutofillMetrics::LogVerificationStatusOfNameTokensOnProfileUsage( |
| const AutofillProfile& profile) { |
| constexpr std::string_view base_histogram_name = |
| "Autofill.NameTokenVerificationStatusAtProfileUsage."; |
| |
| for (const auto& [type, name] : kStructuredNameTypeToNameMap) { |
| // Do not record the status for empty values. |
| if (profile.GetRawInfo(type).empty()) { |
| continue; |
| } |
| |
| VerificationStatus status = profile.GetVerificationStatus(type); |
| base::UmaHistogramEnumeration(base::StrCat({base_histogram_name, name}), |
| status); |
| base::UmaHistogramEnumeration(base::StrCat({base_histogram_name, "Any"}), |
| status); |
| } |
| } |
| |
| void AutofillMetrics::LogVerificationStatusOfAddressTokensOnProfileUsage( |
| const AutofillProfile& profile) { |
| constexpr std::string_view base_histogram_name = |
| "Autofill.AddressTokenVerificationStatusAtProfileUsage."; |
| |
| for (const auto& [type, name] : kStructuredAddressTypeToNameMap) { |
| // Do not record the status for empty values. |
| if (profile.GetRawInfo(type).empty()) { |
| continue; |
| } |
| |
| VerificationStatus status = profile.GetVerificationStatus(type); |
| base::UmaHistogramEnumeration(base::StrCat({base_histogram_name, name}), |
| status); |
| base::UmaHistogramEnumeration(base::StrCat({base_histogram_name, "Any"}), |
| status); |
| } |
| } |
| |
| // static |
| void AutofillMetrics::LogVirtualCardMetadataSynced(bool existing_card) { |
| base::UmaHistogramBoolean("Autofill.VirtualCard.MetadataSynced", |
| existing_card); |
| } |
| |
| // static |
| void AutofillMetrics::LogImageFetchResult(bool succeeded) { |
| base::UmaHistogramBoolean("Autofill.ImageFetcher.Result", succeeded); |
| } |
| |
| // static |
| void AutofillMetrics::LogImageFetcherRequestLatency( |
| const base::TimeDelta& duration) { |
| base::UmaHistogramLongTimes("Autofill.ImageFetcher.RequestLatency", duration); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutocompletePredictionCollisionState( |
| PredictionState prediction_state, |
| AutocompleteState autocomplete_state) { |
| if (prediction_state == PredictionState::kNone && |
| autocomplete_state == AutocompleteState::kNone) { |
| return; |
| } |
| // The buckets are calculated by using the least significant two bits to |
| // encode the `autocomplete_state`, and the next two bits to encode the |
| // `prediction_state`. |
| int bucket = (static_cast<int>(prediction_state) << 2) | |
| static_cast<int>(autocomplete_state); |
| // Without (kNone, kNone), 4*4 - 1 = 15 possible pairs remain. Log the bucket |
| // 0-based, in order to interpret the metric as an enum. |
| DCHECK(1 <= bucket && bucket <= 15); |
| UMA_HISTOGRAM_ENUMERATION("Autofill.Autocomplete.PredictionCollisionState", |
| bucket - 1, 15); |
| } |
| |
| // static |
| void AutofillMetrics::LogAutocompletePredictionCollisionTypes( |
| AutocompleteState autocomplete_state, |
| FieldType server_type, |
| FieldType heuristic_type) { |
| // Convert `autocomplete_state` to a string for the metric's name. |
| std::string autocomplete_suffix; |
| switch (autocomplete_state) { |
| case AutocompleteState::kNone: |
| autocomplete_suffix = "None"; |
| break; |
| case AutocompleteState::kValid: |
| autocomplete_suffix = "Valid"; |
| break; |
| case AutocompleteState::kGarbage: |
| autocomplete_suffix = "Garbage"; |
| break; |
| case AutocompleteState::kOff: |
| autocomplete_suffix = "Off"; |
| break; |
| case AutocompleteState::kPassword: |
| autocomplete_suffix = "Password"; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Log the metric for heuristic and server type. |
| std::string kHistogramName = |
| "Autofill.Autocomplete.PredictionCollisionType2."; |
| if (server_type != NO_SERVER_DATA) { |
| base::UmaHistogramEnumeration( |
| kHistogramName + "Server." + autocomplete_suffix, server_type, |
| FieldType::MAX_VALID_FIELD_TYPE); |
| } |
| base::UmaHistogramEnumeration( |
| kHistogramName + "Heuristics." + autocomplete_suffix, heuristic_type, |
| FieldType::MAX_VALID_FIELD_TYPE); |
| base::UmaHistogramEnumeration( |
| kHistogramName + "ServerOrHeuristics." + autocomplete_suffix, |
| server_type != NO_SERVER_DATA ? server_type : heuristic_type, |
| FieldType::MAX_VALID_FIELD_TYPE); |
| } |
| |
| const std::string PaymentsRpcResultToMetricsSuffix( |
| AutofillClient::PaymentsRpcResult result) { |
| std::string result_suffix; |
| |
| switch (result) { |
| case AutofillClient::PaymentsRpcResult::kSuccess: |
| result_suffix = ".Success"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kPermanentFailure: |
| result_suffix = ".Failure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNetworkError: |
| result_suffix = ".NetworkError"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalTryAgainFailure: |
| case AutofillClient::PaymentsRpcResult::kVcnRetrievalPermanentFailure: |
| result_suffix = ".VcnRetrievalFailure"; |
| break; |
| case AutofillClient::PaymentsRpcResult::kNone: |
| NOTREACHED(); |
| } |
| |
| return result_suffix; |
| } |
| |
| // static |
| void AutofillMetrics::LogNumericQuantityCollidesWithServerPrediction( |
| bool collision) { |
| base::UmaHistogramBoolean( |
| "Autofill.NumericQuantityCollidesWithServerPrediction", collision); |
| } |
| |
| // static |
| void AutofillMetrics:: |
| LogAcceptedFilledFieldWithNumericQuantityHeuristicPrediction( |
| bool accepted) { |
| base::UmaHistogramBoolean( |
| "Autofill.AcceptedFilledFieldWithNumericQuantityHeuristicPrediction", |
| accepted); |
| } |
| |
| // static |
| std::string AutofillMetrics::GetHistogramStringForCardType( |
| absl::variant<AutofillClient::PaymentsRpcCardType, CreditCard::RecordType> |
| card_type) { |
| if (absl::holds_alternative<AutofillClient::PaymentsRpcCardType>(card_type)) { |
| switch (absl::get<AutofillClient::PaymentsRpcCardType>(card_type)) { |
| case AutofillClient::PaymentsRpcCardType::kServerCard: |
| return ".ServerCard"; |
| case AutofillClient::PaymentsRpcCardType::kVirtualCard: |
| return ".VirtualCard"; |
| case AutofillClient::PaymentsRpcCardType::kUnknown: |
| NOTREACHED(); |
| break; |
| } |
| } else if (absl::holds_alternative<CreditCard::RecordType>(card_type)) { |
| switch (absl::get<CreditCard::RecordType>(card_type)) { |
| case CreditCard::RecordType::kFullServerCard: |
| case CreditCard::RecordType::kMaskedServerCard: |
| return ".ServerCard"; |
| case CreditCard::RecordType::kVirtualCard: |
| return ".VirtualCard"; |
| case CreditCard::RecordType::kLocalCard: |
| return ".LocalCard"; |
| } |
| } |
| |
| return ""; |
| } |
| |
| // static |
| void AutofillMetrics::LogDeleteAddressProfileFromPopup() { |
| // Only the "confirmed" bucket can be recorded, as the user cannot cancel this |
| // type of deletion. |
| base::UmaHistogramBoolean("Autofill.ProfileDeleted.Popup", |
| /*delete_confirmed=*/true); |
| base::UmaHistogramBoolean("Autofill.ProfileDeleted.Any", |
| /*delete_confirmed=*/true); |
| } |
| |
| // static |
| void AutofillMetrics::LogDeleteAddressProfileFromKeyboardAccessory() { |
| // Only the "confirmed" bucket is recorded here, as the cancellation can only |
| // be recorded from Java. |
| base::UmaHistogramBoolean("Autofill.ProfileDeleted.KeyboardAccessory", |
| /*delete_confirmed=*/true); |
| base::UmaHistogramBoolean("Autofill.ProfileDeleted.Any", |
| /*delete_confirmed=*/true); |
| } |
| |
| // static |
| uint64_t AutofillMetrics::FormGlobalIdToHash64Bit( |
| const FormGlobalId& form_global_id) { |
| return StrToHash64Bit( |
| base::NumberToString(form_global_id.renderer_id.value()) + |
| form_global_id.frame_token.ToString()); |
| } |
| |
| // static |
| uint64_t AutofillMetrics::FieldGlobalIdToHash64Bit( |
| const FieldGlobalId& field_global_id) { |
| return StrToHash64Bit( |
| base::NumberToString(field_global_id.renderer_id.value()) + |
| field_global_id.frame_token.ToString()); |
| } |
| |
| } // namespace autofill |