| // 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/browser_autofill_manager.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <iterator> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_deref.h" |
| #include "base/check_op.h" |
| #include "base/command_line.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/span.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/hash/hash.h" |
| #include "base/i18n/rtl.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/path_service.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/uuid.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/autofill/core/browser/address_data_manager.h" |
| #include "components/autofill/core/browser/autocomplete_history_manager.h" |
| #include "components/autofill/core/browser/autofill_browser_util.h" |
| #include "components/autofill/core/browser/autofill_client.h" |
| #include "components/autofill/core/browser/autofill_compose_delegate.h" |
| #include "components/autofill/core/browser/autofill_data_util.h" |
| #include "components/autofill/core/browser/autofill_experiments.h" |
| #include "components/autofill/core/browser/autofill_external_delegate.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/autofill_granular_filling_utils.h" |
| #include "components/autofill/core/browser/autofill_optimization_guide.h" |
| #include "components/autofill/core/browser/autofill_plus_address_delegate.h" |
| #include "components/autofill/core/browser/autofill_suggestion_generator.h" |
| #include "components/autofill/core/browser/autofill_trigger_details.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/autofill/core/browser/crowdsourcing/autofill_crowdsourcing_encoding.h" |
| #include "components/autofill/core/browser/crowdsourcing/determine_possible_field_types.h" |
| #include "components/autofill/core/browser/data_model/autofill_data_model.h" |
| #include "components/autofill/core/browser/data_model/autofill_profile.h" |
| #include "components/autofill/core/browser/data_model/autofill_profile_comparator.h" |
| #include "components/autofill/core/browser/data_model/borrowed_transliterator.h" |
| #include "components/autofill/core/browser/data_model/credit_card.h" |
| #include "components/autofill/core/browser/data_model/phone_number.h" |
| #include "components/autofill/core/browser/field_filling_address_util.h" |
| #include "components/autofill/core/browser/field_filling_payments_util.h" |
| #include "components/autofill/core/browser/field_type_utils.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/filling_product.h" |
| #include "components/autofill/core/browser/form_autofill_history.h" |
| #include "components/autofill/core/browser/form_data_importer.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/geo/phone_number_i18n.h" |
| #include "components/autofill/core/browser/logging/log_manager.h" |
| #include "components/autofill/core/browser/metrics/autofill_metrics.h" |
| #include "components/autofill/core/browser/metrics/fallback_autocomplete_unrecognized_metrics.h" |
| #include "components/autofill/core/browser/metrics/field_filling_stats_and_score_metrics.h" |
| #include "components/autofill/core/browser/metrics/form_events/form_event_logger_base.h" |
| #include "components/autofill/core/browser/metrics/form_events/form_events.h" |
| #include "components/autofill/core/browser/metrics/log_event.h" |
| #include "components/autofill/core/browser/metrics/manual_fallback_metrics.h" |
| #include "components/autofill/core/browser/metrics/payments/card_metadata_metrics.h" |
| #include "components/autofill/core/browser/metrics/quality_metrics.h" |
| #include "components/autofill/core/browser/metrics/suggestions_list_metrics.h" |
| #include "components/autofill/core/browser/payments/autofill_offer_manager.h" |
| #include "components/autofill/core/browser/payments/credit_card_access_manager.h" |
| #include "components/autofill/core/browser/payments_data_manager.h" |
| #include "components/autofill/core/browser/personal_data_manager.h" |
| #include "components/autofill/core/browser/profile_token_quality.h" |
| #include "components/autofill/core/browser/randomized_encoder.h" |
| #include "components/autofill/core/browser/suggestions_context.h" |
| #include "components/autofill/core/browser/ui/payments/bubble_show_options.h" |
| #include "components/autofill/core/browser/ui/suggestion.h" |
| #include "components/autofill/core/browser/ui/suggestion_hiding_reason.h" |
| #include "components/autofill/core/browser/ui/suggestion_type.h" |
| #include "components/autofill/core/browser/validation.h" |
| #include "components/autofill/core/common/aliases.h" |
| #include "components/autofill/core/common/autocomplete_parsing_util.h" |
| #include "components/autofill/core/common/autofill_clock.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/autofill_data_validation.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_internals/log_message.h" |
| #include "components/autofill/core/common/autofill_internals/logging_scope.h" |
| #include "components/autofill/core/common/autofill_payments_features.h" |
| #include "components/autofill/core/common/autofill_prefs.h" |
| #include "components/autofill/core/common/autofill_regex_constants.h" |
| #include "components/autofill/core/common/autofill_regexes.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_data_predictions.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #include "components/autofill/core/common/password_form_fill_data.h" |
| #include "components/autofill/core/common/signatures.h" |
| #include "components/autofill/core/common/unique_ids.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/security_interstitials/core/pref_names.h" |
| #include "components/security_state/core/security_state.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/image/image.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace autofill { |
| |
| using base::TimeTicks; |
| using mojom::SubmissionSource; |
| |
| namespace { |
| |
| // The minimum required number of fields for an user perception survey to be |
| // triggered. This makes sure that for example forms that only contain a single |
| // email field do not prompt a survey. Such survey answer would likely taint |
| // our analysis. |
| constexpr size_t kMinNumberAddressFieldsToTriggerAddressUserPerceptionSurvey = |
| 4; |
| |
| // Checks if the user triggered address Autofill through the |
| // Chrome context menu on a field not classified as address. |
| // `type` defines the suggestion type shown. |
| // `autofill_field` is the `AutofillField` from where the user triggered |
| // suggestions. |
| bool IsAddressAutofillManuallyTriggeredOnNonAddressField( |
| SuggestionType type, |
| const AutofillField* autofill_field) { |
| return GetFillingProductFromSuggestionType(type) == |
| FillingProduct::kAddress && |
| (!autofill_field || |
| !IsAddressType(autofill_field->Type().GetStorableType())); |
| } |
| |
| // Checks if the user triggered payments Autofill through the |
| // Chrome context menu on a field not classified as credit card. |
| // `type` defines the suggestion type shown. |
| // `autofill_field` is the `AutofillField` from where the user triggered |
| // suggestions. |
| bool IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField( |
| SuggestionType type, |
| const AutofillField* autofill_field) { |
| return GetFillingProductFromSuggestionType(type) == |
| FillingProduct::kCreditCard && |
| (!autofill_field || |
| GroupTypeOfFieldType(autofill_field->Type().GetStorableType()) != |
| FieldTypeGroup::kCreditCard); |
| } |
| |
| // Converts `filling_stats` to a key-value representation, where the key |
| // is the "stats category" and the value is the number of fields that match |
| // such category. This is used to show users a survey that will measure the |
| // perception of Autofill. |
| std::map<std::string, std::string> FormFillingStatsToSurveyStringData( |
| autofill_metrics::FormGroupFillingStats& filling_stats) { |
| return { |
| {"Accepted fields", base::NumberToString(filling_stats.num_accepted)}, |
| {"Corrected to same type", |
| base::NumberToString(filling_stats.num_corrected_to_same_type)}, |
| {"Corrected to a different type", |
| base::NumberToString(filling_stats.num_corrected_to_different_type)}, |
| {"Corrected to an unknown type", |
| base::NumberToString(filling_stats.num_corrected_to_unknown_type)}, |
| {"Corrected to empty", |
| base::NumberToString(filling_stats.num_corrected_to_empty)}, |
| {"Manually filled to same type", |
| base::NumberToString(filling_stats.num_manually_filled_to_same_type)}, |
| {"Manually filled to a different type", |
| base::NumberToString( |
| filling_stats.num_manually_filled_to_different_type)}, |
| {"Manually filled to an unknown type", |
| base::NumberToString(filling_stats.num_manually_filled_to_unknown_type)}, |
| {"Total corrected", base::NumberToString(filling_stats.TotalCorrected())}, |
| {"Total filled", base::NumberToString(filling_stats.TotalFilled())}, |
| {"Total unfilled", base::NumberToString(filling_stats.TotalUnfilled())}, |
| {"Total manually filled", |
| base::NumberToString(filling_stats.TotalManuallyFilled())}, |
| {"Total number of fields", base::NumberToString(filling_stats.Total())}}; |
| } |
| |
| void LogDeveloperEngagementUkm(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| const FormStructure& form_structure) { |
| if (form_structure.developer_engagement_metrics()) { |
| AutofillMetrics::LogDeveloperEngagementUkm( |
| ukm_recorder, source_id, form_structure.main_frame_origin().GetURL(), |
| form_structure.IsCompleteCreditCardForm(), |
| form_structure.GetFormTypes(), |
| form_structure.developer_engagement_metrics(), |
| form_structure.form_signature()); |
| } |
| } |
| |
| ValuePatternsMetric GetValuePattern(const std::u16string& value) { |
| if (IsUPIVirtualPaymentAddress(value)) { |
| return ValuePatternsMetric::kUpiVpa; |
| } |
| if (IsInternationalBankAccountNumber(value)) { |
| return ValuePatternsMetric::kIban; |
| } |
| return ValuePatternsMetric::kNoPatternFound; |
| } |
| |
| void LogValuePatternsMetric(const FormData& form) { |
| for (const FormFieldData& field : form.fields) { |
| if (!field.IsFocusable()) { |
| continue; |
| } |
| std::u16string value; |
| base::TrimWhitespace(field.value(), base::TRIM_ALL, &value); |
| if (value.empty()) { |
| continue; |
| } |
| base::UmaHistogramEnumeration("Autofill.SubmittedValuePatterns", |
| GetValuePattern(value)); |
| } |
| } |
| |
| bool IsSingleFieldFormFillerFillingProduct(FillingProduct filling_product) { |
| switch (filling_product) { |
| case FillingProduct::kAutocomplete: |
| case FillingProduct::kIban: |
| case FillingProduct::kMerchantPromoCode: |
| return true; |
| case FillingProduct::kPlusAddresses: |
| case FillingProduct::kCompose: |
| case FillingProduct::kPassword: |
| case FillingProduct::kCreditCard: |
| case FillingProduct::kAddress: |
| case FillingProduct::kNone: |
| return false; |
| } |
| } |
| |
| // Is `suggestions` contains Autocomplete suggestions, then this function logs |
| // a metric to record whether Autocomplete would have been suppressed due to |
| // a plus address suggestion. |
| // It only logs these metrics for users that are signed in and tabs that are not |
| // in incognito mode. |
| // TODO(b/327328460): Clean up once the metric is has been evaluated. |
| void MaybeLogAutocompleteSuppressionByPlusAddresses( |
| AutofillClient& client, |
| base::span<const Suggestion> suggestions, |
| FieldTypeGroup focused_field_type_group) { |
| if (client.IsOffTheRecord()) { |
| return; |
| } |
| |
| // Do not log metrics for users that are not signed in. |
| if (signin::IdentityManager* identity_manager = client.GetIdentityManager(); |
| !identity_manager || |
| identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) |
| .IsEmpty()) { |
| return; |
| } |
| |
| if (suggestions.empty() || |
| GetFillingProductFromSuggestionType(suggestions[0].type) != |
| FillingProduct::kAutocomplete) { |
| return; |
| } |
| |
| // If the focused field is not classified as an email address, plus addresses |
| // would never be shown. |
| using enum AutocompleteSuppressionByPlusAddress; |
| if (focused_field_type_group != FieldTypeGroup::kEmail) { |
| base::UmaHistogramEnumeration(kAutocompleteSuppressionByPlusAddressUma, |
| kNotSuppressed); |
| return; |
| } |
| const bool has_email = |
| base::ranges::any_of(suggestions, [](const Suggestion& suggestion) { |
| return IsValidEmailAddress(suggestion.main_text.value); |
| }); |
| base::UmaHistogramEnumeration( |
| kAutocompleteSuppressionByPlusAddressUma, |
| has_email ? kSuppressedWithEmailResults : kSuppressedWithoutEmailResults); |
| } |
| |
| // Emits a metric that measures how long it took to show a single field form |
| // filling suggestion. |
| // TODO(b/324553809): Remove once we know the average time for a suggestion to |
| // show. |
| void LogTimeDelayForSingleFieldFormFill( |
| base::span<const Suggestion> suggestions, |
| base::TimeDelta delay) { |
| if (suggestions.empty()) { |
| return; |
| } |
| const FillingProduct filling_product = |
| GetFillingProductFromSuggestionType(suggestions[0].type); |
| CHECK(IsSingleFieldFormFillerFillingProduct(filling_product)); |
| base::UmaHistogramTimes( |
| base::StrCat({"Autofill.Popup.SingleFieldFormFillerDelay.", |
| FillingProductToString(filling_product)}), |
| delay); |
| } |
| |
| FillDataType GetEventTypeFromSingleFieldSuggestionSuggestionType( |
| SuggestionType type) { |
| switch (type) { |
| case SuggestionType::kAutocompleteEntry: |
| return FillDataType::kSingleFieldFormFillerAutocomplete; |
| case SuggestionType::kMerchantPromoCodeEntry: |
| return FillDataType::kSingleFieldFormFillerPromoCode; |
| case SuggestionType::kIbanEntry: |
| return FillDataType::kSingleFieldFormFillerIban; |
| case SuggestionType::kAccountStoragePasswordEntry: |
| case SuggestionType::kAddressEntry: |
| case SuggestionType::kAllSavedPasswordsEntry: |
| case SuggestionType::kAutofillOptions: |
| case SuggestionType::kClearForm: |
| case SuggestionType::kCompose: |
| case SuggestionType::kComposeDisable: |
| case SuggestionType::kComposeGoToSettings: |
| case SuggestionType::kComposeNeverShowOnThisSiteAgain: |
| case SuggestionType::kComposeSavedStateNotification: |
| case SuggestionType::kCreateNewPlusAddress: |
| case SuggestionType::kCreditCardEntry: |
| case SuggestionType::kDatalistEntry: |
| case SuggestionType::kDeleteAddressProfile: |
| case SuggestionType::kEditAddressProfile: |
| case SuggestionType::kAddressFieldByFieldFilling: |
| case SuggestionType::kCreditCardFieldByFieldFilling: |
| case SuggestionType::kFillEverythingFromAddressProfile: |
| case SuggestionType::kFillExistingPlusAddress: |
| case SuggestionType::kFillFullAddress: |
| case SuggestionType::kFillFullName: |
| case SuggestionType::kFillFullPhoneNumber: |
| case SuggestionType::kFillFullEmail: |
| case SuggestionType::kGeneratePasswordEntry: |
| case SuggestionType::kInsecureContextPaymentDisabledMessage: |
| case SuggestionType::kMixedFormMessage: |
| case SuggestionType::kPasswordAccountStorageEmpty: |
| case SuggestionType::kPasswordAccountStorageOptIn: |
| case SuggestionType::kPasswordAccountStorageOptInAndGenerate: |
| case SuggestionType::kPasswordAccountStorageReSignin: |
| case SuggestionType::kPasswordEntry: |
| case SuggestionType::kPasswordFieldByFieldFilling: |
| case SuggestionType::kFillPassword: |
| case SuggestionType::kViewPasswordDetails: |
| case SuggestionType::kScanCreditCard: |
| case SuggestionType::kSeePromoCodeDetails: |
| case SuggestionType::kTitle: |
| case SuggestionType::kSeparator: |
| case SuggestionType::kShowAccountCards: |
| case SuggestionType::kVirtualCreditCardEntry: |
| case SuggestionType::kWebauthnCredential: |
| case SuggestionType::kWebauthnSignInWithAnotherDevice: |
| case SuggestionType::kDevtoolsTestAddresses: |
| case SuggestionType::kDevtoolsTestAddressEntry: |
| NOTREACHED(); |
| } |
| NOTREACHED(); |
| return FillDataType::kUndefined; |
| } |
| |
| void LogLanguageMetrics(const translate::LanguageState* language_state) { |
| if (language_state) { |
| AutofillMetrics::LogFieldParsingTranslatedFormLanguageMetric( |
| language_state->current_language()); |
| AutofillMetrics::LogFieldParsingPageTranslationStatusMetric( |
| language_state->IsPageTranslated()); |
| } |
| } |
| |
| AutofillMetrics::AutocompleteState AutocompleteStateForSubmittedField( |
| const AutofillField& field) { |
| // An unparsable autocomplete attribute is treated like kNone. |
| auto autocomplete_state = AutofillMetrics::AutocompleteState::kNone; |
| // autocomplete=on is ignored as well. But for the purposes of metrics we care |
| // about cases where the developer tries to disable autocomplete. |
| if (field.autocomplete_attribute() != "on" && |
| ShouldIgnoreAutocompleteAttribute(field.autocomplete_attribute())) { |
| autocomplete_state = AutofillMetrics::AutocompleteState::kOff; |
| } else if (field.parsed_autocomplete()) { |
| autocomplete_state = |
| field.parsed_autocomplete()->field_type != HtmlFieldType::kUnrecognized |
| ? AutofillMetrics::AutocompleteState::kValid |
| : AutofillMetrics::AutocompleteState::kGarbage; |
| |
| if (field.autocomplete_attribute() == "new-password" || |
| field.autocomplete_attribute() == "current-password") { |
| autocomplete_state = AutofillMetrics::AutocompleteState::kPassword; |
| } |
| } |
| |
| return autocomplete_state; |
| } |
| |
| void LogAutocompletePredictionCollisionTypeMetrics( |
| const FormStructure& form_structure) { |
| for (size_t i = 0; i < form_structure.field_count(); i++) { |
| const AutofillField* field = form_structure.field(i); |
| auto heuristic_type = field->heuristic_type(); |
| auto server_type = field->server_type(); |
| |
| auto prediction_state = AutofillMetrics::PredictionState::kNone; |
| if (IsFillableFieldType(heuristic_type)) { |
| prediction_state = IsFillableFieldType(server_type) |
| ? AutofillMetrics::PredictionState::kBoth |
| : AutofillMetrics::PredictionState::kHeuristics; |
| } else if (IsFillableFieldType(server_type)) { |
| prediction_state = AutofillMetrics::PredictionState::kServer; |
| } |
| |
| auto autocomplete_state = AutocompleteStateForSubmittedField(*field); |
| AutofillMetrics::LogAutocompletePredictionCollisionState( |
| prediction_state, autocomplete_state); |
| AutofillMetrics::LogAutocompletePredictionCollisionTypes( |
| autocomplete_state, server_type, heuristic_type); |
| } |
| } |
| |
| const char* SubmissionSourceToString(SubmissionSource source) { |
| switch (source) { |
| case SubmissionSource::NONE: |
| return "NONE"; |
| case SubmissionSource::SAME_DOCUMENT_NAVIGATION: |
| return "SAME_DOCUMENT_NAVIGATION"; |
| case SubmissionSource::XHR_SUCCEEDED: |
| return "XHR_SUCCEEDED"; |
| case SubmissionSource::FRAME_DETACHED: |
| return "FRAME_DETACHED"; |
| case SubmissionSource::PROBABLY_FORM_SUBMITTED: |
| return "PROBABLY_FORM_SUBMITTED"; |
| case SubmissionSource::FORM_SUBMISSION: |
| return "FORM_SUBMISSION"; |
| case SubmissionSource::DOM_MUTATION_AFTER_AUTOFILL: |
| return "DOM_MUTATION_AFTER_AUTOFILL"; |
| } |
| return "Unknown"; |
| } |
| |
| // Returns true if autocomplete=unrecognized (address) fields should receive |
| // suggestions. On desktop, suggestion can only be triggered for them through |
| // manual fallbacks. On mobile, suggestions are always shown. |
| bool ShouldShowSuggestionsForAutocompleteUnrecognizedFields( |
| AutofillSuggestionTriggerSource trigger_source) { |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) |
| return true; |
| #else |
| return IsAutofillManuallyTriggered(trigger_source); |
| #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) |
| } |
| |
| // Checks if the `credit_card` needs to be fetched in order to complete the |
| // current filling flow. |
| // TODO(crbug.com/40227496): Only use parsed data. |
| bool ShouldFetchCreditCard(const FormData& form, |
| const FormFieldData& field, |
| const FormStructure& form_structure, |
| const AutofillField& autofill_field, |
| const CreditCard& credit_card) { |
| if (WillFillCreditCardNumber(form.fields, form_structure.fields(), |
| autofill_field)) { |
| return true; |
| } |
| // This happens for web sites which cache all credit card details except for |
| // the cvc, which is different every time the virtual credit card is being |
| // used. |
| return credit_card.record_type() == CreditCard::RecordType::kVirtualCard && |
| autofill_field.Type().GetStorableType() == |
| CREDIT_CARD_STANDALONE_VERIFICATION_CODE; |
| } |
| |
| // To reduce traffic, only a random sample of browser sessions upload UKM data. |
| // This function returns whether we should record autofill UKM events for the |
| // current session. |
| bool ShouldRecordUkm() { |
| // We only need to generate this random number once while the current process |
| // is running. |
| static const int random_value_per_session = base::RandInt(0, 99); |
| |
| const int kSamplingRate = |
| base::FeatureList::IsEnabled( |
| features::kAutofillLogUKMEventsWithSamplingOnSession) |
| ? features::kAutofillLogUKMEventsWithSamplingOnSessionRate.Get() |
| : 0; |
| |
| return random_value_per_session < kSamplingRate; |
| } |
| |
| // Returns true if the source is only relevant for Compose. |
| bool IsTriggerSourceOnlyRelevantForCompose( |
| AutofillSuggestionTriggerSource source) { |
| switch (source) { |
| case AutofillSuggestionTriggerSource::kTextareaFocusedWithoutClick: |
| case AutofillSuggestionTriggerSource::kComposeDialogLostFocus: |
| return true; |
| case AutofillSuggestionTriggerSource::kUnspecified: |
| case AutofillSuggestionTriggerSource::kFormControlElementClicked: |
| case AutofillSuggestionTriggerSource::kContentEditableClicked: |
| case AutofillSuggestionTriggerSource::kTextFieldDidChange: |
| case AutofillSuggestionTriggerSource::kTextFieldDidReceiveKeyDown: |
| case AutofillSuggestionTriggerSource::kOpenTextDataListChooser: |
| case AutofillSuggestionTriggerSource::kShowCardsFromAccount: |
| case AutofillSuggestionTriggerSource::kPasswordManager: |
| case AutofillSuggestionTriggerSource::kiOS: |
| case AutofillSuggestionTriggerSource::kManualFallbackAddress: |
| case AutofillSuggestionTriggerSource::kManualFallbackPayments: |
| case AutofillSuggestionTriggerSource::kManualFallbackPasswords: |
| case AutofillSuggestionTriggerSource::kManualFallbackPlusAddresses: |
| case AutofillSuggestionTriggerSource:: |
| kShowPromptAfterDialogClosedNonManualFallback: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| BrowserAutofillManager::BrowserAutofillManager(AutofillDriver* driver, |
| const std::string& app_locale) |
| : AutofillManager(driver), |
| external_delegate_(std::make_unique<AutofillExternalDelegate>(this)), |
| app_locale_(app_locale), |
| suggestion_generator_( |
| std::make_unique<AutofillSuggestionGenerator>(unsafe_client())), |
| form_filler_( |
| std::make_unique<FormFiller>(*this, log_manager(), app_locale)) { |
| address_form_event_logger_ = |
| std::make_unique<autofill_metrics::AddressFormEventLogger>( |
| driver->IsInAnyMainFrame(), form_interactions_ukm_logger(), |
| &unsafe_client()); |
| credit_card_form_event_logger_ = |
| std::make_unique<autofill_metrics::CreditCardFormEventLogger>( |
| driver->IsInAnyMainFrame(), form_interactions_ukm_logger(), |
| unsafe_client().GetPersonalDataManager(), &unsafe_client()); |
| autocomplete_unrecognized_fallback_logger_ = std::make_unique< |
| autofill_metrics::AutocompleteUnrecognizedFallbackEventLogger>(); |
| manual_fallback_logger_ = |
| std::make_unique<autofill_metrics::ManualFallbackEventLogger>(); |
| |
| credit_card_access_manager_ = std::make_unique<CreditCardAccessManager>( |
| driver, &unsafe_client(), unsafe_client().GetPersonalDataManager(), |
| credit_card_form_event_logger_.get()); |
| } |
| |
| BrowserAutofillManager::~BrowserAutofillManager() { |
| if (has_parsed_forms_) { |
| base::UmaHistogramBoolean( |
| "Autofill.WebOTP.PhoneNumberCollection.ParseResult", |
| has_observed_phone_number_field_); |
| base::UmaHistogramBoolean("Autofill.WebOTP.OneTimeCode.FromAutocomplete", |
| has_observed_one_time_code_field_); |
| } |
| |
| // Process log events and record into UKM when the form is destroyed or |
| // removed. |
| for (const auto& [form_id, form_structure] : form_structures()) { |
| ProcessFieldLogEventsInForm(*form_structure); |
| } |
| |
| single_field_form_fill_router_->CancelPendingQueries(); |
| |
| address_form_event_logger_->OnDestroyed(); |
| credit_card_form_event_logger_->OnDestroyed(); |
| |
| // We don't flush the `queued_vote_uploads_` here because that would trigger |
| // network requests in the AutofillCrowdsourcingManager, which are managed |
| // with by SimpleURLLoaders owned by the AutofillCrowdsourcingManager. |
| // Destroying the BrowserAutofillManager destroys the |
| // AutofillCrowdsourcingManager and its SimpleURLLoaders, which would |
| // immediately cancel the uploads. As a consequence of this, votes are lost if |
| // the user generates blur votes and closes the tab before the votes are sent |
| // (due to a navigation). |
| } |
| |
| base::WeakPtr<AutofillManager> BrowserAutofillManager::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| CreditCardAccessManager& BrowserAutofillManager::GetCreditCardAccessManager() { |
| return *credit_card_access_manager_; |
| } |
| |
| const CreditCardAccessManager& |
| BrowserAutofillManager::GetCreditCardAccessManager() const { |
| return const_cast<BrowserAutofillManager*>(this) |
| ->GetCreditCardAccessManager(); |
| } |
| |
| bool BrowserAutofillManager::ShouldShowScanCreditCard( |
| const FormData& form, |
| const FormFieldData& field) const { |
| if (!client().HasCreditCardScanFeature() || |
| !IsAutofillPaymentMethodsEnabled()) { |
| return false; |
| } |
| |
| AutofillField* autofill_field = GetAutofillField(form, field); |
| if (!autofill_field) { |
| return false; |
| } |
| |
| bool is_card_number_field = |
| autofill_field->Type().GetStorableType() == CREDIT_CARD_NUMBER && |
| base::ContainsOnlyChars(CreditCard::StripSeparators(field.value()), |
| u"0123456789"); |
| |
| if (!is_card_number_field) { |
| return false; |
| } |
| |
| if (IsFormNonSecure(form)) { |
| return false; |
| } |
| |
| static const int kShowScanCreditCardMaxValueLength = 6; |
| return field.value().size() <= kShowScanCreditCardMaxValueLength; |
| } |
| |
| bool BrowserAutofillManager::ShouldShowCardsFromAccountOption( |
| const FormData& form, |
| const FormFieldData& field, |
| AutofillSuggestionTriggerSource trigger_source) const { |
| // If `trigger_source` is equal to `kShowCardsFromAccount`, that means that |
| // the user accepted "Show cards from account" suggestions and it should not |
| // be shown again. |
| if (trigger_source == |
| AutofillSuggestionTriggerSource::kShowCardsFromAccount) { |
| return false; |
| } |
| // Check whether we are dealing with a credit card field. |
| AutofillField* autofill_field = GetAutofillField(form, field); |
| if (!autofill_field || |
| autofill_field->Type().group() != FieldTypeGroup::kCreditCard || |
| // Exclude CVC and card type fields, because these will not have |
| // suggestions available after the user opts in. |
| autofill_field->Type().GetStorableType() == |
| CREDIT_CARD_VERIFICATION_CODE || |
| autofill_field->Type().GetStorableType() == CREDIT_CARD_TYPE) { |
| return false; |
| } |
| |
| if (IsFormNonSecure(form)) { |
| return false; |
| } |
| |
| return client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .ShouldShowCardsFromAccountOption(); |
| } |
| |
| void BrowserAutofillManager::OnUserAcceptedCardsFromAccountOption() { |
| client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .OnUserAcceptedCardsFromAccountOption(); |
| } |
| |
| void BrowserAutofillManager::RefetchCardsAndUpdatePopup( |
| const FormData& form, |
| const FormFieldData& field_data, |
| const gfx::RectF& element_bounds) { |
| external_delegate_->OnQuery( |
| form, field_data, element_bounds, |
| AutofillSuggestionTriggerSource::kShowCardsFromAccount); |
| AutofillField* autofill_field = GetAutofillField(form, field_data); |
| FieldType field_type = autofill_field |
| ? autofill_field->Type().GetStorableType() |
| : CREDIT_CARD_NUMBER; |
| DCHECK_EQ(FieldTypeGroup::kCreditCard, GroupTypeOfFieldType(field_type)); |
| |
| auto cards = GetCreditCardSuggestions( |
| form, field_data, field_type, |
| AutofillSuggestionTriggerSource::kShowCardsFromAccount); |
| DCHECK(!cards.empty()); |
| external_delegate_->OnSuggestionsReturned(field_data.global_id(), cards); |
| } |
| |
| bool BrowserAutofillManager::ShouldParseForms() { |
| bool autofill_enabled = IsAutofillEnabled(); |
| // If autofill is disabled but the password manager is enabled, we still |
| // need to parse the forms and query the server as the password manager |
| // depends on server classifications. |
| bool password_manager_enabled = client().IsPasswordManagerEnabled(); |
| signin_state_for_metrics_ = |
| client().GetPersonalDataManager() |
| ? client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetPaymentsSigninStateForMetrics() |
| : AutofillMetrics::PaymentsSigninState::kUnknown; |
| if (!has_logged_autofill_enabled_) { |
| AutofillMetrics::LogIsAutofillEnabledAtPageLoad(autofill_enabled, |
| signin_state_for_metrics_); |
| AutofillMetrics::LogIsAutofillProfileEnabledAtPageLoad( |
| IsAutofillProfileEnabled(), signin_state_for_metrics_); |
| AutofillMetrics::LogIsAutofillCreditCardEnabledAtPageLoad( |
| IsAutofillPaymentMethodsEnabled(), signin_state_for_metrics_); |
| has_logged_autofill_enabled_ = true; |
| } |
| |
| // Enable the parsing also for the password manager, so that we fetch server |
| // classifications if the password manager is enabled but autofill is |
| // disabled. |
| return autofill_enabled || password_manager_enabled; |
| } |
| |
| void BrowserAutofillManager::OnFormSubmittedImpl(const FormData& form, |
| bool known_success, |
| SubmissionSource source) { |
| base::UmaHistogramEnumeration("Autofill.FormSubmission.PerProfileType", |
| client().GetProfileType()); |
| LOG_AF(log_manager()) << LoggingScope::kSubmission |
| << LogMessage::kFormSubmissionDetected << Br{} |
| << "known_success: " << known_success << Br{} |
| << "source: " << SubmissionSourceToString(source) |
| << Br{} << form; |
| |
| // Always upload page language metrics. |
| LogLanguageMetrics(client().GetLanguageState()); |
| |
| // Always let the value patterns metric upload data. |
| LogValuePatternsMetric(form); |
| |
| // Note that `ValidateSubmittedForm()` returns nullptr in incognito mode. |
| // Consequently, in incognito mode Autofill doesn't: |
| // - Import |
| // - Vote |
| // - Collect any key metrics (since they are conditioned form submission - see |
| // `FormEventLoggerBase::OnWillSubmitForm()`) |
| // - Collect profile token quality observations |
| std::unique_ptr<FormStructure> submitted_form = ValidateSubmittedForm(form); |
| CHECK(!client().IsOffTheRecord() || !submitted_form); |
| if (!submitted_form) { |
| // We always give Autocomplete a chance to save the data. |
| // TODO(crbug.com/40276862): Verify frequency of plus address (or the other |
| // type(s) checked for below, for that matter) slipping through in this code |
| // path. |
| single_field_form_fill_router_->OnWillSubmitForm( |
| form, submitted_form.get(), client().IsAutocompleteEnabled()); |
| return; |
| } |
| |
| form_submitted_timestamp_ = base::TimeTicks::Now(); |
| |
| // Log metrics about the autocomplete attribute usage in the submitted form. |
| LogAutocompletePredictionCollisionTypeMetrics(*submitted_form); |
| |
| // Log interaction time metrics for the ablation study. |
| if (!initial_interaction_timestamp_.is_null()) { |
| base::TimeDelta time_from_interaction_to_submission = |
| base::TimeTicks::Now() - initial_interaction_timestamp_; |
| DenseSet<FormType> form_types = submitted_form->GetFormTypes(); |
| bool card_form = base::Contains(form_types, FormType::kCreditCardForm); |
| bool address_form = base::Contains(form_types, FormType::kAddressForm); |
| if (card_form) { |
| credit_card_form_event_logger_->SetTimeFromInteractionToSubmission( |
| time_from_interaction_to_submission); |
| } |
| if (address_form) { |
| address_form_event_logger_->SetTimeFromInteractionToSubmission( |
| time_from_interaction_to_submission); |
| } |
| } |
| |
| AutofillPlusAddressDelegate* plus_address_delegate = |
| client().GetPlusAddressDelegate(); |
| |
| FormData form_for_autocomplete = submitted_form->ToFormData(); |
| for (size_t i = 0; i < submitted_form->field_count(); ++i) { |
| AutofillField* autofill_field = submitted_form->field(i); |
| if (autofill_field->Type().GetStorableType() == |
| CREDIT_CARD_VERIFICATION_CODE) { |
| // However, if Autofill has recognized a field as CVC, that shouldn't be |
| // saved. |
| form_for_autocomplete.fields[i].set_should_autocomplete(false); |
| } |
| if (plus_address_delegate && |
| plus_address_delegate->IsPlusAddress( |
| base::UTF16ToUTF8(autofill_field->value()))) { |
| // Similarly to CVC, any plus addresses needn't be saved to autocomplete. |
| // Note that the feature is experimental, and `plus_address_delegate` |
| // will be null if the feature is not enabled (it's disabled by default). |
| form_for_autocomplete.fields[i].set_should_autocomplete(false); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| if (autofill_field->autocomplete_attribute() == "off" && |
| autofill_field->did_trigger_suggestions() && |
| !autofill_field->is_autofilled() && |
| !autofill_field->previously_autofilled() && |
| base::FeatureList::IsEnabled( |
| features::kAutofillSuggestionNStrikeModel)) { |
| // This means that the user triggered suggestions and ignored them. In |
| // that case we record a strike for this specific field. Multiple strikes |
| // will lead to automatic address suggestions to be suppressed. |
| // Currently, this is only done for autocomplete=off fields. |
| client() |
| .GetPersonalDataManager() |
| ->address_data_manager() |
| .AddStrikeToBlockAddressSuggestions( |
| submitted_form->form_signature(), |
| autofill_field->GetFieldSignature(), |
| submitted_form->source_url()); |
| } |
| #endif |
| } |
| |
| single_field_form_fill_router_->OnWillSubmitForm( |
| form_for_autocomplete, submitted_form.get(), |
| client().IsAutocompleteEnabled()); |
| |
| if (IsAutofillProfileEnabled()) { |
| address_form_event_logger_->OnWillSubmitForm(signin_state_for_metrics_, |
| *submitted_form); |
| } |
| if (IsAutofillPaymentMethodsEnabled()) { |
| credit_card_form_event_logger_->OnWillSubmitForm(signin_state_for_metrics_, |
| *submitted_form); |
| } |
| |
| submitted_form->set_submission_source(source); |
| |
| // Update Personal Data with the form's submitted data. |
| // Also triggers offering local/upload credit card save, if applicable. |
| if (submitted_form->IsAutofillable()) { |
| FormDataImporter* form_data_importer = client().GetFormDataImporter(); |
| form_data_importer->ImportAndProcessFormData( |
| *submitted_form, IsAutofillProfileEnabled(), |
| IsAutofillPaymentMethodsEnabled()); |
| // Associate the form signatures of recently submitted address/credit card |
| // forms to `submitted_form`, if it is an address/credit card form itself. |
| // This information is attached to the vote. |
| if (base::FeatureList::IsEnabled(features::kAutofillAssociateForms)) { |
| if (std::optional<FormStructure::FormAssociations> associations = |
| form_data_importer->GetFormAssociations( |
| submitted_form->form_signature())) { |
| submitted_form->set_form_associations(*associations); |
| } |
| } |
| } |
| |
| MaybeStartVoteUploadProcess(std::move(submitted_form), |
| /*observed_submission=*/true); |
| |
| // TODO(crbug.com/41365645): Add FormStructure::Clone() method. |
| // Create another FormStructure instance. |
| submitted_form = ValidateSubmittedForm(form); |
| DCHECK(submitted_form); |
| if (!submitted_form) { |
| return; |
| } |
| |
| submitted_form->set_submission_source(source); |
| |
| if (IsAutofillProfileEnabled()) { |
| address_form_event_logger_->OnFormSubmitted(signin_state_for_metrics_, |
| *submitted_form); |
| } |
| if (IsAutofillPaymentMethodsEnabled()) { |
| credit_card_form_event_logger_->OnFormSubmitted(signin_state_for_metrics_, |
| *submitted_form); |
| if (touch_to_fill_delegate_) { |
| touch_to_fill_delegate_->LogMetricsAfterSubmission(*submitted_form); |
| } |
| } |
| |
| ProfileTokenQuality::SaveObservationsForFilledFormForAllSubmittedProfiles( |
| *submitted_form, form, *client().GetPersonalDataManager()); |
| } |
| |
| bool BrowserAutofillManager::MaybeStartVoteUploadProcess( |
| std::unique_ptr<FormStructure> form_structure, |
| bool observed_submission) { |
| // It is possible for |client().GetPersonalDataManager()| to be null, such as |
| // when used in the Android webview. |
| if (!client().GetPersonalDataManager()) { |
| return false; |
| } |
| |
| // Only upload server statistics and UMA metrics if at least some local data |
| // is available to use as a baseline. |
| std::vector<AutofillProfile*> profiles = |
| client().GetPersonalDataManager()->address_data_manager().GetProfiles(); |
| if (observed_submission && form_structure->IsAutofillable()) { |
| AutofillMetrics::LogNumberOfProfilesAtAutofillableFormSubmission( |
| client() |
| .GetPersonalDataManager() |
| ->address_data_manager() |
| .GetProfiles() |
| .size()); |
| } |
| |
| const std::vector<CreditCard*>& credit_cards = client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetCreditCards(); |
| |
| if (profiles.empty() && credit_cards.empty()) { |
| return false; |
| } |
| |
| if (form_structure->field_count() * (profiles.size() + credit_cards.size()) >= |
| kMaxTypeMatchingCalls) { |
| return false; |
| } |
| |
| // Copy the profile and credit card data, so that it can be accessed on a |
| // separate thread. |
| std::vector<AutofillProfile> copied_profiles; |
| copied_profiles.reserve(profiles.size()); |
| for (const AutofillProfile* profile : profiles) { |
| copied_profiles.push_back(*profile); |
| } |
| |
| std::vector<CreditCard> copied_credit_cards; |
| copied_credit_cards.reserve(credit_cards.size()); |
| for (const CreditCard* card : credit_cards) { |
| copied_credit_cards.push_back(*card); |
| } |
| |
| // Annotate the form with the source language of the page. |
| form_structure->set_current_page_language(GetCurrentPageLanguage()); |
| |
| // Attach the Randomized Encoder. |
| form_structure->set_randomized_encoder( |
| RandomizedEncoder::Create(client().GetPrefs())); |
| |
| // Determine |ADDRESS_HOME_STATE| as a possible types for the fields in the |
| // |form_structure| with the help of |AlternativeStateNameMap|. |
| // |AlternativeStateNameMap| can only be accessed on the main UI thread. |
| PreProcessStateMatchingTypes(copied_profiles, form_structure.get()); |
| |
| // Ownership of |form_structure| is passed to the |
| // BrowserAutofillManager::OnSubmissionFieldTypesDetermined() call. |
| FormStructure* raw_form = form_structure.get(); |
| |
| base::OnceClosure call_after_determine_field_types = |
| base::BindOnce(&BrowserAutofillManager::OnSubmissionFieldTypesDetermined, |
| weak_ptr_factory_.GetWeakPtr(), std::move(form_structure), |
| initial_interaction_timestamp_, base::TimeTicks::Now(), |
| observed_submission, client().GetUkmSourceId()); |
| |
| // If the form was not submitted (e.g. the user just removed the focus from |
| // the form), it's possible that later modifications lead to more accurate |
| // votes. In this case we just want to cache the upload and have a chance to |
| // override it with better data. |
| if (!observed_submission) { |
| call_after_determine_field_types = base::BindOnce( |
| &BrowserAutofillManager::StoreUploadVotesAndLogQualityCallback, |
| weak_ptr_factory_.GetWeakPtr(), raw_form->form_signature(), |
| std::move(call_after_determine_field_types)); |
| } |
| |
| if (!vote_upload_task_runner_) { |
| // If the priority is BEST_EFFORT, the task can be preempted, which is |
| // thought to cause high memory usage (as memory is retained by the task |
| // while it is preempted), https://crbug.com/974249 |
| vote_upload_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE}); |
| } |
| |
| vote_upload_task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&DeterminePossibleFieldTypesForUpload, |
| std::move(copied_profiles), std::move(copied_credit_cards), |
| last_unlocked_credit_card_cvc_, app_locale_, raw_form), |
| std::move(call_after_determine_field_types)); |
| |
| return true; |
| } |
| |
| void BrowserAutofillManager::UpdatePendingForm(const FormData& form) { |
| // Process the current pending form if different than supplied |form|. |
| if (pending_form_data_ && !pending_form_data_->SameFormAs(form)) { |
| ProcessPendingFormForUpload(); |
| } |
| // A new pending form is assigned. |
| pending_form_data_ = std::make_unique<FormData>(form); |
| } |
| |
| void BrowserAutofillManager::ProcessPendingFormForUpload() { |
| if (!pending_form_data_) { |
| return; |
| } |
| |
| // We get the FormStructure corresponding to |pending_form_data_|, used in the |
| // upload process. |pending_form_data_| is reset. |
| std::unique_ptr<FormStructure> upload_form = |
| ValidateSubmittedForm(*pending_form_data_); |
| pending_form_data_.reset(); |
| if (!upload_form) { |
| return; |
| } |
| |
| MaybeStartVoteUploadProcess(std::move(upload_form), |
| /*observed_submission=*/false); |
| } |
| |
| void BrowserAutofillManager::OnTextFieldDidChangeImpl( |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box, |
| const TimeTicks timestamp) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| |
| // Log events when user edits the field. |
| // If the user types into the same field multiple times, repeated |
| // TypingFieldLogEvents are coalesced. |
| autofill_field->AppendLogEventIfNotRepeated(TypingFieldLogEvent{ |
| .has_value_after_typing = ToOptionalBoolean(!field.value().empty())}); |
| |
| UpdatePendingForm(form); |
| |
| if (!user_did_type_ || autofill_field->is_autofilled()) { |
| user_did_type_ = true; |
| form_interactions_ukm_logger()->LogTextFieldDidChange(*form_structure, |
| *autofill_field); |
| } |
| |
| auto* logger = GetEventFormLogger(*autofill_field); |
| if (!autofill_field->is_autofilled()) { |
| if (logger) { |
| logger->OnTypedIntoNonFilledField(); |
| } |
| } |
| |
| if (autofill_field->is_autofilled()) { |
| autofill_field->set_is_autofilled(false); |
| autofill_field->set_previously_autofilled(true); |
| if (logger) { |
| logger->OnEditedAutofilledField(); |
| } |
| } |
| |
| UpdateInitialInteractionTimestamp(timestamp); |
| |
| if (logger) { |
| logger->OnTextFieldDidChange(autofill_field->global_id()); |
| } |
| } |
| |
| bool BrowserAutofillManager::IsFormNonSecure(const FormData& form) const { |
| // Check if testing override applies. |
| if (consider_form_as_secure_for_testing_.has_value() && |
| consider_form_as_secure_for_testing_.value()) { |
| return false; |
| } |
| |
| return IsFormOrClientNonSecure(client(), form); |
| } |
| |
| void BrowserAutofillManager::OnAskForValuesToFillImpl( |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& transformed_box, |
| AutofillSuggestionTriggerSource trigger_source) { |
| if (base::FeatureList::IsEnabled(features::kAutofillDisableFilling)) { |
| return; |
| } |
| |
| // Once the user triggers autofill from the context menu, this event is |
| // recorded, because the IPH configuration limits how many times the IPH can |
| // be shown. |
| if (IsAutofillManuallyTriggered(trigger_source)) { |
| client().NotifyAutofillManualFallbackUsed(); |
| } |
| |
| external_delegate_->SetCurrentDataListValues(field.datalist_options()); |
| external_delegate_->OnQuery(form, field, transformed_box, trigger_source); |
| |
| std::vector<Suggestion> suggestions; |
| SuggestionsContext context; |
| GetAvailableSuggestions(form, field, trigger_source, &suggestions, &context); |
| |
| const bool form_element_was_clicked = |
| trigger_source == |
| AutofillSuggestionTriggerSource::kFormControlElementClicked; |
| |
| if (context.is_autofill_available) { |
| switch (context.suppress_reason) { |
| case SuppressReason::kNotSuppressed: |
| break; |
| |
| case SuppressReason::kAblation: |
| single_field_form_fill_router_->CancelPendingQueries(); |
| external_delegate_->OnSuggestionsReturned(field.global_id(), |
| suggestions); |
| LOG_AF(log_manager()) |
| << LoggingScope::kFilling << LogMessage::kSuggestionSuppressed |
| << " Reason: Ablation experiment"; |
| return; |
| |
| case SuppressReason::kInsecureForm: |
| LOG_AF(log_manager()) |
| << LoggingScope::kFilling << LogMessage::kSuggestionSuppressed |
| << " Reason: Insecure form"; |
| return; |
| case SuppressReason::kAutocompleteOff: |
| LOG_AF(log_manager()) |
| << LoggingScope::kFilling << LogMessage::kSuggestionSuppressed |
| << " Reason: autocomplete=off"; |
| return; |
| case SuppressReason::kAutocompleteUnrecognized: |
| LOG_AF(log_manager()) |
| << LoggingScope::kFilling << LogMessage::kSuggestionSuppressed |
| << " Reason: autocomplete=unrecognized"; |
| return; |
| } |
| |
| if (!suggestions.empty()) { |
| if (context.filling_product == FillingProduct::kCreditCard) { |
| AutofillMetrics::LogIsQueriedCreditCardFormSecure( |
| context.is_context_secure); |
| // TODO(b/41484171): Move to PaymentsSuggestionGenerator. |
| autofill_metrics::LogSuggestionsCount( |
| base::ranges::count_if(suggestions, |
| [](const Suggestion& suggestion) { |
| return GetFillingProductFromSuggestionType( |
| suggestion.type) == |
| FillingProduct::kCreditCard; |
| }), |
| FillingProduct::kCreditCard); |
| } |
| if (context.filling_product == FillingProduct::kAddress) { |
| // TODO(b/41484171): Move to AddressSuggestionGenerator. |
| autofill_metrics::LogSuggestionsCount( |
| base::ranges::count_if(suggestions, |
| [](const Suggestion& suggestion) { |
| return GetFillingProductFromSuggestionType( |
| suggestion.type) == |
| FillingProduct::kAddress; |
| }), |
| FillingProduct::kAddress); |
| } |
| } |
| } |
| |
| // Check if other suggestion sources should be queried. Manual fallbacks can't |
| // trigger different suggestion types. |
| const bool should_offer_other_suggestions = |
| suggestions.empty() && !IsAutofillManuallyTriggered(trigger_source) && |
| trigger_source != AutofillSuggestionTriggerSource:: |
| kShowPromptAfterDialogClosedNonManualFallback; |
| |
| if (should_offer_other_suggestions && |
| (field.form_control_type() == FormControlType::kTextArea || |
| field.form_control_type() == FormControlType::kContentEditable)) { |
| AutofillComposeDelegate* compose_delegate = client().GetComposeDelegate(); |
| std::optional<Suggestion> maybe_compose_suggestion = |
| compose_delegate |
| ? compose_delegate->GetSuggestion(field, trigger_source) |
| : std::nullopt; |
| if (maybe_compose_suggestion) { |
| suggestions.push_back(*std::move(maybe_compose_suggestion)); |
| } |
| } |
| |
| auto ShouldOfferSingleFieldFormFill = [&] { |
| if (!suggestions.empty() || !should_offer_other_suggestions) { |
| return false; |
| } |
| |
| if (trigger_source == |
| AutofillSuggestionTriggerSource::kTextareaFocusedWithoutClick) { |
| return false; |
| } |
| |
| // Do not offer single field form fill suggestions for credit card number, |
| // cvc, and expiration date related fields. Standalone cvc fields (used to |
| // re-authenticate the use of a credit card the website has on file) will be |
| // handled separately because those have the field type |
| // CREDIT_CARD_STANDALONE_VERIFICATION_CODE. |
| FieldType server_type = |
| context.focused_field ? context.focused_field->Type().GetStorableType() |
| : UNKNOWN_TYPE; |
| if (data_util::IsCreditCardExpirationType(server_type) || |
| server_type == CREDIT_CARD_VERIFICATION_CODE || |
| server_type == CREDIT_CARD_NUMBER) { |
| return false; |
| } |
| |
| // Do not offer single field form fill suggestions if popups are suppressed |
| // due to an unrecognized autocomplete attribute. Note that in the context |
| // of Autofill, the popup for credit card related fields is not getting |
| // suppressed due to an unrecognized autocomplete attribute. |
| // TODO(crbug.com/40853053): Revisit here to see whether we should offer |
| // IBAN filling for fields with unrecognized autocomplete attribute |
| if (context.suppress_reason == SuppressReason::kAutocompleteUnrecognized) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| // Display the IPH only if the form can be autofilled and the user has |
| // profiles which can fill the current field. |
| if (GetCachedFormAndField(form, field, &form_structure, |
| &autofill_field) && |
| FieldTypeGroupToFormType(autofill_field->Type().group()) == |
| FormType::kAddressForm && |
| base::ranges::any_of( |
| client() |
| .GetPersonalDataManager() |
| ->address_data_manager() |
| .GetProfiles(), |
| [field_type = autofill_field->Type().GetStorableType()]( |
| AutofillProfile* profile) { |
| return profile->HasInfo(field_type); |
| }) && |
| base::FeatureList::IsEnabled( |
| features::kAutofillEnableManualFallbackIPH)) { |
| client().ShowAutofillFieldIphForManualFallbackFeature(field); |
| } |
| return false; |
| } |
| |
| // Therefore, we check the attribute explicitly. |
| if (context.focused_field && context.focused_field->Type().html_type() == |
| HtmlFieldType::kUnrecognized) { |
| return false; |
| } |
| |
| // Finally, check that the scheme is secure. |
| if (context.suppress_reason == SuppressReason::kInsecureForm) { |
| return false; |
| } |
| return true; |
| }; |
| |
| auto ShouldShowSuggestion = [&] { |
| if (fast_checkout_delegate_ && |
| (fast_checkout_delegate_->IsShowingFastCheckoutUI() || |
| (form_element_was_clicked && |
| fast_checkout_delegate_->TryToShowFastCheckout(form, field, |
| GetWeakPtr())))) { |
| // The Fast Checkout surface is shown, so abort showing regular Autofill |
| // UI. Now the flow is controlled by the `FastCheckoutClient` instead of |
| // `external_delegate_`. |
| // In principle, TTF and Fast Checkout triggering surfaces are different |
| // and the two screens should never coincide. |
| return false; |
| } |
| |
| if (ShouldOfferSingleFieldFormFill()) { |
| // Suggestions come back asynchronously, so the SingleFieldFormFillRouter |
| // will handle sending the results back to the renderer. |
| // TODO(crbug.com/40100455): The callback will only be called once. |
| bool handled_by_single_field_form_filler = |
| single_field_form_fill_router_->OnGetSingleFieldSuggestions( |
| field, client(), |
| base::BindRepeating( |
| &BrowserAutofillManager::OnGetSingleFieldSuggestionsCallback, |
| weak_ptr_factory_.GetWeakPtr(), form_element_was_clicked, |
| form, base::TimeTicks::Now(), |
| context.focused_field ? context.focused_field->Type().group() |
| : FieldTypeGroup::kNoGroup), |
| context); |
| if (handled_by_single_field_form_filler) { |
| return false; |
| } |
| } |
| |
| single_field_form_fill_router_->CancelPendingQueries(); |
| if (touch_to_fill_delegate_ && |
| (touch_to_fill_delegate_->IsShowingTouchToFill() || |
| (form_element_was_clicked && |
| touch_to_fill_delegate_->TryToShowTouchToFill(form, field)))) { |
| // Touch To Fill surface is shown, so abort showing regular Autofill UI. |
| // Now the flow is controlled by the |touch_to_fill_delegate_| instead |
| // of |external_delegate_|. |
| return false; |
| } |
| return true; |
| }; |
| |
| bool show_suggestion = ShouldShowSuggestion(); |
| // When focusing on a field, log whether there is a suggestion for the user |
| // and whether the suggestion is shown. |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (form_element_was_clicked && |
| GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| autofill_field->AppendLogEventIfNotRepeated(AskForValuesToFillFieldLogEvent{ |
| .has_suggestion = ToOptionalBoolean(!suggestions.empty()), |
| .suggestion_is_shown = ToOptionalBoolean(show_suggestion), |
| }); |
| } |
| if (show_suggestion) { |
| // Send Autofill suggestions (could be an empty list). |
| external_delegate_->OnSuggestionsReturned(field.global_id(), suggestions); |
| } |
| } |
| |
| void BrowserAutofillManager::AuthenticateThenFillCreditCardForm( |
| const FormData& form, |
| const FormFieldData& field, |
| const CreditCard& credit_card, |
| const AutofillTriggerDetails& trigger_details) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| credit_card_ = credit_card; |
| credit_card_form_event_logger_->OnDidSelectCardSuggestion( |
| credit_card_, *form_structure, signin_state_for_metrics_); |
| // If no authentication is needed, directly forward filling to FormFiller. |
| if (!ShouldFetchCreditCard(form, field, *form_structure, *autofill_field, |
| credit_card_)) { |
| form_filler_->FillOrPreviewForm( |
| mojom::ActionPersistence::kFill, form, field, &credit_card_, |
| /*optional_cvc=*/std::nullopt, form_structure, autofill_field, |
| trigger_details); |
| return; |
| } |
| credit_card_form_event_logger_->LogDeprecatedCreditCardSelectedMetric( |
| credit_card_, *form_structure, signin_state_for_metrics_); |
| |
| credit_card_form_ = form; |
| credit_card_field_ = field; |
| |
| // CreditCardAccessManager::FetchCreditCard() will trigger |
| // OnCreditCardFetched() in this class after successfully fetching the |
| // card. |
| fetched_credit_card_trigger_source_ = trigger_details.trigger_source; |
| credit_card_access_manager_->FetchCreditCard( |
| &credit_card_, |
| base::BindOnce(&BrowserAutofillManager::OnCreditCardFetched, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BrowserAutofillManager::FillOrPreviewProfileForm( |
| mojom::ActionPersistence action_persistence, |
| const FormData& form, |
| const FormFieldData& field, |
| const AutofillProfile& profile, |
| const AutofillTriggerDetails& trigger_details) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| form_filler_->FillOrPreviewForm(action_persistence, form, field, &profile, |
| /*cvc=*/std::nullopt, form_structure, |
| autofill_field, trigger_details); |
| } |
| |
| void BrowserAutofillManager::FillOrPreviewField( |
| mojom::ActionPersistence action_persistence, |
| mojom::FieldActionType action_type, |
| const FormData& form, |
| const FormFieldData& field, |
| const std::u16string& value, |
| SuggestionType type) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| GetCachedFormAndField(form, field, &form_structure, &autofill_field); |
| form_filler_->FillOrPreviewField(action_persistence, action_type, form, field, |
| form_structure, autofill_field, value, type); |
| if (action_persistence == mojom::ActionPersistence::kFill) { |
| const FormFieldData* const_field = &field; |
| const AutofillField* const_autofill_field = autofill_field; |
| if (type == SuggestionType::kAddressFieldByFieldFilling) { |
| address_form_event_logger_->RecordFillingOperation( |
| form.global_id(), base::make_span(&const_field, 1u), |
| base::make_span(&const_autofill_field, 1u)); |
| } else if (type == SuggestionType::kCreditCardFieldByFieldFilling) { |
| credit_card_form_event_logger_->RecordFillingOperation( |
| form.global_id(), base::make_span(&const_field, 1u), |
| base::make_span(&const_autofill_field, 1u)); |
| } |
| |
| const bool is_address_manual_fallback_on_non_address_field = |
| IsAddressAutofillManuallyTriggeredOnNonAddressField( |
| type, const_autofill_field); |
| const bool is_payments_manual_fallback_on_non_payments_field = |
| IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField( |
| type, const_autofill_field); |
| if (is_address_manual_fallback_on_non_address_field || |
| is_payments_manual_fallback_on_non_payments_field) { |
| manual_fallback_logger_->OnDidFillSuggestion( |
| GetFillingProductFromSuggestionType(type)); |
| } |
| } |
| } |
| |
| void BrowserAutofillManager::UndoAutofill( |
| mojom::ActionPersistence action_persistence, |
| const FormData& form, |
| const FormFieldData& trigger_field) { |
| FormStructure* form_structure = FindCachedFormById(form.global_id()); |
| if (!form_structure) { |
| return; |
| } |
| // This will apply the undo operation and return information about the |
| // operation being undone, for metric purposes. |
| FillingProduct filling_product = form_filler_->UndoAutofill( |
| action_persistence, form, *form_structure, trigger_field); |
| |
| // The remaining logic is only relevant for filling. |
| if (action_persistence != mojom::ActionPersistence::kPreview) { |
| if (filling_product == FillingProduct::kAddress) { |
| address_form_event_logger_->OnDidUndoAutofill(); |
| } else if (filling_product == FillingProduct::kCreditCard) { |
| credit_card_form_event_logger_->OnDidUndoAutofill(); |
| } |
| } |
| } |
| |
| void BrowserAutofillManager::FillOrPreviewCreditCardForm( |
| mojom::ActionPersistence action_persistence, |
| const FormData& form, |
| const FormFieldData& field, |
| const CreditCard& credit_card, |
| const std::u16string& cvc, |
| const AutofillTriggerDetails& trigger_details) { |
| if (!IsValidFormData(form) || !IsValidFormFieldData(field)) { |
| return; |
| } |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| form_filler_->FillOrPreviewForm(action_persistence, form, field, &credit_card, |
| &cvc, form_structure, autofill_field, |
| trigger_details, |
| /*is_refill=*/false); |
| } |
| |
| void BrowserAutofillManager::OnFocusNoLongerOnFormImpl( |
| bool had_interacted_form) { |
| // For historical reasons, Chrome takes action on this message only if focus |
| // was previously on a form with which the user had interacted. |
| // TODO(crbug.com/40726656): Remove need for this short-circuit. |
| if (!had_interacted_form) { |
| return; |
| } |
| |
| ProcessPendingFormForUpload(); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // There is no way of determining whether ChromeVox is in use, so assume it's |
| // being used. |
| external_delegate_->OnAutofillAvailabilityEvent( |
| mojom::AutofillSuggestionAvailability::kNoSuggestions); |
| #else |
| if (external_delegate_->HasActiveScreenReader()) { |
| external_delegate_->OnAutofillAvailabilityEvent( |
| mojom::AutofillSuggestionAvailability::kNoSuggestions); |
| } |
| #endif |
| } |
| |
| void BrowserAutofillManager::OnFocusOnFormFieldImpl( |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box) { |
| if (pending_form_data_ && |
| pending_form_data_->global_id() != form.global_id()) { |
| // A new form has received the focus, so we may have votes to upload for the |
| // old form. |
| ProcessPendingFormForUpload(); |
| } |
| |
| // Notify installed screen readers if the focus is on a field for which there |
| // are suggestions to present. Ignore if a screen reader is not present. If |
| // the platform is ChromeOS, then assume ChromeVox is in use as there is no |
| // way of determining whether it's being used from this point in the code. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| if (!external_delegate_->HasActiveScreenReader()) { |
| return; |
| } |
| #endif |
| |
| // TODO(crbug.com/41392130): Add metrics for performance impact. |
| std::vector<Suggestion> suggestions; |
| SuggestionsContext context; |
| // This code path checks if suggestions to be announced to a screen reader are |
| // available when the focus on a form field changes. This cannot happen in |
| // `OnAskForValuesToFillImpl()`, since the `AutofillSuggestionAvailability` is |
| // a sticky flag and needs to be reset when a non-autofillable field is |
| // focused. The suggestion trigger source doesn't influence the set of |
| // suggestions generated, but only the way suggestions behave when they are |
| // accepted. For this reason, checking whether suggestions are available can |
| // be done with the `kUnspecified` suggestion trigger source. |
| GetAvailableSuggestions(form, field, |
| AutofillSuggestionTriggerSource::kUnspecified, |
| &suggestions, &context); |
| external_delegate_->OnAutofillAvailabilityEvent( |
| (context.suppress_reason == SuppressReason::kNotSuppressed && |
| !suggestions.empty()) |
| ? mojom::AutofillSuggestionAvailability::kAutofillAvailable |
| : mojom::AutofillSuggestionAvailability::kNoSuggestions); |
| } |
| |
| void BrowserAutofillManager::OnSelectControlDidChangeImpl( |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::RectF& bounding_box) { |
| // TODO(crbug.com/40564270): Handle select control change. |
| } |
| |
| void BrowserAutofillManager::OnDidFillAutofillFormDataImpl( |
| const FormData& form, |
| const TimeTicks timestamp) { |
| UpdatePendingForm(form); |
| |
| // Find the FormStructure that corresponds to |form|. Use default form type if |
| // form is not present in our cache, which will happen rarely. |
| FormStructure* form_structure = FindCachedFormById(form.global_id()); |
| DenseSet<FormType> form_types; |
| if (form_structure) { |
| form_types = form_structure->GetFormTypes(); |
| } |
| UpdateInitialInteractionTimestamp(timestamp); |
| } |
| |
| void BrowserAutofillManager::DidShowSuggestions( |
| base::span<const SuggestionType> shown_suggestions_types, |
| const FormData& form, |
| const FormFieldData& field) { |
| NotifyObservers(&Observer::OnSuggestionsShown); |
| |
| bool has_autofill_suggestions = base::ranges::any_of( |
| shown_suggestions_types, |
| AutofillExternalDelegate::IsAutofillAndFirstLayerSuggestionId); |
| if (!has_autofill_suggestions) { |
| return; |
| } |
| |
| if (base::Contains(shown_suggestions_types, FillingProduct::kCreditCard, |
| GetFillingProductFromSuggestionType) && |
| IsCreditCardFidoAuthenticationEnabled()) { |
| credit_card_access_manager_->PrepareToFetchCreditCard(); |
| } |
| |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| const bool has_cached_form_and_field = |
| GetCachedFormAndField(form, field, &form_structure, &autofill_field); |
| |
| // Check if Autofill was triggered via manual fallback on a field that was |
| // either unclassified or classified differently as the target |
| // `FillingProduct`. |
| // Note that in this type of flow we purposely do not log key metrics so we do |
| // not mess with the current denominator (classified forms). |
| const bool is_address_manual_fallback_on_non_address_field = |
| base::ranges::any_of( |
| shown_suggestions_types, [autofill_field](SuggestionType type) { |
| return IsAddressAutofillManuallyTriggeredOnNonAddressField( |
| type, autofill_field); |
| }); |
| const bool is_payments_manual_fallback_on_non_payments_field = |
| base::ranges::any_of( |
| shown_suggestions_types, [autofill_field](SuggestionType type) { |
| return IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField( |
| type, autofill_field); |
| }); |
| if (is_address_manual_fallback_on_non_address_field) { |
| manual_fallback_logger_->OnDidShowSuggestions(FillingProduct::kAddress); |
| return; |
| } |
| if (is_payments_manual_fallback_on_non_payments_field) { |
| manual_fallback_logger_->OnDidShowSuggestions(FillingProduct::kCreditCard); |
| return; |
| } |
| |
| if (!has_cached_form_and_field) { |
| return; |
| } |
| autofill_field->set_did_trigger_suggestions(true); |
| |
| auto* logger = GetEventFormLogger(*autofill_field); |
| if (logger) { |
| logger->OnDidShowSuggestions(*form_structure, *autofill_field, |
| form_structure->form_parsed_timestamp(), |
| signin_state_for_metrics_, |
| client().IsOffTheRecord()); |
| } else if (autofill_field->ShouldSuppressSuggestionsAndFillingByDefault()) { |
| // Suggestions were triggered on an ac=unrecognized address field. |
| autocomplete_unrecognized_fallback_logger_->OnDidShowSuggestions(); |
| } |
| } |
| |
| void BrowserAutofillManager::OnHidePopupImpl() { |
| single_field_form_fill_router_->CancelPendingQueries(); |
| client().HideAutofillSuggestions(SuggestionHidingReason::kRendererEvent); |
| client().HideAutofillFieldIphForManualFallbackFeature(); |
| if (fast_checkout_delegate_) { |
| fast_checkout_delegate_->HideFastCheckout(/*allow_further_runs=*/false); |
| } |
| if (touch_to_fill_delegate_) { |
| touch_to_fill_delegate_->HideTouchToFill(); |
| } |
| } |
| |
| bool BrowserAutofillManager::RemoveAutofillProfileOrCreditCard( |
| Suggestion::BackendId backend_id) { |
| if (const CreditCard* credit_card = GetCreditCard(backend_id)) { |
| // Server cards cannot be deleted from within Chrome. |
| bool allowed_to_delete = CreditCard::IsLocalCard(credit_card); |
| |
| if (allowed_to_delete) { |
| client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .DeleteLocalCreditCards({*credit_card}); |
| } |
| |
| return allowed_to_delete; |
| } |
| |
| if (const AutofillProfile* profile = GetProfile(backend_id)) { |
| client().GetPersonalDataManager()->RemoveByGUID(profile->guid()); |
| return true; |
| } |
| |
| return false; // The ID was valid. The entry may have been deleted in a race. |
| } |
| |
| void BrowserAutofillManager::RemoveCurrentSingleFieldSuggestion( |
| const std::u16string& name, |
| const std::u16string& value, |
| SuggestionType type) { |
| single_field_form_fill_router_->OnRemoveCurrentSingleFieldSuggestion( |
| name, value, type); |
| } |
| |
| void BrowserAutofillManager::OnSingleFieldSuggestionSelected( |
| const std::u16string& value, |
| SuggestionType type, |
| const FormData& form, |
| const FormFieldData& field) { |
| single_field_form_fill_router_->OnSingleFieldSuggestionSelected(value, type); |
| |
| AutofillField* autofill_trigger_field = GetAutofillField(form, field); |
| if (!autofill_trigger_field) { |
| return; |
| } |
| if (IsSingleFieldFormFillerFillingProduct( |
| GetFillingProductFromSuggestionType(type))) { |
| autofill_trigger_field->AppendLogEventIfNotRepeated( |
| TriggerFillFieldLogEvent{ |
| .data_type = |
| GetEventTypeFromSingleFieldSuggestionSuggestionType(type), |
| .associated_country_code = "", |
| .timestamp = AutofillClock::Now()}); |
| } |
| } |
| |
| void BrowserAutofillManager::OnUserHideSuggestions(const FormData& form, |
| const FormFieldData& field) { |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| |
| auto* logger = GetEventFormLogger(*autofill_field); |
| if (logger) { |
| logger->OnUserHideSuggestions(*form_structure, *autofill_field); |
| } |
| } |
| |
| bool BrowserAutofillManager::ShouldClearPreviewedForm() { |
| return credit_card_access_manager_->ShouldClearPreviewedForm(); |
| } |
| |
| void BrowserAutofillManager::OnSelectOrSelectListFieldOptionsDidChangeImpl( |
| const FormData& form) { |
| FormStructure* form_structure = FindCachedFormById(form.global_id()); |
| if (!form_structure) { |
| return; |
| } |
| |
| driver().SendAutofillTypePredictionsToRenderer({form_structure}); |
| |
| if (form_filler_->ShouldTriggerRefill( |
| *form_structure, RefillTriggerReason::kSelectOptionsChanged)) { |
| form_filler_->TriggerRefill( |
| form, {.trigger_source = AutofillTriggerSource::kSelectOptionsChanged}); |
| } |
| } |
| |
| void BrowserAutofillManager::OnJavaScriptChangedAutofilledValueImpl( |
| const FormData& form, |
| const FormFieldData& field, |
| const std::u16string& old_value, |
| bool formatting_only) { |
| // Log to chrome://autofill-internals that a field's value was set by |
| // JavaScript. |
| auto StructureOfString = [](std::u16string str) { |
| for (auto& c : str) { |
| if (base::IsAsciiAlpha(c)) { |
| c = 'a'; |
| } else if (base::IsAsciiDigit(c)) { |
| c = '0'; |
| } else if (base::IsAsciiWhitespace(c)) { |
| c = ' '; |
| } else { |
| c = '$'; |
| } |
| } |
| return str; |
| }; |
| auto GetFieldNumber = [&]() { |
| for (size_t i = 0; i < form.fields.size(); ++i) { |
| if (form.fields[i].global_id() == field.global_id()) { |
| return base::StringPrintf("Field %zu", i); |
| } |
| } |
| return std::string("unknown"); |
| }; |
| LogBuffer change(IsLoggingActive(log_manager())); |
| LOG_AF(change) << Tag{"div"} << Attrib{"class", "form"}; |
| LOG_AF(change) << field << Br{}; |
| LOG_AF(change) << "Old value structure: '" |
| << StructureOfString(old_value.substr(0, 80)) << "'" << Br{}; |
| LOG_AF(change) << "New value structure: '" |
| << StructureOfString(field.value().substr(0, 80)) << "'"; |
| LOG_AF(log_manager()) << LoggingScope::kWebsiteModifiedFieldValue |
| << LogMessage::kJavaScriptChangedAutofilledValue << Br{} |
| << Tag{"table"} << Tr{} << GetFieldNumber() |
| << std::move(change); |
| |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return; |
| } |
| AnalyzeJavaScriptChangedAutofilledValue( |
| *form_structure, *autofill_field, field.value().empty(), formatting_only); |
| if (formatting_only) { |
| return; |
| } |
| form_filler_->MaybeTriggerRefillForExpirationDate( |
| form, field, *form_structure, old_value, |
| {.trigger_source = |
| AutofillTriggerSource::kJavaScriptChangedAutofilledValue}); |
| } |
| |
| void BrowserAutofillManager::AnalyzeJavaScriptChangedAutofilledValue( |
| const FormStructure& form, |
| AutofillField& field, |
| bool cleared_value, |
| bool formatting_only) { |
| if (!formatting_only && |
| base::FeatureList::IsEnabled( |
| features::kAutofillFixCachingOnJavaScriptChanges)) { |
| field.set_is_autofilled(false); |
| field.set_previously_autofilled(true); |
| } |
| // We are interested in reporting the events where JavaScript resets an |
| // autofilled value immediately after filling. For a reset, the value |
| // needs to be empty. |
| if (!cleared_value) { |
| return; |
| } |
| base::TimeTicks now = base::TimeTicks::Now(); |
| std::optional<base::TimeTicks> original_fill_time = |
| form_filler_->GetOriginalFillingTime(form.global_id()); |
| if (!original_fill_time) { |
| return; |
| } |
| base::TimeDelta delta = now - *original_fill_time; |
| // If the filling happened too long ago, maybe this is just an effect of |
| // the user pressing a "reset form" button. |
| if (delta >= form_filler_->get_limit_before_refill()) { |
| return; |
| } |
| if (auto* logger = GetEventFormLogger(field)) { |
| logger->OnAutofilledFieldWasClearedByJavaScriptShortlyAfterFill(form); |
| } |
| } |
| |
| void BrowserAutofillManager::OnCreditCardFetched( |
| CreditCardFetchResult result, |
| const CreditCard* credit_card) { |
| if (result != CreditCardFetchResult::kSuccess) { |
| driver().RendererShouldClearPreviewedForm(); |
| return; |
| } |
| // In the failure case, `credit_card` can be `nullptr`, but in the success |
| // case it is non-null. |
| CHECK(credit_card); |
| OnCreditCardFetchedSuccessfully(*credit_card); |
| |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(credit_card_form_, credit_card_field_, |
| &form_structure, &autofill_field)) { |
| return; |
| } |
| |
| FillOrPreviewCreditCardForm( |
| mojom::ActionPersistence::kFill, credit_card_form_, credit_card_field_, |
| *credit_card, credit_card->cvc(), |
| {.trigger_source = fetched_credit_card_trigger_source_.value_or( |
| AutofillTriggerSource::kCreditCardCvcPopup)}); |
| } |
| |
| void BrowserAutofillManager::OnDidEndTextFieldEditingImpl() { |
| external_delegate_->DidEndTextFieldEditing(); |
| // Should not hide the Touch To Fill surface, since it is an overlay UI |
| // which ends editing. |
| } |
| |
| bool BrowserAutofillManager::IsAutofillEnabled() const { |
| return IsAutofillProfileEnabled() || IsAutofillPaymentMethodsEnabled(); |
| } |
| |
| bool BrowserAutofillManager::IsAutofillProfileEnabled() const { |
| return prefs::IsAutofillProfileEnabled(client().GetPrefs()); |
| } |
| |
| bool BrowserAutofillManager::IsAutofillPaymentMethodsEnabled() const { |
| return prefs::IsAutofillPaymentMethodsEnabled(client().GetPrefs()); |
| } |
| |
| const FormData& BrowserAutofillManager::last_query_form() const { |
| return external_delegate_->query_form(); |
| } |
| |
| bool BrowserAutofillManager::ShouldUploadForm(const FormStructure& form) { |
| return IsAutofillEnabled() && !client().IsOffTheRecord() && |
| form.ShouldBeUploaded(); |
| } |
| |
| void BrowserAutofillManager:: |
| FetchPotentialCardLastFourDigitsCombinationFromDOM() { |
| driver().GetFourDigitCombinationsFromDOM(base::BindOnce( |
| [](base::WeakPtr<BrowserAutofillManager> self, |
| const std::vector<std::string>& four_digit_combinations_in_dom) { |
| if (!self) { |
| return; |
| } |
| self->four_digit_combinations_in_dom_ = four_digit_combinations_in_dom; |
| }, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BrowserAutofillManager::OnGetSingleFieldSuggestionsCallback( |
| bool form_element_was_clicked, |
| const FormData& form, |
| base::TimeTicks request_start_time, |
| FieldTypeGroup focused_field_type_group, |
| FieldGlobalId field_id, |
| const std::vector<Suggestion>& suggestions) { |
| MaybeLogAutocompleteSuppressionByPlusAddresses(client(), suggestions, |
| focused_field_type_group); |
| LogTimeDelayForSingleFieldFormFill( |
| suggestions, base::TimeTicks::Now() - request_start_time); |
| // TODO(b/309163415): Replace parameter of FormFieldData in |
| // `TryToShowTouchToFill` by FieldGlobalId. |
| if (form_element_was_clicked && touch_to_fill_delegate_ && |
| base::FeatureList::IsEnabled(features::kAutofillEnableServerIban) && |
| touch_to_fill_delegate_->TryToShowTouchToFill( |
| form, *form.FindFieldByGlobalId(field_id))) { |
| return; |
| } |
| external_delegate_->OnSuggestionsReturned(field_id, suggestions); |
| } |
| |
| void BrowserAutofillManager::StoreUploadVotesAndLogQualityCallback( |
| FormSignature form_signature, |
| base::OnceClosure callback) { |
| // Remove entries with the same FormSignature to replace them. |
| WipeLogQualityAndVotesUploadCallback(form_signature); |
| |
| // Entries in queued_vote_uploads_ are submitted after navigations or form |
| // submissions. To reduce the risk of collecting too much data that is not |
| // send, we allow only `kMaxEntriesInQueue` entries. Anything in excess will |
| // be sent when the queue becomes to long. |
| constexpr int kMaxEntriesInQueue = 10; |
| while (queued_vote_uploads_.size() >= kMaxEntriesInQueue) { |
| base::OnceCallback oldest_callback = |
| std::move(queued_vote_uploads_.back().second); |
| queued_vote_uploads_.pop_back(); |
| std::move(oldest_callback).Run(); |
| } |
| |
| queued_vote_uploads_.emplace_front(form_signature, std::move(callback)); |
| } |
| |
| void BrowserAutofillManager::WipeLogQualityAndVotesUploadCallback( |
| FormSignature form_signature) { |
| std::erase_if(queued_vote_uploads_, [form_signature](const auto& entry) { |
| return entry.first == form_signature; |
| }); |
| } |
| |
| void BrowserAutofillManager::FlushPendingLogQualityAndVotesUploadCallbacks() { |
| std::list<std::pair<FormSignature, base::OnceClosure>> queued_vote_uploads = |
| std::exchange(queued_vote_uploads_, {}); |
| for (auto& i : queued_vote_uploads) { |
| std::move(i.second).Run(); |
| } |
| } |
| |
| // We explicitly pass in all the time stamps of interest, as the cached ones |
| // might get reset before this method executes. |
| void BrowserAutofillManager::UploadVotesAndLogQuality( |
| std::unique_ptr<FormStructure> submitted_form, |
| base::TimeTicks interaction_time, |
| base::TimeTicks submission_time, |
| bool observed_submission, |
| ukm::SourceId source_id) { |
| // If the form is submitted, we don't need to send pending votes from blur |
| // (un-focus) events. |
| if (observed_submission) { |
| WipeLogQualityAndVotesUploadCallback(submitted_form->form_signature()); |
| } |
| if (submitted_form->ShouldRunHeuristics() || |
| submitted_form->ShouldRunHeuristicsForSingleFieldForms() || |
| submitted_form->ShouldBeQueried()) { |
| autofill_metrics::LogQualityMetrics( |
| *submitted_form, submitted_form->form_parsed_timestamp(), |
| interaction_time, submission_time, form_interactions_ukm_logger(), |
| observed_submission); |
| if (observed_submission) { |
| // Ensure that callbacks for blur votes get sent as well here because |
| // we are not sure whether a full navigation with a Reset() call follows. |
| FlushPendingLogQualityAndVotesUploadCallbacks(); |
| } |
| } |
| if (!submitted_form->ShouldBeUploaded()) { |
| return; |
| } |
| if (ShouldRecordUkm() && ShouldUploadUkm(*submitted_form)) { |
| AutofillMetrics::LogAutofillFieldInfoAfterSubmission( |
| client().GetUkmRecorder(), source_id, *submitted_form, submission_time); |
| } |
| if (!client().GetCrowdsourcingManager()) { |
| return; |
| } |
| const PersonalDataManager* pdm = client().GetPersonalDataManager(); |
| FieldTypeSet non_empty_types; |
| for (const AutofillProfile* profile : |
| pdm->address_data_manager().GetProfiles()) { |
| profile->GetNonEmptyTypes(app_locale_, &non_empty_types); |
| } |
| for (const CreditCard* card : pdm->payments_data_manager().GetCreditCards()) { |
| card->GetNonEmptyTypes(app_locale_, &non_empty_types); |
| } |
| // As CVC is not stored, treat it separately. |
| if (!last_unlocked_credit_card_cvc_.empty() || |
| non_empty_types.contains(CREDIT_CARD_NUMBER)) { |
| non_empty_types.insert(CREDIT_CARD_VERIFICATION_CODE); |
| } |
| client().GetCrowdsourcingManager()->StartUploadRequest( |
| /*upload_contents=*/EncodeUploadRequest(*submitted_form, non_empty_types, |
| /*login_form_signature=*/{}, |
| observed_submission), |
| submitted_form->submission_source(), submitted_form->active_field_count(), |
| client().GetPrefs()); |
| } |
| |
| const gfx::Image& BrowserAutofillManager::GetCardImage( |
| const CreditCard& credit_card) { |
| gfx::Image* card_art_image = |
| client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetCreditCardArtImageForUrl(credit_card.card_art_url()); |
| return card_art_image |
| ? *card_art_image |
| : ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| CreditCard::IconResourceId(credit_card.network())); |
| } |
| |
| void BrowserAutofillManager::OnSubmissionFieldTypesDetermined( |
| std::unique_ptr<FormStructure> submitted_form, |
| base::TimeTicks interaction_time, |
| base::TimeTicks submission_time, |
| bool observed_submission, |
| ukm::SourceId source_id) { |
| auto count_types = [&submitted_form](FormType type) { |
| return base::ranges::count_if( |
| submitted_form->fields(), |
| [=](const std::unique_ptr<AutofillField>& field) { |
| return FieldTypeGroupToFormType(field->Type().group()) == type; |
| }); |
| }; |
| |
| size_t address_fields_count = count_types(FormType::kAddressForm); |
| autofill_metrics::FormGroupFillingStats address_filling_stats = |
| autofill_metrics::GetFormFillingStatsForFormType(FormType::kAddressForm, |
| *submitted_form); |
| const bool can_trigger_address_survey = |
| address_fields_count >= |
| kMinNumberAddressFieldsToTriggerAddressUserPerceptionSurvey && |
| address_filling_stats.TotalFilled() > 0 && |
| base::FeatureList::IsEnabled( |
| features::kAutofillAddressUserPerceptionSurvey); |
| |
| size_t credit_card_fields_count = count_types(FormType::kCreditCardForm); |
| autofill_metrics::FormGroupFillingStats credit_card_filling_stats = |
| autofill_metrics::GetFormFillingStatsForFormType( |
| FormType::kCreditCardForm, *submitted_form); |
| const bool can_trigger_credit_card_survey = |
| credit_card_fields_count > 0 && |
| credit_card_filling_stats.TotalFilled() > 0; |
| |
| if (can_trigger_address_survey) { |
| client().TriggerUserPerceptionOfAutofillSurvey( |
| FillingProduct::kAddress, |
| FormFillingStatsToSurveyStringData(address_filling_stats)); |
| } else if (can_trigger_credit_card_survey && |
| base::FeatureList::IsEnabled( |
| features::kAutofillCreditCardUserPerceptionSurvey)) { |
| client().TriggerUserPerceptionOfAutofillSurvey( |
| FillingProduct::kCreditCard, |
| FormFillingStatsToSurveyStringData(credit_card_filling_stats)); |
| } |
| UploadVotesAndLogQuality(std::move(submitted_form), interaction_time, |
| submission_time, observed_submission, source_id); |
| } |
| |
| void BrowserAutofillManager::Reset() { |
| // Process log events and record into UKM when the form is destroyed or |
| // removed. |
| for (const auto& [form_id, form_structure] : form_structures()) { |
| ProcessFieldLogEventsInForm(*form_structure); |
| } |
| |
| // Note that upload_request_ is not reset here because the prompt to |
| // save a card is shown after page navigation. |
| ProcessPendingFormForUpload(); |
| FlushPendingLogQualityAndVotesUploadCallbacks(); |
| DCHECK(!pending_form_data_); |
| // `credit_card_access_manager_` needs to be reset before resetting |
| // `credit_card_form_event_logger_`, since it keeps a raw pointer to it. |
| credit_card_access_manager_.reset(); |
| // {address, credit_card}_form_event_logger_ need to be reset before |
| // AutofillManager::Reset() because ~FormEventLoggerBase() uses |
| // form_interactions_ukm_logger_ that is created and assigned in |
| // AutofillManager::Reset(). The new form_interactions_ukm_logger_ instance |
| // is needed for constructing the new *form_event_logger_ instances which is |
| // why calling AutofillManager::Reset() after constructing *form_event_logger_ |
| // instances is not an option. |
| address_form_event_logger_->OnDestroyed(); |
| address_form_event_logger_.reset(); |
| credit_card_form_event_logger_->OnDestroyed(); |
| credit_card_form_event_logger_.reset(); |
| AutofillManager::Reset(); |
| address_form_event_logger_ = |
| std::make_unique<autofill_metrics::AddressFormEventLogger>( |
| driver().IsInAnyMainFrame(), form_interactions_ukm_logger(), |
| &unsafe_client()); |
| credit_card_form_event_logger_ = |
| std::make_unique<autofill_metrics::CreditCardFormEventLogger>( |
| driver().IsInAnyMainFrame(), form_interactions_ukm_logger(), |
| unsafe_client().GetPersonalDataManager(), &unsafe_client()); |
| credit_card_access_manager_ = std::make_unique<CreditCardAccessManager>( |
| &driver(), &unsafe_client(), unsafe_client().GetPersonalDataManager(), |
| credit_card_form_event_logger_.get()); |
| autocomplete_unrecognized_fallback_logger_ = std::make_unique< |
| autofill_metrics::AutocompleteUnrecognizedFallbackEventLogger>(); |
| manual_fallback_logger_ = |
| std::make_unique<autofill_metrics::ManualFallbackEventLogger>(); |
| |
| has_logged_autofill_enabled_ = false; |
| user_did_type_ = false; |
| credit_card_ = CreditCard(); |
| credit_card_form_ = FormData(); |
| credit_card_field_ = FormFieldData(); |
| last_unlocked_credit_card_cvc_.clear(); |
| initial_interaction_timestamp_ = TimeTicks(); |
| fetched_credit_card_trigger_source_ = std::nullopt; |
| if (touch_to_fill_delegate_) { |
| touch_to_fill_delegate_->Reset(); |
| } |
| form_filler_->Reset(); |
| form_submitted_timestamp_ = TimeTicks(); |
| four_digit_combinations_in_dom_.clear(); |
| } |
| |
| bool BrowserAutofillManager::RefreshDataModels() { |
| if (!IsAutofillEnabled()) { |
| return false; |
| } |
| |
| credit_card_access_manager_->UpdateCreditCardFormEventLogger(); |
| |
| const std::vector<AutofillProfile*>& profiles = |
| client().GetPersonalDataManager()->address_data_manager().GetProfiles(); |
| address_form_event_logger_->set_record_type_count(profiles.size()); |
| |
| return !profiles.empty() || !client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetCreditCards() |
| .empty(); |
| } |
| |
| CreditCard* BrowserAutofillManager::GetCreditCard( |
| Suggestion::BackendId unique_id) { |
| return client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetCreditCardByGUID(absl::get<Suggestion::Guid>(unique_id).value()); |
| } |
| |
| AutofillProfile* BrowserAutofillManager::GetProfile( |
| Suggestion::BackendId unique_id) { |
| std::string guid = absl::get<Suggestion::Guid>(unique_id).value(); |
| if (base::Uuid::ParseCaseInsensitive(guid).is_valid()) { |
| return client() |
| .GetPersonalDataManager() |
| ->address_data_manager() |
| .GetProfileByGUID(guid); |
| } |
| return nullptr; |
| } |
| |
| void BrowserAutofillManager::OnDidFillOrPreviewForm( |
| mojom::ActionPersistence action_persistence, |
| const FormStructure& form_structure, |
| const AutofillField& trigger_autofill_field, |
| base::span<const FormFieldData*> safe_filled_fields, |
| base::span<const AutofillField*> safe_filled_autofill_fields, |
| const base::flat_set<FieldGlobalId>& filled_fields, |
| const base::flat_set<FieldGlobalId>& safe_fields, |
| absl::variant<const AutofillProfile*, const CreditCard*> |
| profile_or_credit_card, |
| const AutofillTriggerDetails& trigger_details, |
| bool is_refill) { |
| client().DidFillOrPreviewForm(action_persistence, |
| trigger_details.trigger_source, is_refill); |
| NotifyObservers(&Observer::OnFillOrPreviewDataModelForm, |
| form_structure.global_id(), action_persistence, |
| safe_filled_fields, profile_or_credit_card); |
| if (action_persistence == mojom::ActionPersistence::kPreview) { |
| return; |
| } |
| CHECK_EQ(action_persistence, mojom::ActionPersistence::kFill); |
| if (absl::holds_alternative<const CreditCard*>(profile_or_credit_card)) { |
| if (is_refill) { |
| credit_card_form_event_logger_->OnDidRefill(signin_state_for_metrics_, |
| form_structure); |
| } else { |
| credit_card_form_event_logger_->RecordFillingOperation( |
| form_structure.global_id(), safe_filled_fields, |
| safe_filled_autofill_fields); |
| // The originally selected masked card is `credit_card_`. So we must log |
| // `credit_card_` as opposed to |
| // `absl::get<CreditCard*>(profile_or_credit_card)` to correctly indicate |
| // whether the user filled the form using a masked card suggestion. |
| credit_card_form_event_logger_->OnDidFillFormFillingSuggestion( |
| credit_card_, form_structure, trigger_autofill_field, filled_fields, |
| safe_fields, signin_state_for_metrics_, |
| trigger_details.trigger_source); |
| |
| client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .RecordUseOfCard( |
| absl::get<const CreditCard*>(profile_or_credit_card)); |
| } |
| } else { |
| CHECK(absl::holds_alternative<const AutofillProfile*>( |
| profile_or_credit_card)); |
| const AutofillProfile* profile = |
| absl::get<const AutofillProfile*>(profile_or_credit_card); |
| if (!trigger_autofill_field |
| .ShouldSuppressSuggestionsAndFillingByDefault()) { |
| if (is_refill) { |
| address_form_event_logger_->OnDidRefill(signin_state_for_metrics_, |
| form_structure); |
| } else { |
| address_form_event_logger_->RecordFillingOperation( |
| form_structure.global_id(), safe_filled_fields, |
| safe_filled_autofill_fields); |
| address_form_event_logger_->OnDidFillFormFillingSuggestion( |
| *profile, form_structure, trigger_autofill_field, |
| signin_state_for_metrics_, trigger_details.trigger_source); |
| } |
| } else if (!is_refill) { |
| address_form_event_logger_->RecordFillingOperation( |
| form_structure.global_id(), safe_filled_fields, |
| safe_filled_autofill_fields); |
| autocomplete_unrecognized_fallback_logger_ |
| ->OnDidFillFormFillingSuggestion(); |
| } |
| if (!is_refill) { |
| client().GetPersonalDataManager()->address_data_manager().RecordUseOf( |
| *profile); |
| } |
| } |
| } |
| |
| std::unique_ptr<FormStructure> BrowserAutofillManager::ValidateSubmittedForm( |
| const FormData& form) { |
| // Ignore forms not present in our cache. These are typically forms with |
| // wonky JavaScript that also makes them not auto-fillable. |
| FormStructure* cached_submitted_form = FindCachedFormById(form.global_id()); |
| if (!cached_submitted_form || !ShouldUploadForm(*cached_submitted_form)) { |
| return nullptr; |
| } |
| |
| auto submitted_form = std::make_unique<FormStructure>(form); |
| submitted_form->RetrieveFromCache( |
| *cached_submitted_form, |
| FormStructure::RetrieveFromCacheReason::kFormImport); |
| |
| return submitted_form; |
| } |
| |
| AutofillField* BrowserAutofillManager::GetAutofillField( |
| const FormData& form, |
| const FormFieldData& field) const { |
| if (!client().GetPersonalDataManager()) { |
| return nullptr; |
| } |
| |
| FormStructure* form_structure = nullptr; |
| AutofillField* autofill_field = nullptr; |
| if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) { |
| return nullptr; |
| } |
| |
| if (!form_structure->IsAutofillable()) { |
| return nullptr; |
| } |
| |
| return autofill_field; |
| } |
| |
| void BrowserAutofillManager::OnCreditCardFetchedSuccessfully( |
| const CreditCard& credit_card) { |
| last_unlocked_credit_card_cvc_ = credit_card.cvc(); |
| // If the synced down card is a virtual card, let the client know so that it |
| // can show the UI to help user to manually fill the form, if needed. |
| if (credit_card.record_type() == CreditCard::RecordType::kVirtualCard) { |
| DCHECK(!credit_card.cvc().empty()); |
| client().GetFormDataImporter()->CacheFetchedVirtualCard( |
| credit_card.LastFourDigits()); |
| |
| VirtualCardManualFallbackBubbleOptions options; |
| options.masked_card_name = credit_card.CardNameForAutofillDisplay(); |
| options.masked_card_number_last_four = |
| credit_card.ObfuscatedNumberWithVisibleLastFourDigits(); |
| options.virtual_card = credit_card; |
| // TODO(crbug.com/40927041): Remove CVC from |
| // VirtualCardManualFallbackBubbleOptions. |
| options.virtual_card_cvc = credit_card.cvc(); |
| options.card_image = GetCardImage(credit_card); |
| client().OnVirtualCardDataAvailable(options); |
| } |
| |
| // After a server card is fetched, save its instrument id. |
| client().GetFormDataImporter()->SetFetchedCardInstrumentId( |
| credit_card.instrument_id()); |
| |
| if (credit_card.record_type() == CreditCard::RecordType::kFullServerCard || |
| credit_card.record_type() == CreditCard::RecordType::kVirtualCard) { |
| credit_card_access_manager_->CacheUnmaskedCardInfo(credit_card, |
| credit_card.cvc()); |
| } |
| } |
| |
| std::vector<Suggestion> BrowserAutofillManager::GetProfileSuggestions( |
| const FormData& form, |
| const FormStructure* form_structure, |
| const FormFieldData& trigger_field, |
| const AutofillField* trigger_autofill_field, |
| AutofillSuggestionTriggerSource trigger_source) const { |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| if (trigger_source != |
| AutofillSuggestionTriggerSource::kManualFallbackAddress) { |
| bool should_suppress = |
| client() |
| .GetPersonalDataManager() |
| ->address_data_manager() |
| .AreAddressSuggestionsBlocked( |
| CalculateFormSignature(form), |
| CalculateFieldSignatureForField(trigger_field), form.url); |
| base::UmaHistogramBoolean("Autofill.Suggestion.StrikeSuppression.Address", |
| should_suppress); |
| if (should_suppress) { |
| // If the user already reached the strike limit on this particular field, |
| // address suggestions are suppressed. |
| return {}; |
| } |
| } |
| #endif |
| address_form_event_logger_->OnDidPollSuggestions(trigger_field, |
| signin_state_for_metrics_); |
| const bool triggering_field_is_not_address_field = |
| !form_structure || |
| (trigger_autofill_field && |
| !IsAddressType(trigger_autofill_field->Type().GetStorableType())); |
| |
| if (triggering_field_is_not_address_field) { |
| // Since Autofill was triggered from a field that is not classified as |
| // address, we consider the `field_types` (i.e, the fields found in the |
| // "form") to be a single unclassified field. Note that in this flow it is |
| // not used and only holds semantic value. |
| return suggestion_generator_->GetSuggestionsForProfiles( |
| /*field_types=*/{UNKNOWN_TYPE}, trigger_field, UNKNOWN_TYPE, |
| /*last_targeted_fields=*/std::nullopt, trigger_source); |
| } |
| // If not manual fallback, `form_structure` and `autofill_field` should exist. |
| CHECK(form_structure && trigger_autofill_field); |
| std::optional<FieldTypeSet> last_address_fields_to_fill_for_section = |
| external_delegate_->GetLastFieldTypesToFillForSection( |
| trigger_autofill_field->section()); |
| // Getting the filling-relevant fields so that suggestions are based only on |
| // those fields. Function BrowserAutofillManager::GetFieldFillingSkipReasons |
| // assumes that the passed FormData and FormStructure have the same size. If |
| // it's not the case we just assume as a fallback that all fields are |
| // relevant. |
| base::flat_map<FieldGlobalId, FieldFillingSkipReason> skip_reasons = |
| form.fields.size() == form_structure->field_count() |
| ? form_filler_->GetFieldFillingSkipReasons( |
| form, *form_structure, *trigger_autofill_field, |
| last_address_fields_to_fill_for_section |
| ? GetTargetServerFieldsForTypeAndLastTargetedFields( |
| *last_address_fields_to_fill_for_section, |
| trigger_autofill_field->Type().GetStorableType()) |
| : kAllFieldTypes, |
| /*type_groups_originally_filled=*/std::nullopt, |
| FillingProduct::kAddress, |
| /*skip_unrecognized_autocomplete_fields=*/trigger_source != |
| AutofillSuggestionTriggerSource::kManualFallbackAddress, |
| /*is_refill=*/false, /*is_expired_credit_card=*/false) |
| : base::flat_map<FieldGlobalId, FieldFillingSkipReason>(); |
| FieldTypeSet field_types; |
| for (size_t i = 0; i < form_structure->field_count(); ++i) { |
| const AutofillField* autofill_field = form_structure->field(i); |
| auto it = skip_reasons.find(autofill_field->global_id()); |
| if (it == skip_reasons.end() || |
| it->second == FieldFillingSkipReason::kNotSkipped) { |
| field_types.insert(autofill_field->Type().GetStorableType()); |
| } |
| } |
| return suggestion_generator_->GetSuggestionsForProfiles( |
| field_types, trigger_field, |
| trigger_autofill_field->Type().GetStorableType(), |
| last_address_fields_to_fill_for_section, trigger_source); |
| } |
| |
| std::vector<Suggestion> BrowserAutofillManager::GetCreditCardSuggestions( |
| const FormData& form, |
| const FormFieldData& trigger_field, |
| FieldType trigger_field_type, |
| AutofillSuggestionTriggerSource trigger_source) const { |
| credit_card_form_event_logger_->OnDidPollSuggestions( |
| trigger_field, signin_state_for_metrics_); |
| |
| std::vector<Suggestion> suggestions; |
| bool with_offer = false; |
| bool with_cvc = false; |
| bool is_virtual_card_standalone_cvc_field = false; |
| autofill_metrics::CardMetadataLoggingContext context; |
| if (!IsInAutofillSuggestionsDisabledExperiment()) { |
| if (trigger_field_type == CREDIT_CARD_STANDALONE_VERIFICATION_CODE && |
| !four_digit_combinations_in_dom_.empty()) { |
| base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour> |
| virtual_card_guid_to_last_four_map = |
| GetVirtualCreditCardsForStandaloneCvcField( |
| trigger_field.origin()); |
| if (!virtual_card_guid_to_last_four_map.empty()) { |
| suggestions = |
| suggestion_generator_->GetSuggestionsForVirtualCardStandaloneCvc( |
| trigger_field, context, virtual_card_guid_to_last_four_map); |
| is_virtual_card_standalone_cvc_field = true; |
| } |
| } else { |
| suggestions = suggestion_generator_->GetSuggestionsForCreditCards( |
| trigger_field, trigger_field_type, trigger_source, |
| ShouldShowScanCreditCard(form, trigger_field), |
| ShouldShowCardsFromAccountOption(form, trigger_field, trigger_source), |
| with_offer, with_cvc, context); |
| } |
| } |
| |
| credit_card_form_event_logger_->OnDidFetchSuggestion( |
| suggestions, with_offer, with_cvc, is_virtual_card_standalone_cvc_field, |
| std::move(context)); |
| return suggestions; |
| } |
| |
| base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour> |
| BrowserAutofillManager::GetVirtualCreditCardsForStandaloneCvcField( |
| const url::Origin& origin) const { |
| base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour> |
| virtual_card_guid_to_last_four_map; |
| const std::vector<CreditCard*> cards = client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetCreditCards(); |
| const std::vector<VirtualCardUsageData*> usage_data = |
| client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetVirtualCardUsageData(); |
| |
| for (const CreditCard* credit_card : cards) { |
| // As we only provide virtual card suggestions for standalone CVC fields, |
| // check if the card is an enrolled virtual card. |
| if (credit_card->virtual_card_enrollment_state() != |
| CreditCard::VirtualCardEnrollmentState::kEnrolled) { |
| continue; |
| } |
| // Check if card has virtual card usage data on the url origin. |
| auto usage_data_iter = base::ranges::find_if( |
| usage_data, |
| [&origin, &credit_card](VirtualCardUsageData* virtual_card_usage_data) { |
| return virtual_card_usage_data->instrument_id().value() == |
| credit_card->instrument_id() && |
| virtual_card_usage_data->merchant_origin() == origin; |
| }); |
| |
| // If card has eligible usage data, check if last four is in the url DOM. |
| if (usage_data_iter != usage_data.end()) { |
| VirtualCardUsageData::VirtualCardLastFour virtual_card_last_four = |
| (*usage_data_iter)->virtual_card_last_four(); |
| if (base::Contains(four_digit_combinations_in_dom_, |
| base::UTF16ToUTF8(virtual_card_last_four.value()))) { |
| // Card has usage data on webpage and last four is present in DOM. |
| virtual_card_guid_to_last_four_map.insert( |
| {credit_card->guid(), virtual_card_last_four}); |
| } |
| } |
| } |
| return virtual_card_guid_to_last_four_map; |
| } |
| |
| // TODO(crbug.com/40219607) Eliminate and replace with a listener? |
| // Should we do the same with all the other BrowserAutofillManager events? |
| void BrowserAutofillManager::OnBeforeProcessParsedForms() { |
| has_parsed_forms_ = true; |
| |
| // Record the current sync state to be used for metrics on this page. |
| signin_state_for_metrics_ = client() |
| .GetPersonalDataManager() |
| ->payments_data_manager() |
| .GetPaymentsSigninStateForMetrics(); |
| |
| // Setup the url for metrics that we will collect for this form. |
| form_interactions_ukm_logger()->OnFormsParsed(client().GetUkmSourceId()); |
| } |
| |
| void BrowserAutofillManager::OnFormProcessed( |
| const FormData& form, |
| const FormStructure& form_structure) { |
| // If a standalone cvc field is found in the form, query the DOM for last four |
| // combinations. Used to search for the virtual card last four for a virtual |
| // card saved on file of a merchant webpage. |
| if (base::FeatureList::IsEnabled( |
| features::kAutofillParseVcnCardOnFileStandaloneCvcFields)) { |
| auto contains_standalone_cvc_field = |
| base::ranges::any_of(form_structure.fields(), [](const auto& field) { |
| return field->Type().GetStorableType() == |
| CREDIT_CARD_STANDALONE_VERIFICATION_CODE; |
| }); |
| if (contains_standalone_cvc_field) { |
| FetchPotentialCardLastFourDigitsCombinationFromDOM(); |
| } |
| } |
| if (data_util::ContainsPhone(data_util::DetermineGroups(form_structure))) { |
| has_observed_phone_number_field_ = true; |
| } |
| // TODO(crbug.com/41405154): avoid logging developer engagement multiple |
| // times for a given form if it or other forms on the page are dynamic. |
| LogDeveloperEngagementUkm(client().GetUkmRecorder(), |
| client().GetUkmSourceId(), form_structure); |
| |
| for (const auto& field : form_structure) { |
| if (field->Type().html_type() == HtmlFieldType::kOneTimeCode) { |
| has_observed_one_time_code_field_ = true; |
| break; |
| } |
| } |
| // Log the type of form that was parsed. |
| DenseSet<FormType> form_types = form_structure.GetFormTypes(); |
| bool card_form = base::Contains(form_types, FormType::kCreditCardForm); |
| bool address_form = base::Contains(form_types, FormType::kAddressForm); |
| if (card_form) { |
| credit_card_form_event_logger_->OnDidParseForm(form_structure); |
| } |
| if (address_form) { |
| address_form_event_logger_->OnDidParseForm(form_structure); |
| } |
| // `autofill_optimization_guide_` is not present on unsupported platforms. |
| if (auto* autofill_optimization_guide = |
| client().GetAutofillOptimizationGuide()) { |
| // Initiate necessary pre-processing based on the forms and fields that are |
| // parsed, as well as the information that the user has saved in the web |
| // database based on `client().GetPersonalDataManager()`. |
| autofill_optimization_guide->OnDidParseForm( |
| form_structure, client().GetPersonalDataManager()); |
| } |
| // If a form with the same FormGlobalId was previously filled, the structure |
| // of the form changed, and there has not been a refill attempt on that form |
| // yet, start the process of triggering a refill. |
| if (form_filler_->ShouldTriggerRefill(form_structure, |
| RefillTriggerReason::kFormChanged)) { |
| form_filler_->ScheduleRefill( |
| form, form_structure, |
| {.trigger_source = AutofillTriggerSource::kFormsSeen}); |
| } |
| } |
| |
| void BrowserAutofillManager::UpdateInitialInteractionTimestamp( |
| const TimeTicks& interaction_timestamp) { |
| if (initial_interaction_timestamp_.is_null() || |
| interaction_timestamp < initial_interaction_timestamp_) { |
| initial_interaction_timestamp_ = interaction_timestamp; |
| } |
| } |
| |
| void BrowserAutofillManager::GetAvailableSuggestions( |
| const FormData& form, |
| const FormFieldData& field, |
| AutofillSuggestionTriggerSource trigger_source, |
| std::vector<Suggestion>* suggestions, |
| SuggestionsContext* context) { |
| DCHECK(suggestions); |
| DCHECK(context); |
| |
| // Compose suggestions are not populated in this method. |
| if (IsTriggerSourceOnlyRelevantForCompose(trigger_source)) { |
| return; |
| } |
| |
| if (trigger_source == |
| AutofillSuggestionTriggerSource::kManualFallbackPlusAddresses) { |
| *suggestions = client().GetPlusAddressDelegate()->GetSuggestions( |
| client().GetLastCommittedPrimaryMainFrameOrigin(), |
| client().IsOffTheRecord(), field.value(), trigger_source); |
| return; |
| } |
| |
| // Need to refresh models before using the form_event_loggers. |
| RefreshDataModels(); |
| |
| bool got_autofillable_form = |
| GetCachedFormAndField(form, field, &context->form_structure, |
| &context->focused_field) && |
| // Don't send suggestions or track forms that should not be parsed. |
| context->form_structure->ShouldBeParsed(); |
| |
| if (!ShouldShowSuggestionsForAutocompleteUnrecognizedFields(trigger_source) && |
| got_autofillable_form && |
| context->focused_field->ShouldSuppressSuggestionsAndFillingByDefault()) { |
| // Pre-`AutofillPredictionsForAutocompleteUnrecognized`, autocomplete |
| // suggestions were shown if all types of the form were suppressed or |
| // unknown. If at least a single field had predictions (and the form was |
| // thus considered autofillable), autocomplete suggestions were suppressed |
| // for fields with a suppressed prediction. |
| // To retain this behavior, the `suppress_reason` is only set if the form |
| // contains a field that triggers (non-fallback) suggestions. |
| // By not setting it, the autocomplete suggestion logic downstream is |
| // triggered, since no Autofill `suggestions` are available. |
| if (!base::ranges::all_of(*context->form_structure, [](const auto& field) { |
| return field->ShouldSuppressSuggestionsAndFillingByDefault() || |
| field->Type().GetStorableType() == UNKNOWN_TYPE; |
| })) { |
| context->suppress_reason = SuppressReason::kAutocompleteUnrecognized; |
| } |
| suggestions->clear(); |
| return; |
| } |
| if (got_autofillable_form) { |
| auto* logger = GetEventFormLogger(*context->focused_field); |
| if (logger) { |
| logger->OnDidInteractWithAutofillableForm(*(context->form_structure), |
| signin_state_for_metrics_); |
| } |
| } |
| context->filling_product = GetPreferredSuggestionFillingProduct( |
| got_autofillable_form ? context->focused_field->Type().GetStorableType() |
| : UNKNOWN_TYPE, |
| trigger_source); |
| // If the feature is enabled and this is a mixed content form, we show a |
| // warning message and don't offer autofill. The warning is shown even if |
| // there are no autofill suggestions available. |
| if (IsFormMixedContent(client(), form) && |
| client().GetPrefs()->FindPreference( |
| ::prefs::kMixedFormsWarningsEnabled) && |
| client().GetPrefs()->GetBoolean(::prefs::kMixedFormsWarningsEnabled)) { |
| suggestions->clear(); |
| // If the user begins typing, we interpret that as dismissing the warning. |
| // No suggestions are allowed, but the warning is no longer shown. |
| if (field.DidUserType()) { |
| context->suppress_reason = SuppressReason::kInsecureForm; |
| } else { |
| Suggestion warning_suggestion( |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_MIXED_FORM)); |
| warning_suggestion.type = SuggestionType::kMixedFormMessage; |
| suggestions->emplace_back(warning_suggestion); |
| } |
| return; |
| } |
| context->is_context_secure = !IsFormNonSecure(form); |
| |
| context->is_autofill_available = |
| IsAutofillEnabled() && |
| (IsAutofillManuallyTriggered(trigger_source) || got_autofillable_form); |
| if (!context->is_autofill_available) { |
| return; |
| } |
| |
| if (context->filling_product == FillingProduct::kCreditCard) { |
| FieldType trigger_field_type = |
| context->focused_field |
| ? context->focused_field->Type().GetStorableType() |
| : UNKNOWN_TYPE; |
| *suggestions = GetCreditCardSuggestions(form, field, trigger_field_type, |
| trigger_source); |
| } else if (context->filling_product == FillingProduct::kAddress) { |
| // Profile suggestions fill ac=unrecognized fields only when triggered |
| // through manual fallbacks. As such, suggestion labels differ depending on |
| // the `trigger_source`. |
| *suggestions = |
| GetProfileSuggestions(form, context->form_structure, field, |
| context->focused_field, trigger_source); |
| if (context->focused_field && |
| context->focused_field->Type().group() == FieldTypeGroup::kEmail && |
| client().GetPlusAddressDelegate()) { |
| std::vector<Suggestion> plus_address_suggestions = |
| client().GetPlusAddressDelegate()->GetSuggestions( |
| client().GetLastCommittedPrimaryMainFrameOrigin(), |
| client().IsOffTheRecord(), field.value(), trigger_source); |
| suggestions->insert( |
| suggestions->cbegin(), |
| std::make_move_iterator(plus_address_suggestions.begin()), |
| std::make_move_iterator(plus_address_suggestions.end())); |
| } |
| } |
| |
| // Ablation experiment |
| if (context->filling_product == FillingProduct::kAddress || |
| context->filling_product == FillingProduct::kCreditCard) { |
| FormTypeForAblationStudy form_type = |
| context->filling_product == FillingProduct::kCreditCard |
| ? FormTypeForAblationStudy::kPayment |
| : FormTypeForAblationStudy::kAddress; |
| // If ablation_group is AblationGroup::kDefault or AblationGroup::kControl, |
| // no ablation happens in the following. |
| AblationGroup ablation_group = client().GetAblationStudy().GetAblationGroup( |
| client().GetLastCommittedPrimaryMainFrameURL(), form_type); |
| context->ablation_group = ablation_group; |
| // Note that we don't set the ablation group if there are no suggestions. |
| // In that case we stick to kDefault. |
| context->conditional_ablation_group = |
| !suggestions->empty() ? ablation_group : AblationGroup::kDefault; |
| |
| // In both cases (credit card and address forms), we inform the other event |
| // logger also about the ablation. |
| // This prevents for example that for an encountered address form we log a |
| // sample Autofill.Funnel.ParsedAsType.CreditCard = 0 (which would be |
| // recorded by the credit_card_form_event_logger_). For the complementary |
| // event logger, the conditional ablation status is logged as kDefault to |
| // not imply that data would be filled without ablation. |
| if (context->filling_product == FillingProduct::kCreditCard) { |
| credit_card_form_event_logger_->SetAblationStatus( |
| context->ablation_group, context->conditional_ablation_group); |
| address_form_event_logger_->SetAblationStatus(context->ablation_group, |
| AblationGroup::kDefault); |
| } else if (context->filling_product == FillingProduct::kAddress) { |
| address_form_event_logger_->SetAblationStatus( |
| context->ablation_group, context->conditional_ablation_group); |
| credit_card_form_event_logger_->SetAblationStatus( |
| context->ablation_group, AblationGroup::kDefault); |
| } |
| |
| if (!suggestions->empty() && ablation_group == AblationGroup::kAblation) { |
| // Logic for disabling/ablating autofill. |
| context->suppress_reason = SuppressReason::kAblation; |
| suggestions->clear(); |
| return; |
| } |
| } |
| if (suggestions->empty() || |
| context->filling_product != FillingProduct::kCreditCard) { |
| return; |
| } |
| // Don't provide credit card suggestions for non-secure pages, but do |
| // provide them for secure pages with passive mixed content (see |
| // implementation of IsContextSecure). |
| if (!context->is_context_secure) { |
| // Replace the suggestion content with a warning message explaining why |
| // Autofill is disabled for a website. The string is different if the |
| // credit card autofill HTTP warning experiment is enabled. |
| Suggestion warning_suggestion( |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION)); |
| warning_suggestion.type = |
| SuggestionType::kInsecureContextPaymentDisabledMessage; |
| suggestions->assign(1, warning_suggestion); |
| } |
| } |
| |
| autofill_metrics::FormEventLoggerBase* |
| BrowserAutofillManager::GetEventFormLogger(const AutofillField& field) const { |
| if (field.ShouldSuppressSuggestionsAndFillingByDefault()) { |
| // Ignore ac=unrecognized fields in key metrics. |
| return nullptr; |
| } |
| switch (FieldTypeGroupToFormType(field.Type().group())) { |
| case FormType::kAddressForm: |
| return address_form_event_logger_.get(); |
| case FormType::kCreditCardForm: |
| return credit_card_form_event_logger_.get(); |
| case FormType::kPasswordForm: |
| case FormType::kUnknownFormType: |
| return nullptr; |
| } |
| NOTREACHED_NORETURN(); |
| } |
| |
| void BrowserAutofillManager::PreProcessStateMatchingTypes( |
| const std::vector<AutofillProfile>& profiles, |
| FormStructure* form_structure) { |
| for (const auto& profile : profiles) { |
| std::optional<AlternativeStateNameMap::CanonicalStateName> |
| canonical_state_name_from_profile = |
| profile.GetAddress().GetCanonicalizedStateName(); |
| |
| if (!canonical_state_name_from_profile) { |
| continue; |
| } |
| |
| const std::u16string& country_code = |
| profile.GetInfo(AutofillType(HtmlFieldType::kCountryCode), app_locale_); |
| |
| for (auto& field : *form_structure) { |
| if (field->state_is_a_matching_type()) { |
| continue; |
| } |
| |
| std::optional<AlternativeStateNameMap::CanonicalStateName> |
| canonical_state_name_from_text = |
| AlternativeStateNameMap::GetCanonicalStateName( |
| base::UTF16ToUTF8(country_code), field->value()); |
| |
| if (canonical_state_name_from_text && |
| canonical_state_name_from_text.value() == |
| canonical_state_name_from_profile.value()) { |
| field->set_state_is_a_matching_type(); |
| } |
| } |
| } |
| } |
| |
| void BrowserAutofillManager::ReportAutofillWebOTPMetrics(bool used_web_otp) { |
| // It's possible that a frame without any form uses WebOTP. e.g. a server may |
| // send the verification code to a phone number that was collected beforehand |
| // and uses the WebOTP API for authentication purpose without user manually |
| // entering the code. |
| if (!has_parsed_forms() && !used_web_otp) { |
| return; |
| } |
| |
| if (has_observed_phone_number_field()) { |
| phone_collection_metric_state_ |= phone_collection_metric::kPhoneCollected; |
| } |
| if (has_observed_one_time_code_field()) { |
| phone_collection_metric_state_ |= phone_collection_metric::kOTCUsed; |
| } |
| if (used_web_otp) { |
| phone_collection_metric_state_ |= phone_collection_metric::kWebOTPUsed; |
| } |
| |
| ukm::UkmRecorder* recorder = client().GetUkmRecorder(); |
| ukm::SourceId source_id = client().GetUkmSourceId(); |
| AutofillMetrics::LogWebOTPPhoneCollectionMetricStateUkm( |
| recorder, source_id, phone_collection_metric_state_); |
| |
| base::UmaHistogramEnumeration( |
| "Autofill.WebOTP.PhonePlusWebOTPPlusOTC", |
| static_cast<PhoneCollectionMetricState>(phone_collection_metric_state_)); |
| } |
| |
| void BrowserAutofillManager::ProcessFieldLogEventsInForm( |
| const FormStructure& form_structure) { |
| // TODO(crbug.com/40225658): Log metrics if at least one field in the form was |
| // classified as a certain type. |
| LogEventCountsUMAMetric(form_structure); |
| |
| // ShouldUploadUkm reduces the UKM load by ignoring e.g. search boxes at best |
| // effort. |
| bool should_upload_ukm = ShouldRecordUkm() && ShouldUploadUkm(form_structure); |
| |
| for (const auto& autofill_field : form_structure) { |
| if (should_upload_ukm) { |
| form_interactions_ukm_logger()->LogAutofillFieldInfoAtFormRemove( |
| form_structure, *autofill_field, |
| AutocompleteStateForSubmittedField(*autofill_field)); |
| } |
| |
| // Clear log events. |
| // Not conditions on kAutofillLogUKMEventsWithSamplingOnSession because |
| // there may be other reasons to log events. |
| autofill_field->ClearLogEvents(); |
| } |
| |
| // Log FormSummary UKM event. |
| if (should_upload_ukm) { |
| AutofillMetrics::FormEventSet form_events; |
| form_events.insert_all( |
| address_form_event_logger_->GetFormEvents(form_structure.global_id())); |
| form_events.insert_all(credit_card_form_event_logger_->GetFormEvents( |
| form_structure.global_id())); |
| form_interactions_ukm_logger()->LogAutofillFormSummaryAtFormRemove( |
| form_structure, form_events, initial_interaction_timestamp_, |
| form_submitted_timestamp_); |
| } |
| } |
| |
| bool BrowserAutofillManager::ShouldUploadUkm( |
| const FormStructure& form_structure) { |
| if (!form_structure.ShouldBeParsed()) { |
| return false; |
| } |
| |
| // Return true if the field is a visible text input field which has predicted |
| // types from heuristics or the server. |
| auto is_focusable_predicted_text_field = |
| [](const std::unique_ptr<AutofillField>& field) { |
| return field->IsTextInputElement() && field->IsFocusable() && |
| ((field->server_type() != NO_SERVER_DATA && |
| field->server_type() != UNKNOWN_TYPE) || |
| field->heuristic_type() != UNKNOWN_TYPE || |
| field->html_type() != HtmlFieldType::kUnspecified); |
| }; |
| |
| size_t num_text_fields = base::ranges::count_if( |
| form_structure.fields(), is_focusable_predicted_text_field); |
| if (num_text_fields == 0) { |
| return false; |
| } |
| |
| // If the form contains a single text field and this contains the string |
| // "search" in its name/id/placeholder, the function return false and the form |
| // is not recorded into UKM. The form is considered a search box. |
| if (num_text_fields == 1) { |
| auto it = base::ranges::find_if(form_structure.fields(), |
| is_focusable_predicted_text_field); |
| if (base::ToLowerASCII((*it)->placeholder()).find(u"search") != |
| std::string::npos || |
| base::ToLowerASCII((*it)->name()).find(u"search") != |
| std::string::npos || |
| base::ToLowerASCII((*it)->label()).find(u"search") != |
| std::string::npos || |
| base::ToLowerASCII((*it)->aria_label()).find(u"search") != |
| std::string::npos) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void BrowserAutofillManager::LogEventCountsUMAMetric( |
| const FormStructure& form_structure) { |
| size_t num_ask_for_values_to_fill_event = 0; |
| size_t num_trigger_fill_event = 0; |
| size_t num_fill_event = 0; |
| size_t num_typing_event = 0; |
| size_t num_heuristic_prediction_event = 0; |
| size_t num_autocomplete_attribute_event = 0; |
| size_t num_server_prediction_event = 0; |
| size_t num_rationalization_event = 0; |
| |
| for (const auto& autofill_field : form_structure) { |
| for (const auto& log_event : autofill_field->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 (absl::holds_alternative<AskForValuesToFillFieldLogEvent>(log_event)) { |
| ++num_ask_for_values_to_fill_event; |
| } else if (absl::holds_alternative<TriggerFillFieldLogEvent>(log_event)) { |
| ++num_trigger_fill_event; |
| } else if (absl::holds_alternative<FillFieldLogEvent>(log_event)) { |
| ++num_fill_event; |
| } else if (absl::holds_alternative<TypingFieldLogEvent>(log_event)) { |
| ++num_typing_event; |
| } else if (absl::holds_alternative<HeuristicPredictionFieldLogEvent>( |
| log_event)) { |
| ++num_heuristic_prediction_event; |
| } else if (absl::holds_alternative<AutocompleteAttributeFieldLogEvent>( |
| log_event)) { |
| ++num_autocomplete_attribute_event; |
| } else if (absl::holds_alternative<ServerPredictionFieldLogEvent>( |
| log_event)) { |
| ++num_server_prediction_event; |
| } else if (absl::holds_alternative<RationalizationFieldLogEvent>( |
| log_event)) { |
| ++num_rationalization_event; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| size_t total_num_log_events = |
| num_ask_for_values_to_fill_event + num_trigger_fill_event + |
| num_fill_event + num_typing_event + num_heuristic_prediction_event + |
| num_autocomplete_attribute_event + num_server_prediction_event + |
| num_rationalization_event; |
| // Record the number of each type of log events into UMA to decide if we need |
| // to clear them before the form is submitted or destroyed. |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.AskForValuesToFillEvent", |
| num_ask_for_values_to_fill_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.TriggerFillEvent", |
| num_trigger_fill_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.FillEvent", num_fill_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.TypingEvent", num_typing_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.HeuristicPredictionEvent", |
| num_heuristic_prediction_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.AutocompleteAttributeEvent", |
| num_autocomplete_attribute_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.ServerPredictionEvent", |
| num_server_prediction_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.RationalizationEvent", |
| num_rationalization_event); |
| UMA_HISTOGRAM_COUNTS_10000("Autofill.LogEvent.All", total_num_log_events); |
| } |
| |
| void BrowserAutofillManager::SetFastCheckoutRunId( |
| FieldTypeGroup field_type_group, |
| int64_t run_id) { |
| switch (FieldTypeGroupToFormType(field_type_group)) { |
| case FormType::kAddressForm: |
| address_form_event_logger_->SetFastCheckoutRunId(run_id); |
| return; |
| case FormType::kCreditCardForm: |
| credit_card_form_event_logger_->SetFastCheckoutRunId(run_id); |
| break; |
| case FormType::kPasswordForm: |
| case FormType::kUnknownFormType: |
| // FastCheckout only supports address and credit card forms. |
| NOTREACHED(); |
| } |
| } |
| |
| } // namespace autofill |