summaryrefslogtreecommitdiff
path: root/chromium/components/autofill/core/browser/form_data_importer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/autofill/core/browser/form_data_importer.cc')
-rw-r--r--chromium/components/autofill/core/browser/form_data_importer.cc444
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