blob: c4f1237031e796d0baabec9d5099c75c7d412c1d [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/suggestion_selection.h"
#include <string>
#include <vector>
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/address_i18n.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_profile_comparator.h"
#include "components/autofill/core/browser/suggestion.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_util.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_formatter.h"
namespace autofill {
namespace suggestion_selection {
namespace {
using ::i18n::addressinput::AddressField;
using ::i18n::addressinput::GetStreetAddressLinesAsSingleLine;
using ::i18n::addressinput::STREET_ADDRESS;
// In addition to just getting the values out of the autocomplete profile, this
// function handles formatting of the street address into a single string.
base::string16 GetInfoInOneLine(const AutofillProfile* profile,
const AutofillType& type,
const std::string& app_locale) {
std::vector<base::string16> results;
AddressField address_field;
if (i18n::FieldForType(type.GetStorableType(), &address_field) &&
address_field == STREET_ADDRESS) {
std::string street_address_line;
GetStreetAddressLinesAsSingleLine(
*i18n::CreateAddressDataFromAutofillProfile(*profile, app_locale),
&street_address_line);
return base::UTF8ToUTF16(street_address_line);
}
return profile->GetInfo(type, app_locale);
}
} // namespace
// As of November 2018, 50 profiles should be more than enough to cover at least
// 99% of all times the dropdown is shown.
constexpr size_t kMaxSuggestedProfilesCount = 50;
// As of November 2018, displaying 10 suggestions cover at least 99% of the
// indices clicked by our users. The suggestions will also refine as they type.
constexpr size_t kMaxUniqueSuggestionsCount = 10;
std::vector<Suggestion> GetPrefixMatchedSuggestions(
const AutofillType& type,
const base::string16& field_contents_canon,
const AutofillProfileComparator& comparator,
const std::vector<AutofillProfile*>& profiles,
std::vector<AutofillProfile*>* matched_profiles) {
std::vector<Suggestion> suggestions;
for (size_t i = 0; i < profiles.size() &&
matched_profiles->size() < kMaxSuggestedProfilesCount;
i++) {
AutofillProfile* profile = profiles[i];
bool use_server_validation = base::FeatureList::IsEnabled(
autofill::features::kAutofillProfileServerValidation);
bool use_client_validation = base::FeatureList::IsEnabled(
autofill::features::kAutofillProfileClientValidation);
ServerFieldType server_field_type = type.GetStorableType();
if ((use_client_validation &&
profile->GetValidityState(server_field_type,
AutofillProfile::CLIENT) ==
AutofillProfile::INVALID) ||
(use_server_validation &&
profile->GetValidityState(server_field_type,
AutofillProfile::SERVER) ==
AutofillProfile::INVALID)) {
continue;
}
base::string16 value =
GetInfoInOneLine(profile, type, comparator.app_locale());
if (value.empty())
continue;
bool prefix_matched_suggestion;
base::string16 suggestion_canon = comparator.NormalizeForComparison(value);
if (IsValidSuggestionForFieldContents(
suggestion_canon, field_contents_canon, type,
/* is_masked_server_card= */ false, &prefix_matched_suggestion)) {
matched_profiles->push_back(profile);
suggestions.push_back(Suggestion(value));
suggestions.back().backend_id = profile->guid();
suggestions.back().match = prefix_matched_suggestion
? Suggestion::PREFIX_MATCH
: Suggestion::SUBSTRING_MATCH;
}
}
// Prefix matches should precede other token matches.
if (IsFeatureSubstringMatchEnabled()) {
std::stable_sort(suggestions.begin(), suggestions.end(),
[](const Suggestion& a, const Suggestion& b) {
return a.match < b.match;
});
}
return suggestions;
}
std::vector<Suggestion> GetUniqueSuggestions(
const std::vector<ServerFieldType>& other_field_types,
const std::string app_locale,
const std::vector<AutofillProfile*> matched_profiles,
const std::vector<Suggestion>& suggestions,
std::vector<AutofillProfile*>* unique_matched_profiles) {
std::vector<Suggestion> unique_suggestions;
// Limit number of unique profiles as having too many makes the browser hang
// due to drawing calculations (and is also not very useful for the user).
ServerFieldTypeSet types(other_field_types.begin(), other_field_types.end());
for (size_t i = 0; i < matched_profiles.size() &&
unique_suggestions.size() < kMaxUniqueSuggestionsCount;
++i) {
bool include = true;
AutofillProfile* profile_a = matched_profiles[i];
for (size_t j = 0; j < matched_profiles.size(); ++j) {
AutofillProfile* profile_b = matched_profiles[j];
// Check if profile A is a subset of profile B. If not, continue.
if (i == j || suggestions[i].value != suggestions[j].value ||
!profile_a->IsSubsetOfForFieldSet(*profile_b, app_locale, types)) {
continue;
}
// Check if profile B is also a subset of profile A. If so, the
// profiles are identical. Include the first one but not the second.
if (i < j &&
profile_b->IsSubsetOfForFieldSet(*profile_a, app_locale, types)) {
continue;
}
// One-way subset. Don't include profile A.
include = false;
break;
}
if (include) {
unique_matched_profiles->push_back(matched_profiles[i]);
unique_suggestions.push_back(suggestions[i]);
}
}
return unique_suggestions;
}
bool IsValidSuggestionForFieldContents(base::string16 suggestion_canon,
base::string16 field_contents_canon,
const AutofillType& type,
bool is_masked_server_card,
bool* is_prefix_matched) {
*is_prefix_matched = true;
// Phones should do a substring match because they can be trimmed to remove
// the first parts (e.g. country code or prefix). It is still considered a
// prefix match in order to put it at the top of the suggestions.
if ((type.group() == PHONE_HOME || type.group() == PHONE_BILLING) &&
suggestion_canon.find(field_contents_canon) != base::string16::npos) {
return true;
}
// For card number fields, suggest the card if:
// - the number matches any part of the card, or
// - it's a masked card and there are 6 or fewer typed so far.
if (type.GetStorableType() == CREDIT_CARD_NUMBER) {
if (suggestion_canon.find(field_contents_canon) == base::string16::npos &&
(!is_masked_server_card || field_contents_canon.size() >= 6)) {
return false;
}
return true;
}
if (base::StartsWith(suggestion_canon, field_contents_canon,
base::CompareCase::SENSITIVE)) {
return true;
}
if (IsFeatureSubstringMatchEnabled() &&
suggestion_canon.length() >= field_contents_canon.length() &&
GetTextSelectionStart(suggestion_canon, field_contents_canon, false) !=
base::string16::npos) {
*is_prefix_matched = false;
return true;
}
return false;
}
void RemoveProfilesNotUsedSinceTimestamp(
base::Time min_last_used,
std::vector<AutofillProfile*>* profiles) {
const size_t original_size = profiles->size();
profiles->erase(
std::stable_partition(profiles->begin(), profiles->end(),
[min_last_used](const AutofillDataModel* m) {
return m->use_date() > min_last_used;
}),
profiles->end());
const size_t num_profiles_supressed = original_size - profiles->size();
AutofillMetrics::LogNumberOfAddressesSuppressedForDisuse(
num_profiles_supressed);
}
} // namespace suggestion_selection
} // namespace autofill