blob: 127a1cbaf3e2cc18720fd212404da80fd033333d [file] [log] [blame]
// Copyright 2013 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/personal_data_manager.h"
#include <stddef.h>
#include <algorithm>
#include <list>
#include <map>
#include <string>
#include <utility>
#include "base/i18n/case_conversion.h"
#include "base/i18n/timezone.h"
#include "base/profiler/scoped_tracker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/address_i18n.h"
#include "components/autofill/core/browser/autofill-inl.h"
#include "components/autofill/core/browser/autofill_country.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/country_data.h"
#include "components/autofill/core/browser/country_names.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/personal_data_manager_observer.h"
#include "components/autofill/core/browser/phone_number.h"
#include "components/autofill/core/browser/phone_number_i18n.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_pref_names.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/common/signin_pref_names.h"
#include "components/variations/variations_associated_data.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 {
using ::i18n::addressinput::AddressField;
using ::i18n::addressinput::GetStreetAddressLinesAsSingleLine;
using ::i18n::addressinput::STREET_ADDRESS;
template<typename T>
class FormGroupMatchesByGUIDFunctor {
public:
explicit FormGroupMatchesByGUIDFunctor(const std::string& guid)
: guid_(guid) {
}
bool operator()(const T& form_group) {
return form_group.guid() == guid_;
}
bool operator()(const T* form_group) {
return form_group->guid() == guid_;
}
private:
const std::string guid_;
};
template<typename T, typename C>
typename C::const_iterator FindElementByGUID(const C& container,
const std::string& guid) {
return std::find_if(container.begin(),
container.end(),
FormGroupMatchesByGUIDFunctor<T>(guid));
}
template<typename T, typename C>
bool FindByGUID(const C& container, const std::string& guid) {
return FindElementByGUID<T>(container, guid) != container.end();
}
template<typename T>
class IsEmptyFunctor {
public:
explicit IsEmptyFunctor(const std::string& app_locale)
: app_locale_(app_locale) {
}
bool operator()(const T& form_group) {
return form_group.IsEmpty(app_locale_);
}
private:
const std::string app_locale_;
};
// Returns true if minimum requirements for import of a given |profile| have
// been met. An address submitted via a form must have at least the fields
// required as determined by its country code.
// No verification of validity of the contents is preformed. This is an
// existence check only.
bool IsMinimumAddress(const AutofillProfile& profile,
const std::string& app_locale) {
// All countries require at least one address line.
if (profile.GetRawInfo(ADDRESS_HOME_LINE1).empty())
return false;
std::string country_code =
base::UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY));
if (country_code.empty())
country_code = AutofillCountry::CountryCodeForLocale(app_locale);
AutofillCountry country(country_code, app_locale);
if (country.requires_city() && profile.GetRawInfo(ADDRESS_HOME_CITY).empty())
return false;
if (country.requires_state() &&
profile.GetRawInfo(ADDRESS_HOME_STATE).empty())
return false;
if (country.requires_zip() && profile.GetRawInfo(ADDRESS_HOME_ZIP).empty())
return false;
return true;
}
// Return true if the |field_type| and |value| are valid within the context
// of importing a form.
bool IsValidFieldTypeAndValue(const std::set<ServerFieldType>& types_seen,
ServerFieldType field_type,
const base::string16& value) {
// Abandon the import if two fields of the same type are encountered.
// This indicates ambiguous data or miscategorization of types.
// Make an exception for PHONE_HOME_NUMBER however as both prefix and
// suffix are stored against this type, and for EMAIL_ADDRESS because it is
// common to see second 'confirm email address' fields on forms.
if (types_seen.count(field_type) &&
field_type != PHONE_HOME_NUMBER &&
field_type != EMAIL_ADDRESS)
return false;
// Abandon the import if an email address value shows up in a field that is
// not an email address.
if (field_type != EMAIL_ADDRESS && IsValidEmailAddress(value))
return false;
return true;
}
// 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);
}
// Receives the loaded profiles from the web data service and stores them in
// |*dest|. The pending handle is the address of the pending handle
// corresponding to this request type. This function is used to save both
// server and local profiles and credit cards.
template <typename ValueType>
void ReceiveLoadedDbValues(WebDataServiceBase::Handle h,
const WDTypedResult* result,
WebDataServiceBase::Handle* pending_handle,
ScopedVector<ValueType>* dest) {
DCHECK_EQ(*pending_handle, h);
*pending_handle = 0;
const WDResult<std::vector<ValueType*>>* r =
static_cast<const WDResult<std::vector<ValueType*>>*>(result);
dest->clear();
for (ValueType* value : r->GetValue())
dest->push_back(value);
}
// A helper function for finding the maximum value in a string->int map.
static bool CompareVotes(const std::pair<std::string, int>& a,
const std::pair<std::string, int>& b) {
return a.second < b.second;
}
// Returns whether the |suggestion| is valid considering the
// |field_contents_canon|, the |type| and |is_masked_server_card|. Assigns true
// to |is_prefix_matched| if the |field_contents_canon| is a prefix to
// |suggestion|, assigns false otherwise.
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 fewers 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;
}
} // namespace
const char kFrecencyFieldTrialName[] = "AutofillProfileOrderByFrecency";
const char kFrecencyFieldTrialLimitParam[] = "limit";
PersonalDataManager::PersonalDataManager(const std::string& app_locale)
: database_(NULL),
is_data_loaded_(false),
pending_profiles_query_(0),
pending_server_profiles_query_(0),
pending_creditcards_query_(0),
pending_server_creditcards_query_(0),
app_locale_(app_locale),
pref_service_(NULL),
account_tracker_(NULL),
is_off_the_record_(false),
has_logged_profile_count_(false),
has_logged_credit_card_count_(false) {}
void PersonalDataManager::Init(scoped_refptr<AutofillWebDataService> database,
PrefService* pref_service,
AccountTrackerService* account_tracker,
SigninManagerBase* signin_manager,
bool is_off_the_record) {
CountryNames::SetLocaleString(app_locale_);
database_ = database;
SetPrefService(pref_service);
account_tracker_ = account_tracker;
signin_manager_ = signin_manager;
is_off_the_record_ = is_off_the_record;
if (!is_off_the_record_)
AutofillMetrics::LogIsAutofillEnabledAtStartup(IsAutofillEnabled());
// WebDataService may not be available in tests.
if (!database_.get())
return;
LoadProfiles();
LoadCreditCards();
database_->AddObserver(this);
}
PersonalDataManager::~PersonalDataManager() {
CancelPendingQuery(&pending_profiles_query_);
CancelPendingQuery(&pending_server_profiles_query_);
CancelPendingQuery(&pending_creditcards_query_);
CancelPendingQuery(&pending_server_creditcards_query_);
if (database_.get())
database_->RemoveObserver(this);
}
void PersonalDataManager::OnWebDataServiceRequestDone(
WebDataServiceBase::Handle h,
const WDTypedResult* result) {
DCHECK(pending_profiles_query_ || pending_server_profiles_query_ ||
pending_creditcards_query_ || pending_server_creditcards_query_);
// TODO(robliao): Remove ScopedTracker below once https://crbug.com/422460 is
// fixed.
tracked_objects::ScopedTracker tracking_profile(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
"422460 PersonalDataManager::OnWebDataServiceRequestDone"));
if (!result) {
// Error from the web database.
if (h == pending_creditcards_query_)
pending_creditcards_query_ = 0;
else if (h == pending_profiles_query_)
pending_profiles_query_ = 0;
return;
}
switch (result->GetType()) {
case AUTOFILL_PROFILES_RESULT:
if (h == pending_profiles_query_) {
ReceiveLoadedDbValues(h, result, &pending_profiles_query_,
&web_profiles_);
LogProfileCount(); // This only logs local profiles.
} else {
ReceiveLoadedDbValues(h, result, &pending_server_profiles_query_,
&server_profiles_);
if (!server_profiles_.empty()) {
std::string account_id = signin_manager_->GetAuthenticatedAccountId();
base::string16 email =
base::UTF8ToUTF16(
account_tracker_->GetAccountInfo(account_id).email);
DCHECK(!email.empty());
for (AutofillProfile* profile : server_profiles_)
profile->SetRawInfo(EMAIL_ADDRESS, email);
}
}
break;
case AUTOFILL_CREDITCARDS_RESULT:
if (h == pending_creditcards_query_) {
ReceiveLoadedDbValues(h, result, &pending_creditcards_query_,
&local_credit_cards_);
LogLocalCreditCardCount();
} else {
ReceiveLoadedDbValues(h, result, &pending_server_creditcards_query_,
&server_credit_cards_);
// If the user has a saved unmasked server card and the experiment is
// disabled, force mask all cards back to the unsaved state.
if (!OfferStoreUnmaskedCards())
ResetFullServerCards();
}
break;
default:
NOTREACHED();
}
// If all requests have responded, then all personal data is loaded.
if (pending_profiles_query_ == 0 &&
pending_creditcards_query_ == 0 &&
pending_server_profiles_query_ == 0 &&
pending_server_creditcards_query_ == 0) {
is_data_loaded_ = true;
NotifyPersonalDataChanged();
}
}
void PersonalDataManager::AutofillMultipleChanged() {
Refresh();
}
void PersonalDataManager::AddObserver(PersonalDataManagerObserver* observer) {
observers_.AddObserver(observer);
}
void PersonalDataManager::RemoveObserver(
PersonalDataManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
bool PersonalDataManager::ImportFormData(
const FormStructure& form,
bool should_return_local_card,
std::unique_ptr<CreditCard>* imported_credit_card) {
// We try the same |form| for both credit card and address import/update.
// - ImportCreditCard may update an existing card, or fill
// |imported_credit_card| with an extracted card. See .h for details of
// |should_return_local_card|.
bool cc_import =
ImportCreditCard(form, should_return_local_card, imported_credit_card);
// - ImportAddressProfiles may eventually save or update one or more address
// profiles.
bool address_import = ImportAddressProfiles(form);
if (cc_import || address_import)
return true;
FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_,
OnInsufficientFormData());
return false;
}
void PersonalDataManager::RecordUseOf(const AutofillDataModel& data_model) {
if (is_off_the_record_ || !database_.get())
return;
CreditCard* credit_card = GetCreditCardByGUID(data_model.guid());
if (credit_card) {
credit_card->RecordAndLogUse();
if (credit_card->record_type() == CreditCard::LOCAL_CARD)
database_->UpdateCreditCard(*credit_card);
else
database_->UpdateServerCardUsageStats(*credit_card);
Refresh();
return;
}
AutofillProfile* profile = GetProfileByGUID(data_model.guid());
if (profile) {
profile->RecordAndLogUse();
if (profile->record_type() == AutofillProfile::LOCAL_PROFILE)
database_->UpdateAutofillProfile(*profile);
else if (profile->record_type() == AutofillProfile::SERVER_PROFILE)
database_->UpdateServerAddressUsageStats(*profile);
Refresh();
}
}
void PersonalDataManager::AddProfile(const AutofillProfile& profile) {
if (is_off_the_record_)
return;
if (profile.IsEmpty(app_locale_))
return;
// Don't add an existing profile.
if (FindByGUID<AutofillProfile>(web_profiles_, profile.guid()))
return;
if (!database_.get())
return;
// Don't add a duplicate.
if (FindByContents(web_profiles_, profile))
return;
// Add the new profile to the web database.
database_->AddAutofillProfile(profile);
// Refresh our local cache and send notifications to observers.
Refresh();
}
void PersonalDataManager::UpdateProfile(const AutofillProfile& profile) {
if (is_off_the_record_)
return;
AutofillProfile* existing_profile = GetProfileByGUID(profile.guid());
if (!existing_profile)
return;
// Don't overwrite the origin for a profile that is already stored.
if (existing_profile->EqualsSansOrigin(profile))
return;
if (profile.IsEmpty(app_locale_)) {
RemoveByGUID(profile.guid());
return;
}
if (!database_.get())
return;
// Make the update.
database_->UpdateAutofillProfile(profile);
// Refresh our local cache and send notifications to observers.
Refresh();
}
AutofillProfile* PersonalDataManager::GetProfileByGUID(
const std::string& guid) {
const std::vector<AutofillProfile*>& profiles = GetProfiles();
std::vector<AutofillProfile*>::const_iterator iter =
FindElementByGUID<AutofillProfile>(profiles, guid);
return (iter != profiles.end()) ? *iter : NULL;
}
void PersonalDataManager::AddCreditCard(const CreditCard& credit_card) {
if (is_off_the_record_)
return;
if (credit_card.IsEmpty(app_locale_))
return;
if (FindByGUID<CreditCard>(local_credit_cards_, credit_card.guid()))
return;
if (!database_.get())
return;
// Don't add a duplicate.
if (FindByContents(local_credit_cards_, credit_card))
return;
// Add the new credit card to the web database.
database_->AddCreditCard(credit_card);
// Refresh our local cache and send notifications to observers.
Refresh();
}
void PersonalDataManager::UpdateCreditCard(const CreditCard& credit_card) {
DCHECK_EQ(CreditCard::LOCAL_CARD, credit_card.record_type());
if (is_off_the_record_)
return;
CreditCard* existing_credit_card = GetCreditCardByGUID(credit_card.guid());
if (!existing_credit_card)
return;
// Don't overwrite the origin for a credit card that is already stored.
if (existing_credit_card->Compare(credit_card) == 0)
return;
if (credit_card.IsEmpty(app_locale_)) {
RemoveByGUID(credit_card.guid());
return;
}
if (!database_.get())
return;
// Make the update.
database_->UpdateCreditCard(credit_card);
// Refresh our local cache and send notifications to observers.
Refresh();
}
void PersonalDataManager::UpdateServerCreditCard(
const CreditCard& credit_card) {
DCHECK_NE(CreditCard::LOCAL_CARD, credit_card.record_type());
if (is_off_the_record_ || !database_.get())
return;
// Look up by server id, not GUID.
CreditCard* existing_credit_card = nullptr;
for (auto it : server_credit_cards_) {
if (credit_card.server_id() == it->server_id()) {
existing_credit_card = it;
break;
}
}
if (!existing_credit_card)
return;
DCHECK_NE(existing_credit_card->record_type(), credit_card.record_type());
DCHECK_EQ(existing_credit_card->Label(), credit_card.Label());
if (existing_credit_card->record_type() == CreditCard::MASKED_SERVER_CARD) {
database_->UnmaskServerCreditCard(credit_card,
credit_card.number());
} else {
database_->MaskServerCreditCard(credit_card.server_id());
}
Refresh();
}
void PersonalDataManager::ResetFullServerCard(const std::string& guid) {
for (const CreditCard* card : server_credit_cards_) {
if (card->guid() == guid) {
DCHECK_EQ(card->record_type(), CreditCard::FULL_SERVER_CARD);
CreditCard card_copy = *card;
card_copy.set_record_type(CreditCard::MASKED_SERVER_CARD);
card_copy.SetNumber(card->LastFourDigits());
UpdateServerCreditCard(card_copy);
break;
}
}
}
void PersonalDataManager::ResetFullServerCards() {
for (const CreditCard* card : server_credit_cards_) {
if (card->record_type() == CreditCard::FULL_SERVER_CARD) {
CreditCard card_copy = *card;
card_copy.set_record_type(CreditCard::MASKED_SERVER_CARD);
card_copy.SetNumber(card->LastFourDigits());
UpdateServerCreditCard(card_copy);
}
}
}
void PersonalDataManager::ClearAllServerData() {
// This could theoretically be called before we get the data back from the
// database on startup, and it could get called when the wallet pref is
// off (meaning this class won't even query for the server data) so don't
// check the server_credit_cards_/profiles_ before posting to the DB.
database_->ClearAllServerData();
// The above call will eventually clear our server data by notifying us
// that the data changed and then this class will re-fetch. Preemptively
// clear so that tests can synchronously verify that this data was cleared.
server_credit_cards_.clear();
server_profiles_.clear();
}
void PersonalDataManager::AddServerCreditCardForTest(
std::unique_ptr<CreditCard> credit_card) {
server_credit_cards_.push_back(credit_card.release());
}
void PersonalDataManager::RemoveByGUID(const std::string& guid) {
if (is_off_the_record_)
return;
bool is_credit_card = FindByGUID<CreditCard>(local_credit_cards_, guid);
bool is_profile = !is_credit_card &&
FindByGUID<AutofillProfile>(web_profiles_, guid);
if (!is_credit_card && !is_profile)
return;
if (!database_.get())
return;
if (is_credit_card)
database_->RemoveCreditCard(guid);
else
database_->RemoveAutofillProfile(guid);
// Refresh our local cache and send notifications to observers.
Refresh();
}
CreditCard* PersonalDataManager::GetCreditCardByGUID(const std::string& guid) {
const std::vector<CreditCard*>& credit_cards = GetCreditCards();
std::vector<CreditCard*>::const_iterator iter =
FindElementByGUID<CreditCard>(credit_cards, guid);
return (iter != credit_cards.end()) ? *iter : NULL;
}
void PersonalDataManager::GetNonEmptyTypes(
ServerFieldTypeSet* non_empty_types) {
for (AutofillProfile* profile : GetProfiles())
profile->GetNonEmptyTypes(app_locale_, non_empty_types);
for (CreditCard* card : GetCreditCards())
card->GetNonEmptyTypes(app_locale_, non_empty_types);
}
bool PersonalDataManager::IsDataLoaded() const {
return is_data_loaded_;
}
const std::vector<AutofillProfile*>& PersonalDataManager::GetProfiles() const {
return GetProfiles(false);
}
const std::vector<AutofillProfile*>& PersonalDataManager::web_profiles() const {
return web_profiles_.get();
}
const std::vector<CreditCard*>& PersonalDataManager::GetLocalCreditCards()
const {
return local_credit_cards_.get();
}
const std::vector<CreditCard*>& PersonalDataManager::GetCreditCards() const {
credit_cards_.clear();
credit_cards_.insert(credit_cards_.end(), local_credit_cards_.begin(),
local_credit_cards_.end());
if (pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) {
credit_cards_.insert(credit_cards_.end(), server_credit_cards_.begin(),
server_credit_cards_.end());
}
return credit_cards_;
}
bool PersonalDataManager::HasServerData() const {
return !server_credit_cards_.empty() || !server_profiles_.empty();
}
void PersonalDataManager::Refresh() {
LoadProfiles();
LoadCreditCards();
}
std::vector<Suggestion> PersonalDataManager::GetProfileSuggestions(
const AutofillType& type,
const base::string16& field_contents,
bool field_is_autofilled,
const std::vector<ServerFieldType>& other_field_types) {
if (IsInAutofillSuggestionsDisabledExperiment())
return std::vector<Suggestion>();
base::string16 field_contents_canon =
AutofillProfile::CanonicalizeProfileString(field_contents);
std::vector<AutofillProfile*> profiles = GetProfiles(true);
// Rank the suggestions by frecency (see AutofillDataModel for details).
base::Time comparison_time = base::Time::Now();
std::sort(profiles.begin(), profiles.end(),
[comparison_time](const AutofillDataModel* a,
const AutofillDataModel* b) {
return a->CompareFrecency(b, comparison_time);
});
std::vector<Suggestion> suggestions;
// Match based on a prefix search.
std::vector<AutofillProfile*> matched_profiles;
for (AutofillProfile* profile : profiles) {
base::string16 value = GetInfoInOneLine(profile, type, app_locale_);
if (value.empty())
continue;
bool prefix_matched_suggestion;
base::string16 suggestion_canon =
AutofillProfile::CanonicalizeProfileString(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;
});
}
// Don't show two suggestions if one is a subset of the other.
std::vector<AutofillProfile*> unique_matched_profiles;
std::vector<Suggestion> unique_suggestions;
ServerFieldTypeSet types(other_field_types.begin(), other_field_types.end());
for (size_t i = 0; i < matched_profiles.size(); ++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]);
}
}
// Generate disambiguating labels based on the list of matches.
std::vector<base::string16> labels;
AutofillProfile::CreateInferredLabels(
unique_matched_profiles, &other_field_types, type.GetStorableType(), 1,
app_locale_, &labels);
DCHECK_EQ(unique_suggestions.size(), labels.size());
for (size_t i = 0; i < labels.size(); i++)
unique_suggestions[i].label = labels[i];
// Get the profile suggestions limit value set for the current frecency field
// trial group or SIZE_MAX if no limit is defined.
std::string limit_str = variations::GetVariationParamValue(
kFrecencyFieldTrialName, kFrecencyFieldTrialLimitParam);
size_t limit = base::StringToSizeT(limit_str, &limit) ? limit : SIZE_MAX;
unique_suggestions.resize(std::min(unique_suggestions.size(), limit));
return unique_suggestions;
}
std::vector<Suggestion> PersonalDataManager::GetCreditCardSuggestions(
const AutofillType& type,
const base::string16& field_contents) {
if (IsInAutofillSuggestionsDisabledExperiment())
return std::vector<Suggestion>();
const std::vector<CreditCard*> credit_cards = GetCreditCards();
std::list<const CreditCard*> cards_to_suggest(credit_cards.begin(),
credit_cards.end());
DedupeCreditCardToSuggest(&cards_to_suggest);
// Rank the cards by frecency (see AutofillDataModel for details). All expired
// cards should be suggested last, also by frecency.
base::Time comparison_time = base::Time::Now();
cards_to_suggest.sort(
[comparison_time](const CreditCard* a, const CreditCard* b) {
bool a_is_expired = a->IsExpired(comparison_time);
if (a_is_expired != b->IsExpired(comparison_time))
return !a_is_expired;
return a->CompareFrecency(b, comparison_time);
});
return GetSuggestionsForCards(type, field_contents, cards_to_suggest);
}
bool PersonalDataManager::IsAutofillEnabled() const {
return ::autofill::IsAutofillEnabled(pref_service_);
}
std::string PersonalDataManager::CountryCodeForCurrentTimezone() const {
return base::CountryCodeForCurrentTimezone();
}
void PersonalDataManager::SetPrefService(PrefService* pref_service) {
enabled_pref_.reset(new BooleanPrefMember);
wallet_enabled_pref_.reset(new BooleanPrefMember);
pref_service_ = pref_service;
// |pref_service_| can be NULL in tests.
if (pref_service_) {
enabled_pref_->Init(prefs::kAutofillEnabled, pref_service_,
base::Bind(&PersonalDataManager::EnabledPrefChanged,
base::Unretained(this)));
wallet_enabled_pref_->Init(prefs::kAutofillWalletImportEnabled,
pref_service_,
base::Bind(&PersonalDataManager::EnabledPrefChanged,
base::Unretained(this)));
}
}
// static
bool PersonalDataManager::IsValidLearnableProfile(
const AutofillProfile& profile,
const std::string& app_locale) {
if (!IsMinimumAddress(profile, app_locale))
return false;
base::string16 email = profile.GetRawInfo(EMAIL_ADDRESS);
if (!email.empty() && !IsValidEmailAddress(email))
return false;
// Reject profiles with invalid US state information.
if (profile.IsPresentButInvalid(ADDRESS_HOME_STATE))
return false;
// Reject profiles with invalid US zip information.
if (profile.IsPresentButInvalid(ADDRESS_HOME_ZIP))
return false;
return true;
}
// static
std::string PersonalDataManager::MergeProfile(
const AutofillProfile& new_profile,
std::vector<AutofillProfile*> existing_profiles,
const std::string& app_locale,
std::vector<AutofillProfile>* merged_profiles) {
merged_profiles->clear();
// Sort the existing profiles in decreasing order of frecency, so the "best"
// profiles are checked first.
base::Time comparison_time = base::Time::Now();
std::sort(existing_profiles.begin(), existing_profiles.end(),
[comparison_time](const AutofillDataModel* a,
const AutofillDataModel* b) {
return a->CompareFrecency(b, comparison_time);
});
// Set to true if |existing_profiles| already contains an equivalent profile.
bool matching_profile_found = false;
std::string guid = new_profile.guid();
// If we have already saved this address, merge in any missing values.
// Only merge with the first match.
for (AutofillProfile* existing_profile : existing_profiles) {
if (!matching_profile_found && !new_profile.PrimaryValue().empty() &&
existing_profile->SaveAdditionalInfo(new_profile, app_locale)) {
// Unverified profiles should always be updated with the newer data,
// whereas verified profiles should only ever be overwritten by verified
// data. If an automatically aggregated profile would overwrite a
// verified profile, just drop it.
matching_profile_found = true;
guid = existing_profile->guid();
// We set the modification date so that immediate requests for profiles
// will properly reflect the fact that this profile has been modified
// recently. After writing to the database and refreshing the local copies
// the profile will have a very slightly newer time reflecting what's
// actually stored in the database.
existing_profile->set_modification_date(base::Time::Now());
}
merged_profiles->push_back(*existing_profile);
}
// If the new profile was not merged with an existing one, add it to the list.
if (!matching_profile_found) {
merged_profiles->push_back(new_profile);
// Similar to updating merged profiles above, set the modification date on
// new profiles.
merged_profiles->back().set_modification_date(base::Time::Now());
AutofillMetrics::LogProfileActionOnFormSubmitted(
AutofillMetrics::NEW_PROFILE_CREATED);
}
return guid;
}
bool PersonalDataManager::IsCountryOfInterest(const std::string& country_code)
const {
DCHECK_EQ(2U, country_code.size());
const std::vector<AutofillProfile*>& profiles = web_profiles();
std::list<std::string> country_codes;
for (size_t i = 0; i < profiles.size(); ++i) {
country_codes.push_back(base::ToLowerASCII(base::UTF16ToASCII(
profiles[i]->GetRawInfo(ADDRESS_HOME_COUNTRY))));
}
std::string timezone_country = CountryCodeForCurrentTimezone();
if (!timezone_country.empty())
country_codes.push_back(base::ToLowerASCII(timezone_country));
// Only take the locale into consideration if all else fails.
if (country_codes.empty()) {
country_codes.push_back(base::ToLowerASCII(
AutofillCountry::CountryCodeForLocale(app_locale())));
}
return std::find(country_codes.begin(), country_codes.end(),
base::ToLowerASCII(country_code)) != country_codes.end();
}
const std::string& PersonalDataManager::GetDefaultCountryCodeForNewAddress()
const {
if (default_country_code_.empty())
default_country_code_ = MostCommonCountryCodeFromProfiles();
// Failing that, guess based on system timezone.
if (default_country_code_.empty())
default_country_code_ = CountryCodeForCurrentTimezone();
// Failing that, guess based on locale.
if (default_country_code_.empty())
default_country_code_ = AutofillCountry::CountryCodeForLocale(app_locale());
return default_country_code_;
}
// static
void PersonalDataManager::DedupeCreditCardToSuggest(
std::list<const CreditCard*>* cards_to_suggest) {
for (auto outer_it = cards_to_suggest->begin();
outer_it != cards_to_suggest->end(); ++outer_it) {
// If considering a full server card, look for local cards that are
// duplicates of it and remove them.
if ((*outer_it)->record_type() == CreditCard::FULL_SERVER_CARD) {
for (auto inner_it = cards_to_suggest->begin();
inner_it != cards_to_suggest->end();) {
auto inner_it_copy = inner_it++;
if ((*inner_it_copy)->IsLocalDuplicateOfServerCard(**outer_it))
cards_to_suggest->erase(inner_it_copy);
}
// If considering a local card, look for masked server cards that are
// duplicates of it and remove them.
} else if ((*outer_it)->record_type() == CreditCard::LOCAL_CARD) {
for (auto inner_it = cards_to_suggest->begin();
inner_it != cards_to_suggest->end();) {
auto inner_it_copy = inner_it++;
if ((*inner_it_copy)->record_type() == CreditCard::MASKED_SERVER_CARD &&
(*outer_it)->IsLocalDuplicateOfServerCard(**inner_it_copy)) {
cards_to_suggest->erase(inner_it_copy);
}
}
}
}
}
void PersonalDataManager::SetProfiles(std::vector<AutofillProfile>* profiles) {
if (is_off_the_record_)
return;
// Remove empty profiles from input.
profiles->erase(std::remove_if(profiles->begin(), profiles->end(),
IsEmptyFunctor<AutofillProfile>(app_locale_)),
profiles->end());
if (!database_.get())
return;
// Any profiles that are not in the new profile list should be removed from
// the web database.
for (const AutofillProfile* it : web_profiles_) {
if (!FindByGUID<AutofillProfile>(*profiles, it->guid()))
database_->RemoveAutofillProfile(it->guid());
}
// Update the web database with the existing profiles.
for (const AutofillProfile& it : *profiles) {
if (FindByGUID<AutofillProfile>(web_profiles_, it.guid()))
database_->UpdateAutofillProfile(it);
}
// Add the new profiles to the web database. Don't add a duplicate.
for (const AutofillProfile& it : *profiles) {
if (!FindByGUID<AutofillProfile>(web_profiles_, it.guid()) &&
!FindByContents(web_profiles_, it))
database_->AddAutofillProfile(it);
}
// Copy in the new profiles.
web_profiles_.clear();
for (const AutofillProfile& it : *profiles) {
web_profiles_.push_back(new AutofillProfile(it));
}
// Refresh our local cache and send notifications to observers.
Refresh();
}
void PersonalDataManager::SetCreditCards(
std::vector<CreditCard>* credit_cards) {
if (is_off_the_record_)
return;
// Remove empty credit cards from input.
credit_cards->erase(std::remove_if(credit_cards->begin(), credit_cards->end(),
IsEmptyFunctor<CreditCard>(app_locale_)),
credit_cards->end());
if (!database_.get())
return;
// Any credit cards that are not in the new credit card list should be
// removed.
for (const CreditCard* card : local_credit_cards_) {
if (!FindByGUID<CreditCard>(*credit_cards, card->guid()))
database_->RemoveCreditCard(card->guid());
}
// Update the web database with the existing credit cards.
for (const CreditCard& card : *credit_cards) {
if (FindByGUID<CreditCard>(local_credit_cards_, card.guid()))
database_->UpdateCreditCard(card);
}
// Add the new credit cards to the web database. Don't add a duplicate.
for (const CreditCard& card : *credit_cards) {
if (!FindByGUID<CreditCard>(local_credit_cards_, card.guid()) &&
!FindByContents(local_credit_cards_, card))
database_->AddCreditCard(card);
}
// Copy in the new credit cards.
local_credit_cards_.clear();
for (const CreditCard& card : *credit_cards)
local_credit_cards_.push_back(new CreditCard(card));
// Refresh our local cache and send notifications to observers.
Refresh();
}
void PersonalDataManager::LoadProfiles() {
if (!database_.get()) {
NOTREACHED();
return;
}
CancelPendingQuery(&pending_profiles_query_);
CancelPendingQuery(&pending_server_profiles_query_);
pending_profiles_query_ = database_->GetAutofillProfiles(this);
pending_server_profiles_query_ = database_->GetServerProfiles(this);
}
void PersonalDataManager::LoadCreditCards() {
if (!database_.get()) {
NOTREACHED();
return;
}
CancelPendingQuery(&pending_creditcards_query_);
CancelPendingQuery(&pending_server_creditcards_query_);
pending_creditcards_query_ = database_->GetCreditCards(this);
pending_server_creditcards_query_ = database_->GetServerCreditCards(this);
}
void PersonalDataManager::CancelPendingQuery(
WebDataServiceBase::Handle* handle) {
if (*handle) {
if (!database_.get()) {
NOTREACHED();
return;
}
database_->CancelRequest(*handle);
}
*handle = 0;
}
std::string PersonalDataManager::SaveImportedProfile(
const AutofillProfile& imported_profile) {
if (is_off_the_record_)
return std::string();
// Don't save a web profile if the data in the profile is a subset of a
// server profile, but do record the fact that it was used.
for (const AutofillProfile* profile : server_profiles_) {
if (imported_profile.IsSubsetOf(*profile, app_locale_)) {
RecordUseOf(*profile);
return profile->guid();
}
}
std::vector<AutofillProfile> profiles;
std::string guid = MergeProfile(imported_profile, web_profiles_.get(),
app_locale_, &profiles);
SetProfiles(&profiles);
return guid;
}
void PersonalDataManager::NotifyPersonalDataChanged() {
FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_,
OnPersonalDataChanged());
}
std::string PersonalDataManager::SaveImportedCreditCard(
const CreditCard& imported_card) {
DCHECK(!imported_card.number().empty());
if (is_off_the_record_)
return std::string();
// Set to true if |imported_card| is merged into the credit card list.
bool merged = false;
std::string guid = imported_card.guid();
std::vector<CreditCard> credit_cards;
for (CreditCard* card : local_credit_cards_) {
// If |imported_card| has not yet been merged, check whether it should be
// with the current |card|.
if (!merged && card->UpdateFromImportedCard(imported_card, app_locale_)) {
guid = card->guid();
merged = true;
}
credit_cards.push_back(*card);
}
if (!merged)
credit_cards.push_back(imported_card);
SetCreditCards(&credit_cards);
return guid;
}
void PersonalDataManager::LogProfileCount() const {
if (!has_logged_profile_count_) {
AutofillMetrics::LogStoredProfileCount(web_profiles_.size());
has_logged_profile_count_ = true;
}
}
void PersonalDataManager::LogLocalCreditCardCount() const {
if (!has_logged_credit_card_count_) {
AutofillMetrics::LogStoredLocalCreditCardCount(local_credit_cards_.size());
has_logged_credit_card_count_ = true;
}
}
std::string PersonalDataManager::MostCommonCountryCodeFromProfiles() const {
if (!IsAutofillEnabled())
return std::string();
// Count up country codes from existing profiles.
std::map<std::string, int> votes;
// TODO(estade): can we make this GetProfiles() instead? It seems to cause
// errors in tests on mac trybots. See http://crbug.com/57221
const std::vector<AutofillProfile*>& profiles = web_profiles();
const std::vector<std::string>& country_codes =
CountryDataMap::GetInstance()->country_codes();
for (size_t i = 0; i < profiles.size(); ++i) {
std::string country_code = base::ToUpperASCII(base::UTF16ToASCII(
profiles[i]->GetRawInfo(ADDRESS_HOME_COUNTRY)));
if (std::find(country_codes.begin(), country_codes.end(), country_code) !=
country_codes.end()) {
// Verified profiles count 100x more than unverified ones.
votes[country_code] += profiles[i]->IsVerified() ? 100 : 1;
}
}
// Take the most common country code.
if (!votes.empty()) {
std::map<std::string, int>::iterator iter =
std::max_element(votes.begin(), votes.end(), CompareVotes);
return iter->first;
}
return std::string();
}
void PersonalDataManager::EnabledPrefChanged() {
default_country_code_.clear();
if (!pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) {
// Re-mask all server cards when the user turns off wallet card
// integration.
ResetFullServerCards();
}
NotifyPersonalDataChanged();
}
bool PersonalDataManager::ImportAddressProfiles(const FormStructure& form) {
if (!form.field_count())
return false;
// Relevant sections for address fields.
std::set<std::string> sections;
for (const AutofillField* field : form) {
if (field->Type().group() != CREDIT_CARD)
sections.insert(field->section());
}
// We save a maximum of 2 profiles per submitted form (e.g. for shipping and
// billing).
static const size_t kMaxNumAddressProfilesSaved = 2;
size_t num_saved_profiles = 0;
for (const std::string& section : sections) {
if (num_saved_profiles == kMaxNumAddressProfilesSaved)
break;
if (ImportAddressProfileForSection(form, section))
num_saved_profiles++;
}
return num_saved_profiles > 0;
}
bool PersonalDataManager::ImportAddressProfileForSection(
const FormStructure& form,
const std::string& section) {
// The candidate for profile import. There are many ways for the candidate to
// be rejected (see everywhere this function returns false).
AutofillProfile candidate_profile;
candidate_profile.set_origin(form.source_url().spec());
// We only set complete phone, so aggregate phone parts in these vars and set
// complete at the end.
PhoneNumber::PhoneCombineHelper combined_phone;
// Used to detect and discard address forms with multiple fields of the same
// type.
std::set<ServerFieldType> types_seen;
// Go through each |form| field and attempt to constitute a valid profile.
for (const AutofillField* field : form) {
// Reject fields that are not within the specified |section|.
if (field->section() != section)
continue;
base::string16 value;
base::TrimWhitespace(field->value, base::TRIM_ALL, &value);
// If we don't know the type of the field, or the user hasn't entered any
// information into the field, or the field is non-focusable (hidden), then
// skip it.
if (!field->IsFieldFillable() || !field->is_focusable || value.empty())
continue;
AutofillType field_type = field->Type();
// Credit card fields are handled by ImportCreditCard().
if (field_type.group() == CREDIT_CARD)
continue;
// 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.
ServerFieldType server_field_type = field_type.GetStorableType();
if (server_field_type == EMAIL_ADDRESS &&
types_seen.count(server_field_type) &&
candidate_profile.GetRawInfo(EMAIL_ADDRESS) != value)
return false;
// If the field type and |value| don't pass basic validity checks then
// abandon the import.
if (!IsValidFieldTypeAndValue(types_seen, server_field_type, value))
return false;
types_seen.insert(server_field_type);
// We need to store phone data in the variables, before building the whole
// number at the end. If |value| is not from a phone field, home.SetInfo()
// returns false and data is stored directly in |candidate_profile|.
if (!combined_phone.SetInfo(field_type, value))
candidate_profile.SetInfo(field_type, value, app_locale_);
// Reject profiles with invalid country information.
if (server_field_type == ADDRESS_HOME_COUNTRY &&
candidate_profile.GetRawInfo(ADDRESS_HOME_COUNTRY).empty())
return false;
}
// Construct the phone number. Reject the whole profile if the number is
// invalid.
if (!combined_phone.IsEmpty()) {
base::string16 constructed_number;
if (!combined_phone.ParseNumber(candidate_profile, app_locale_,
&constructed_number) ||
!candidate_profile.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER),
constructed_number, app_locale_)) {
return false;
}
}
// Reject the profile if minimum address and validation requirements are not
// met.
if (!IsValidLearnableProfile(candidate_profile, app_locale_))
return false;
SaveImportedProfile(candidate_profile);
return true;
}
bool PersonalDataManager::ImportCreditCard(
const FormStructure& form,
bool should_return_local_card,
std::unique_ptr<CreditCard>* imported_credit_card) {
DCHECK(!imported_credit_card->get());
// The candidate for credit card import. There are many ways for the candidate
// to be rejected (see everywhere this function returns false, below).
CreditCard candidate_credit_card;
candidate_credit_card.set_origin(form.source_url().spec());
std::set<ServerFieldType> types_seen;
for (const AutofillField* field : form) {
base::string16 value;
base::TrimWhitespace(field->value, base::TRIM_ALL, &value);
// If we don't know the type of the field, or the user hasn't entered any
// information into the field, or the field is non-focusable (hidden), then
// skip it.
if (!field->IsFieldFillable() || !field->is_focusable || value.empty())
continue;
AutofillType field_type = field->Type();
// Field was not identified as a credit card field.
if (field_type.group() != CREDIT_CARD)
continue;
// If we've seen the same credit card field type twice in the same form,
// abort credit card import/update.
ServerFieldType server_field_type = field_type.GetStorableType();
if (types_seen.count(server_field_type))
return false;
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")) {
DCHECK_EQ(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, server_field_type);
candidate_credit_card.SetInfoForMonthInputType(value);
continue;
}
// CreditCard handles storing the |value| according to |field_type|.
bool saved = candidate_credit_card.SetInfo(field_type, value, app_locale_);
// Saving with the option text (here |value|) may fail for the expiration
// month. Attempt to save with the option value. First find the index of the
// option text in the select options and try the corresponding value.
if (!saved && server_field_type == CREDIT_CARD_EXP_MONTH) {
for (size_t i = 0; i < field->option_contents.size(); ++i) {
if (value == field->option_contents[i]) {
candidate_credit_card.SetInfo(field_type, field->option_values[i],
app_locale_);
break;
}
}
}
}
// Reject the credit card if we did not detect enough filled credit card
// fields (such as valid number, month, year).
if (!candidate_credit_card.IsValid())
return false;
// Attempt to merge with an existing credit card. Don't present a prompt if we
// have already saved this card number, unless |should_return_local_card| is
// true which indicates that upload is enabled. In this case, it's useful to
// present the upload prompt to the user to promote the card from a local card
// to a synced server card.
for (const CreditCard* card : local_credit_cards_) {
// Make a local copy so that the data in |local_credit_cards_| isn't
// modified directly by the UpdateFromImportedCard() call.
CreditCard card_copy(*card);
if (card_copy.UpdateFromImportedCard(candidate_credit_card,
app_locale_)) {
UpdateCreditCard(card_copy);
// If we should not return the local card, return that we merged it,
// without setting |imported_credit_card|.
if (!should_return_local_card)
return true;
break;
}
}
// Also don't offer to save if we already have this stored as a server card.
// We only check the number because if the new card has the same number as the
// server card, upload is guaranteed to fail. There's no mechanism for entries
// with the same number but different names or expiration dates as there is
// for local cards.
for (const CreditCard* card : server_credit_cards_) {
if (candidate_credit_card.HasSameNumberAs(*card))
return false;
}
imported_credit_card->reset(new CreditCard(candidate_credit_card));
return true;
}
const std::vector<AutofillProfile*>& PersonalDataManager::GetProfiles(
bool record_metrics) const {
profiles_.clear();
profiles_.insert(profiles_.end(), web_profiles().begin(),
web_profiles().end());
if (pref_service_->GetBoolean(prefs::kAutofillWalletImportEnabled)) {
profiles_.insert(
profiles_.end(), server_profiles_.begin(), server_profiles_.end());
}
return profiles_;
}
std::vector<Suggestion> PersonalDataManager::GetSuggestionsForCards(
const AutofillType& type,
const base::string16& field_contents,
const std::list<const CreditCard*>& cards_to_suggest) const {
std::vector<Suggestion> suggestions;
std::list<const CreditCard*> substring_matched_cards;
base::string16 field_contents_lower = base::i18n::ToLower(field_contents);
for (const CreditCard* credit_card : cards_to_suggest) {
// The value of the stored data for this field type in the |credit_card|.
base::string16 creditcard_field_value =
credit_card->GetInfo(type, app_locale_);
if (creditcard_field_value.empty())
continue;
base::string16 creditcard_field_lower =
base::i18n::ToLower(creditcard_field_value);
bool prefix_matched_suggestion;
if (IsValidSuggestionForFieldContents(
creditcard_field_lower, field_contents_lower, type,
credit_card->record_type() == CreditCard::MASKED_SERVER_CARD,
&prefix_matched_suggestion)) {
// Make a new suggestion.
suggestions.push_back(Suggestion());
Suggestion* suggestion = &suggestions.back();
suggestion->value = credit_card->GetInfo(type, app_locale_);
suggestion->icon = base::UTF8ToUTF16(credit_card->type());
suggestion->backend_id = credit_card->guid();
suggestion->match = prefix_matched_suggestion
? Suggestion::PREFIX_MATCH
: Suggestion::SUBSTRING_MATCH;
// If the value is the card number, the label is the expiration date.
// Otherwise the label is the card number, or if that is empty the
// cardholder name. The label should never repeat the value.
if (type.GetStorableType() == CREDIT_CARD_NUMBER) {
suggestion->value = credit_card->TypeAndLastFourDigits();
suggestion->label = credit_card->GetInfo(
AutofillType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR), app_locale_);
} else if (credit_card->number().empty()) {
if (type.GetStorableType() != CREDIT_CARD_NAME_FULL) {
suggestion->label = credit_card->GetInfo(
AutofillType(CREDIT_CARD_NAME_FULL), app_locale_);
}
} else {
#if defined(OS_ANDROID)
// Since Android places the label on its own row, there's more
// horizontal
// space to work with. Show "Amex - 1234" rather than desktop's "*1234".
suggestion->label = credit_card->TypeAndLastFourDigits();
#else
suggestion->label = base::ASCIIToUTF16("*");
suggestion->label.append(credit_card->LastFourDigits());
#endif
}
}
}
// 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;
}
} // namespace autofill