diff options
Diffstat (limited to 'chromium/components/autofill/core/browser/form_data_importer.cc')
-rw-r--r-- | chromium/components/autofill/core/browser/form_data_importer.cc | 444 |
1 files changed, 309 insertions, 135 deletions
diff --git a/chromium/components/autofill/core/browser/form_data_importer.cc b/chromium/components/autofill/core/browser/form_data_importer.cc index f696d514f28..e87e427c1e8 100644 --- a/chromium/components/autofill/core/browser/form_data_importer.cc +++ b/chromium/components/autofill/core/browser/form_data_importer.cc @@ -16,6 +16,7 @@ #include <utility> #include "base/bind.h" +#include "base/containers/contains.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" @@ -23,11 +24,13 @@ #include "components/autofill/core/browser/autofill_client.h" #include "components/autofill/core/browser/autofill_type.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/autofill_structured_address_name.h" #include "components/autofill/core/browser/data_model/autofill_structured_address_utils.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/form_structure.h" +#include "components/autofill/core/browser/form_types.h" #include "components/autofill/core/browser/geo/autofill_country.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h" #include "components/autofill/core/browser/logging/log_manager.h" @@ -35,6 +38,7 @@ #include "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h" #include "components/autofill/core/browser/personal_data_manager.h" #include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/autofill_internals/log_message.h" #include "components/autofill/core/common/autofill_internals/logging_scope.h" @@ -107,7 +111,8 @@ bool IsValidFieldTypeAndValue(const ServerFieldTypeSet types_seen, bool IsMinimumAddress(const AutofillProfile& profile, const std::string& predicted_country_code, const std::string& app_locale, - LogBuffer* import_log_buffer) { + LogBuffer* import_log_buffer, + bool collect_metrics) { AutofillCountry country(predicted_country_code, app_locale); // Include the details of the country to the log. @@ -116,9 +121,8 @@ bool IsMinimumAddress(const AutofillProfile& profile, // Check the |ADDRESS_HOME_LINE1| requirement. bool is_line1_missing = false; - if (country.requires_line1() && - profile.GetRawInfo(ADDRESS_HOME_LINE1).empty() && - profile.GetRawInfo(ADDRESS_HOME_STREET_NAME).empty()) { + if (country.requires_line1() && !profile.HasRawInfo(ADDRESS_HOME_LINE1) && + !profile.HasRawInfo(ADDRESS_HOME_STREET_NAME)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed << "Missing required ADDRESS_HOME_LINE1." << CTag{}; @@ -128,8 +132,7 @@ bool IsMinimumAddress(const AutofillProfile& profile, // Check the |ADDRESS_HOME_CITY| requirement. bool is_city_missing = false; - if (country.requires_city() && - profile.GetRawInfo(ADDRESS_HOME_CITY).empty()) { + if (country.requires_city() && !profile.HasRawInfo(ADDRESS_HOME_CITY)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed << "Missing required ADDRESS_HOME_CITY." << CTag{}; @@ -139,8 +142,7 @@ bool IsMinimumAddress(const AutofillProfile& profile, // Check the |ADDRESS_HOME_STATE| requirement. bool is_state_missing = false; - if (country.requires_state() && - profile.GetRawInfo(ADDRESS_HOME_STATE).empty()) { + if (country.requires_state() && !profile.HasRawInfo(ADDRESS_HOME_STATE)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed << "Missing required ADDRESS_HOME_STATE." << CTag{}; @@ -150,7 +152,7 @@ bool IsMinimumAddress(const AutofillProfile& profile, // Check the |ADDRESS_HOME_ZIP| requirement. bool is_zip_missing = false; - if (country.requires_zip() && profile.GetRawInfo(ADDRESS_HOME_ZIP).empty()) { + if (country.requires_zip() && !profile.HasRawInfo(ADDRESS_HOME_ZIP)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed << "Missing required ADDRESS_HOME_ZIP." << CTag{}; @@ -160,8 +162,8 @@ bool IsMinimumAddress(const AutofillProfile& profile, bool is_zip_or_state_requirement_violated = false; if (country.requires_zip_or_state() && - profile.GetRawInfo(ADDRESS_HOME_ZIP).empty() && - profile.GetRawInfo(ADDRESS_HOME_STATE).empty()) { + !profile.HasRawInfo(ADDRESS_HOME_ZIP) && + !profile.HasRawInfo(ADDRESS_HOME_STATE)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed @@ -173,8 +175,8 @@ bool IsMinimumAddress(const AutofillProfile& profile, bool is_line1_or_house_number_violated = false; if (country.requires_line1_or_house_number() && - profile.GetRawInfo(ADDRESS_HOME_LINE1).empty() && - profile.GetRawInfo(ADDRESS_HOME_HOUSE_NUMBER).empty()) { + !profile.HasRawInfo(ADDRESS_HOME_LINE1) && + !profile.HasRawInfo(ADDRESS_HOME_HOUSE_NUMBER)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed @@ -185,29 +187,33 @@ bool IsMinimumAddress(const AutofillProfile& profile, } // Collect metrics regarding the requirements. - AutofillMetrics::LogAddressFormImportRequirementMetric( - is_line1_missing ? AddressImportRequirement::LINE1_REQUIREMENT_VIOLATED - : AddressImportRequirement::LINE1_REQUIREMENT_FULFILLED); - - AutofillMetrics::LogAddressFormImportRequirementMetric( - is_city_missing ? AddressImportRequirement::CITY_REQUIREMENT_VIOLATED - : AddressImportRequirement::CITY_REQUIREMENT_FULFILLED); - - AutofillMetrics::LogAddressFormImportRequirementMetric( - is_state_missing ? AddressImportRequirement::STATE_REQUIREMENT_VIOLATED - : AddressImportRequirement::STATE_REQUIREMENT_FULFILLED); - - AutofillMetrics::LogAddressFormImportRequirementMetric( - is_zip_missing ? AddressImportRequirement::ZIP_REQUIREMENT_VIOLATED - : AddressImportRequirement::ZIP_REQUIREMENT_FULFILLED); - - AutofillMetrics::LogAddressFormImportRequirementMetric( - is_zip_or_state_requirement_violated - ? AddressImportRequirement::ZIP_OR_STATE_REQUIREMENT_VIOLATED - : AddressImportRequirement::ZIP_OR_STATE_REQUIREMENT_FULFILLED); - - AutofillMetrics::LogAddressFormImportCountrySpecificFieldRequirementsMetric( - is_zip_missing, is_state_missing, is_city_missing, is_line1_missing); + if (collect_metrics) { + AutofillMetrics::LogAddressFormImportRequirementMetric( + is_line1_missing + ? AddressImportRequirement::LINE1_REQUIREMENT_VIOLATED + : AddressImportRequirement::LINE1_REQUIREMENT_FULFILLED); + + AutofillMetrics::LogAddressFormImportRequirementMetric( + is_city_missing ? AddressImportRequirement::CITY_REQUIREMENT_VIOLATED + : AddressImportRequirement::CITY_REQUIREMENT_FULFILLED); + + AutofillMetrics::LogAddressFormImportRequirementMetric( + is_state_missing + ? AddressImportRequirement::STATE_REQUIREMENT_VIOLATED + : AddressImportRequirement::STATE_REQUIREMENT_FULFILLED); + + AutofillMetrics::LogAddressFormImportRequirementMetric( + is_zip_missing ? AddressImportRequirement::ZIP_REQUIREMENT_VIOLATED + : AddressImportRequirement::ZIP_REQUIREMENT_FULFILLED); + + AutofillMetrics::LogAddressFormImportRequirementMetric( + is_zip_or_state_requirement_violated + ? AddressImportRequirement::ZIP_OR_STATE_REQUIREMENT_VIOLATED + : AddressImportRequirement::ZIP_OR_STATE_REQUIREMENT_FULFILLED); + + AutofillMetrics::LogAddressFormImportCountrySpecificFieldRequirementsMetric( + is_zip_missing, is_state_missing, is_city_missing, is_line1_missing); + } // Return true if all requirements are fulfilled. return !(is_line1_missing || is_city_missing || is_state_missing || @@ -245,9 +251,14 @@ FormDataImporter::FormDataImporter(AutofillClient* client, std::make_unique<VirtualCardEnrollmentManager>(personal_data_manager, payments_client, client)) { + if (personal_data_manager_) + personal_data_manager_->AddObserver(this); } -FormDataImporter::~FormDataImporter() = default; +FormDataImporter::~FormDataImporter() { + if (personal_data_manager_) + personal_data_manager_->RemoveObserver(this); +}; void FormDataImporter::ImportFormData(const FormStructure& submitted_form, bool profile_autofill_enabled, @@ -337,13 +348,6 @@ bool FormDataImporter::IsValidLearnableProfile( const std::string& predicted_country_code, const std::string& app_locale, LogBuffer* import_log_buffer) { - // Check if the imported address qualifies as a minimum address. - bool is_not_minimum_address = false; - if (!IsMinimumAddress(profile, predicted_country_code, app_locale, - import_log_buffer)) { - is_not_minimum_address = true; - } - // Check that the email address is valid if it is supplied. bool is_email_invalid = false; std::u16string email = profile.GetRawInfo(EMAIL_ADDRESS); @@ -394,8 +398,85 @@ bool FormDataImporter::IsValidLearnableProfile( : AddressImportRequirement::ZIP_VALID_REQUIREMENT_FULFILLED); // Return true if none of the requirements is violated. - return !(is_not_minimum_address || is_email_invalid || is_state_invalid || - is_zip_invalid); + return !(is_email_invalid || is_state_invalid || is_zip_invalid); +} + +bool FormDataImporter::ComplementCountry( + AutofillProfile& profile, + const std::string& predicted_country_code) { + // TODO(crbug.com/1297032): Cleanup `kAutofillComplementCountryCodeOnImport` + // check when launched. + bool should_complement_country = + !profile.HasRawInfo(ADDRESS_HOME_COUNTRY) && + base::FeatureList::IsEnabled( + features::kAutofillAddressProfileSavePrompt) && + base::FeatureList::IsEnabled( + features::kAutofillComplementCountryCodeOnImport); + return should_complement_country && + profile.SetInfoWithVerificationStatus( + AutofillType(ADDRESS_HOME_COUNTRY), + base::ASCIIToUTF16(predicted_country_code), app_locale_, + VerificationStatus::kObserved); +} + +bool FormDataImporter::SetPhoneNumber( + AutofillProfile& profile, + PhoneNumber::PhoneCombineHelper& combined_phone, + const std::string& predicted_country_code) { + if (combined_phone.IsEmpty()) { + return true; + } + const std::string predicted_country_code_without_variation = + GetPredictedCountryCode(profile, "", app_locale_, nullptr); + auto SetWithRegion = [&](const std::string& region) { + std::u16string constructed_number; + // `ParseNumber()` implicity accepts both a country code and a locale. This + // will be refactored with crbug.com/1296077. The parameter for + // `SetInfoWithVerificationStatus()` has to be consistent with + // `ParseNumber()`. + return combined_phone.ParseNumber(profile, region, &constructed_number) && + profile.SetInfoWithVerificationStatus( + PHONE_HOME_WHOLE_NUMBER, constructed_number, + /*app_locale=*/region, VerificationStatus::kObserved); + }; + // If `AutofillConsiderVariationCountryCodeForPhoneNumbers` is enabled, + // a consistent country code prediction for addresses and phone numbers is + // used. Otherwise the variation service state is not considered for phone + // numbers. This makes a difference, if the country code cannot be found + // in the `profile`. + // TODO(crbug.com/1295721): Cleanup when launched. + bool success_with_locale = SetWithRegion(app_locale_); + if (predicted_country_code == predicted_country_code_without_variation || + !base::FeatureList::IsEnabled( + features::kAutofillConsiderVariationCountryCodeForPhoneNumbers)) + return success_with_locale; + // AutofillConsiderVariationCountryCodeForPhoneNumbers is enabled and makes + // a difference for the region used. Parse the number with the new region and + // check if this actually changes the parsing outcome to measure the impact. + bool success_with_variation_code = SetWithRegion(predicted_country_code); + AutofillMetrics::LogPhoneNumberImportParsingResult( + success_with_variation_code, success_with_locale); + // Keep the current state, even if the parsing worked with the locale but not + // the variation country code. Because once + // `AutofillConsiderVariationCountryCodeForPhoneNumbers` is launched, only + // region = `predicted_country_code` will be used for parsing. + return success_with_variation_code; +} + +void FormDataImporter::RemoveInaccessibleProfileValues( + AutofillProfile& profile, + const std::string& predicted_country_code) { + if (base::FeatureList::IsEnabled( + features::kAutofillRemoveInaccessibleProfileValues)) { + const ServerFieldTypeSet inaccessible_fields = + profile.FindInaccessibleProfileValues(predicted_country_code); + profile.ClearFields(inaccessible_fields); + AutofillMetrics::LogRemovedSettingInaccessibleFields( + !inaccessible_fields.empty()); + for (const ServerFieldType inaccessible_field : inaccessible_fields) { + AutofillMetrics::LogRemovedSettingInaccessibleField(inaccessible_field); + } + } } void FormDataImporter::CacheFetchedVirtualCard( @@ -488,20 +569,20 @@ bool FormDataImporter::ImportAddressProfiles( // Run the import on the union of the section if the import was not // successful and if there is more than one section. if (num_complete_profiles > 0) { - AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::LogAddressFormImportStatusMetric( AutofillMetrics::AddressProfileImportStatusMetric::REGULAR_IMPORT); } else if (sections.size() > 1) { // Try to import by combining all sections. if (ImportAddressProfileForSection(form, "", import_candidates, &import_log_buffer)) { num_complete_profiles++; - AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::LogAddressFormImportStatusMetric( AutofillMetrics::AddressProfileImportStatusMetric:: SECTION_UNION_IMPORT); } } if (num_complete_profiles == 0) { - AutofillMetrics::LogAddressFormImportStatustMetric( + AutofillMetrics::LogAddressFormImportStatusMetric( AutofillMetrics::AddressProfileImportStatusMetric::NO_IMPORT); } } @@ -552,6 +633,9 @@ bool FormDataImporter::ImportAddressProfileForSection( // Metadata about the way we construct candidate_profile. ProfileImportMetadata import_metadata; + // Tracks if any of the fields belongs to FormType::kAddressForm. + bool has_address_related_fields = false; + // Go through each |form| field and attempt to constitute a valid profile. for (const auto& field : form) { // Reject fields that are not within the specified |section|. @@ -579,6 +663,9 @@ bool FormDataImporter::ImportAddressProfileForSection( if (field_type.group() == FieldTypeGroup::kCreditCard) continue; + has_address_related_fields |= + FieldTypeGroupToFormType(field_type.group()) == FormType::kAddressForm; + // There can be multiple email fields (e.g. in the case of 'confirm email' // fields) but they must all contain the same value, else the profile is // invalid. @@ -632,7 +719,7 @@ bool FormDataImporter::ImportAddressProfileForSection( // Reject profiles with invalid country information. if (server_field_type == ADDRESS_HOME_COUNTRY && - candidate_profile.GetRawInfo(ADDRESS_HOME_COUNTRY).empty()) { + !candidate_profile.HasRawInfo(ADDRESS_HOME_COUNTRY)) { // The country code was not successfully determined from the value in // the country field. This can be caused by a localization that does not // match the |app_locale|. Try setting the value again using the @@ -649,7 +736,7 @@ bool FormDataImporter::ImportAddressProfileForSection( field_type, value, page_language, VerificationStatus::kObserved); } // Check if the country code was still not determined correctly. - if (candidate_profile.GetRawInfo(ADDRESS_HOME_COUNTRY).empty()) { + if (!candidate_profile.HasRawInfo(ADDRESS_HOME_COUNTRY)) { if (import_log_buffer) { *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed << "Missing country." << CTag{}; @@ -659,93 +746,68 @@ bool FormDataImporter::ImportAddressProfileForSection( } } - const std::string predicted_country_code = GetPredictedCountryCode( - candidate_profile, client_->GetVariationConfigCountryCode(), app_locale_, - import_log_buffer); - // If the form doesn't contain a country field, complement the profile using - // |predicted_country_code|. To give users the opportunity to edit, this is - // only done with explicit save prompts enabled. - // TODO(crbug.com/1297032): Cleanup kAutofillComplementCountryCodeOnImport - // check when launched. - if (!has_invalid_country && - candidate_profile.GetRawInfo(ADDRESS_HOME_COUNTRY).empty() && - base::FeatureList::IsEnabled( - features::kAutofillAddressProfileSavePrompt) && - base::FeatureList::IsEnabled( - features::kAutofillComplementCountryCodeOnImport)) { - candidate_profile.SetInfoWithVerificationStatus( - AutofillType(ADDRESS_HOME_COUNTRY), - base::ASCIIToUTF16(predicted_country_code), app_locale_, - VerificationStatus::kObserved); - import_metadata.did_complement_country = true; - } - - // Construct the phone number. Reject the whole profile if the number is - // invalid, unless |kAutofillRemoveInvalidPhoneNumberOnImport| is enabled. - if (!combined_phone.IsEmpty()) { - const std::string predicted_country_code_without_variation = - GetPredictedCountryCode(candidate_profile, "", app_locale_, nullptr); - // If kAutofillConsiderVariationCountryCodeForPhoneNumbers is enabled, - // a consistent country code prediction for addresses and phone numbers is - // used. Otherwise the variation service state is not considered for phone - // numbers. This makes a difference, if the country code cannot be found - // in the profile. - // ParseNumber() implicity accepts both a country code and a locale. This - // will be refactored with crbug/1296077. The parameter for - // SetInfoWithVerificationStatus() has to be consistent with ParseNumber(). - // TODO(crbug.com/1295721): Cleanup when launched. - const std::string& phone_number_region = - predicted_country_code != predicted_country_code_without_variation && - base::FeatureList::IsEnabled( - features:: - kAutofillConsiderVariationCountryCodeForPhoneNumbers) - ? predicted_country_code - : app_locale_; - std::u16string constructed_number; - if (!combined_phone.ParseNumber(candidate_profile, phone_number_region, - &constructed_number) || - !candidate_profile.SetInfoWithVerificationStatus( - AutofillType(PHONE_HOME_WHOLE_NUMBER), constructed_number, - phone_number_region, VerificationStatus::kObserved)) { - if (base::FeatureList::IsEnabled( - features::kAutofillRemoveInvalidPhoneNumberOnImport)) { - DCHECK(candidate_profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER).empty()); - import_metadata.did_remove_invalid_phone_number = true; - } else { - if (import_log_buffer) { - *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed - << "Invalid phone number." << CTag{}; - } - has_invalid_phone_number = true; + const std::string variation_country_code = + client_->GetVariationConfigCountryCode(); + std::string predicted_country_code = + GetPredictedCountryCode(candidate_profile, variation_country_code, + app_locale_, import_log_buffer); + + if (!SetPhoneNumber(candidate_profile, combined_phone, + predicted_country_code)) { + if (base::FeatureList::IsEnabled( + features::kAutofillRemoveInvalidPhoneNumberOnImport)) { + candidate_profile.ClearFields({PHONE_HOME_WHOLE_NUMBER}); + import_metadata.did_remove_invalid_phone_number = true; + } else { + has_invalid_phone_number = true; + if (import_log_buffer) { + *import_log_buffer << LogMessage::kImportAddressProfileFromFormFailed + << "Invalid phone number." << CTag{}; } } } - // Filter unexpected values that are not shown in the settings. - if (base::FeatureList::IsEnabled( - features::kAutofillRemoveInaccessibleProfileValues)) { - const ServerFieldTypeSet inaccessible_fields = - candidate_profile.FindInaccessibleProfileValues(predicted_country_code); - candidate_profile.ClearFields(inaccessible_fields); - AutofillMetrics::LogRemovedSettingInaccessibleFields( - !inaccessible_fields.empty()); - for (const ServerFieldType inaccessible_field : inaccessible_fields) { - AutofillMetrics::LogRemovedSettingInaccessibleField( - predicted_country_code, inaccessible_field); - } - } + // This is done prior to checking the validity of the profile, because multi- + // step import profile merging requires the profile to be finalized. Ideally + // we would return false here if it fails, but that breaks the metrics. + bool finalized_import = candidate_profile.FinalizeAfterImport(); - // Reject the profile if minimum address and validation requirements are not - // met. - bool is_invalid_learnable_profile = + // Reject the profile if the validation requirements are not met. + // |IsValidLearnableProfile()| goes first to collect metrics. + bool has_invalid_information = !IsValidLearnableProfile(candidate_profile, predicted_country_code, - app_locale_, import_log_buffer); + app_locale_, import_log_buffer) || + has_multiple_distinct_email_addresses || has_invalid_field_types || + has_invalid_country || has_invalid_phone_number; + + // Profiles with valid information qualify for multi-step imports. + // This requires the profile to be finalized to apply the merging logic. + if (finalized_import && has_address_related_fields && + !has_invalid_information) { + ProcessMultiStepImport(candidate_profile, import_metadata, + url::Origin::Create(form.source_url())); + // The predicted country code has possibly changed, if |candidate_profile| + // was merged with a profile containing country information. + predicted_country_code = + GetPredictedCountryCode(candidate_profile, variation_country_code, + app_locale_, /*import_log_buffer=*/nullptr); + } + + // Only complement the country if no invalid country was entered in the form. + // For multi-step imports, |did_complement_country| might be set twice, but as + // the metric is only logged if it wasn't present before, this is fine. + import_metadata.did_complement_country = + !has_invalid_country && + ComplementCountry(candidate_profile, predicted_country_code); + + RemoveInaccessibleProfileValues(candidate_profile, predicted_country_code); // Do not import a profile if any of the requirements is violated. + // |IsMinimumAddress()| goes first to collect metrics. bool all_fulfilled = - !(has_multiple_distinct_email_addresses || has_invalid_field_types || - has_invalid_country || has_invalid_phone_number || - is_invalid_learnable_profile); + IsMinimumAddress(candidate_profile, predicted_country_code, app_locale_, + import_log_buffer, /*collect_metrics=*/true) && + !has_invalid_information; // Collect metrics regarding the requirements for an address profile import. AutofillMetrics::LogAddressFormImportRequirementMetric( @@ -783,10 +845,7 @@ bool FormDataImporter::ImportAddressProfileForSection( // If the profile does not fulfill import requirements but contains the // structured address or name information, it is eligible for silently // updating the existing profiles. - if (!all_fulfilled && !candidate_has_structured_data) - return false; - - if (!candidate_profile.FinalizeAfterImport()) + if (!finalized_import || (!all_fulfilled && !candidate_has_structured_data)) return false; // At this stage, the saving of the profile can only be omitted by the @@ -871,7 +930,7 @@ bool FormDataImporter::ProcessCreditCardImportCandidate( if (imported_credit_card && imported_credit_card->virtual_card_enrollment_state() == CreditCard::VirtualCardEnrollmentState::UNENROLLED_AND_ELIGIBLE) { - virtual_card_enrollment_manager_->OfferVirtualCardEnroll( + virtual_card_enrollment_manager_->InitVirtualCardEnroll( *imported_credit_card, VirtualCardEnrollmentSource::kDownstream); return true; } @@ -1095,7 +1154,7 @@ CreditCard FormDataImporter::ExtractCreditCardFromForm( types_seen.insert(server_field_type); } // If |field| is an HTML5 month input, handle it as a special case. - if (base::LowerCaseEqualsASCII(field->form_control_type, "month")) { + if (base::EqualsCaseInsensitiveASCII(field->form_control_type, "month")) { DCHECK_EQ(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, server_field_type); candidate_credit_card.SetInfoForMonthInputType(value); continue; @@ -1158,4 +1217,119 @@ bool FormDataImporter::ShouldOfferUploadCardOrLocalCardSave( return true; } +void FormDataImporter::ProcessMultiStepImport( + AutofillProfile& profile, + ProfileImportMetadata& import_metadata, + const url::Origin& origin) { + if (!base::FeatureList::IsEnabled( + features::kAutofillEnableMultiStepImports)) { + return; + } + + RemoveOutdatedMultiStepCandidates(origin); + bool has_min_address_requirements = + MergeProfileWithMultiStepCandidates(profile, import_metadata, origin); + + if (!has_min_address_requirements || + features::kAutofillEnableMultiStepImportComplements.Get()) { + // Add |profile| as a |multistep_candidate|. This happens for incomplete + // profiles, which can then be complemented in later steps. When + // |kAutofillEnableMultiStepImportComplements| is enabled, complete profiles + // are stored too, which enables updating them in later steps. + // In the latter case, Autofill tries to import the `profile`. This logs + // metrics depending on `import_metadata`. To prevent double counting, + // an we store an empty `ProfileImportMetadata` object in this case. + multistep_candidates_.push_front(MultiStepFormProfileCandidate{ + .profile = profile, + .import_metadata = has_min_address_requirements + ? ProfileImportMetadata() + : import_metadata, + .timestamp = AutofillClock::Now()}); + multistep_candidates_origin_ = origin; + } +} + +void FormDataImporter::RemoveOutdatedMultiStepCandidates( + const url::Origin& origin) { + // All |multistep_candidates| share |multistep_candidates_origin|. + if (multistep_candidates_origin_.has_value() && + multistep_candidates_origin_.value() != origin) { + multistep_candidates_.clear(); + } else { + // Remove candidates that reached their TTL. + const base::TimeDelta ttl = + features::kAutofillMultiStepImportCandidateTTL.Get(); + const base::Time now = AutofillClock::Now(); + while (!multistep_candidates_.empty() && + now - multistep_candidates_.back().timestamp > ttl) { + multistep_candidates_.pop_back(); + } + } + if (multistep_candidates_.empty()) { + multistep_candidates_origin_.reset(); + } +} + +bool FormDataImporter::MergeProfileWithMultiStepCandidates( + AutofillProfile& profile, + ProfileImportMetadata& import_metadata, + const url::Origin& origin) { + // Greedily merge with a prefix of |multistep_candidates|. + AutofillProfileComparator comparator(app_locale_); + std::deque<MultiStepFormProfileCandidate>::iterator merge_candidate = + multistep_candidates_.begin(); + AutofillProfile completed_profile = profile; + ProfileImportMetadata completed_metadata = import_metadata; + // Country completion has not happened yet, so this field can be ignored. + DCHECK(!completed_metadata.did_remove_invalid_phone_number); + while ( + merge_candidate != multistep_candidates_.end() && + comparator.AreMergeable(completed_profile, merge_candidate->profile) && + completed_profile.MergeDataFrom(merge_candidate->profile, app_locale_)) { + // ProfileImportMetadata is only relevant for metrics. If the phone number + // was removed from a partial profile, we still want that removal to appear + // in the metrics, because it would have hindered that partial profile from + // import and merging. + completed_metadata.did_remove_invalid_phone_number |= + merge_candidate->import_metadata.did_remove_invalid_phone_number; + merge_candidate++; + } + + // The minimum address requirements depend on the country, which has possibly + // changed as a result of the merge. + if (IsMinimumAddress( + completed_profile, + GetPredictedCountryCode(completed_profile, + client_->GetVariationConfigCountryCode(), + app_locale_, /*import_log_buffer=*/nullptr), + app_locale_, + /*import_log_buffer=*/nullptr, /*collect_metrics=*/false)) { + profile = std::move(completed_profile); + import_metadata = std::move(completed_metadata); + multistep_candidates_.clear(); + return true; + } else { + // Remove all profiles that couldn't be merged. + multistep_candidates_.erase(merge_candidate, multistep_candidates_.end()); + return false; + } +} + +void FormDataImporter::OnBrowsingHistoryCleared( + const history::DeletionInfo& deletion_info) { + // Delete all multi-step import candidates when: + // - The entire browsing history is cleared, or + // - At least one URL from the same origin as `multistep_candidates_origin_` + // is deleted. + if (deletion_info.IsAllHistory() || + (multistep_candidates_origin_.has_value() && + base::Contains(deletion_info.deleted_rows(), + *multistep_candidates_origin_, + [](const history::URLRow& url_row) { + return url::Origin::Create(url_row.url()); + }))) { + ClearMultiStepImportCandidates(); + } +} + } // namespace autofill |