blob: 39e72f6be1d69903cdcbe13cb0c2f24ba626b516 [file] [log] [blame]
// Copyright 2014 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/search_engines/template_url_prepopulate_data.h"
#include <algorithm>
#include <random>
#include <vector>
#include "base/check_deref.h"
#include "base/check_is_test.h"
#include "base/containers/contains.h"
#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.h"
#include "build/build_config.h"
#include "components/country_codes/country_codes.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/eea_countries_ids.h"
#include "components/search_engines/prepopulated_engines.h"
#include "components/search_engines/search_engine_choice/search_engine_choice_service.h"
#include "components/search_engines/search_engine_choice/search_engine_choice_utils.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_service.h"
#include "components/version_info/version_info.h"
namespace TemplateURLPrepopulateData {
// Helpers --------------------------------------------------------------------
namespace {
#include "components/search_engines/search_engine_countries-inc.cc"
std::vector<std::unique_ptr<TemplateURLData>>
GetPrepopulatedEnginesForEeaRegionCountries(int country_id,
PrefService* prefs) {
CHECK(search_engines::IsEeaChoiceCountry(country_id) &&
search_engines::IsChoiceScreenFlagEnabled(
search_engines::ChoicePromo::kAny));
uint64_t profile_seed;
if (prefs) {
profile_seed = prefs->GetInt64(
prefs::kDefaultSearchProviderChoiceScreenRandomShuffleSeed);
int seed_version_number = prefs->GetInteger(
prefs::kDefaultSearchProviderChoiceScreenShuffleMilestone);
int current_version_number = version_info::GetMajorVersionNumberAsInt();
// Ensure that the generated seed is not 0 to avoid accidental re-seeding
// and re-shuffle on every chrome update.
while (profile_seed == 0 || current_version_number != seed_version_number) {
profile_seed = base::RandUint64();
prefs->SetInt64(
prefs::kDefaultSearchProviderChoiceScreenRandomShuffleSeed,
profile_seed);
prefs->SetInteger(
prefs::kDefaultSearchProviderChoiceScreenShuffleMilestone,
current_version_number);
seed_version_number = current_version_number;
}
} else {
// TODO(crbug.com/40287734): Avoid passing null prefs and unbranch the code.
CHECK_IS_TEST();
// Choosing a fixed magic number to ensure a stable shuffle in tests too.
profile_seed = 42;
}
std::vector<std::unique_ptr<TemplateURLData>> t_urls = base::ToVector(
GetPrepopulationSetFromCountryID(country_id),
[](const EngineAndTier& entry) {
return TemplateURLDataFromPrepopulatedEngine(*entry.search_engine);
});
std::default_random_engine generator;
generator.seed(profile_seed);
std::shuffle(t_urls.begin(), t_urls.end(), generator);
CHECK_LE(t_urls.size(), kMaxEeaPrepopulatedEngines);
return t_urls;
}
std::vector<std::unique_ptr<TemplateURLData>> GetPrepopulatedTemplateURLData(
int country_id,
PrefService* prefs) {
std::vector<std::unique_ptr<TemplateURLData>> t_urls;
if (search_engines::IsEeaChoiceCountry(country_id) &&
search_engines::IsChoiceScreenFlagEnabled(
search_engines::ChoicePromo::kAny)) {
CHECK(prefs);
if (search_engines::HasSearchEngineCountryListOverride()) {
auto country_override =
absl::get<search_engines::SearchEngineCountryListOverride>(
search_engines::GetSearchEngineCountryOverride().value());
switch (country_override) {
case search_engines::SearchEngineCountryListOverride::kEeaAll:
return GetAllEeaRegionPrepopulatedEngines();
case search_engines::SearchEngineCountryListOverride::kEeaDefault:
return GetDefaultPrepopulatedEngines();
}
}
return GetPrepopulatedEnginesForEeaRegionCountries(country_id, prefs);
}
std::vector<EngineAndTier> engines =
GetPrepopulationSetFromCountryID(country_id);
for (const EngineAndTier& engine : engines) {
if (engine.tier == SearchEngineTier::kTopEngines) {
t_urls.push_back(
TemplateURLDataFromPrepopulatedEngine(*engine.search_engine));
}
}
return t_urls;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class SearchProviderOverrideStatus {
// No preferences are available for `prefs::kSearchProviderOverrides`.
kNoPref = 0,
// The preferences for `prefs::kSearchProviderOverrides` do not contain valid
// template URLs.
kEmptyPref = 1,
// The preferences for `prefs::kSearchProviderOverrides` contain valid
// template URL(s).
kPrefHasValidUrls = 2,
kMaxValue = kPrefHasValidUrls
};
std::vector<std::unique_ptr<TemplateURLData>> GetOverriddenTemplateURLData(
PrefService* prefs) {
std::vector<std::unique_ptr<TemplateURLData>> t_urls;
if (!prefs)
return t_urls;
const base::Value::List& list =
prefs->GetList(prefs::kSearchProviderOverrides);
for (const base::Value& engine : list) {
if (engine.is_dict()) {
auto t_url = TemplateURLDataFromOverrideDictionary(engine.GetDict());
if (t_url) {
t_urls.push_back(std::move(t_url));
}
}
}
base::UmaHistogramEnumeration(
"Search.SearchProviderOverrideStatus",
!t_urls.empty() ? SearchProviderOverrideStatus::kPrefHasValidUrls
: (prefs->HasPrefPath(prefs::kSearchProviderOverrides)
? SearchProviderOverrideStatus::kEmptyPref
: SearchProviderOverrideStatus::kNoPref));
return t_urls;
}
} // namespace
// Global functions -----------------------------------------------------------
void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
country_codes::RegisterProfilePrefs(registry);
registry->RegisterListPref(prefs::kSearchProviderOverrides);
registry->RegisterIntegerPref(prefs::kSearchProviderOverridesVersion, -1);
registry->RegisterInt64Pref(
prefs::kDefaultSearchProviderChoiceScreenRandomShuffleSeed, 0);
registry->RegisterIntegerPref(
prefs::kDefaultSearchProviderChoiceScreenShuffleMilestone, 0);
registry->RegisterBooleanPref(
prefs::kDefaultSearchProviderKeywordsUseExtendedList, false);
registry->RegisterBooleanPref(prefs::kDefaultSearchProviderChoicePending,
false);
}
int GetDataVersion(PrefService* prefs) {
// Allow tests to override the local version.
return (prefs && prefs->HasPrefPath(prefs::kSearchProviderOverridesVersion)) ?
prefs->GetInteger(prefs::kSearchProviderOverridesVersion) :
kCurrentDataVersion;
}
std::vector<std::unique_ptr<TemplateURLData>> GetPrepopulatedEngines(
PrefService* prefs,
search_engines::SearchEngineChoiceService* search_engine_choice_service,
size_t* default_search_provider_index,
bool include_current_default,
TemplateURLService* template_url_service,
bool* was_current_default_inserted) {
// If there is a set of search engines in the preferences file, it overrides
// the built-in set.
std::vector<std::unique_ptr<TemplateURLData>> t_urls =
GetOverriddenTemplateURLData(prefs);
if (t_urls.empty()) {
// `search_engine_choice_service` (and `prefs`) can be null in tests.
// TODO(b/318801987): Make sure `prefs` and `search_engine_choice_service`
// are always not null.
int country_id = search_engine_choice_service
? search_engine_choice_service->GetCountryId()
: country_codes::GetCurrentCountryID();
t_urls = GetPrepopulatedTemplateURLData(country_id, prefs);
if (include_current_default && template_url_service) {
CHECK(search_engines::IsChoiceScreenFlagEnabled(
search_engines::ChoicePromo::kAny));
// This would add the current default search engine to the top of the
// returned list if it's not already there.
const TemplateURL* default_search_engine =
template_url_service->GetDefaultSearchProvider();
bool inserted_default = false;
if (default_search_engine &&
!base::Contains(t_urls, default_search_engine->prepopulate_id(),
[](const std::unique_ptr<TemplateURLData>& engine) {
return engine->prepopulate_id;
})) {
t_urls.insert(t_urls.begin(), std::make_unique<TemplateURLData>(
default_search_engine->data()));
inserted_default = true;
}
if (was_current_default_inserted != nullptr) {
*was_current_default_inserted = inserted_default;
}
// TODO(b/325015554): Pull this higher in the stack, where recording some
// histograms would be more expected.
search_engines::RecordIsDefaultProviderAddedToChoices(inserted_default);
}
}
if (default_search_provider_index) {
const auto itr =
base::ranges::find(t_urls, google.id, &TemplateURLData::prepopulate_id);
*default_search_provider_index =
itr == t_urls.end() ? 0 : std::distance(t_urls.begin(), itr);
}
return t_urls;
}
std::unique_ptr<TemplateURLData> GetPrepopulatedEngine(
PrefService* prefs,
search_engines::SearchEngineChoiceService* search_engine_choice_service,
int prepopulated_id) {
auto engines = TemplateURLPrepopulateData::GetPrepopulatedEngines(
prefs, search_engine_choice_service, nullptr);
for (auto& engine : engines) {
if (engine->prepopulate_id == prepopulated_id)
return std::move(engine);
}
return nullptr;
}
#if BUILDFLAG(IS_ANDROID)
std::vector<std::unique_ptr<TemplateURLData>> GetLocalPrepopulatedEngines(
const std::string& country_code,
PrefService& prefs) {
int country_id = country_codes::CountryStringToCountryID(country_code);
if (country_id == country_codes::kCountryIDUnknown) {
LOG(ERROR) << "Unknown country code specified: " << country_code;
return std::vector<std::unique_ptr<TemplateURLData>>();
}
return GetPrepopulatedTemplateURLData(country_id, &prefs);
}
#endif
std::unique_ptr<TemplateURLData> GetPrepopulatedEngineFromFullList(
PrefService* prefs,
search_engines::SearchEngineChoiceService* search_engine_choice_service,
int prepopulated_id) {
// TODO(crbug.com/40940777): Refactor to better share code with
// `GetPrepopulatedEngine()`.
// If there is a set of search engines in the preferences file, we look for
// the ID there first.
for (std::unique_ptr<TemplateURLData>& data :
GetOverriddenTemplateURLData(prefs)) {
if (data->prepopulate_id == prepopulated_id) {
return std::move(data);
}
}
// We look in the profile country's prepopulated set first. This is intended
// to help using the right entry for the case where we have multiple ones in
// the full list that share a same prepopulated id.
const int country = search_engine_choice_service->GetCountryId();
for (const EngineAndTier& engine_and_tier :
GetPrepopulationSetFromCountryID(country)) {
if (engine_and_tier.search_engine->id == prepopulated_id) {
return TemplateURLDataFromPrepopulatedEngine(
*engine_and_tier.search_engine);
}
}
// Fallback: just grab the first matching entry from the complete list. In
// case of IDs shared across multiple entries, we might be returning the
// wrong one for the profile country. We can look into better heuristics in
// future work.
for (size_t i = 0; i < kAllEnginesLength; ++i) {
const PrepopulatedEngine* engine = kAllEngines[i];
if (engine->id == prepopulated_id) {
return TemplateURLDataFromPrepopulatedEngine(*engine);
}
}
return nullptr;
}
void ClearPrepopulatedEnginesInPrefs(PrefService* prefs) {
if (!prefs)
return;
prefs->ClearPref(prefs::kSearchProviderOverrides);
prefs->ClearPref(prefs::kSearchProviderOverridesVersion);
}
std::unique_ptr<TemplateURLData> GetPrepopulatedDefaultSearch(
PrefService* prefs,
search_engines::SearchEngineChoiceService* search_engine_choice_service) {
size_t default_search_index;
// This could be more efficient. We load all URLs but keep only the default.
std::vector<std::unique_ptr<TemplateURLData>> loaded_urls =
GetPrepopulatedEngines(prefs, search_engine_choice_service,
&default_search_index);
return (default_search_index < loaded_urls.size())
? std::move(loaded_urls[default_search_index])
: nullptr;
}
std::vector<const PrepopulatedEngine*> GetAllPrepopulatedEngines() {
return std::vector<const PrepopulatedEngine*>(
&kAllEngines[0], &kAllEngines[0] + kAllEnginesLength);
}
std::vector<std::unique_ptr<TemplateURLData>>
GetAllEeaRegionPrepopulatedEngines() {
std::vector<std::unique_ptr<TemplateURLData>> result;
// We use a `flat_set` to filter out engines that have the same prepopulated
// id. For example, `yahoo_fr` and `yahoo_de` have the same prepopulated id
// because they point to the same search engine so we only want to record one
// instance.
base::flat_set<int> used_engines;
for (int eea_country_id : search_engines::kEeaChoiceCountriesIds) {
std::vector<EngineAndTier> country_engines =
GetPrepopulationSetFromCountryID(eea_country_id);
for (const EngineAndTier& engine : country_engines) {
raw_ptr<const PrepopulatedEngine> search_engine = engine.search_engine;
if (!base::Contains(used_engines, search_engine->id)) {
result.push_back(TemplateURLDataFromPrepopulatedEngine(*search_engine));
used_engines.emplace(search_engine->id);
}
}
}
return result;
}
std::vector<std::unique_ptr<TemplateURLData>> GetDefaultPrepopulatedEngines() {
return base::ToVector(engines_default, [](const EngineAndTier& entry) {
return TemplateURLDataFromPrepopulatedEngine(*entry.search_engine);
});
}
// Test Utilities -------------------------------------------------------------
const std::vector<raw_ptr<const PrepopulatedEngine>>
GetPrepopulationSetFromCountryIDForTesting(int country_id) {
return base::ToVector(GetPrepopulationSetFromCountryID(country_id),
&EngineAndTier::search_engine);
}
} // namespace TemplateURLPrepopulateData