| // 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/data_model/addresses/autofill_profile.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <ostream> |
| #include <ranges> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/feature_list.h" |
| #include "base/hash/sha1.h" |
| #include "base/i18n/case_conversion.h" |
| #include "base/i18n/char_iterator.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversion_utils.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/uuid.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/autofill_field.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/autofill/core/browser/country_type.h" |
| #include "components/autofill/core/browser/data_model/addresses/address.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_i18n_api.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_normalization_utils.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_profile_comparator.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_structured_address_utils.h" |
| #include "components/autofill/core/browser/data_model/addresses/contact_info.h" |
| #include "components/autofill/core/browser/data_model/addresses/phone_number.h" |
| #include "components/autofill/core/browser/data_model/usage_history_information.h" |
| #include "components/autofill/core/browser/data_quality/addresses/profile_token_quality.h" |
| #include "components/autofill/core/browser/data_quality/autofill_data_util.h" |
| #include "components/autofill/core/browser/data_quality/validation.h" |
| #include "components/autofill/core/browser/field_type_utils.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/geo/address_i18n.h" |
| #include "components/autofill/core/browser/geo/autofill_country.h" |
| #include "components/autofill/core/browser/geo/country_names.h" |
| #include "components/autofill/core/browser/geo/phone_number_i18n.h" |
| #include "components/autofill/core/browser/geo/state_names.h" |
| #include "components/autofill/core/browser/metrics/autofill_metrics.h" |
| #include "components/autofill/core/browser/ui/addresses/autofill_address_util.h" |
| #include "components/autofill/core/common/autofill_clock.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_l10n_util.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/logging/log_buffer.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "third_party/libaddressinput/chromium/addressinput_util.h" |
| #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h" |
| #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/android/jni_string.h" |
| #include "components/autofill/android/main_autofill_jni_headers/AutofillProfile_jni.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| using ::i18n::addressinput::AddressData; |
| using ::i18n::addressinput::AddressField; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| constexpr char kAddressComponentsDefaultLocality[] = "en-US"; |
| |
| // Returns `NAME_FULL` for first, middle, and last name field types, and groups |
| // phone number types similarly. |
| FieldType GetStorableTypeCollapsingGroups(FieldType type) { |
| if (GroupTypeOfFieldType(type) == FieldTypeGroup::kName) { |
| return NAME_FULL; |
| } |
| if (GroupTypeOfFieldType(type) == FieldTypeGroup::kPhone) { |
| return PHONE_HOME_WHOLE_NUMBER; |
| } |
| return type; |
| } |
| |
| // Returns a value that represents specificity/privacy of the given type. This |
| // is used for prioritizing which data types are shown in inferred labels. For |
| // example, if the profile is going to fill ADDRESS_HOME_ZIP, it should |
| // prioritize showing that over ADDRESS_HOME_STATE in the suggestion sublabel. |
| int SpecificityForType(FieldType type) { |
| static constexpr auto kOrder = |
| std::to_array({ADDRESS_HOME_LINE1, ADDRESS_HOME_LINE2, EMAIL_ADDRESS, |
| PHONE_HOME_WHOLE_NUMBER, NAME_FULL, ADDRESS_HOME_ZIP, |
| ADDRESS_HOME_SORTING_CODE, COMPANY_NAME, ADDRESS_HOME_CITY, |
| ADDRESS_HOME_STATE, ADDRESS_HOME_COUNTRY}); |
| CHECK_NE(type, EMPTY_TYPE); |
| if (auto it = std::ranges::find(kOrder, type); it != kOrder.end()) { |
| return it - kOrder.begin(); |
| } |
| // The priority of other types is arbitrary, but deterministic. |
| return 100 + type; |
| } |
| |
| // Fills `distinguishing_fields` with a list of fields to use when creating |
| // labels that can help to distinguish between two profiles. Draws fields from |
| // `suggested_fields` if it is non-NULL; otherwise returns a default list. |
| // If `suggested_fields` is non-NULL, does not include `excluded_fields` in the |
| // list. Otherwise, `excluded_fields` is ignored, and should be set to |
| // an empty list by convention. The resulting list of fields is sorted in |
| // decreasing order of importance. |
| void GetFieldsForDistinguishingProfiles( |
| const std::vector<FieldType>* suggested_fields, |
| FieldTypeSet excluded_fields, |
| std::vector<FieldType>* distinguishing_fields) { |
| std::vector<FieldType> default_fields; |
| if (!suggested_fields) { |
| default_fields.append_range( |
| AutofillProfile::kDefaultDistinguishingFieldsForLabels); |
| if (excluded_fields.empty()) { |
| distinguishing_fields->swap(default_fields); |
| return; |
| } |
| suggested_fields = &default_fields; |
| } |
| |
| // Keep track of which fields we've seen so that we avoid duplicate entries. |
| // Always ignore fields of unknown type and those part of `excluded_fields`. |
| FieldTypeSet seen_fields(excluded_fields, &GetStorableTypeCollapsingGroups); |
| seen_fields.insert(UNKNOWN_TYPE); |
| |
| distinguishing_fields->clear(); |
| for (const FieldType& it : *suggested_fields) { |
| FieldType suggested_type = GetStorableTypeCollapsingGroups(it); |
| if (seen_fields.insert(suggested_type).second) { |
| distinguishing_fields->push_back(suggested_type); |
| } |
| } |
| std::ranges::sort(*distinguishing_fields, {}, &SpecificityForType); |
| |
| // Special case: If one of the excluded fields is a partial name (e.g. |
| // `NAME_FIRST`) or phone number (e.g `PHONE_HOME_CITY_CODE`) and the |
| // suggested fields include other name or phone fields fields, include |
| // `NAME_FULL` or `PHONE_HOME_WHOLE_NUMBER` in the list of distinguishing |
| // fields as a last-ditch fallback. This allows us to distinguish between |
| // profiles that are identical except for the name or phone number. |
| // TODO(crbug.com/380273791): Clean up this special case. It might be possible |
| // to just append `PHONE_HOME_WHOLE_NUMBER` at the end. |
| for (FieldType excluded_field : excluded_fields) { |
| FieldType effective_excluded_type = |
| GetStorableTypeCollapsingGroups(excluded_field); |
| if (excluded_field == effective_excluded_type) { |
| continue; |
| } |
| for (const FieldType& it : *suggested_fields) { |
| if (it != excluded_field && |
| GetStorableTypeCollapsingGroups(it) == effective_excluded_type) { |
| distinguishing_fields->push_back(effective_excluded_type); |
| break; |
| } |
| } |
| } |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Constructs an AutofillProfile using the provided `existing_profile` as a |
| // foundation. In case that the `existing_profile` is invalid, an empty profile |
| // with a unique identifier (GUID) corresponding to the Java profile |
| // (`jprofile`) is initialized. |
| AutofillProfile CreateStarterProfile( |
| const base::android::JavaRef<jobject>& jprofile, |
| JNIEnv* env, |
| const AutofillProfile* existing_profile) { |
| std::string guid = Java_AutofillProfile_getGUID(env, jprofile); |
| if (!existing_profile) { |
| AutofillProfile::RecordType record_type = |
| Java_AutofillProfile_getRecordType(env, jprofile); |
| AddressCountryCode country_code = |
| AddressCountryCode(Java_AutofillProfile_getCountryCode(env, jprofile)); |
| AutofillProfile profile = AutofillProfile(record_type, country_code); |
| // Only set the guid if CreateStartProfile is called on an existing profile |
| // (java guid not empty). Otherwise, keep the generated one. |
| // TODO(crbug.com/40282123): `guid` should be always empty when existing |
| // profile is not set. CHECK should be added when this condition holds. |
| if (!guid.empty()) { |
| profile.set_guid(guid); |
| } |
| return profile; |
| } |
| |
| CHECK_EQ(existing_profile->guid(), guid); |
| return *existing_profile; |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| } // namespace |
| |
| AutofillProfile::AutofillProfile(const std::string& guid, |
| RecordType record_type, |
| AddressCountryCode country_code) |
| : guid_(guid), |
| name_(/*alternative_names_supported=*/country_code == |
| AddressCountryCode("JP")), |
| phone_number_(this), |
| address_(country_code), |
| record_type_(record_type), |
| initial_creator_id_(kInitialCreatorChrome), |
| token_quality_(this), |
| usage_history_information_(/*usage_history_size=*/1) {} |
| |
| AutofillProfile::AutofillProfile(RecordType record_type, |
| AddressCountryCode country_code) |
| : AutofillProfile(base::Uuid::GenerateRandomV4().AsLowercaseString(), |
| record_type, |
| country_code) {} |
| |
| AutofillProfile::AutofillProfile(AddressCountryCode country_code) |
| : AutofillProfile(RecordType::kLocalOrSyncable, country_code) {} |
| |
| AutofillProfile::AutofillProfile(const AccountInfo& info) |
| : AutofillProfile(RecordType::kAccountNameEmail, |
| i18n_model_definition::kLegacyHierarchyCountryCode) { |
| SetRawInfo(NAME_FULL, base::UTF8ToUTF16(info.GetFullName().value_or(""))); |
| SetRawInfo(EMAIL_ADDRESS, base::UTF8ToUTF16(info.GetEmail())); |
| FinalizeAfterImport(); |
| } |
| |
| AutofillProfile::AutofillProfile(const AutofillProfile& profile) |
| : name_(/*alternative_names_supported=*/profile.GetAddressCountryCode() == |
| AddressCountryCode("JP")), |
| phone_number_(this), |
| address_(profile.GetAddress()), |
| token_quality_(this), |
| usage_history_information_(profile.usage_history_information_), |
| is_devtools_testing_profile_(profile.is_devtools_testing_profile_) { |
| operator=(profile); |
| } |
| |
| AutofillProfile::~AutofillProfile() = default; |
| |
| AutofillProfile& AutofillProfile::operator=(const AutofillProfile& profile) { |
| if (this == &profile) { |
| return *this; |
| } |
| |
| usage_history_information_.set_use_count( |
| profile.usage_history_information_.use_count()); |
| for (size_t i = 1; i <= usage_history_information_.usage_history_size(); |
| ++i) { |
| usage_history_information_.set_use_date( |
| profile.usage_history_information_.use_date(i), i); |
| } |
| usage_history_information_.set_modification_date( |
| profile.usage_history_information_.modification_date()); |
| |
| set_guid(profile.guid()); |
| |
| set_profile_label(profile.profile_label()); |
| |
| name_ = profile.name_; |
| email_ = profile.email_; |
| company_ = profile.company_; |
| phone_number_ = profile.phone_number_; |
| phone_number_.set_profile(this); |
| |
| address_ = profile.address_; |
| set_language_code(profile.language_code()); |
| |
| record_type_ = profile.record_type_; |
| initial_creator_id_ = profile.initial_creator_id_; |
| |
| token_quality_ = profile.token_quality_; |
| token_quality_.set_profile(this); |
| |
| is_devtools_testing_profile_ = profile.is_devtools_testing_profile_; |
| |
| return *this; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| base::android::ScopedJavaLocalRef<jobject> AutofillProfile::CreateJavaObject( |
| std::string_view app_locale) const { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> jprofile = |
| Java_AutofillProfile_Constructor( |
| env, guid(), static_cast<int32_t>(record_type()), language_code()); |
| |
| for (FieldType type : AutofillProfile::kDatabaseStoredTypes) { |
| auto status = static_cast<int32_t>(GetVerificationStatus(type)); |
| // TODO(crbug.com/40278253): Reconcile usage of GetInfo and GetRawInfo |
| // below. |
| if (type == NAME_FULL) { |
| Java_AutofillProfile_setInfo(env, jprofile, static_cast<int32_t>(type), |
| GetInfo(type, app_locale), status); |
| } else { |
| Java_AutofillProfile_setInfo(env, jprofile, static_cast<int32_t>(type), |
| GetRawInfo(type), status); |
| } |
| } |
| return jprofile; |
| } |
| |
| // static |
| AutofillProfile AutofillProfile::CreateFromJavaObject( |
| const base::android::JavaRef<jobject>& jprofile, |
| const AutofillProfile* existing_profile, |
| std::string_view app_locale) { |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| AutofillProfile profile = |
| CreateStarterProfile(jprofile, env, existing_profile); |
| |
| std::vector<int> field_types = |
| Java_AutofillProfile_getFieldTypes(env, jprofile); |
| |
| std::vector<std::tuple<FieldType, std::u16string, VerificationStatus>> |
| modified_fields; |
| for (int int_field_type : field_types) { |
| FieldType field_type = |
| ToSafeFieldType(int_field_type).value_or(NO_SERVER_DATA); |
| CHECK(field_type != NO_SERVER_DATA); |
| VerificationStatus status = |
| Java_AutofillProfile_getInfoStatus(env, jprofile, field_type); |
| std::u16string value = |
| Java_AutofillProfile_getInfo(env, jprofile, field_type); |
| |
| if (value != profile.GetInfo(field_type, app_locale) || |
| status != profile.GetVerificationStatus(field_type)) { |
| modified_fields.emplace_back(field_type, value, status); |
| } |
| } |
| |
| for (const auto& field_data : modified_fields) { |
| const auto& [field_type, value, status] = field_data; |
| // TODO(crbug.com/40278253): Reconcile usage of GetInfo and GetRawInfo |
| // below. |
| if (field_type == NAME_FULL || field_type == ADDRESS_HOME_COUNTRY) { |
| profile.SetInfoWithVerificationStatus(field_type, value, app_locale, |
| status); |
| } else { |
| profile.SetRawInfoWithVerificationStatus(field_type, value, status); |
| } |
| } |
| |
| profile.set_language_code( |
| Java_AutofillProfile_getLanguageCode(env, jprofile)); |
| profile.FinalizeAfterImport(); |
| |
| return profile; |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| double AutofillProfile::GetRankingScore(base::Time current_time) const { |
| return usage_history_information_.GetRankingScore(current_time); |
| } |
| |
| bool AutofillProfile::HasGreaterRankingThan(const AutofillProfile* other, |
| base::Time comparison_time) const { |
| return usage_history_information_.HasGreaterRankingThan( |
| other->usage_history_information_, comparison_time); |
| } |
| |
| void AutofillProfile::GetMatchingTypes(std::u16string_view text, |
| std::string_view app_locale, |
| FieldTypeSet* matching_types) const { |
| FieldTypeSet matching_types_in_this_profile; |
| for (const auto* form_group : FormGroups()) { |
| form_group->GetMatchingTypes(text, app_locale, |
| &matching_types_in_this_profile); |
| } |
| |
| for (auto type : matching_types_in_this_profile) { |
| matching_types->insert(type); |
| } |
| } |
| |
| std::u16string AutofillProfile::GetRawInfo(FieldType type) const { |
| const FormGroup* form_group = FormGroupForType(type); |
| return form_group ? form_group->GetRawInfo(type) : std::u16string(); |
| } |
| |
| void AutofillProfile::SetRawInfoWithVerificationStatus( |
| FieldType type, |
| std::u16string_view value, |
| VerificationStatus status) { |
| FormGroup* form_group = MutableFormGroupForType(type); |
| if (form_group) { |
| if (type == ADDRESS_HOME_COUNTRY) { |
| auto old_country_code = GetAddressCountryCode(); |
| form_group->SetRawInfoWithVerificationStatus(type, value, status); |
| OnProfileCountryUpdate(old_country_code, GetAddressCountryCode()); |
| return; |
| } |
| |
| form_group->SetRawInfoWithVerificationStatus(type, value, status); |
| } |
| } |
| |
| FieldTypeSet AutofillProfile::GetSupportedTypes() const { |
| FieldTypeSet supported_types; |
| for (const FormGroup* form_group : FormGroups()) { |
| supported_types.insert_all(form_group->GetSupportedTypes()); |
| } |
| return supported_types; |
| } |
| |
| FieldType AutofillProfile::GetStorableTypeOf(FieldType type) const { |
| const FieldTypeGroup group = GroupTypeOfFieldType(type); |
| if (group == FieldTypeGroup::kAddress) { |
| return address_.GetRoot().GetStorableTypeOf(type).value_or(type); |
| } else if (group == FieldTypeGroup::kName) { |
| return name_.GetStorableTypeOf(type).value_or(type); |
| } else if (group == FieldTypeGroup::kPhone) { |
| // The only storable phone number type is PHONE_HOME_WHOLE_NUMBER. |
| return PHONE_HOME_WHOLE_NUMBER; |
| } else { |
| // The other FieldTypeGroups only support storable types. |
| return type; |
| } |
| } |
| |
| FieldTypeSet AutofillProfile::GetUserVisibleTypes() const { |
| FieldTypeSet visible_types{PHONE_HOME_WHOLE_NUMBER, EMAIL_ADDRESS}; |
| std::string components_language_code; |
| for (const AutofillAddressUIComponent& component : GetAddressComponents( |
| GetAddressCountryCode().value(), kAddressComponentsDefaultLocality, |
| /*enable_field_labels_localization=*/false, |
| /*include_literals=*/false, components_language_code)) { |
| visible_types.insert(component.field); |
| } |
| |
| return visible_types; |
| } |
| |
| bool AutofillProfile::IsEmpty(std::string_view app_locale) const { |
| FieldTypeSet types; |
| GetNonEmptyTypes(app_locale, &types); |
| return types.empty(); |
| } |
| |
| bool AutofillProfile::IsPresentButInvalid(FieldType type) const { |
| std::string country = base::UTF16ToUTF8(GetRawInfo(ADDRESS_HOME_COUNTRY)); |
| std::u16string data = GetRawInfo(type); |
| if (data.empty()) { |
| return false; |
| } |
| |
| switch (type) { |
| case ADDRESS_HOME_STATE: |
| return country == "US" && !IsValidState(data); |
| |
| case ADDRESS_HOME_ZIP: |
| return !IsValidZip(data, AddressCountryCode(country), |
| base::FeatureList::IsEnabled( |
| features::kAutofillExtendZipCodeValidation)); |
| |
| case PHONE_HOME_WHOLE_NUMBER: |
| return !i18n::PhoneObject(data, country, /*infer_country_code=*/false) |
| .IsValidNumber(); |
| |
| case EMAIL_ADDRESS: |
| return !IsValidEmailAddress(data); |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| int AutofillProfile::Compare(const AutofillProfile& profile) const { |
| static constexpr auto kTypes = |
| std::to_array<FieldType>({NAME_FULL, |
| NAME_FIRST, |
| NAME_MIDDLE, |
| NAME_LAST, |
| NAME_LAST_FIRST, |
| NAME_LAST_SECOND, |
| NAME_LAST_CONJUNCTION, |
| ALTERNATIVE_FULL_NAME, |
| ALTERNATIVE_GIVEN_NAME, |
| ALTERNATIVE_FAMILY_NAME, |
| COMPANY_NAME, |
| ADDRESS_HOME_STREET_ADDRESS, |
| ADDRESS_HOME_DEPENDENT_LOCALITY, |
| ADDRESS_HOME_CITY, |
| ADDRESS_HOME_STATE, |
| ADDRESS_HOME_ZIP, |
| ADDRESS_HOME_ZIP_AND_CITY, |
| ADDRESS_HOME_SORTING_CODE, |
| ADDRESS_HOME_COUNTRY, |
| ADDRESS_HOME_LANDMARK, |
| ADDRESS_HOME_OVERFLOW, |
| ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY, |
| ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK, |
| ADDRESS_HOME_OVERFLOW_AND_LANDMARK, |
| ADDRESS_HOME_BETWEEN_STREETS, |
| ADDRESS_HOME_BETWEEN_STREETS_1, |
| ADDRESS_HOME_BETWEEN_STREETS_2, |
| ADDRESS_HOME_ADMIN_LEVEL2, |
| ADDRESS_HOME_HOUSE_NUMBER, |
| ADDRESS_HOME_STREET_NAME, |
| ADDRESS_HOME_SUBPREMISE, |
| ADDRESS_HOME_STREET_LOCATION, |
| ADDRESS_HOME_APT, |
| ADDRESS_HOME_APT_NUM, |
| ADDRESS_HOME_APT_TYPE, |
| ADDRESS_HOME_FLOOR, |
| EMAIL_ADDRESS, |
| PHONE_HOME_WHOLE_NUMBER}); |
| |
| // When adding field types, ensure that they don't need to be added here and |
| // update the last checked value. |
| static_assert(FieldType::MAX_VALID_FIELD_TYPE == 220, |
| "New field type needs to be reviewed for inclusion in the " |
| "profile comparison logic."); |
| |
| for (FieldType type : kTypes) { |
| int comparison = GetRawInfo(type).compare(profile.GetRawInfo(type)); |
| if (comparison != 0) { |
| return comparison; |
| } |
| |
| // If the value is empty, the verification status can be ambiguous because |
| // the value could be either build from its empty child nodes or parsed |
| // from its parent. Therefore, it should not be considered when evaluating |
| // the similarity of two profiles. |
| if (profile.GetRawInfo(type).empty()) { |
| continue; |
| } |
| |
| if (IsLessSignificantVerificationStatus( |
| GetVerificationStatus(type), profile.GetVerificationStatus(type))) { |
| return -1; |
| } |
| if (IsLessSignificantVerificationStatus(profile.GetVerificationStatus(type), |
| GetVerificationStatus(type))) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| bool AutofillProfile::EqualsForLegacySyncPurposes( |
| const AutofillProfile& profile) const { |
| return usage_history_information_.use_count() == |
| profile.usage_history_information_.use_count() && |
| usage_history_information_.UseDateEqualsInSeconds( |
| profile.usage_history()) && |
| EqualsSansGuid(profile); |
| } |
| |
| bool AutofillProfile::EqualsForUpdatePurposes( |
| const AutofillProfile& new_profile) const { |
| return usage_history_information_.use_count() == |
| new_profile.usage_history_information_.use_count() && |
| usage_history_information_.UseDateEqualsInSeconds( |
| new_profile.usage_history()) && |
| language_code() == new_profile.language_code() && |
| token_quality() == new_profile.token_quality() && |
| Compare(new_profile) == 0; |
| } |
| |
| bool AutofillProfile::operator==(const AutofillProfile& profile) const { |
| return guid() == profile.guid() && EqualsSansGuid(profile); |
| } |
| |
| bool AutofillProfile::IsSubsetOf(const AutofillProfileComparator& comparator, |
| const AutofillProfile& profile) const { |
| return IsSubsetOfForFieldSet(comparator, profile, |
| AutofillProfile::kDatabaseStoredTypes); |
| } |
| |
| bool AutofillProfile::IsSubsetOfForFieldSet( |
| const AutofillProfileComparator& comparator, |
| const AutofillProfile& profile, |
| const FieldTypeSet& types) const { |
| SCOPED_UMA_HISTOGRAM_TIMER("Autofill.Timing.IsSubsetOfForFieldSet"); |
| |
| // Do not process `profile` if it is trivially unrelated to `this` for having |
| // different source country code, for performance reasons. |
| if (types.contains(ADDRESS_HOME_COUNTRY) && |
| !data_util::HaveNonConflictingCountryCodes( |
| GetAddressCountryCode(), profile.GetAddressCountryCode())) { |
| return false; |
| } |
| |
| const std::string& app_locale = comparator.app_locale(); |
| const AddressComponent& address = GetAddress().GetRoot(); |
| const AddressComponent& other_address = profile.GetAddress().GetRoot(); |
| |
| for (FieldType type : types) { |
| // Prefer GetInfo over GetRawInfo so that a reasonable value is retrieved |
| // when the raw data is empty or unnormalized. For example, suppose a |
| // profile's first and last names are set but its full name is not set. |
| // GetInfo for the NAME_FULL type returns the constituent name parts; |
| // however, GetRawInfo returns an empty string. |
| const std::u16string value = GetInfo(type, app_locale); |
| const std::u16string other_value = profile.GetInfo(type, app_locale); |
| if (value.empty() || value == other_value || type == ADDRESS_HOME_COUNTRY) { |
| continue; |
| } |
| // TODO(crbug.com/40257475): Use rewriter rules for all kAddressHome types. |
| if (type == ADDRESS_HOME_STREET_ADDRESS || type == ADDRESS_HOME_LINE1 || |
| type == ADDRESS_HOME_LINE2 || type == ADDRESS_HOME_LINE3) { |
| // This will compare street addresses after applying appropriate address |
| // rewriter rules to both values, so that for example US streets like |
| // `Main Street` and `main st` evaluate to equal. |
| const AddressCountryCode common_country_code = |
| AddressComponent::GetCommonCountry(address.GetCountryCode(), |
| other_address.GetCountryCode()); |
| if (address.GetValueForComparisonForType(type, common_country_code) != |
| other_address.GetValueForComparisonForType(type, |
| common_country_code)) { |
| return false; |
| } |
| } else if (type == NAME_FULL) { |
| if (!profile.GetNameInfo().IsNameVariantOf(value, app_locale)) { |
| // Check whether the full name of |this| can be derived from the full |
| // name of |profile| if the form contains a full name field. |
| // |
| // Suppose the full name of |this| is Mia Park and |profile|'s full name |
| // is Mia L Park. Mia Park can be derived from Mia L Park, so |this| |
| // could be a subset of |profile|. |
| // |
| // If the form contains fields for a name's constituent parts, e.g. |
| // NAME_FIRST, then these values are compared according to the |
| // conditions that follow. |
| return false; |
| } |
| } else if (type == PHONE_HOME_WHOLE_NUMBER || |
| type == PHONE_HOME_CITY_AND_NUMBER) { |
| if (!i18n::PhoneNumbersMatch( |
| value, other_value, |
| base::UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)), |
| app_locale)) { |
| return false; |
| } |
| } else if (!AutofillProfileComparator::Compare(value, other_value)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool AutofillProfile::IsStrictSupersetOf( |
| const AutofillProfileComparator& comparator, |
| const AutofillProfile& profile) const { |
| return profile.IsSubsetOf(comparator, *this) && |
| !IsSubsetOf(comparator, profile); |
| } |
| |
| AddressCountryCode AutofillProfile::GetAddressCountryCode() const { |
| return GetAddress().GetAddressCountryCode(); |
| } |
| |
| bool AutofillProfile::IsAccountProfile() const { |
| switch (record_type()) { |
| case RecordType::kLocalOrSyncable: |
| return false; |
| case RecordType::kAccount: |
| case RecordType::kAccountHome: |
| case RecordType::kAccountWork: |
| case RecordType::kAccountNameEmail: |
| return true; |
| } |
| NOTREACHED(); |
| } |
| |
| bool AutofillProfile::IsHomeAndWorkProfile() const { |
| switch (record_type()) { |
| case RecordType::kLocalOrSyncable: |
| case RecordType::kAccount: |
| case RecordType::kAccountNameEmail: |
| return false; |
| case RecordType::kAccountHome: |
| case RecordType::kAccountWork: |
| return true; |
| } |
| NOTREACHED(); |
| } |
| |
| void AutofillProfile::OverwriteDataFromForLegacySync( |
| const AutofillProfile& profile) { |
| DCHECK_EQ(guid(), profile.guid()); |
| |
| // Some fields should not got overwritten by empty values; back-up the |
| // values. |
| std::string language_code_value = language_code(); |
| |
| // Structured names should not be simply overwritten but it should be |
| // attempted to merge the names. |
| bool is_structured_name_mergeable = false; |
| NameInfo name_info = GetNameInfo(); |
| is_structured_name_mergeable = |
| name_info.IsStructuredNameMergeable(profile.GetNameInfo()); |
| name_info.MergeStructuredName( |
| profile.GetNameInfo(), |
| usage_history().use_date() < profile.usage_history().use_date()); |
| |
| // ProfileTokenQuality is not synced through legacy sync - and as a result, |
| // `profile` has no observations. Make sure that observations for token values |
| // that haven't changed are kept. |
| ProfileTokenQuality token_quality = std::move(token_quality_); |
| token_quality.ResetObservationsForDifferingTokens(profile); |
| |
| *this = profile; |
| |
| if (language_code().empty()) { |
| set_language_code(language_code_value); |
| } |
| |
| // For structured names, use the merged name if possible. |
| // If the full name of |profile| is empty, maintain the complete name |
| // structure. Note, this should only happen if the complete name is empty. For |
| // the legacy implementation, set the full name if |profile| does not contain |
| // a full name. |
| if (is_structured_name_mergeable || !HasRawInfo(NAME_FULL)) { |
| name_ = std::move(name_info); |
| } |
| |
| token_quality_ = std::move(token_quality); |
| } |
| |
| bool AutofillProfile::MergeDataFrom(const AutofillProfile& profile, |
| std::string_view app_locale) { |
| AutofillProfileComparator comparator(app_locale); |
| DCHECK(comparator.AreMergeable(*this, profile)); |
| |
| NameInfo name( |
| /*alternative_names_supported=*/profile.GetAddressCountryCode() == |
| AddressCountryCode("JP")); |
| EmailInfo email; |
| CompanyInfo company; |
| PhoneNumber phone_number(this); |
| Address address(profile.GetAddressCountryCode()); |
| |
| DVLOG(1) << "Merging profiles:\nSource = " << profile << "\nDest = " << *this; |
| |
| // The comparator's merge operations are biased to prefer the data in the |
| // first profile parameter when the data is the same modulo case. We expect |
| // the caller to pass the incoming profile in this position to prefer |
| // accepting updates instead of preserving the original data. I.e., passing |
| // the incoming profile first accepts case and diacritic changes, for example, |
| // the other ways does not. |
| if (!NameInfo::MergeNames( |
| profile.GetNameInfo(), profile.GetAddressCountryCode(), GetNameInfo(), |
| GetAddressCountryCode(), |
| usage_history().use_date() < profile.usage_history().use_date(), |
| name) || |
| !comparator.MergeEmailAddresses(profile, *this, email) || |
| !comparator.MergeCompanyNames(profile, *this, company) || |
| !comparator.MergePhoneNumbers(profile, *this, phone_number) || |
| !comparator.MergeAddresses(profile, *this, address)) { |
| DUMP_WILL_BE_NOTREACHED(); |
| return false; |
| } |
| |
| set_language_code(profile.language_code()); |
| |
| usage_history_information_.MergeUsageHistories(profile.usage_history()); |
| |
| // Update the fields which need to be modified, if any. Note: that we're |
| // comparing the fields for representational equality below (i.e., are the |
| // values byte for byte the same). |
| |
| bool modified = false; |
| |
| if (name_ != name) { |
| MergeFormGroupTokenQuality(name, profile); |
| name_ = name; |
| modified = true; |
| } |
| |
| if (email_ != email) { |
| MergeFormGroupTokenQuality(email, profile); |
| email_ = email; |
| modified = true; |
| } |
| |
| if (company_ != company) { |
| MergeFormGroupTokenQuality(company, profile); |
| company_ = company; |
| modified = true; |
| } |
| |
| if (phone_number_ != phone_number) { |
| MergeFormGroupTokenQuality(phone_number, profile); |
| phone_number_ = phone_number; |
| modified = true; |
| } |
| |
| if (address_ != address) { |
| MergeFormGroupTokenQuality(address, profile); |
| address_ = address; |
| modified = true; |
| } |
| |
| return modified; |
| } |
| |
| void AutofillProfile::MergeFormGroupTokenQuality( |
| const FormGroup& merged_group, |
| const AutofillProfile& other_profile) { |
| for (FieldType type : merged_group.GetSupportedTypes()) { |
| const std::u16string& merged_value = merged_group.GetRawInfo(type); |
| if (!AutofillProfile::kDatabaseStoredTypes.contains(type) || |
| merged_value == GetRawInfo(type)) { |
| // Quality information is only tracked for stored types. If the merged |
| // value matches the existing value, its token quality is kept. |
| continue; |
| } |
| if (merged_value == other_profile.GetRawInfo(type)) { |
| // The merged value comes from the `other_profile`, so its token quality |
| // is carried over. |
| token_quality_.CopyObservationsForStoredType( |
| type, other_profile.token_quality_); |
| } else { |
| // The `merged_value` matches neither `*this` nor the `other_profile`'s |
| // value, because the values were combined in some way. This generally |
| // doesn't happen, because the merging logic only merges values if one is |
| // a subset/substring of the other. However, in some cases, formatting |
| // differences can make this case reachable. For example, merging the |
| // phone numbers "5550199" and "555.0199" gives "555-0199". |
| // Since observations cannot be merged, reset the token quality. |
| token_quality_.ResetObservationsForStoredType(type); |
| } |
| } |
| } |
| |
| void AutofillProfile::OnProfileCountryUpdate( |
| const AddressCountryCode& old_country_code, |
| const AddressCountryCode& new_country_code) { |
| if (old_country_code == new_country_code) { |
| return; |
| } |
| |
| name_.OnCountryChange(new_country_code); |
| } |
| |
| // static |
| std::vector<std::u16string> AutofillProfile::CreateDifferentiatingLabels( |
| base::span<const AutofillProfile* const> profiles, |
| std::string_view app_locale) { |
| const size_t kMinimalFieldsShown = 2; |
| return CreateInferredLabels(profiles, /*suggested_fields=*/std::nullopt, |
| /*excluded_fields=*/{}, kMinimalFieldsShown, |
| app_locale); |
| } |
| |
| // static |
| std::vector<std::u16string> AutofillProfile::CreateInferredLabels( |
| base::span<const AutofillProfile* const> profiles, |
| const std::optional<FieldTypeSet> suggested_fields, |
| FieldTypeSet excluded_fields, |
| size_t minimal_fields_shown, |
| std::string_view app_locale) { |
| std::vector<FieldType> fields_to_use; |
| std::vector<FieldType> suggested_fields_types = |
| suggested_fields |
| ? std::vector(suggested_fields->begin(), suggested_fields->end()) |
| : std::vector<FieldType>(); |
| GetFieldsForDistinguishingProfiles( |
| suggested_fields ? &suggested_fields_types : nullptr, excluded_fields, |
| &fields_to_use); |
| |
| // Construct the default label for each profile. Also construct a map that |
| // associates each label with the profiles that have this info. This map is |
| // then used to detect which labels need further differentiating fields. Note |
| // that the actual displayed label might slightly differ due to formatting, |
| // but it is not needed to format the text for differentiating the labels. |
| std::map<std::u16string, std::list<size_t>> labels_to_profiles; |
| for (size_t i = 0; i < profiles.size(); ++i) { |
| std::u16string label = profiles[i]->ConstructInferredLabel( |
| fields_to_use, minimal_fields_shown, app_locale); |
| labels_to_profiles[label].push_back(i); |
| } |
| |
| std::vector<std::u16string> labels; |
| labels.resize(profiles.size()); |
| for (auto& it : labels_to_profiles) { |
| if (it.second.size() == 1) { |
| // This label is unique, so use it without any further ado. |
| size_t profile_index = it.second.front(); |
| labels[profile_index] = it.first; |
| } else { |
| // We have more than one profile with the same label, so add |
| // differentiating fields. |
| CreateInferredLabelsHelper( |
| profiles, it.second, fields_to_use, minimal_fields_shown, app_locale, |
| labels); |
| } |
| } |
| return labels; |
| } |
| |
| std::u16string AutofillProfile::ConstructInferredLabel( |
| base::span<const FieldType> included_fields, |
| size_t num_fields_to_use, |
| std::string_view app_locale) const { |
| // TODO(estade): use libaddressinput? |
| std::u16string separator = |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR); |
| |
| const std::u16string& profile_region_code = GetInfo( |
| AutofillType(ADDRESS_HOME_COUNTRY, /*is_country_code=*/true), app_locale); |
| std::string address_region_code = base::UTF16ToUTF8(profile_region_code); |
| |
| // A copy of |this| pruned down to contain only data for the address fields in |
| // |included_fields|. |
| AutofillProfile trimmed_profile(guid(), RecordType::kLocalOrSyncable, |
| GetAddressCountryCode()); |
| trimmed_profile.SetInfo( |
| AutofillType(ADDRESS_HOME_COUNTRY, /*is_country_code=*/true), |
| profile_region_code, app_locale); |
| trimmed_profile.set_language_code(language_code()); |
| AutofillCountry country(address_region_code); |
| |
| std::vector<FieldType> remaining_fields; |
| for (size_t i = 0; i < included_fields.size() && num_fields_to_use > 0; ++i) { |
| if (!country.IsAddressFieldSettingAccessible(included_fields[i]) || |
| included_fields[i] == ADDRESS_HOME_COUNTRY) { |
| remaining_fields.push_back(included_fields[i]); |
| continue; |
| } |
| |
| std::u16string field_value = GetInfo(included_fields[i], app_locale); |
| if (field_value.empty()) { |
| continue; |
| } |
| trimmed_profile.SetInfo(included_fields[i], field_value, app_locale); |
| --num_fields_to_use; |
| } |
| |
| std::unique_ptr<AddressData> address_data = |
| i18n::CreateAddressDataFromAutofillProfile(trimmed_profile, app_locale); |
| std::string address_line; |
| ::i18n::addressinput::GetFormattedNationalAddressLine(*address_data, |
| &address_line); |
| std::u16string label = base::UTF8ToUTF16(address_line); |
| |
| for (std::vector<FieldType>::const_iterator it = remaining_fields.begin(); |
| it != remaining_fields.end() && num_fields_to_use > 0; ++it) { |
| std::u16string field_value; |
| // Special case whole numbers: we want the user-formatted (raw) version, not |
| // the canonicalized version we'll fill into the page. |
| if (*it == PHONE_HOME_WHOLE_NUMBER) { |
| field_value = GetRawInfo(*it); |
| } else { |
| field_value = GetInfo(*it, app_locale); |
| } |
| if (field_value.empty()) { |
| continue; |
| } |
| |
| if (!label.empty()) { |
| label.append(separator); |
| } |
| |
| label.append(field_value); |
| --num_fields_to_use; |
| } |
| |
| // If country code is missing, libaddressinput won't be used to format the |
| // address. In this case the suggestion might include a multi-line street |
| // address which needs to be flattened. |
| base::ReplaceChars(label, u"\n", separator, &label); |
| |
| return label; |
| } |
| |
| void AutofillProfile::RecordAndLogUse() { |
| const base::Time now = AutofillClock::Now(); |
| const base::TimeDelta time_since_last_used = |
| now - usage_history_information_.use_date(); |
| usage_history_information_.RecordUseDate(now); |
| // Ensure that use counts are not skewed by multiple filling operations of the |
| // form. This is especially important for forms fully annotated with |
| // autocomplete=unrecognized. For such forms, keyboard accessory chips only |
| // fill a single field at a time as per |
| // `AutofillSuggestionsForAutocompleteUnrecognizedFieldsOnMobile`. |
| if (time_since_last_used.InSeconds() >= 60) { |
| if (usage_history_information_.use_count() == 1) { |
| // The max is the number of days a profile wasn't used before it gets |
| // deleted (see `kDisusedDataModelDeletionTimeDelta`). |
| base::UmaHistogramCustomCounts("Autofill.DaysUntilFirstUsage.Profile", |
| time_since_last_used.InDays(), 1, 395, |
| 100); |
| } |
| usage_history_information_.set_use_count( |
| usage_history_information_.use_count() + 1); |
| UMA_HISTOGRAM_COUNTS_1000("Autofill.DaysSinceLastUse.Profile", |
| time_since_last_used.InDays()); |
| } |
| LogVerificationStatuses(); |
| } |
| |
| void AutofillProfile::LogVerificationStatuses() { |
| AutofillMetrics::LogVerificationStatusOfNameTokensOnProfileUsage(*this); |
| AutofillMetrics::LogVerificationStatusOfAddressTokensOnProfileUsage(*this); |
| } |
| |
| VerificationStatus AutofillProfile::GetVerificationStatus( |
| const FieldType type) const { |
| const FormGroup* form_group = FormGroupForType(type); |
| if (!form_group) { |
| return VerificationStatus::kNoStatus; |
| } |
| |
| return form_group->GetVerificationStatus(type); |
| } |
| |
| std::u16string AutofillProfile::GetInfo(const AutofillType& type, |
| std::string_view app_locale) const { |
| const FormGroup* form_group = FormGroupForType(type.GetAddressType()); |
| if (!form_group) { |
| return std::u16string(); |
| } |
| return form_group->GetInfo(type, app_locale); |
| } |
| |
| bool AutofillProfile::SetInfoWithVerificationStatus(const AutofillType& type, |
| std::u16string_view value, |
| std::string_view app_locale, |
| VerificationStatus status) { |
| FormGroup* form_group = MutableFormGroupForType(type.GetAddressType()); |
| if (!form_group) { |
| return false; |
| } |
| |
| const std::u16string_view trimmed_value = |
| base::TrimWhitespace(value, base::TRIM_ALL); |
| if (type.GetAddressType() == ADDRESS_HOME_COUNTRY) { |
| const AddressCountryCode old_country_code = GetAddressCountryCode(); |
| const bool response = form_group->SetInfoWithVerificationStatus( |
| type, trimmed_value, app_locale, status); |
| OnProfileCountryUpdate(old_country_code, GetAddressCountryCode()); |
| return response; |
| } |
| |
| return form_group->SetInfoWithVerificationStatus(type, trimmed_value, |
| app_locale, status); |
| } |
| |
| bool AutofillProfile::SetInfoWithVerificationStatus(FieldType type, |
| std::u16string_view value, |
| std::string_view app_locale, |
| VerificationStatus status) { |
| return SetInfoWithVerificationStatus(AutofillType(type), value, app_locale, |
| status); |
| } |
| |
| // static |
| void AutofillProfile::CreateInferredLabelsHelper( |
| base::span<const AutofillProfile* const> profiles, |
| const std::list<size_t>& indices, |
| const std::vector<FieldType>& field_types, |
| size_t num_fields_to_include, |
| std::string_view app_locale, |
| std::vector<std::u16string>& labels) { |
| // For efficiency, we first construct a map of fields to their text values and |
| // each value's frequency. |
| std::map<FieldType, std::map<std::u16string, size_t>> |
| field_text_frequencies_by_field; |
| for (const FieldType& field_type : field_types) { |
| std::map<std::u16string, size_t>& field_text_frequencies = |
| field_text_frequencies_by_field[field_type]; |
| |
| for (const auto& it : indices) { |
| const AutofillProfile* profile = profiles[it]; |
| std::u16string field_text = profile->GetInfo(field_type, app_locale); |
| |
| // If this label is not already in the map, add it with frequency 0. |
| if (!field_text_frequencies.contains(field_text)) { |
| field_text_frequencies[field_text] = 0; |
| } |
| |
| // Now, increment the frequency for this label. |
| ++field_text_frequencies[field_text]; |
| } |
| } |
| |
| // Now comes the meat of the algorithm. For each profile, we scan the list of |
| // fields to use, looking for two things: |
| // 1. A (non-empty) field that differentiates the profile from all others |
| // 2. At least |num_fields_to_include| non-empty fields |
| // Before we've satisfied condition (2), we include all fields, even ones that |
| // are identical across all the profiles. Once we've satisfied condition (2), |
| // we only include fields that that have at last two distinct values. |
| for (const auto& it : indices) { |
| const AutofillProfile* profile = profiles[it]; |
| |
| std::vector<FieldType> label_fields; |
| bool found_differentiating_field = false; |
| for (FieldType field_type : field_types) { |
| // Skip over empty fields. |
| std::u16string field_text = profile->GetInfo(field_type, app_locale); |
| if (field_text.empty()) { |
| continue; |
| } |
| |
| std::map<std::u16string, size_t>& field_text_frequencies = |
| field_text_frequencies_by_field[field_type]; |
| |
| bool current_field_is_differentiating = |
| !field_text_frequencies.contains(u"") && |
| field_text_frequencies[field_text] == 1; |
| found_differentiating_field |= current_field_is_differentiating; |
| |
| // Once we've found enough non-empty fields, skip over any remaining |
| // fields that are identical across all the profiles. |
| if (label_fields.size() >= num_fields_to_include && |
| field_text_frequencies.size() == 1) { |
| continue; |
| } |
| |
| label_fields.push_back(field_type); |
| |
| // If we've (1) found a differentiating field and (2) found at least |
| // |num_fields_to_include| non-empty fields, we're done! |
| if (found_differentiating_field && |
| label_fields.size() >= num_fields_to_include) { |
| break; |
| } |
| } |
| |
| // The final order of the `label_fields` is established by a third party |
| // library: libaddressinput. Libaddressinput has a different order for each |
| // country, depending on what makes sense in that country. Chrome code has |
| // no control over the final order of the labels. |
| labels[it] = profile->ConstructInferredLabel( |
| label_fields, label_fields.size(), app_locale); |
| } |
| } |
| |
| const FormGroup* AutofillProfile::FormGroupForType(FieldType type) const { |
| return const_cast<AutofillProfile*>(this)->MutableFormGroupForType(type); |
| } |
| |
| FormGroup* AutofillProfile::MutableFormGroupForType(FieldType type) { |
| switch (GroupTypeOfFieldType(type)) { |
| case FieldTypeGroup::kName: |
| return &name_; |
| case FieldTypeGroup::kEmail: |
| return &email_; |
| case FieldTypeGroup::kCompany: |
| return &company_; |
| case FieldTypeGroup::kPhone: |
| return &phone_number_; |
| case FieldTypeGroup::kAddress: |
| return &address_; |
| case FieldTypeGroup::kNoGroup: |
| case FieldTypeGroup::kCreditCard: |
| case FieldTypeGroup::kIban: |
| case FieldTypeGroup::kPasswordField: |
| case FieldTypeGroup::kUsernameField: |
| case FieldTypeGroup::kTransaction: |
| case FieldTypeGroup::kStandaloneCvcField: |
| case FieldTypeGroup::kUnfillable: |
| case FieldTypeGroup::kAutofillAi: |
| case FieldTypeGroup::kLoyaltyCard: |
| case FieldTypeGroup::kOneTimePassword: |
| return nullptr; |
| } |
| NOTREACHED(); |
| } |
| |
| bool AutofillProfile::EqualsSansGuid(const AutofillProfile& profile) const { |
| return language_code() == profile.language_code() && |
| profile_label() == profile.profile_label() && |
| record_type() == profile.record_type() && Compare(profile) == 0; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const AutofillProfile& profile) { |
| os << profile.guid() << " label: " << profile.profile_label() << " " |
| << profile.usage_history().use_count() << " " |
| << profile.usage_history().use_date() << " " << profile.language_code() |
| << std::endl; |
| |
| for (FieldType type : profile.GetSupportedTypes()) { |
| os << FieldTypeToStringView(type) << ": " << profile.GetRawInfo(type) << "(" |
| << profile.GetVerificationStatus(type) << ")" << std::endl; |
| } |
| |
| return os; |
| } |
| |
| LogBuffer& operator<<(LogBuffer& buffer, const AutofillProfile& profile) { |
| auto get_record_type = [](AutofillProfile::RecordType record_type) { |
| switch (record_type) { |
| case AutofillProfile::RecordType::kLocalOrSyncable: |
| return "kLocalOrSyncable"; |
| case AutofillProfile::RecordType::kAccount: |
| return "kAccount"; |
| case AutofillProfile::RecordType::kAccountHome: |
| return "kAccountHome"; |
| case AutofillProfile::RecordType::kAccountWork: |
| return "kAccountWork"; |
| case AutofillProfile::RecordType::kAccountNameEmail: |
| return "kAccountNameEmail"; |
| } |
| NOTREACHED(); |
| }; |
| buffer << Tag{"table"}; |
| buffer << Tr{} << "guid" << profile.guid_; |
| buffer << Tr{} << "record_type" << get_record_type(profile.record_type_); |
| buffer << Tr{} << "name" << profile.name_; |
| buffer << Tr{} << "address" << profile.address_; |
| buffer << Tr{} << "email" << profile.email_; |
| buffer << Tr{} << "company" << profile.company_; |
| buffer << Tr{} << "phone" << profile.phone_number_; |
| buffer << CTag{"table"}; |
| return buffer; |
| } |
| |
| bool AutofillProfile::FinalizeAfterImport() { |
| bool success = true; |
| success &= name_.FinalizeAfterImport(); |
| success &= address_.FinalizeAfterImport(); |
| return success; |
| } |
| |
| AutofillProfile AutofillProfile::ConvertToAccountProfile() const { |
| DCHECK(record_type() != RecordType::kAccount); |
| AutofillProfile account_profile = *this; |
| // Since GUIDs are assumed to be unique across all profile record types, a new |
| // GUID is assigned. |
| account_profile.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| account_profile.record_type_ = RecordType::kAccount; |
| // Initial creator is unused for kLocalOrSyncable profiles. |
| account_profile.initial_creator_id_ = kInitialCreatorChrome; |
| return account_profile; |
| } |
| |
| AutofillProfile AutofillProfile::ConvertToLocalOrSyncableProfile() const { |
| DCHECK(record_type() != RecordType::kLocalOrSyncable); |
| AutofillProfile local_or_syncable_profile = *this; |
| // Since GUIDs are assumed to be unique across all profile record types, a new |
| // GUID is assigned. |
| local_or_syncable_profile.set_guid( |
| base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| local_or_syncable_profile.record_type_ = RecordType::kLocalOrSyncable; |
| return local_or_syncable_profile; |
| } |
| |
| FieldTypeSet AutofillProfile::FindInaccessibleProfileValues() const { |
| FieldTypeSet inaccessible_fields; |
| const std::string stored_country = |
| base::UTF16ToUTF8(GetRawInfo(ADDRESS_HOME_COUNTRY)); |
| // Consider only AddressFields which are invisible in the settings for some |
| // countries. |
| for (const AddressField& adress_field : |
| {AddressField::ADMIN_AREA, AddressField::LOCALITY, |
| AddressField::DEPENDENT_LOCALITY, AddressField::POSTAL_CODE, |
| AddressField::SORTING_CODE}) { |
| FieldType field_type = i18n::TypeForField(adress_field); |
| CHECK_EQ(GroupTypeOfFieldType(field_type), FieldTypeGroup::kAddress); |
| if (HasRawInfo(field_type) && |
| !GetAddress().IsAddressFieldSettingAccessible(field_type)) { |
| inaccessible_fields.insert(field_type); |
| } |
| } |
| return inaccessible_fields; |
| } |
| |
| void AutofillProfile::ClearFields(const FieldTypeSet& fields) { |
| for (FieldType field_type : fields) { |
| SetRawInfoWithVerificationStatus(field_type, u"", |
| VerificationStatus::kNoStatus); |
| } |
| } |
| |
| void AutofillProfile::MigrateRegularNameToPhoneticName() { |
| name_.MigrateRegularNameToPhoneticName(); |
| } |
| |
| UsageHistoryInformation& AutofillProfile::usage_history() { |
| return usage_history_information_; |
| } |
| const UsageHistoryInformation& AutofillProfile::usage_history() const { |
| return usage_history_information_; |
| } |
| |
| } // namespace autofill |
| |
| #if BUILDFLAG(IS_ANDROID) |
| DEFINE_JNI(AutofillProfile) |
| #endif |