blob: 3ec01ebcc70eb62beccdecb62341775bf8b49d81 [file] [log] [blame]
Avi Drissman8ba1bad2022-09-13 19:22:361// Copyright 2014 The Chromium Authors
ben@chromium.orgd82443b2009-01-15 19:54:562// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
hashimoto@chromium.orgbf5c532d2014-07-05 00:29:535#include "components/search_engines/template_url_service.h"
Fabio Tirelo07ea7dd2023-12-06 02:13:446
Fabio Tirelo5efc04da2024-01-04 21:31:377#include <algorithm>
Fabio Tirelo07ea7dd2023-12-06 02:13:448#include <iterator>
Fabio Tirelo78ca8262023-10-31 14:53:189#include <memory>
Fabio Tirelo07ea7dd2023-12-06 02:13:4410#include <string>
11#include <utility>
ben@chromium.orgd82443b2009-01-15 19:54:5612
stevet@chromium.orgd88cb202011-08-17 20:03:0113#include "base/auto_reset.h"
manukh873036392020-11-09 17:23:2914#include "base/base64.h"
15#include "base/base64url.h"
Nicolas Dossou-Gbete0cd7f0c12024-01-02 14:37:0816#include "base/check_deref.h"
Jack Yamminef973df622023-09-05 14:57:1117#include "base/check_is_test.h"
Jan Wilken Dörrie986f0a62020-12-09 23:29:1218#include "base/containers/contains.h"
Fabio Tirelo07ea7dd2023-12-06 02:13:4419#include "base/containers/flat_map.h"
a-v-y7084ddf2017-03-09 13:15:4820#include "base/debug/crash_logging.h"
Jack Yammineabb0ba22023-07-20 10:46:3421#include "base/feature_list.h"
a-v-y7084ddf2017-03-09 13:15:4822#include "base/format_macros.h"
Avi Drissman12be0312023-01-11 09:16:0923#include "base/functional/bind.h"
24#include "base/functional/callback.h"
25#include "base/functional/callback_helpers.h"
Tommy C. Li088b42f2022-11-15 00:51:2826#include "base/i18n/case_conversion.h"
Keishi Hattori0e45c022021-11-27 09:25:5227#include "base/memory/raw_ptr.h"
Yash Joshid3da59f02023-03-23 22:24:4828#include "base/metrics/field_trial_params.h"
Angela Yoeurnga79210e2022-08-23 19:59:4629#include "base/metrics/histogram_functions.h"
asvitkinea0f05db2015-06-16 21:45:4630#include "base/metrics/histogram_macros.h"
David Sanders88b24432022-02-28 01:10:0231#include "base/observer_list.h"
manukh873036392020-11-09 17:23:2932#include "base/rand_util.h"
Tommy Li276535cc2020-11-23 19:11:0133#include "base/ranges/algorithm.h"
tfarina@chromium.org1988e1c2013-02-28 20:27:4234#include "base/strings/string_split.h"
Hans Wennborgb3e433a2020-04-21 11:21:4035#include "base/strings/string_util.h"
avi@chromium.orge309f312013-06-07 21:50:0836#include "base/strings/utf_string_conversions.h"
Xiaohan Wangcff600f2022-01-14 14:55:3337#include "build/build_config.h"
Jun Ishiguro6bae99ca2023-11-06 10:03:4938#include "build/chromeos_buildflags.h"
Fabio Tirelo78ca8262023-10-31 14:53:1839#include "components/omnibox/common/omnibox_features.h"
sdefresne29487962015-07-03 09:24:3940#include "components/pref_registry/pref_registry_syncable.h"
brettwf00b9b42016-02-01 22:11:3841#include "components/prefs/pref_service.h"
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:2342#include "components/search_engines/choice_made_location.h"
Fabio Tirelo07ea7dd2023-12-06 02:13:4443#include "components/search_engines/enterprise_site_search_manager.h"
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:5544#include "components/search_engines/keyword_web_data_service.h"
Boris Sazonov2b77815a2024-01-17 15:35:0445#include "components/search_engines/search_engine_choice/search_engine_choice_service.h"
Nicolas Dossou-Gbete9b7cf4bc2024-04-12 13:10:0446#include "components/search_engines/search_engine_choice/search_engine_choice_utils.h"
Fabio Tirelob8f43492023-07-18 20:28:2447#include "components/search_engines/search_engine_type.h"
hashimoto@chromium.org2d3e7102014-06-21 05:57:1748#include "components/search_engines/search_engines_pref_names.h"
hashimoto@chromium.org7e310422014-07-03 12:44:4549#include "components/search_engines/search_terms_data.h"
Fabio Tirelo07ea7dd2023-12-06 02:13:4450#include "components/search_engines/template_url.h"
51#include "components/search_engines/template_url_data.h"
hashimoto@chromium.org0915b352014-06-25 19:58:1452#include "components/search_engines/template_url_prepopulate_data.h"
hashimoto@chromium.orgbf5c532d2014-07-05 00:29:5353#include "components/search_engines/template_url_service_client.h"
54#include "components/search_engines/template_url_service_observer.h"
Angela Yoeurng54419ec62022-06-07 18:00:0155#include "components/search_engines/template_url_starter_pack_data.h"
hashimoto@chromium.orgbf5c532d2014-07-05 00:29:5356#include "components/search_engines/util.h"
Peter Kastingebaac2972020-04-10 23:54:4157#include "components/sync/model/sync_change.h"
Mikel Astized5d94962018-09-27 13:42:2458#include "components/sync/model/sync_change_processor.h"
Victor Hugo Vianna Silvae92e3002021-07-26 14:38:5059#include "components/sync/protocol/entity_specifics.pb.h"
Max Boguefef332d2016-07-28 22:09:0960#include "components/sync/protocol/search_engine_specifics.pb.h"
rsleevi24f64dc22015-08-07 21:39:2161#include "components/url_formatter/url_fixer.h"
hashimoto@chromium.org67a832b2014-06-28 20:45:0062#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
erikwright@chromium.org8f1f8ac2014-05-11 23:23:0563#include "url/gurl.h"
ben@chromium.orgd82443b2009-01-15 19:54:5664
Xiaohan Wangcff600f2022-01-14 14:55:3365#if BUILDFLAG(IS_ANDROID)
Peter Wenf517f7d2019-06-13 15:28:2766#include "components/search_engines/android/template_url_service_android.h"
67#endif
68
levin@chromium.orgd8522742010-08-27 17:55:4469typedef SearchHostToURLsMap::TemplateURLSet TemplateURLSet;
stevet@chromium.orgd88cb202011-08-17 20:03:0170typedef TemplateURLService::SyncDataMap SyncDataMap;
ben@chromium.orgd82443b2009-01-15 19:54:5671
joaodasilva@chromium.org18b5f6842011-05-23 11:51:4572namespace {
73
stevet@chromium.orgc3dd7892012-08-27 21:28:2874const char kDeleteSyncedEngineHistogramName[] =
75 "Search.DeleteSyncedSearchEngine";
Angela Yoeurnga79210e2022-08-23 19:59:4676// TODO(yoangela): Consider sharing this const with
77// "Omnibox.KeywordModeUsageByEngineType.Accepted" in omnibox_edit_model.cc.
78const char kKeywordModeUsageByEngineTypeHistogramName[] =
79 "Omnibox.KeywordModeUsageByEngineType";
stevet@chromium.orgc3dd7892012-08-27 21:28:2880
81// Values for an enumerated histogram used to track whenever an ACTION_DELETE is
Tommy Li9c7646a2020-10-28 00:47:4982// sent to the server for search engines. These are persisted. Do not re-number.
stevet@chromium.orgc3dd7892012-08-27 21:28:2883enum DeleteSyncedSearchEngineEvent {
Tommy Li9c7646a2020-10-28 00:47:4984 DELETE_ENGINE_USER_ACTION = 0,
85 DELETE_ENGINE_PRE_SYNC = 1,
86 DELETE_ENGINE_EMPTY_FIELD = 2,
stevet@chromium.orgc3dd7892012-08-27 21:28:2887 DELETE_ENGINE_MAX,
88};
89
stevet@chromium.org612ee4942012-05-14 23:55:5490// Returns true iff the change in |change_list| at index |i| should not be sent
91// up to the server based on its GUIDs presence in |sync_data| or when compared
92// to changes after it in |change_list|.
93// The criteria is:
94// 1) It is an ACTION_UPDATE or ACTION_DELETE and the sync_guid associated
95// with it is NOT found in |sync_data|. We can only update and remove
96// entries that were originally from the Sync server.
97// 2) It is an ACTION_ADD and the sync_guid associated with it is found in
98// |sync_data|. We cannot re-add entries that Sync already knew about.
99// 3) There is an update after an update for the same GUID. We prune earlier
100// ones just to save bandwidth (Sync would normally coalesce them).
101bool ShouldRemoveSyncChange(size_t index,
akalin@chromium.org65f173552012-06-28 22:43:58102 syncer::SyncChangeList* change_list,
stevet@chromium.org612ee4942012-05-14 23:55:54103 const SyncDataMap* sync_data) {
104 DCHECK(index < change_list->size());
akalin@chromium.org65f173552012-06-28 22:43:58105 const syncer::SyncChange& change_i = (*change_list)[index];
stevet@chromium.org612ee4942012-05-14 23:55:54106 const std::string guid = change_i.sync_data().GetSpecifics()
107 .search_engine().sync_guid();
akalin@chromium.org65f173552012-06-28 22:43:58108 syncer::SyncChange::SyncChangeType type = change_i.change_type();
109 if ((type == syncer::SyncChange::ACTION_UPDATE ||
110 type == syncer::SyncChange::ACTION_DELETE) &&
stevet@chromium.org612ee4942012-05-14 23:55:54111 sync_data->find(guid) == sync_data->end())
112 return true;
akalin@chromium.org65f173552012-06-28 22:43:58113 if (type == syncer::SyncChange::ACTION_ADD &&
stevet@chromium.org612ee4942012-05-14 23:55:54114 sync_data->find(guid) != sync_data->end())
115 return true;
akalin@chromium.org65f173552012-06-28 22:43:58116 if (type == syncer::SyncChange::ACTION_UPDATE) {
stevet@chromium.org612ee4942012-05-14 23:55:54117 for (size_t j = index + 1; j < change_list->size(); j++) {
akalin@chromium.org65f173552012-06-28 22:43:58118 const syncer::SyncChange& change_j = (*change_list)[j];
119 if ((syncer::SyncChange::ACTION_UPDATE == change_j.change_type()) &&
pkasting@chromium.org7b596f62012-05-08 01:34:25120 (change_j.sync_data().GetSpecifics().search_engine().sync_guid() ==
stevet@chromium.org612ee4942012-05-14 23:55:54121 guid))
122 return true;
pkasting@chromium.org7b596f62012-05-08 01:34:25123 }
124 }
stevet@chromium.org612ee4942012-05-14 23:55:54125 return false;
126}
127
128// Remove SyncChanges that should not be sent to the server from |change_list|.
akalin@chromium.org65f173552012-06-28 22:43:58129// This is done to eliminate incorrect SyncChanges added by the merge and
stevet@chromium.org612ee4942012-05-14 23:55:54130// conflict resolution logic when it is unsure of whether or not an entry is new
131// from Sync or originally from the local model. This also removes changes that
132// would be otherwise be coalesced by Sync in order to save bandwidth.
133void PruneSyncChanges(const SyncDataMap* sync_data,
akalin@chromium.org65f173552012-06-28 22:43:58134 syncer::SyncChangeList* change_list) {
stevet@chromium.org612ee4942012-05-14 23:55:54135 for (size_t i = 0; i < change_list->size(); ) {
136 if (ShouldRemoveSyncChange(i, change_list, sync_data))
137 change_list->erase(change_list->begin() + i);
138 else
139 ++i;
140 }
pkasting@chromium.org7b596f62012-05-08 01:34:25141}
142
stevet@chromium.org7d574f0b2012-08-14 21:32:51143// Returns true if |turl|'s GUID is not found inside |sync_data|. This is to be
144// used in MergeDataAndStartSyncing to differentiate between TemplateURLs from
145// Sync and TemplateURLs that were initially local, assuming |sync_data| is the
146// |initial_sync_data| parameter.
147bool IsFromSync(const TemplateURL* turl, const SyncDataMap& sync_data) {
Jan Wilken Dörrie45d34f42019-06-08 09:40:54148 return base::Contains(sync_data, turl->sync_guid());
stevet@chromium.org7d574f0b2012-08-14 21:32:51149}
joaodasilva@chromium.org18b5f6842011-05-23 11:51:45150
stevet@chromium.org78a857c2012-08-21 20:31:48151// Log the number of instances of a keyword that exist, with zero or more
152// underscores, which could occur as the result of conflict resolution.
153void LogDuplicatesHistogram(
154 const TemplateURLService::TemplateURLVector& template_urls) {
Jan Wilken Dörriefa241ba2021-03-11 17:57:01155 std::map<std::u16string, int> duplicates;
jdoerrie3feb1852018-10-05 12:16:44156 for (auto it = template_urls.begin(); it != template_urls.end(); ++it) {
Jan Wilken Dörriefa241ba2021-03-11 17:57:01157 std::u16string keyword = (*it)->keyword();
Jan Wilken Dörrie756999e2021-03-23 15:05:24158 base::TrimString(keyword, u"_", &keyword);
stevet@chromium.org78a857c2012-08-21 20:31:48159 duplicates[keyword]++;
160 }
161
162 // Count the keywords with duplicates.
163 int num_dupes = 0;
Jan Wilken Dörriefa241ba2021-03-11 17:57:01164 for (std::map<std::u16string, int>::const_iterator it = duplicates.begin();
pnoland122369ab2016-05-31 18:43:31165 it != duplicates.end(); ++it) {
stevet@chromium.org78a857c2012-08-21 20:31:48166 if (it->second > 1)
167 num_dupes++;
168 }
169
170 UMA_HISTOGRAM_COUNTS_100("Search.SearchEngineDuplicateCounts", num_dupes);
171}
172
avi8a64b715b2016-09-02 17:30:04173bool Contains(TemplateURLService::OwnedTemplateURLVector* template_urls,
Kevin Bailey499298a2018-03-16 14:54:43174 const TemplateURL* turl) {
avi8a64b715b2016-09-02 17:30:04175 return FindTemplateURL(template_urls, turl) != template_urls->end();
mpearson6456fb62015-11-13 06:44:28176}
177
a-v-yf4eb4352017-02-25 18:46:59178bool IsCreatedByExtension(const TemplateURL* template_url) {
ianwena27b1df2016-09-23 23:08:23179 return template_url->type() ==
180 TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION ||
181 template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION;
182}
183
Fabio Tirelo07ea7dd2023-12-06 02:13:44184// Checks if `new_values` has updated versions of `existing_turl`. Only fields
185// set by the `SiteSearchSettings` policy are checked.
186bool ShouldMergeEnterpriseSiteSearchEngines(const TemplateURL& existing_turl,
187 const TemplateURL& new_values) {
188 CHECK_EQ(existing_turl.keyword(), new_values.keyword());
189
190 return existing_turl.short_name() != new_values.short_name() ||
191 existing_turl.url() != new_values.url() ||
192 existing_turl.featured_by_policy() != new_values.featured_by_policy();
193}
194
195// Creates a new `TemplateURL` that copies updates fields from `new_values` into
196// `existing_turl`. Only fields set by the `SiteSearchSettings` policy are
197// copied from `new_values`, all other fields are copied unchanged from
198// `existing_turl`.
199TemplateURL MergeEnterpriseSiteSearchEngines(const TemplateURL& existing_turl,
200 const TemplateURL& new_values) {
201 CHECK_EQ(existing_turl.keyword(), new_values.keyword());
202
203 TemplateURLData merged_data(existing_turl.data());
204 merged_data.SetShortName(new_values.short_name());
205 merged_data.SetURL(new_values.url());
206 merged_data.featured_by_policy = new_values.featured_by_policy();
207 return TemplateURL(merged_data);
208}
209
mpearson6456fb62015-11-13 06:44:28210} // namespace
vasilii@chromium.orga50e38f2013-07-22 16:47:00211
vasilii@chromium.orga50e38f2013-07-22 16:47:00212// TemplateURLService::LessWithPrefix -----------------------------------------
213
erg@google.com8e5c89a2011-06-07 18:13:33214class TemplateURLService::LessWithPrefix {
ben@chromium.orgd82443b2009-01-15 19:54:56215 public:
216 // We want to find the set of keywords that begin with a prefix. The STL
217 // algorithms will return the set of elements that are "equal to" the
218 // prefix, where "equal(x, y)" means "!(cmp(x, y) || cmp(y, x))". When
219 // cmp() is the typical std::less<>, this results in lexicographic equality;
220 // we need to extend this to mark a prefix as "not less than" a keyword it
221 // begins, which will cause the desired elements to be considered "equal to"
222 // the prefix. Note: this is still a strict weak ordering, as required by
223 // equal_range() (though I will not prove that here).
224 //
225 // Unfortunately the calling convention is not "prefix and element" but
226 // rather "two elements", so we pass the prefix as a fake "element" which has
227 // a NULL KeywordDataElement pointer.
Angela Yoeurngcec8a4642023-06-15 20:33:59228 bool operator()(const KeywordToTURL::value_type& elem1,
229 const KeywordToTURL::value_type& elem2) const {
230 return (elem1.second == nullptr)
avi8a64b715b2016-09-02 17:30:04231 ? (elem2.first.compare(0, elem1.first.length(), elem1.first) > 0)
232 : (elem1.first < elem2.first);
ben@chromium.orgd82443b2009-01-15 19:54:56233 }
234};
235
Esmael El-Moslimanyb4306e02018-04-18 21:24:33236// TemplateURLService::Scoper -------------------------------------------------
237
238class TemplateURLService::Scoper {
239 public:
240 // Keep one of these handles in scope to coalesce all the notifications into a
241 // single notification. Likewise, BatchModeScoper defers web data service
242 // operations into a batch operation.
243 //
244 // Notifications are sent when the last outstanding handle is destroyed and
245 // |model_mutated_notification_pending_| is true.
246 //
247 // The web data service batch operation is performed when the batch mode level
248 // is 0 and more than one operation is pending. This check happens when
249 // BatchModeScoper is destroyed.
250 explicit Scoper(TemplateURLService* service)
251 : batch_mode_scoper_(
252 std::make_unique<KeywordWebDataService::BatchModeScoper>(
253 service->web_data_service_.get())),
254 service_(service) {
255 ++service_->outstanding_scoper_handles_;
256 }
257
Peter Boström09c01822021-09-20 22:43:27258 Scoper(const Scoper&) = delete;
259 Scoper& operator=(const Scoper&) = delete;
260
Esmael El-Moslimanyb4306e02018-04-18 21:24:33261 // When a Scoper is destroyed, the handle count is updated. If the handle
262 // count is at zero, notify the observers that the model has changed if
263 // service is loaded and model was mutated.
264 ~Scoper() {
265 DCHECK_GT(service_->outstanding_scoper_handles_, 0);
266
267 --service_->outstanding_scoper_handles_;
268 if (service_->outstanding_scoper_handles_ == 0 &&
269 service_->model_mutated_notification_pending_) {
270 service_->model_mutated_notification_pending_ = false;
271
272 if (!service_->loaded_)
273 return;
274
275 for (auto& observer : service_->model_observers_)
276 observer.OnTemplateURLServiceChanged();
277 }
278 }
279
280 private:
281 std::unique_ptr<KeywordWebDataService::BatchModeScoper> batch_mode_scoper_;
Keishi Hattori0e45c022021-11-27 09:25:52282 raw_ptr<TemplateURLService> service_;
Esmael El-Moslimanyb4306e02018-04-18 21:24:33283};
vasilii@chromium.orga50e38f2013-07-22 16:47:00284
Fabio Tirelo07ea7dd2023-12-06 02:13:44285// TemplateURLService::PreLoadingProviders -------------------------------------
286
287class TemplateURLService::PreLoadingProviders {
288 public:
289 PreLoadingProviders() = default;
290 ~PreLoadingProviders() = default;
291
292 const TemplateURL* default_search_provider() const {
293 return default_search_provider_.get();
294 }
295
296 TemplateURL* default_search_provider() {
297 return default_search_provider_.get();
298 }
299
300 void set_default_search_provider(
301 std::unique_ptr<TemplateURL> default_search_provider) {
302 default_search_provider_ = std::move(default_search_provider);
303 }
304
305 TemplateURLService::OwnedTemplateURLVector TakeSiteSearchEngines() {
306 return std::move(site_search_engines_);
307 }
308
309 void set_site_search_engines(
310 TemplateURLService::OwnedTemplateURLVector&& site_search_engines) {
311 site_search_engines_ = std::move(site_search_engines);
312 }
313
314 // Looks up `keyword` and returns the best `TemplateURL` for it. Returns
315 // `nullptr` if the keyword was not found. The caller should not try to delete
316 // the returned pointer; the data store retains ownership of it.
317 const TemplateURL* GetTemplateURLForKeyword(
318 const std::u16string& keyword) const {
319 return GetTemplateURLForSelector(base::BindRepeating(
320 [](const std::u16string& keyword, const TemplateURL& turl) {
321 return turl.keyword() == keyword;
322 },
323 keyword));
324 }
325
326 // Returns that `TemplateURL` with the specified GUID, or nullptr if not
327 // found.The caller should not try to delete the returned pointer; the data
328 // store retains ownership of it.
329 const TemplateURL* GetTemplateURLForGUID(const std::string& guid) const {
330 return GetTemplateURLForSelector(base::BindRepeating(
331 [](const std::string& guid, const TemplateURL& turl) {
332 return turl.sync_guid() == guid;
333 },
334 guid));
335 }
336
337 // Returns the best `TemplateURL` found with a URL using the specified `host`,
338 // or nullptr if no such URL can be found.
339 const TemplateURL* GetTemplateURLForHost(
340 const std::string& host,
341 const SearchTermsData& search_terms_data) const {
342 return GetTemplateURLForSelector(base::BindRepeating(
343 [](const std::string& host, const SearchTermsData* search_terms_data,
344 const TemplateURL& turl) {
345 return turl.GenerateSearchURL(*search_terms_data).host_piece() ==
346 host;
347 },
348 host, &search_terms_data));
349 }
350
351 private:
352 // Returns a pointer to a `TemplateURL` `t` such that `selector(t) == true`.
353 // Prioritizes DSP.
354 const TemplateURL* GetTemplateURLForSelector(
355 base::RepeatingCallback<bool(const TemplateURL& turl)> selector) const {
356 if (default_search_provider() && selector.Run(*default_search_provider())) {
357 return default_search_provider();
358 }
359
360 for (auto& site_search_engine : site_search_engines_) {
361 if (selector.Run(*site_search_engine)) {
362 return site_search_engine.get();
363 }
364 }
365
366 return nullptr;
367 }
368
369 // A temporary location for the DSE until Web Data has been loaded and it can
370 // be merged into |template_urls_|.
371 std::unique_ptr<TemplateURL> default_search_provider_;
372
373 // A temporary location for site search set by policy until Web Data has been
374 // loaded and it can be merged into |template_urls_|.
375 TemplateURLService::OwnedTemplateURLVector site_search_engines_;
376};
377
vasilii@chromium.orga50e38f2013-07-22 16:47:00378// TemplateURLService ---------------------------------------------------------
hashimoto@chromium.org7e310422014-07-03 12:44:45379TemplateURLService::TemplateURLService(
380 PrefService* prefs,
Boris Sazonov2b77815a2024-01-17 15:35:04381 search_engines::SearchEngineChoiceService* search_engine_choice_service,
dchengd967d9502016-04-21 22:36:51382 std::unique_ptr<SearchTermsData> search_terms_data,
dcheng4082bfc2014-08-28 19:22:02383 const scoped_refptr<KeywordWebDataService>& web_data_service,
dchengd967d9502016-04-21 22:36:51384 std::unique_ptr<TemplateURLServiceClient> client,
Jun Ishiguro6bae99ca2023-11-06 10:03:49385 const base::RepeatingClosure& dsp_change_callback
386#if BUILDFLAG(IS_CHROMEOS_LACROS)
Fabio Tirelo07ea7dd2023-12-06 02:13:44387 ,
388 bool for_lacros_main_profile
Jun Ishiguro6bae99ca2023-11-06 10:03:49389#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
390 )
hashimoto@chromium.org7e310422014-07-03 12:44:45391 : prefs_(prefs),
Boris Sazonov2b77815a2024-01-17 15:35:04392 search_engine_choice_service_(search_engine_choice_service),
dcheng51ace48a2015-12-26 22:45:17393 search_terms_data_(std::move(search_terms_data)),
hashimoto@chromium.org7e310422014-07-03 12:44:45394 web_data_service_(web_data_service),
dcheng51ace48a2015-12-26 22:45:17395 client_(std::move(client)),
hashimoto@chromium.org82fe07e2014-07-02 07:40:22396 dsp_change_callback_(dsp_change_callback),
Fabio Tirelo07ea7dd2023-12-06 02:13:44397 pre_loading_providers_(std::make_unique<PreLoadingProviders>()),
caitkp@chromium.org95d064c2014-05-06 07:12:49398 default_search_manager_(
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54399 prefs,
Boris Sazonov2b77815a2024-01-17 15:35:04400 search_engine_choice_service,
Vasilii Sukhanov9e32af72018-04-17 18:47:15401 base::BindRepeating(&TemplateURLService::ApplyDefaultSearchChange,
Jun Ishiguro6bae99ca2023-11-06 10:03:49402 base::Unretained(this))
403#if BUILDFLAG(IS_CHROMEOS_LACROS)
Fabio Tirelo07ea7dd2023-12-06 02:13:44404 ,
405 for_lacros_main_profile
Jun Ishiguro6bae99ca2023-11-06 10:03:49406#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
407 ),
Fabio Tirelo78ca8262023-10-31 14:53:18408 enterprise_site_search_manager_(GetEnterpriseSiteSearchManager(prefs)) {
Boris Sazonov2b77815a2024-01-17 15:35:04409 if (!search_engine_choice_service_) {
410 CHECK_IS_TEST();
411 CHECK(!prefs);
412 }
hashimoto@chromium.org7e310422014-07-03 12:44:45413 DCHECK(search_terms_data_);
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54414 Init();
415}
416
417TemplateURLService::TemplateURLService(
418 PrefService* prefs,
Boris Sazonov2b77815a2024-01-17 15:35:04419 search_engines::SearchEngineChoiceService* search_engine_choice_service,
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54420 base::span<const TemplateURLService::Initializer> initializers)
421 : TemplateURLService(
422 prefs,
Boris Sazonov2b77815a2024-01-17 15:35:04423 search_engine_choice_service,
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54424 /*search_terms_data=*/std::make_unique<SearchTermsData>(),
425 /*web_data_service=*/nullptr,
426 /*client=*/nullptr,
427 /*dsp_change_callback=*/base::RepeatingClosure()
428#if BUILDFLAG(IS_CHROMEOS_LACROS)
429 ,
430 /*for_lacros_main_profile=*/false
431#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
432 ) {
433 // This constructor is not intended to be used outside of tests.
434 CHECK_IS_TEST();
Boris Sazonov2b77815a2024-01-17 15:35:04435 CHECK(search_engine_choice_service_ || !prefs);
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54436 ApplyInitializersForTesting(initializers); // IN-TEST
ben@chromium.orgd82443b2009-01-15 19:54:56437}
438
erg@google.com8e5c89a2011-06-07 18:13:33439TemplateURLService::TemplateURLService(const Initializer* initializers,
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54440 const size_t count)
Fabio Tirelo07ea7dd2023-12-06 02:13:44441 : pre_loading_providers_(std::make_unique<PreLoadingProviders>()),
442 default_search_manager_(
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54443 /*pref_service=*/nullptr,
Boris Sazonov2b77815a2024-01-17 15:35:04444 /*search_engine_choice_service=*/nullptr,
Vasilii Sukhanov9e32af72018-04-17 18:47:15445 base::BindRepeating(&TemplateURLService::ApplyDefaultSearchChange,
Jun Ishiguro6bae99ca2023-11-06 10:03:49446 base::Unretained(this))
447#if BUILDFLAG(IS_CHROMEOS_LACROS)
Fabio Tirelo07ea7dd2023-12-06 02:13:44448 ,
449 false
Jun Ishiguro6bae99ca2023-11-06 10:03:49450#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
451 ),
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:54452 enterprise_site_search_manager_(
453 GetEnterpriseSiteSearchManager(/*prefs=*/nullptr)) {
454 CHECK_IS_TEST();
455 Init();
456 ApplyInitializersForTesting(base::make_span(initializers, count)); // IN-TEST
ben@chromium.orgd82443b2009-01-15 19:54:56457}
458
erg@google.com8e5c89a2011-06-07 18:13:33459TemplateURLService::~TemplateURLService() {
hashimoto@chromium.org37b324602014-07-02 07:30:49460 // |web_data_service_| should be deleted during Shutdown().
Zinovy Nis701103b2018-05-10 22:59:38461 DCHECK(!web_data_service_);
ben@chromium.orgd82443b2009-01-15 19:54:56462}
463
ben@chromium.orgd82443b2009-01-15 19:54:56464// static
sdefresne29487962015-07-03 09:24:39465void TemplateURLService::RegisterProfilePrefs(
466 user_prefs::PrefRegistrySyncable* registry) {
Xiaohan Wangcff600f2022-01-14 14:55:33467#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
melandoryc1105ec2016-07-01 12:11:53468 uint32_t flags = PrefRegistry::NO_REGISTRATION_FLAGS;
469#else
470 uint32_t flags = user_prefs::PrefRegistrySyncable::SYNCABLE_PREF;
471#endif
sdefresne29487962015-07-03 09:24:39472 registry->RegisterStringPref(prefs::kSyncedDefaultSearchProviderGUID,
473 std::string(),
melandoryc1105ec2016-07-01 12:11:53474 flags);
Jack Yammineabb0ba22023-07-20 10:46:34475 registry->RegisterStringPref(prefs::kDefaultSearchProviderGUID,
476 std::string());
sdefresne29487962015-07-03 09:24:39477 registry->RegisterBooleanPref(prefs::kDefaultSearchProviderEnabled, true);
Roman Aroraebf0ed7f2020-06-10 22:33:06478 registry->RegisterBooleanPref(
479 prefs::kDefaultSearchProviderContextMenuAccessAllowed, true);
Jack Yammine4ed56a12023-08-02 07:54:59480
Jens Mueller087e5c42023-10-05 11:36:05481 if (search_engines::IsChoiceScreenFlagEnabled(
482 search_engines::ChoicePromo::kAny)) {
Jack Yammine4ed56a12023-08-02 07:54:59483 registry->RegisterInt64Pref(
484 prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp, 0);
David Rogerc549c542023-11-29 15:13:31485 registry->RegisterStringPref(
486 prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
487 std::string());
Sylvain Defresnee7fac2a32024-04-10 13:32:34488
489#if BUILDFLAG(IS_IOS)
490 registry->RegisterIntegerPref(
491 prefs::kDefaultSearchProviderChoiceScreenSkippedCount, 0);
492#endif
Jack Yammine4ed56a12023-08-02 07:54:59493 }
sdefresne29487962015-07-03 09:24:39494}
495
Xiaohan Wangcff600f2022-01-14 14:55:33496#if BUILDFLAG(IS_ANDROID)
Peter Wenf517f7d2019-06-13 15:28:27497base::android::ScopedJavaLocalRef<jobject> TemplateURLService::GetJavaObject() {
498 if (!template_url_service_android_) {
499 template_url_service_android_ =
500 std::make_unique<TemplateUrlServiceAndroid>(this);
501 }
502 return template_url_service_android_->GetJavaObject();
503}
504#endif
505
mpearson511ce60b2015-05-23 23:47:09506bool TemplateURLService::CanAddAutogeneratedKeyword(
Jan Wilken Dörriefa241ba2021-03-11 17:57:01507 const std::u16string& keyword,
Tommy Liabcfd142021-01-22 20:51:34508 const GURL& url) {
jeanluc@google.comb6fd1fa72010-10-05 23:49:31509 DCHECK(!keyword.empty()); // This should only be called for non-empty
510 // keywords. If we need to support empty kewords
511 // the code needs to change slightly.
Kevin Bailey499298a2018-03-16 14:54:43512 const TemplateURL* existing_url = GetTemplateURLForKeyword(keyword);
ben@chromium.orgd82443b2009-01-15 19:54:56513 if (existing_url) {
Tommy Libf31dec2020-12-17 21:26:35514 // TODO(tommycli): Currently, this code goes one step beyond
515 // safe_for_autoreplace() and also forbids automatically modifying
516 // prepopulated engines. That's debatable, as we already update prepopulated
517 // provider favicons as the user browses. See UpdateProviderFavicons().
518 return existing_url->safe_for_autoreplace() &&
519 existing_url->prepopulate_id() == 0;
ben@chromium.orgd82443b2009-01-15 19:54:56520 }
521
mpearson511ce60b2015-05-23 23:47:09522 // We don't have a TemplateURL with keyword. We still may not allow this
523 // keyword if there's evidence we may have created this keyword before and
524 // the user renamed it (because, for instance, the keyword is a common word
525 // that may interfere with search queries). An easy heuristic for this is
526 // whether the user has a TemplateURL that has been manually modified (e.g.,
527 // renamed) connected to the same host.
pkasting@chromium.org3954c3a2012-04-10 20:17:55528 return !url.is_valid() || url.host().empty() ||
mpearson511ce60b2015-05-23 23:47:09529 CanAddAutogeneratedKeywordForHost(url.host());
ben@chromium.orgd82443b2009-01-15 19:54:56530}
531
Fabio Tirelo41316deb2023-11-24 21:33:11532bool TemplateURLService::IsPrepopulatedOrDefaultProviderByPolicy(
jeffschiller9c4df022017-04-14 00:47:38533 const TemplateURL* t_url) const {
Fabio Tirelo6e6df8bf2023-11-22 17:00:15534 return (t_url->prepopulate_id() > 0 ||
535 t_url->created_by_policy() ==
536 TemplateURLData::CreatedByPolicy::kDefaultSearchProvider ||
Pavel Yatsukc5fa92d2019-09-26 21:43:27537 t_url->created_from_play_api()) &&
538 t_url->SupportsReplacement(search_terms_data());
ltian3b5e1a342016-11-30 19:48:11539}
540
jeffschiller9c4df022017-04-14 00:47:38541bool TemplateURLService::ShowInDefaultList(const TemplateURL* t_url) const {
ltian3b5e1a342016-11-30 19:48:11542 return t_url == default_search_provider_ ||
Fabio Tirelo41316deb2023-11-24 21:33:11543 IsPrepopulatedOrDefaultProviderByPolicy(t_url);
ltiancf06dd012016-11-18 08:49:12544}
545
Fabio Tirelocba0d892023-12-19 18:45:41546bool TemplateURLService::ShowInActivesList(const TemplateURL* t_url) const {
547 return t_url->is_active() == TemplateURLData::ActiveStatus::kTrue ||
548 (t_url->created_by_policy() ==
549 TemplateURLData::CreatedByPolicy::kSiteSearch &&
Fabio Tirelo5efc04da2024-01-04 21:31:37550 t_url->featured_by_policy());
Fabio Tirelocba0d892023-12-19 18:45:41551}
552
553bool TemplateURLService::HiddenFromLists(const TemplateURL* t_url) const {
554 // Hide synthetic entries created by SiteSearchSettings policy, since they
555 // are only used for discoverability and the corresponding entry that doesn't
556 // start with "@" is already shown in the actives list.
557 return t_url->created_by_policy() ==
558 TemplateURLData::CreatedByPolicy::kSiteSearch &&
Fabio Tirelo5efc04da2024-01-04 21:31:37559 t_url->featured_by_policy();
Fabio Tirelocba0d892023-12-19 18:45:41560}
561
Angela Yoeurngcec8a4642023-06-15 20:33:59562void TemplateURLService::AddMatchingKeywords(const std::u16string& prefix,
563 bool supports_replacement_only,
564 TemplateURLVector* matches) {
565 AddMatchingKeywordsHelper(keyword_to_turl_, prefix, supports_replacement_only,
566 matches);
mpearson6456fb62015-11-13 06:44:28567}
ben@chromium.orgd82443b2009-01-15 19:54:56568
pkasting@chromium.org3613347d2012-04-27 20:27:37569TemplateURL* TemplateURLService::GetTemplateURLForKeyword(
Jan Wilken Dörriefa241ba2021-03-11 17:57:01570 const std::u16string& keyword) {
jeffschiller612137a2017-04-19 04:34:24571 return const_cast<TemplateURL*>(
572 static_cast<const TemplateURLService*>(this)->
573 GetTemplateURLForKeyword(keyword));
574}
575
576const TemplateURL* TemplateURLService::GetTemplateURLForKeyword(
Jan Wilken Dörriefa241ba2021-03-11 17:57:01577 const std::u16string& keyword) const {
Tommy Li70e35282020-11-11 23:52:56578 // Finds and returns the best match for |keyword|.
Angela Yoeurngcec8a4642023-06-15 20:33:59579 const auto match_range = keyword_to_turl_.equal_range(keyword);
Tommy Li70e35282020-11-11 23:52:56580 if (match_range.first != match_range.second) {
581 // Among the matches for |keyword| in the multimap, return the best one.
582 return std::min_element(
583 match_range.first, match_range.second,
584 [](const auto& a, const auto& b) {
Fabio Tireloc0de81c2024-01-10 18:07:15585 return a.second->IsBetterThanConflictingEngine(b.second);
Tommy Li70e35282020-11-11 23:52:56586 })
Angela Yoeurngcec8a4642023-06-15 20:33:59587 ->second;
Tommy Li70e35282020-11-11 23:52:56588 }
589
Fabio Tirelo07ea7dd2023-12-06 02:13:44590 return loaded_ ? nullptr
591 : pre_loading_providers_->GetTemplateURLForKeyword(keyword);
ben@chromium.orgd82443b2009-01-15 19:54:56592}
593
pkasting@chromium.org3613347d2012-04-27 20:27:37594TemplateURL* TemplateURLService::GetTemplateURLForGUID(
595 const std::string& sync_guid) {
jeffschiller612137a2017-04-19 04:34:24596return const_cast<TemplateURL*>(
597 static_cast<const TemplateURLService*>(this)->
598 GetTemplateURLForGUID(sync_guid));
599}
600
601const TemplateURL* TemplateURLService::GetTemplateURLForGUID(
602 const std::string& sync_guid) const {
jdoerrie3feb1852018-10-05 12:16:44603 auto elem(guid_to_turl_.find(sync_guid));
Fabio Tirelo07ea7dd2023-12-06 02:13:44604 if (elem != guid_to_turl_.end()) {
pkasting@chromium.org3954c3a2012-04-10 20:17:55605 return elem->second;
Fabio Tirelo07ea7dd2023-12-06 02:13:44606 }
607
608 return loaded_ ? nullptr
609 : pre_loading_providers_->GetTemplateURLForGUID(sync_guid);
stevet@chromium.orgd88cb202011-08-17 20:03:01610}
611
pkasting@chromium.org3613347d2012-04-27 20:27:37612TemplateURL* TemplateURLService::GetTemplateURLForHost(
613 const std::string& host) {
jeffschiller612137a2017-04-19 04:34:24614 return const_cast<TemplateURL*>(
615 static_cast<const TemplateURLService*>(this)->
616 GetTemplateURLForHost(host));
617}
618
619const TemplateURL* TemplateURLService::GetTemplateURLForHost(
620 const std::string& host) const {
Tommy C. Li39c534102022-05-09 16:49:32621 if (loaded_) {
622 // `provider_map_` takes care of finding the best TemplateURL for `host`.
hashimoto@chromium.org168d08722014-06-18 07:13:28623 return provider_map_->GetTemplateURLForHost(host);
Tommy C. Li39c534102022-05-09 16:49:32624 }
Fabio Tirelo07ea7dd2023-12-06 02:13:44625
626 return loaded_ ? nullptr
627 : pre_loading_providers_->GetTemplateURLForHost(
628 host, search_terms_data());
ben@chromium.orgd82443b2009-01-15 19:54:56629}
630
Tommy C. Li069c55052022-05-06 19:04:11631size_t TemplateURLService::GetTemplateURLCountForHostForLogging(
632 const std::string& host) const {
633 DCHECK(loaded_);
Sophie Chang4c700d32022-05-06 21:39:42634 auto* host_urls = provider_map_->GetURLsForHost(host);
635 return host_urls ? host_urls->size() : 0;
Tommy C. Li069c55052022-05-06 19:04:11636}
637
avi8a64b715b2016-09-02 17:30:04638TemplateURL* TemplateURLService::Add(
639 std::unique_ptr<TemplateURL> template_url) {
a-v-yc134e762017-04-18 15:31:31640 DCHECK(template_url);
641 DCHECK(
642 !IsCreatedByExtension(template_url.get()) ||
643 (!FindTemplateURLForExtension(template_url->extension_info_->extension_id,
644 template_url->type()) &&
645 template_url->id() == kInvalidTemplateURLID));
646
Esmael El-Moslimanyb4306e02018-04-18 21:24:33647 return Add(std::move(template_url), true);
ben@chromium.orgd82443b2009-01-15 19:54:56648}
649
avi8a64b715b2016-09-02 17:30:04650TemplateURL* TemplateURLService::AddWithOverrides(
651 std::unique_ptr<TemplateURL> template_url,
Jan Wilken Dörriefa241ba2021-03-11 17:57:01652 const std::u16string& short_name,
653 const std::u16string& keyword,
avi8a64b715b2016-09-02 17:30:04654 const std::string& url) {
mpearson3c6d7af2015-05-13 23:59:53655 DCHECK(!short_name.empty());
pkasting@chromium.org7b596f62012-05-08 01:34:25656 DCHECK(!keyword.empty());
pkasting@chromium.org4e40f59bb2012-04-19 01:07:08657 DCHECK(!url.empty());
mpearson3c6d7af2015-05-13 23:59:53658 template_url->data_.SetShortName(short_name);
pkasting@chromium.org3613347d2012-04-27 20:27:37659 template_url->data_.SetKeyword(keyword);
660 template_url->SetURL(url);
avi8a64b715b2016-09-02 17:30:04661 return Add(std::move(template_url));
pkasting@chromium.org573889f22012-04-07 01:31:54662}
663
Kevin Bailey499298a2018-03-16 14:54:43664void TemplateURLService::Remove(const TemplateURL* template_url) {
Tommy Lia83fbf92021-01-12 21:04:47665 // CHECK that we aren't trying to Remove() the default search provider.
666 // This has happened before, and causes permanent damage to the user Profile,
667 // which can then be Synced to other installations. It's better to crash
668 // immediately, and that's why this isn't a DCHECK. https://crbug.com/1164024
669 {
670 const TemplateURL* default_provider = GetDefaultSearchProvider();
671
672 // TODO(tommycli): Once we are sure this never happens in practice, we can
673 // remove this CrashKeyString, but we should keep the CHECK.
674 static base::debug::CrashKeyString* crash_key =
675 base::debug::AllocateCrashKeyString("removed_turl_keyword",
676 base::debug::CrashKeySize::Size256);
677 base::debug::ScopedCrashKeyString auto_clear(
678 crash_key, base::UTF16ToUTF8(template_url->keyword()));
679
680 CHECK_NE(template_url, default_provider);
Tommy Li50aeacaf2021-01-20 21:21:12681
Tommy Lid8e4850ef2021-03-05 18:18:19682 // Before we are loaded, we want to CHECK that we aren't accidentally
683 // removing the in-table representation of the Default Search Engine.
684 //
685 // But users in the wild do indeed have engines with duplicated sync GUIDs.
686 // For instance, Extensions Override Settings API used to have a bug that
687 // would clone GUIDs. So therefore skip the check after loading.
688 // https://crbug.com/1166372#c13
689 if (!loaded() && default_provider &&
Tommy Li50aeacaf2021-01-20 21:21:12690 default_provider->type() !=
691 TemplateURL::Type::NORMAL_CONTROLLED_BY_EXTENSION &&
692 template_url->type() !=
693 TemplateURL::Type::NORMAL_CONTROLLED_BY_EXTENSION) {
Tommy Lia83fbf92021-01-12 21:04:47694 CHECK_NE(template_url->sync_guid(), default_provider->sync_guid());
Tommy Li50aeacaf2021-01-20 21:21:12695 }
Tommy Lia83fbf92021-01-12 21:04:47696 }
Esmael El-Moslimanyb4306e02018-04-18 21:24:33697
698 auto i = FindTemplateURL(&template_urls_, template_url);
699 if (i == template_urls_.end())
700 return;
701
702 Scoper scoper(this);
703 model_mutated_notification_pending_ = true;
704
705 RemoveFromMaps(template_url);
706
707 // Remove it from the vector containing all TemplateURLs.
708 std::unique_ptr<TemplateURL> scoped_turl = std::move(*i);
709 template_urls_.erase(i);
710
711 if (template_url->type() == TemplateURL::NORMAL) {
Fabio Tirelo07ea7dd2023-12-06 02:13:44712 if (web_data_service_) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:33713 web_data_service_->RemoveKeyword(template_url->id());
Fabio Tirelo07ea7dd2023-12-06 02:13:44714 }
Esmael El-Moslimanyb4306e02018-04-18 21:24:33715 // Inform sync of the deletion.
716 ProcessTemplateURLChange(FROM_HERE, template_url,
717 syncer::SyncChange::ACTION_DELETE);
718
719 // The default search engine can't be deleted. But the user defined DSE can
720 // be hidden by an extension or policy and then deleted. Clean up the user
721 // prefs then.
Jack Yammineabb0ba22023-07-20 10:46:34722 if (prefs_ && (template_url->sync_guid() ==
723 GetDefaultSearchProviderPrefValue(*prefs_))) {
724 SetDefaultSearchProviderPrefValue(*prefs_, std::string());
Esmael El-Moslimanyb4306e02018-04-18 21:24:33725 }
726
727 UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName,
728 DELETE_ENGINE_USER_ACTION, DELETE_ENGINE_MAX);
729 }
730
Fabio Tirelo07ea7dd2023-12-06 02:13:44731 if (loaded_ && client_) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:33732 client_->DeleteAllSearchTermsForKeyword(template_url->id());
Fabio Tirelo07ea7dd2023-12-06 02:13:44733 }
ben@chromium.orgd82443b2009-01-15 19:54:56734}
735
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25736void TemplateURLService::RemoveExtensionControlledTURL(
hashimoto@chromium.orgc73b2b52014-06-27 09:42:33737 const std::string& extension_id,
738 TemplateURL::Type type) {
hashimoto@chromium.orgc73b2b52014-06-27 09:42:33739 TemplateURL* url = FindTemplateURLForExtension(extension_id, type);
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25740 if (!url)
741 return;
Esmael El-Moslimanyb4306e02018-04-18 21:24:33742 // NULL this out so that we can call Remove.
erikwright@chromium.orga80ec962014-05-12 00:55:38743 if (default_search_provider_ == url)
avi8a64b715b2016-09-02 17:30:04744 default_search_provider_ = nullptr;
a-v-y858416e2017-03-01 16:42:45745 Remove(url);
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25746}
747
stevet@chromium.orgd88cb202011-08-17 20:03:01748void TemplateURLService::RemoveAutoGeneratedBetween(base::Time created_after,
749 base::Time created_before) {
Alan Screend6a8b67412021-01-29 21:35:57750 RemoveAutoGeneratedForUrlsBetween(base::NullCallback(), created_after,
751 created_before);
mkwst@chromium.org8d4ec682012-02-17 12:20:17752}
753
msramekdb42f892016-08-31 11:53:42754void TemplateURLService::RemoveAutoGeneratedForUrlsBetween(
Alan Screend6a8b67412021-01-29 21:35:57755 const base::RepeatingCallback<bool(const GURL&)>& url_filter,
mkwst@chromium.org8d4ec682012-02-17 12:20:17756 base::Time created_after,
757 base::Time created_before) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:33758 Scoper scoper(this);
759
levin@chromium.orgce75ec62010-08-21 00:34:46760 for (size_t i = 0; i < template_urls_.size();) {
Tommy Libf31dec2020-12-17 21:26:35761 TemplateURL* turl = template_urls_[i].get();
762 if (turl->date_created() >= created_after &&
763 (created_before.is_null() || turl->date_created() < created_before) &&
764 turl->safe_for_autoreplace() && turl->prepopulate_id() == 0 &&
Angela Yoeurng667768392023-01-13 00:04:19765 turl->starter_pack_id() == 0 && !MatchesDefaultSearchProvider(turl) &&
msramekdb42f892016-08-31 11:53:42766 (url_filter.is_null() ||
Tommy Libf31dec2020-12-17 21:26:35767 url_filter.Run(turl->GenerateSearchURL(search_terms_data())))) {
768 Remove(turl);
levin@chromium.orgce75ec62010-08-21 00:34:46769 } else {
770 ++i;
771 }
772 }
773}
774
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25775void TemplateURLService::RegisterOmniboxKeyword(
a-v-yf4eb4352017-02-25 18:46:59776 const std::string& extension_id,
777 const std::string& extension_name,
778 const std::string& keyword,
779 const std::string& template_url_string,
780 const base::Time& extension_install_time) {
yoz@chromium.orgd356c982012-12-12 19:32:55781 DCHECK(loaded_);
ben@chromium.orgd82443b2009-01-15 19:54:56782
hashimoto@chromium.orgc73b2b52014-06-27 09:42:33783 if (FindTemplateURLForExtension(extension_id,
784 TemplateURL::OMNIBOX_API_EXTENSION))
785 return;
ben@chromium.orgd82443b2009-01-15 19:54:56786
hashimoto@chromium.orgc73b2b52014-06-27 09:42:33787 TemplateURLData data;
mpearson3c6d7af2015-05-13 23:59:53788 data.SetShortName(base::UTF8ToUTF16(extension_name));
hashimoto@chromium.orgc73b2b52014-06-27 09:42:33789 data.SetKeyword(base::UTF8ToUTF16(keyword));
790 data.SetURL(template_url_string);
Jinho Bangfa6b5752018-01-03 20:52:19791 Add(std::make_unique<TemplateURL>(data, TemplateURL::OMNIBOX_API_EXTENSION,
a-v-yc134e762017-04-18 15:31:31792 extension_id, extension_install_time,
793 false));
ben@chromium.orgd82443b2009-01-15 19:54:56794}
795
pkasting@chromium.org3613347d2012-04-27 20:27:37796TemplateURLService::TemplateURLVector TemplateURLService::GetTemplateURLs() {
avi8a64b715b2016-09-02 17:30:04797 TemplateURLVector result;
798 for (const auto& turl : template_urls_)
799 result.push_back(turl.get());
800 return result;
ben@chromium.orgd82443b2009-01-15 19:54:56801}
802
Nicolas Dossou-Gbete4a319dbd2024-03-29 17:01:15803std::unique_ptr<search_engines::ChoiceScreenData>
804TemplateURLService::GetChoiceScreenData() {
805 OwnedTemplateURLVector owned_template_urls;
806 bool was_current_default_inserted = false;
807
808 // We call `GetPrepopulatedEngines` instead of
809 // `GetSearchProvidersUsingLoadedEngines` because the latter will return the
810 // list of search engines that might have been modified by the user (by
811 // changing the engine's keyword in settings for example).
812 // Changing this will cause issues in the icon generation behavior that's
813 // handled by `generate_search_engine_icons.py`.
814 std::vector<std::unique_ptr<TemplateURLData>> engines =
815 TemplateURLPrepopulateData::GetPrepopulatedEngines(
816 prefs_, search_engine_choice_service_,
817 /*default_search_provider_index=*/nullptr,
818 /*include_current_default=*/true, /*template_url_service=*/this,
819 /*was_current_default_inserted=*/&was_current_default_inserted);
820 for (const auto& engine : engines) {
821 owned_template_urls.push_back(std::make_unique<TemplateURL>(*engine));
822 }
823
824 return std::make_unique<search_engines::ChoiceScreenData>(
825 std::move(owned_template_urls),
826 search_engine_choice_service_->GetCountryId(),
827 /*list_is_modified_by_current_default=*/was_current_default_inserted,
828 search_terms_data());
829}
830
Nicolas Dossou-Gbete0cd7f0c12024-01-02 14:37:08831#if BUILDFLAG(IS_ANDROID)
832TemplateURLService::OwnedTemplateURLDataVector
833TemplateURLService::GetTemplateURLsForCountry(const std::string& country_code) {
834 return TemplateURLPrepopulateData::GetLocalPrepopulatedEngines(
835 country_code, CHECK_DEREF(prefs_.get()));
836}
837#endif
838
pkasting@chromium.org3613347d2012-04-27 20:27:37839void TemplateURLService::IncrementUsageCount(TemplateURL* url) {
pkasting@chromium.org7b596f62012-05-08 01:34:25840 DCHECK(url);
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25841 // Extension-controlled search engines are not persisted.
ianwena27b1df2016-09-23 23:08:23842 if (url->type() != TemplateURL::NORMAL)
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25843 return;
avi8a64b715b2016-09-02 17:30:04844 if (!Contains(&template_urls_, url))
pkasting@chromium.org7b596f62012-05-08 01:34:25845 return;
pkasting@chromium.org3613347d2012-04-27 20:27:37846 ++url->data_.usage_count;
vasilii@chromium.orgbdcbcd82013-10-28 13:40:25847
Zinovy Nis701103b2018-05-10 22:59:38848 if (web_data_service_)
hashimoto@chromium.org37b324602014-07-02 07:30:49849 web_data_service_->UpdateKeyword(url->data());
ben@chromium.orgd82443b2009-01-15 19:54:56850}
851
pkasting@chromium.org3613347d2012-04-27 20:27:37852void TemplateURLService::ResetTemplateURL(TemplateURL* url,
Jan Wilken Dörriefa241ba2021-03-11 17:57:01853 const std::u16string& title,
854 const std::u16string& keyword,
erg@google.com8e5c89a2011-06-07 18:13:33855 const std::string& search_url) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:33856 DCHECK(!IsCreatedByExtension(url));
857 DCHECK(!keyword.empty());
858 DCHECK(!search_url.empty());
859 TemplateURLData data(url->data());
860 data.SetShortName(title);
861 data.SetKeyword(keyword);
862 if (search_url != data.url()) {
863 data.SetURL(search_url);
864 // The urls have changed, reset the favicon url.
865 data.favicon_url = GURL();
866 }
867 data.safe_for_autoreplace = false;
868 data.last_modified = clock_->Now();
Angela Yoeurng7ff86ed52021-08-12 20:03:24869 data.is_active = TemplateURLData::ActiveStatus::kTrue;
870
Esmael El-Moslimanyb4306e02018-04-18 21:24:33871 Update(url, TemplateURL(data));
ben@chromium.orgd82443b2009-01-15 19:54:56872}
873
Angela Yoeurng895f45ac2021-08-20 16:26:46874void TemplateURLService::SetIsActiveTemplateURL(TemplateURL* url,
875 bool is_active) {
876 DCHECK(url);
877
878 TemplateURLData data(url->data());
Angela Yoeurnga79210e2022-08-23 19:59:46879 std::string histogram_name = kKeywordModeUsageByEngineTypeHistogramName;
880 if (is_active) {
881 data.is_active = TemplateURLData::ActiveStatus::kTrue;
Angela Yoeurng38806b42022-10-13 00:20:54882 data.safe_for_autoreplace = false;
Angela Yoeurnga79210e2022-08-23 19:59:46883 histogram_name.append(".Activated");
884 } else {
885 data.is_active = TemplateURLData::ActiveStatus::kFalse;
886 histogram_name.append(".Deactivated");
887 }
Angela Yoeurng895f45ac2021-08-20 16:26:46888
889 Update(url, TemplateURL(data));
Angela Yoeurnga79210e2022-08-23 19:59:46890
891 base::UmaHistogramEnumeration(
892 histogram_name, url->GetBuiltinEngineType(),
893 BuiltinEngineType::KEYWORD_MODE_ENGINE_TYPE_MAX);
Angela Yoeurng895f45ac2021-08-20 16:26:46894}
895
Tommy Lia56419a2020-12-17 23:40:48896TemplateURL* TemplateURLService::CreatePlayAPISearchEngine(
Jan Wilken Dörriefa241ba2021-03-11 17:57:01897 const std::u16string& title,
898 const std::u16string& keyword,
Pavel Yatsukbec91d22019-10-07 19:14:55899 const std::string& search_url,
Pavel Yatsuk025b85f2019-10-25 15:22:46900 const std::string& suggestions_url,
Jens Mueller3ef45a32023-07-05 16:33:13901 const std::string& favicon_url,
902 const std::string& new_tab_url,
903 const std::string& image_url,
904 const std::string& image_url_post_params,
905 const std::string& image_translate_url,
906 const std::string& image_translate_source_language_param_key,
907 const std::string& image_translate_target_language_param_key) {
Tommy Lia56419a2020-12-17 23:40:48908 // It's the caller's responsibility to check that there are no existing
909 // Play API for engine, but still CHECK this to avoid polluting the database.
910 // Currently, we never update Play API engine data. If we ever want to do
911 // that, we need to change how this method behaves.
Angela Yoeurngcec8a4642023-06-15 20:33:59912 const auto match_range = keyword_to_turl_.equal_range(keyword);
Tommy Lia56419a2020-12-17 23:40:48913 for (auto it = match_range.first; it != match_range.second; ++it) {
Angela Yoeurngcec8a4642023-06-15 20:33:59914 CHECK(!it->second->created_from_play_api());
Tommy Lia56419a2020-12-17 23:40:48915 }
916
Pavel Yatsukbec91d22019-10-07 19:14:55917 TemplateURLData data;
Pavel Yatsukbec91d22019-10-07 19:14:55918 data.SetShortName(title);
919 data.SetKeyword(keyword);
920 data.SetURL(search_url);
Pavel Yatsuk025b85f2019-10-25 15:22:46921 data.suggestions_url = suggestions_url;
Pavel Yatsukbec91d22019-10-07 19:14:55922 data.favicon_url = GURL(favicon_url);
Jens Mueller3ef45a32023-07-05 16:33:13923 data.new_tab_url = new_tab_url;
924 data.image_url = image_url;
925 data.image_url_post_params = image_url_post_params;
926 data.image_translate_url = image_translate_url;
927 data.image_translate_source_language_param_key =
928 image_translate_source_language_param_key;
929 data.image_translate_target_language_param_key =
930 image_translate_target_language_param_key;
Pavel Yatsukbec91d22019-10-07 19:14:55931 data.created_from_play_api = true;
Tommy Libf31dec2020-12-17 21:26:35932 // Play API engines are created by explicit user gesture, and should not be
933 // auto-replaceable by an auto-generated engine as the user browses.
934 data.safe_for_autoreplace = false;
Angela Yoeurng7ff86ed52021-08-12 20:03:24935 data.is_active = TemplateURLData::ActiveStatus::kTrue;
Tommy Libf31dec2020-12-17 21:26:35936
Tommy Lia56419a2020-12-17 23:40:48937 // The Play API search engine is not guaranteed to be the best engine for
938 // |keyword|, if there are user-defined, extension, or policy engines.
939 // In practice on Android, this rarely happens, as there is only policy.
940 return Add(std::make_unique<TemplateURL>(data));
Pavel Yatsukbec91d22019-10-07 19:14:55941}
942
Tommy C. Li2589a7a542018-05-11 00:44:31943void TemplateURLService::UpdateProviderFavicons(
944 const GURL& potential_search_url,
945 const GURL& favicon_url) {
946 DCHECK(loaded_);
947 DCHECK(potential_search_url.is_valid());
948
949 const TemplateURLSet* urls_for_host =
950 provider_map_->GetURLsForHost(potential_search_url.host());
951 if (!urls_for_host)
952 return;
953
Tommy C. Li7dc6c9b2018-05-24 21:46:14954 // Make a copy of the container of the matching TemplateURLs, as the original
955 // container is invalidated as we update the contained TemplateURLs.
956 TemplateURLSet urls_for_host_copy(*urls_for_host);
957
Tommy C. Li2589a7a542018-05-11 00:44:31958 Scoper scoper(this);
Tommy C. Li7dc6c9b2018-05-24 21:46:14959 for (TemplateURL* turl : urls_for_host_copy) {
Tommy C. Li2589a7a542018-05-11 00:44:31960 if (!IsCreatedByExtension(turl) &&
961 turl->IsSearchURL(potential_search_url, search_terms_data()) &&
962 turl->favicon_url() != favicon_url) {
963 TemplateURLData data(turl->data());
964 data.favicon_url = favicon_url;
965 Update(turl, TemplateURL(data));
966 }
967 }
968}
969
jeffschiller9c4df022017-04-14 00:47:38970bool TemplateURLService::CanMakeDefault(const TemplateURL* url) const {
Igor Ruvinovf266d2a42022-06-24 17:52:16971 return (default_search_provider_source_ == DefaultSearchManager::FROM_USER ||
972 default_search_provider_source_ ==
973 DefaultSearchManager::FROM_POLICY_RECOMMENDED ||
974 default_search_provider_source_ ==
975 DefaultSearchManager::FROM_FALLBACK) &&
976 (url != GetDefaultSearchProvider()) &&
977 url->url_ref().SupportsReplacement(search_terms_data()) &&
Angela Yoeurng8f467d952024-02-14 00:36:31978 (url->type() == TemplateURL::NORMAL) && (url->starter_pack_id() == 0);
levin@chromium.org2de7e002010-10-11 22:58:20979}
980
erikwright@chromium.orgf1cb5582014-04-25 07:35:26981void TemplateURLService::SetUserSelectedDefaultSearchProvider(
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:23982 TemplateURL* url,
983 search_engines::ChoiceMadeLocation choice_made_location) {
erikwright@chromium.orga80ec962014-05-12 00:55:38984 // Omnibox keywords cannot be made default. Extension-controlled search
985 // engines can be made default only by the extension itself because they
986 // aren't persisted.
ianwena27b1df2016-09-23 23:08:23987 DCHECK(!url || !IsCreatedByExtension(url));
Angela Yoeurngeff157d2021-10-25 19:49:59988 if (url) {
989 url->data_.is_active = TemplateURLData::ActiveStatus::kTrue;
990 }
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:23991
992 bool selection_added = false;
993
erikwright@chromium.orga80ec962014-05-12 00:55:38994 if (load_failed_) {
995 // Skip the DefaultSearchManager, which will persist to user preferences.
996 if ((default_search_provider_source_ == DefaultSearchManager::FROM_USER) ||
997 (default_search_provider_source_ ==
998 DefaultSearchManager::FROM_FALLBACK)) {
avi8a64b715b2016-09-02 17:30:04999 ApplyDefaultSearchChange(url ? &url->data() : nullptr,
erikwright@chromium.orga80ec962014-05-12 00:55:381000 DefaultSearchManager::FROM_USER);
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:231001 selection_added = true;
Nicolas Dossou-Gbete709c2702024-02-05 20:03:321002 } else {
1003 // When we are setting the search engine choice from choice screens,
1004 // the DSP source is expected to allow the search engine to be changed by
Nicolas Dossou-Gbete03549d12024-04-02 17:13:491005 // the user. But theoretically there is a possibility that a policy
1006 // kicked in after a choice screen was shown, that could be a way to
1007 // enter this state
1008 // TODO(crbug.com/328041262): Investigate mitigation options.
Nicolas Dossou-Gbete709c2702024-02-05 20:03:321009 CHECK_NE(choice_made_location, search_engines::ChoiceMadeLocation::kOther,
Nicolas Dossou-Gbete03549d12024-04-02 17:13:491010 base::NotFatalUntil::M127);
erikwright@chromium.orga80ec962014-05-12 00:55:381011 }
1012 } else {
Vasilii Sukhanov9e32af72018-04-17 18:47:151013 // We rely on the DefaultSearchManager to call ApplyDefaultSearchChange if,
1014 // in fact, the effective DSE changes.
Angela Yoeurngeff157d2021-10-25 19:49:591015 if (url) {
Mihai Sardarescu27fe06b2024-03-12 10:07:461016 default_search_manager_.SetUserSelectedDefaultSearchEngine(
1017 url->data(), choice_made_location);
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:231018 selection_added = true;
Angela Yoeurngeff157d2021-10-25 19:49:591019 } else {
erikwright@chromium.orga80ec962014-05-12 00:55:381020 default_search_manager_.ClearUserSelectedDefaultSearchEngine();
Angela Yoeurngeff157d2021-10-25 19:49:591021 }
erikwright@chromium.orga80ec962014-05-12 00:55:381022 }
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:231023
Nicolas Dossou-Gbete5dec07352024-01-22 16:15:391024 if (selection_added &&
1025 // The choice record below should only be done when called from a path
1026 // associated with a fully featured search engine choice screen.
1027 choice_made_location != search_engines::ChoiceMadeLocation::kOther) {
1028 CHECK(search_engine_choice_service_); // See b/321280848.
Boris Sazonov2b77815a2024-01-17 15:35:041029 search_engine_choice_service_->RecordChoiceMade(choice_made_location, this);
Nicolas Dossou-Gbete9f5f6f02024-01-08 17:38:231030 }
1031
1032#if BUILDFLAG(IS_ANDROID)
1033 if (prefs_) {
1034 // Commit the pref immediately so it isn't lost if the app is killed.
1035 // TODO(b/316887441): Investigate removing this.
1036 prefs_->CommitPendingWrite();
1037 }
1038#endif
ben@chromium.orgd82443b2009-01-15 19:54:561039}
1040
blundelld130d592015-06-21 19:29:131041const TemplateURL* TemplateURLService::GetDefaultSearchProvider() const {
Keishi Hattori0e45c022021-11-27 09:25:521042 return loaded_ ? default_search_provider_.get()
Fabio Tirelo07ea7dd2023-12-06 02:13:441043 : pre_loading_providers_->default_search_provider();
sky@chromium.orgfca94f22010-09-04 14:54:201044}
1045
Devlin Cronin34f1b63d2020-06-04 20:45:341046const TemplateURL*
1047TemplateURLService::GetDefaultSearchProviderIgnoringExtensions() const {
1048 std::unique_ptr<TemplateURLData> next_search =
1049 default_search_manager_.GetDefaultSearchEngineIgnoringExtensions();
1050 if (!next_search)
1051 return nullptr;
1052
1053 // Find the TemplateURL matching the data retrieved.
Peter Kasting03752ef52022-09-02 18:58:351054 auto iter = base::ranges::find_if(
1055 template_urls_, [this, &next_search](const auto& turl_to_check) {
1056 return TemplateURL::MatchesData(turl_to_check.get(), next_search.get(),
1057 search_terms_data());
1058 });
Devlin Cronin34f1b63d2020-06-04 20:45:341059 return iter == template_urls_.end() ? nullptr : iter->get();
1060}
1061
rlp@chromium.org2328ee22013-08-08 23:00:191062bool TemplateURLService::IsSearchResultsPageFromDefaultSearchProvider(
blundelld130d592015-06-21 19:29:131063 const GURL& url) const {
1064 const TemplateURL* default_provider = GetDefaultSearchProvider();
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:191065 return default_provider &&
1066 default_provider->IsSearchURL(url, search_terms_data());
rlp@chromium.org2328ee22013-08-08 23:00:191067}
1068
Moe Ahmadi5ca7afb2022-10-04 17:39:021069GURL TemplateURLService::GenerateSearchURLForDefaultSearchProvider(
1070 const std::u16string& search_terms) const {
1071 const TemplateURL* default_provider = GetDefaultSearchProvider();
1072 return default_provider ? default_provider->GenerateSearchURL(
1073 search_terms_data(), search_terms)
1074 : GURL();
1075}
1076
Arthur Sonzognic571efb2024-01-26 20:26:181077std::optional<TemplateURLService::SearchMetadata>
Tommy C. Li088b42f2022-11-15 00:51:281078TemplateURLService::ExtractSearchMetadata(const GURL& url) const {
Tommy C. Lid95e6482023-01-30 18:54:241079 const TemplateURL* template_url = GetTemplateURLForHost(url.host());
1080 if (!template_url) {
Arthur Sonzognic571efb2024-01-26 20:26:181081 return std::nullopt;
Tommy C. Li088b42f2022-11-15 00:51:281082 }
1083
Tommy C. Lid95e6482023-01-30 18:54:241084 GURL normalized_url;
1085 std::u16string normalized_search_terms;
1086 bool is_valid_search_url =
1087 template_url && template_url->KeepSearchTermsInURL(
1088 url, search_terms_data(),
1089 /*keep_search_intent_params=*/true,
Tommy C. Li088b42f2022-11-15 00:51:281090
Tommy C. Lid95e6482023-01-30 18:54:241091 /*normalize_search_terms=*/true, &normalized_url,
1092 &normalized_search_terms);
Tommy C. Li088b42f2022-11-15 00:51:281093 if (!is_valid_search_url) {
Arthur Sonzognic571efb2024-01-26 20:26:181094 return std::nullopt;
Tommy C. Li088b42f2022-11-15 00:51:281095 }
1096
Tommy C. Li982c3fee2023-02-17 19:36:191097 return SearchMetadata{template_url, normalized_url, normalized_search_terms};
Tommy C. Li088b42f2022-11-15 00:51:281098}
1099
Tom Lukaszewicz9ca12ba2022-02-28 21:31:351100bool TemplateURLService::IsSideSearchSupportedForDefaultSearchProvider() const {
1101 const TemplateURL* default_provider = GetDefaultSearchProvider();
1102 return default_provider && default_provider->IsSideSearchSupported();
1103}
1104
Anudeep Palankifd1c54e2022-09-13 20:03:091105bool TemplateURLService::IsSideImageSearchSupportedForDefaultSearchProvider()
1106 const {
1107 const TemplateURL* default_provider = GetDefaultSearchProvider();
1108 return default_provider && default_provider->IsSideImageSearchSupported();
1109}
1110
Tom Lukaszewicz9ca12ba2022-02-28 21:31:351111GURL TemplateURLService::GenerateSideSearchURLForDefaultSearchProvider(
1112 const GURL& search_url,
1113 const std::string& version) const {
1114 DCHECK(IsSideSearchSupportedForDefaultSearchProvider());
1115 return GetDefaultSearchProvider()->GenerateSideSearchURL(search_url, version,
1116 search_terms_data());
1117}
1118
Pengchao Cai470355c0d2022-10-21 03:01:291119GURL TemplateURLService::RemoveSideSearchParamFromURL(
1120 const GURL& search_url) const {
1121 if (!IsSideSearchSupportedForDefaultSearchProvider())
1122 return search_url;
1123 return GetDefaultSearchProvider()->RemoveSideSearchParamFromURL(search_url);
1124}
1125
Anudeep Palankifd1c54e2022-09-13 20:03:091126GURL TemplateURLService::GenerateSideImageSearchURLForDefaultSearchProvider(
1127 const GURL& search_url,
1128 const std::string& version) const {
1129 DCHECK(IsSideImageSearchSupportedForDefaultSearchProvider());
1130 return GetDefaultSearchProvider()->GenerateSideImageSearchURL(search_url,
1131 version);
1132}
1133
1134GURL TemplateURLService::RemoveSideImageSearchParamFromURL(
1135 const GURL& search_url) const {
1136 if (!IsSideImageSearchSupportedForDefaultSearchProvider())
1137 return search_url;
1138 return GetDefaultSearchProvider()->RemoveSideImageSearchParamFromURL(
1139 search_url);
1140}
1141
jeffschiller9c4df022017-04-14 00:47:381142bool TemplateURLService::IsExtensionControlledDefaultSearch() const {
erikwright@chromium.orga80ec962014-05-12 00:55:381143 return default_search_provider_source_ ==
1144 DefaultSearchManager::FROM_EXTENSION;
avayvod@chromium.org75a4eca2011-10-26 20:40:091145}
1146
vasilii@chromium.orge937a9702013-10-03 22:59:431147void TemplateURLService::RepairPrepopulatedSearchEngines() {
vasilii@chromium.org4a40facd2013-05-29 14:44:561148 // Can't clean DB if it hasn't been loaded.
1149 DCHECK(loaded());
vasilii@chromium.org4a40facd2013-05-29 14:44:561150
Esmael El-Moslimanyb4306e02018-04-18 21:24:331151 Scoper scoper(this);
1152
erikwright@chromium.orga80ec962014-05-12 00:55:381153 if ((default_search_provider_source_ == DefaultSearchManager::FROM_USER) ||
1154 (default_search_provider_source_ ==
1155 DefaultSearchManager::FROM_FALLBACK)) {
1156 // Clear |default_search_provider_| in case we want to remove the engine it
1157 // points to. This will get reset at the end of the function anyway.
avi8a64b715b2016-09-02 17:30:041158 default_search_provider_ = nullptr;
erikwright@chromium.orga80ec962014-05-12 00:55:381159 }
1160
avie8828f22016-09-02 18:07:581161 std::vector<std::unique_ptr<TemplateURLData>> prepopulated_urls =
Boris Sazonov2b77815a2024-01-17 15:35:041162 TemplateURLPrepopulateData::GetPrepopulatedEngines(
1163 prefs_, search_engine_choice_service_, nullptr);
vasilii@chromium.orge937a9702013-10-03 22:59:431164 DCHECK(!prepopulated_urls.empty());
Angela Yoeurng6e3d6b72022-04-18 21:26:171165 ActionsFromCurrentData actions(CreateActionsFromCurrentPrepopulateData(
erikwright@chromium.orga80ec962014-05-12 00:55:381166 &prepopulated_urls, template_urls_, default_search_provider_));
pkasting@chromium.orgad94ea32014-04-02 01:40:471167
vasilii@chromium.orge937a9702013-10-03 22:59:431168 // Remove items.
jdoerrie3feb1852018-10-05 12:16:441169 for (auto i = actions.removed_engines.begin();
vasilii@chromium.orge937a9702013-10-03 22:59:431170 i < actions.removed_engines.end(); ++i)
Esmael El-Moslimanyb4306e02018-04-18 21:24:331171 Remove(*i);
vasilii@chromium.org4a40facd2013-05-29 14:44:561172
vasilii@chromium.orge937a9702013-10-03 22:59:431173 // Edit items.
jdoerrie3feb1852018-10-05 12:16:441174 for (auto i(actions.edited_engines.begin()); i < actions.edited_engines.end();
1175 ++i) {
hashimoto@chromium.org168d08722014-06-18 07:13:281176 TemplateURL new_values(i->second);
Esmael El-Moslimanyb4306e02018-04-18 21:24:331177 Update(i->first, new_values);
vasilii@chromium.orga50e38f2013-07-22 16:47:001178 }
1179
vasilii@chromium.orge937a9702013-10-03 22:59:431180 // Add items.
erikwright@chromium.orgc6dea6b2014-04-30 18:06:041181 for (std::vector<TemplateURLData>::const_iterator i =
1182 actions.added_engines.begin();
1183 i < actions.added_engines.end();
1184 ++i) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:331185 Add(std::make_unique<TemplateURL>(*i));
erikwright@chromium.orgc6dea6b2014-04-30 18:06:041186 }
vasilii@chromium.org4a40facd2013-05-29 14:44:561187
erikwright@chromium.orga80ec962014-05-12 00:55:381188 base::AutoReset<DefaultSearchChangeOrigin> change_origin(
1189 &dsp_change_origin_, DSP_CHANGE_PROFILE_RESET);
1190
1191 default_search_manager_.ClearUserSelectedDefaultSearchEngine();
1192
pkastinga078dc8d2017-04-07 18:55:501193 if (default_search_provider_) {
a-v-y02d3f50b2017-02-16 12:03:551194 // Set fallback engine as user selected, because repair is considered a user
1195 // action and we are expected to sync new fallback engine to other devices.
1196 const TemplateURLData* fallback_engine_data =
1197 default_search_manager_.GetFallbackSearchEngine();
1198 if (fallback_engine_data) {
1199 TemplateURL* fallback_engine =
1200 FindPrepopulatedTemplateURL(fallback_engine_data->prepopulate_id);
1201 // The fallback engine is created from built-in/override data that should
1202 // always have a prepopulate ID, so this engine should always exist after
a-v-y7084ddf2017-03-09 13:15:481203 // a repair.
a-v-y02d3f50b2017-02-16 12:03:551204 DCHECK(fallback_engine);
1205 // Write the fallback engine's GUID to prefs, which will cause
Jack Yammineabb0ba22023-07-20 10:46:341206 // OnDefaultSearchProviderGUIDChanged() to set it as the new
a-v-y02d3f50b2017-02-16 12:03:551207 // user-selected engine.
Jack Yammineabb0ba22023-07-20 10:46:341208 SetDefaultSearchProviderPrefValue(*prefs_, fallback_engine->sync_guid());
a-v-y02d3f50b2017-02-16 12:03:551209 }
pkastinga078dc8d2017-04-07 18:55:501210 } else {
1211 // If the default search provider came from a user pref we would have been
1212 // notified of the new (fallback-provided) value in
1213 // ClearUserSelectedDefaultSearchEngine() above. Since we are here, the
1214 // value was presumably originally a fallback value (which may have been
1215 // repaired).
1216 DefaultSearchManager::Source source;
1217 const TemplateURLData* new_dse =
1218 default_search_manager_.GetDefaultSearchEngine(&source);
pkastinga078dc8d2017-04-07 18:55:501219 ApplyDefaultSearchChange(new_dse, source);
vasilii@chromium.orge937a9702013-10-03 22:59:431220 }
vasilii@chromium.org4a40facd2013-05-29 14:44:561221}
1222
Angela Yoeurng54419ec62022-06-07 18:00:011223void TemplateURLService::RepairStarterPackEngines() {
1224 DCHECK(loaded());
1225
1226 Scoper scoper(this);
1227
1228 std::vector<std::unique_ptr<TemplateURLData>> starter_pack_engines =
1229 TemplateURLStarterPackData::GetStarterPackEngines();
1230 DCHECK(!starter_pack_engines.empty());
1231 ActionsFromCurrentData actions(CreateActionsFromCurrentStarterPackData(
1232 &starter_pack_engines, template_urls_));
1233
1234 // Remove items.
1235 for (auto i = actions.removed_engines.begin();
1236 i < actions.removed_engines.end(); ++i) {
1237 Remove(*i);
1238 }
1239
1240 // Edit items.
1241 for (auto i(actions.edited_engines.begin()); i < actions.edited_engines.end();
1242 ++i) {
1243 Update(i->first, TemplateURL(i->second));
1244 }
1245
1246 // Add items.
1247 for (std::vector<TemplateURLData>::const_iterator i =
1248 actions.added_engines.begin();
1249 i < actions.added_engines.end(); ++i) {
1250 Add(std::make_unique<TemplateURL>(*i));
1251 }
1252}
1253
erg@google.com8e5c89a2011-06-07 18:13:331254void TemplateURLService::AddObserver(TemplateURLServiceObserver* observer) {
ben@chromium.orgd82443b2009-01-15 19:54:561255 model_observers_.AddObserver(observer);
1256}
1257
erg@google.com8e5c89a2011-06-07 18:13:331258void TemplateURLService::RemoveObserver(TemplateURLServiceObserver* observer) {
ben@chromium.orgd82443b2009-01-15 19:54:561259 model_observers_.RemoveObserver(observer);
1260}
1261
erg@google.com8e5c89a2011-06-07 18:13:331262void TemplateURLService::Load() {
blundell1c4f5c42015-12-14 11:28:171263 if (loaded_ || load_handle_ || disable_load_)
ben@chromium.orgd82443b2009-01-15 19:54:561264 return;
1265
Zinovy Nis701103b2018-05-10 22:59:381266 if (web_data_service_)
hashimoto@chromium.org37b324602014-07-02 07:30:491267 load_handle_ = web_data_service_->GetKeywords(this);
erikwright@chromium.orga80ec962014-05-12 00:55:381268 else
levin@chromium.orgd8522742010-08-27 17:55:441269 ChangeToLoadedState();
ben@chromium.orgd82443b2009-01-15 19:54:561270}
1271
Peter Kasting7ba9440c2020-11-22 01:49:021272base::CallbackListSubscription TemplateURLService::RegisterOnLoadedCallback(
Ella Ge2ee935352020-12-22 09:44:371273 base::OnceClosure callback) {
Peter Kasting7ba9440c2020-11-22 01:49:021274 return loaded_ ? base::CallbackListSubscription()
Ella Ge2ee935352020-12-22 09:44:371275 : on_loaded_callbacks_.Add(std::move(callback));
caitkp@chromium.org3118d342013-10-08 17:29:031276}
1277
Angela Yoeurnga79210e2022-08-23 19:59:461278void TemplateURLService::EmitTemplateURLActiveOnStartupHistogram(
1279 OwnedTemplateURLVector* template_urls) {
1280 DCHECK(template_urls);
1281
1282 for (auto& turl : *template_urls) {
1283 std::string histogram_name = kKeywordModeUsageByEngineTypeHistogramName;
1284 histogram_name.append(
1285 (turl->is_active() == TemplateURLData::ActiveStatus::kTrue)
1286 ? ".ActiveOnStartup"
1287 : ".InactiveOnStartup");
1288 base::UmaHistogramEnumeration(
1289 histogram_name, turl->GetBuiltinEngineType(),
1290 BuiltinEngineType::KEYWORD_MODE_ENGINE_TYPE_MAX);
1291 }
1292}
1293
erg@google.com8e5c89a2011-06-07 18:13:331294void TemplateURLService::OnWebDataServiceRequestDone(
hashimoto@chromium.org37b324602014-07-02 07:30:491295 KeywordWebDataService::Handle h,
avi1ce1e6002016-10-19 17:25:531296 std::unique_ptr<WDTypedResult> result) {
ben@chromium.orgd82443b2009-01-15 19:54:561297 // Reset the load_handle so that we don't try and cancel the load in
1298 // the destructor.
1299 load_handle_ = 0;
1300
1301 if (!result) {
sky@chromium.orgb3b2d6e2009-12-18 00:19:441302 // Results are null if the database went away or (most likely) wasn't
1303 // loaded.
sky@chromium.orgb3b2d6e2009-12-18 00:19:441304 load_failed_ = true;
avi8a64b715b2016-09-02 17:30:041305 web_data_service_ = nullptr;
levin@chromium.orgd8522742010-08-27 17:55:441306 ChangeToLoadedState();
ben@chromium.orgd82443b2009-01-15 19:54:561307 return;
1308 }
1309
avi8a64b715b2016-09-02 17:30:041310 std::unique_ptr<OwnedTemplateURLVector> template_urls =
Jinho Bangfa6b5752018-01-03 20:52:191311 std::make_unique<OwnedTemplateURLVector>();
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551312 WDKeywordsResult::Metadata updated_keywords_metadata;
robliao66fe8b192015-03-13 01:29:181313 {
robliao66fe8b192015-03-13 01:29:181314 GetSearchProvidersUsingKeywordResult(
Boris Sazonov2b77815a2024-01-17 15:35:041315 *result, web_data_service_.get(), prefs_, search_engine_choice_service_,
1316 template_urls.get(),
robliao66fe8b192015-03-13 01:29:181317 (default_search_provider_source_ == DefaultSearchManager::FROM_USER)
Fabio Tirelo07ea7dd2023-12-06 02:13:441318 ? pre_loading_providers_->default_search_provider()
avi8a64b715b2016-09-02 17:30:041319 : nullptr,
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551320 search_terms_data(), updated_keywords_metadata, &pre_sync_deletes_);
robliao66fe8b192015-03-13 01:29:181321 }
ben@chromium.orgd82443b2009-01-15 19:54:561322
Esmael El-Moslimanyb4306e02018-04-18 21:24:331323 Scoper scoper(this);
pkasting@chromium.orgad94ea32014-04-02 01:40:471324
robliao66fe8b192015-03-13 01:29:181325 {
avi8a64b715b2016-09-02 17:30:041326 PatchMissingSyncGUIDs(template_urls.get());
Angela Yoeurng7ff86ed52021-08-12 20:03:241327 MaybeSetIsActiveSearchEngines(template_urls.get());
Angela Yoeurnga79210e2022-08-23 19:59:461328 EmitTemplateURLActiveOnStartupHistogram(template_urls.get());
avi8a64b715b2016-09-02 17:30:041329 SetTemplateURLs(std::move(template_urls));
robliao66fe8b192015-03-13 01:29:181330
robliao66fe8b192015-03-13 01:29:181331 // This initializes provider_map_ which should be done before
1332 // calling UpdateKeywordSearchTermsForURL.
robliao66fe8b192015-03-13 01:29:181333 ChangeToLoadedState();
1334
robliao66fe8b192015-03-13 01:29:181335 // Index any visits that occurred before we finished loading.
1336 for (size_t i = 0; i < visits_to_add_.size(); ++i)
1337 UpdateKeywordSearchTermsForURL(visits_to_add_[i]);
1338 visits_to_add_.clear();
1339
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551340 if (updated_keywords_metadata.HasBuiltinKeywordData()) {
Nicolas Dossou-Gbete354c10a52024-01-22 11:18:371341 web_data_service_->SetBuiltinKeywordDataVersion(
1342 updated_keywords_metadata.builtin_keyword_data_version);
1343 web_data_service_->SetBuiltinKeywordMilestone(
1344 updated_keywords_metadata.builtin_keyword_milestone);
1345 web_data_service_->SetBuiltinKeywordCountry(
1346 updated_keywords_metadata.builtin_keyword_country);
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551347 }
Angela Yoeurngd8e54932022-04-27 23:53:161348
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551349 if (updated_keywords_metadata.HasStarterPackData()) {
Angela Yoeurngd8e54932022-04-27 23:53:161350 web_data_service_->SetStarterPackKeywordVersion(
Nicolas Dossou-Gbeteaf259e72024-01-19 20:53:551351 updated_keywords_metadata.starter_pack_version);
1352 }
robliao66fe8b192015-03-13 01:29:181353 }
caitkp@chromium.orgb504a8e2014-05-22 12:34:131354
1355 if (default_search_provider_) {
Fabio Tirelob8f43492023-07-18 20:28:241356 SearchEngineType engine_type =
1357 default_search_provider_->GetEngineType(search_terms_data());
1358 base::UmaHistogramEnumeration("Search.DefaultSearchProviderType2",
1359 engine_type, SEARCH_ENGINE_MAX);
Fabio Tirelo6e6df8bf2023-11-22 17:00:151360 if (default_search_provider_->created_by_policy() ==
1361 TemplateURLData::CreatedByPolicy::kDefaultSearchProvider) {
Fabio Tirelob8f43492023-07-18 20:28:241362 base::UmaHistogramEnumeration(
1363 "Search.DefaultSearchProviderType2.SetByEnterprisePolicy",
1364 engine_type, SEARCH_ENGINE_MAX);
1365 }
caitkp@chromium.orgb504a8e2014-05-22 12:34:131366 }
ben@chromium.orgd82443b2009-01-15 19:54:561367}
1368
Jan Wilken Dörriefa241ba2021-03-11 17:57:011369std::u16string TemplateURLService::GetKeywordShortName(
1370 const std::u16string& keyword,
Angela Yoeurng9c589242024-02-15 05:33:081371 bool* is_omnibox_api_extension_keyword,
1372 bool* is_ask_google_keyword) const {
jeffschiller612137a2017-04-19 04:34:241373 const TemplateURL* template_url = GetTemplateURLForKeyword(keyword);
mpcomplete@chromium.org56ad3792010-05-28 17:45:331374
erg@google.com8e5c89a2011-06-07 18:13:331375 // TODO(sky): Once LocationBarView adds a listener to the TemplateURLService
mpcomplete@chromium.org56ad3792010-05-28 17:45:331376 // to track changes to the model, this should become a DCHECK.
1377 if (template_url) {
Angela Yoeurng9c589242024-02-15 05:33:081378 *is_ask_google_keyword = template_url->starter_pack_id() ==
1379 TemplateURLStarterPackData::kAskGoogle;
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251380 *is_omnibox_api_extension_keyword =
ianwena27b1df2016-09-23 23:08:231381 template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION;
mpcomplete@chromium.org56ad3792010-05-28 17:45:331382 return template_url->AdjustedShortNameForLocaleDirection();
1383 }
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251384 *is_omnibox_api_extension_keyword = false;
Jan Wilken Dörriefa241ba2021-03-11 17:57:011385 return std::u16string();
mpcomplete@chromium.org56ad3792010-05-28 17:45:331386}
1387
hashimoto@chromium.org7e310422014-07-03 12:44:451388void TemplateURLService::OnHistoryURLVisited(const URLVisitedDetails& details) {
blundell@chromium.orga594a012014-05-17 19:13:161389 if (!loaded_)
hashimoto@chromium.org7e310422014-07-03 12:44:451390 visits_to_add_.push_back(details);
blundell@chromium.orga594a012014-05-17 19:13:161391 else
hashimoto@chromium.org7e310422014-07-03 12:44:451392 UpdateKeywordSearchTermsForURL(details);
ben@chromium.orgd82443b2009-01-15 19:54:561393}
1394
caitkp@google.comef0c3ac2013-04-16 18:05:591395void TemplateURLService::Shutdown() {
treib9f5ce2d42017-04-13 09:08:031396 for (auto& observer : model_observers_)
1397 observer.OnTemplateURLServiceShuttingDown();
1398
sdefresnebb1e5752014-10-20 12:31:171399 if (client_)
1400 client_->Shutdown();
caitkp@google.comef0c3ac2013-04-16 18:05:591401 // This check has to be done at Shutdown() instead of in the dtor to ensure
hashimoto@chromium.org37b324602014-07-02 07:30:491402 // that no clients of KeywordWebDataService are holding ptrs to it after the
1403 // first phase of the KeyedService Shutdown() process.
caitkp@google.comef0c3ac2013-04-16 18:05:591404 if (load_handle_) {
Zinovy Nis701103b2018-05-10 22:59:381405 DCHECK(web_data_service_);
hashimoto@chromium.org37b324602014-07-02 07:30:491406 web_data_service_->CancelRequest(load_handle_);
caitkp@google.comef0c3ac2013-04-16 18:05:591407 }
avi8a64b715b2016-09-02 17:30:041408 web_data_service_ = nullptr;
caitkp@google.comef0c3ac2013-04-16 18:05:591409}
1410
Mikel Astiz095de452019-03-11 06:29:581411void TemplateURLService::WaitUntilReadyToSync(base::OnceClosure done) {
Mikel Astiz5c49e652019-04-02 19:53:561412 DCHECK(!on_loaded_callback_for_sync_);
1413
Mikel Astiz095de452019-03-11 06:29:581414 // We force a load here to allow remote updates to be processed, without
1415 // waiting for the lazy load.
1416 Load();
1417
1418 if (loaded_)
1419 std::move(done).Run();
1420 else
1421 on_loaded_callback_for_sync_ = std::move(done);
1422}
1423
akalin@chromium.org65f173552012-06-28 22:43:581424syncer::SyncDataList TemplateURLService::GetAllSyncData(
akalin@chromium.orga4a147652012-07-03 23:41:321425 syncer::ModelType type) const {
1426 DCHECK_EQ(syncer::SEARCH_ENGINES, type);
stevet@chromium.orgd88cb202011-08-17 20:03:011427
akalin@chromium.org65f173552012-06-28 22:43:581428 syncer::SyncDataList current_data;
avi8a64b715b2016-09-02 17:30:041429 for (const auto& turl : template_urls_) {
stevet@chromium.orgc4296fa2011-10-15 00:18:071430 // We don't sync keywords managed by policy.
Fabio Tirelo6e6df8bf2023-11-22 17:00:151431 if (turl->created_by_policy() !=
1432 TemplateURLData::CreatedByPolicy::kNoPolicy) {
stevet@chromium.orgc4296fa2011-10-15 00:18:071433 continue;
Fabio Tirelo6e6df8bf2023-11-22 17:00:151434 }
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251435 // We don't sync extension-controlled search engines.
ianwena27b1df2016-09-23 23:08:231436 if (turl->type() != TemplateURL::NORMAL)
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251437 continue;
avi8a64b715b2016-09-02 17:30:041438 current_data.push_back(CreateSyncDataFromTemplateURL(*turl));
stevet@chromium.orgd88cb202011-08-17 20:03:011439 }
1440
1441 return current_data;
1442}
1443
Arthur Sonzognic571efb2024-01-26 20:26:181444std::optional<syncer::ModelError> TemplateURLService::ProcessSyncChanges(
Brett Wilson190fd2ac2017-09-12 05:04:301445 const base::Location& from_here,
akalin@chromium.org65f173552012-06-28 22:43:581446 const syncer::SyncChangeList& change_list) {
stevet@chromium.orgd88cb202011-08-17 20:03:011447 if (!models_associated_) {
Victor Hugo Vianna Silvad591a082020-04-16 10:56:361448 return syncer::ModelError(FROM_HERE, "Models not yet associated.");
stevet@chromium.orgd88cb202011-08-17 20:03:011449 }
pkasting@chromium.org7b596f62012-05-08 01:34:251450 DCHECK(loaded_);
stevet@chromium.orgd88cb202011-08-17 20:03:011451
dbeam@chromium.org997ec9f2012-11-21 04:44:141452 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
Esmael El-Moslimanyb4306e02018-04-18 21:24:331453
1454 Scoper scoper(this);
stevet@chromium.orgd88cb202011-08-17 20:03:011455
stevet@chromium.org7124eee2012-06-13 22:04:081456 // We've started syncing, so set our origin member to the base Sync value.
1457 // As we move through Sync Code, we may set this to increasingly specific
1458 // origins so we can tell what exactly caused a DSP change.
Peter Kastingbef7bc522021-09-08 17:53:301459 base::AutoReset<DefaultSearchChangeOrigin> change_origin_unintentional(
1460 &dsp_change_origin_, DSP_CHANGE_SYNC_UNINTENTIONAL);
stevet@chromium.org7124eee2012-06-13 22:04:081461
akalin@chromium.org65f173552012-06-28 22:43:581462 syncer::SyncChangeList new_changes;
Arthur Sonzognic571efb2024-01-26 20:26:181463 std::optional<syncer::ModelError> error;
jdoerrie3feb1852018-10-05 12:16:441464 for (auto iter = change_list.begin(); iter != change_list.end(); ++iter) {
akalin@chromium.orga4a147652012-07-03 23:41:321465 DCHECK_EQ(syncer::SEARCH_ENGINES, iter->sync_data().GetDataType());
stevet@chromium.orgd88cb202011-08-17 20:03:011466
Peter Kastingebaac2972020-04-10 23:54:411467 TemplateURL* existing_turl = GetTemplateURLForGUID(
1468 iter->sync_data().GetSpecifics().search_engine().sync_guid());
1469 std::unique_ptr<TemplateURL> turl =
dchengd967d9502016-04-21 22:36:511470 CreateTemplateURLFromTemplateURLAndSyncData(
Boris Sazonov2b77815a2024-01-17 15:35:041471 client_.get(), prefs_, search_engine_choice_service_,
1472 search_terms_data(), existing_turl, iter->sync_data(),
1473 &new_changes);
Zinovy Nis701103b2018-05-10 22:59:381474 if (!turl)
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081475 continue;
stevet@chromium.orgd88cb202011-08-17 20:03:011476
Peter Kastingebaac2972020-04-10 23:54:411477 const std::string error_msg =
1478 "ProcessSyncChanges failed on ChangeType " +
1479 syncer::SyncChange::ChangeTypeToString(iter->change_type());
Tommy Li9c7646a2020-10-28 00:47:491480 if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
1481 if (!existing_turl) {
Tommy C. Li8de3d172023-06-01 19:36:051482 // Can't DELETE a non-existent engine.
Marc Treib7bf627d2023-01-18 18:17:561483 error = syncer::ModelError(FROM_HERE, error_msg);
Tommy Li9c7646a2020-10-28 00:47:491484 continue;
1485 }
1486
1487 // We can get an ACTION_DELETE for the default search provider if the user
1488 // has changed the default search provider on a different machine, and we
1489 // get the search engine update before the preference update.
1490 //
Rushan Suleymanovb25693f2023-09-22 15:21:321491 // In this case, postpone the delete, because we never want to reset the
Tommy Li9c7646a2020-10-28 00:47:491492 // default search provider as a result of ACTION_DELETE. If the preference
Rushan Suleymanovb25693f2023-09-22 15:21:321493 // update arrives later, the engine will be removed. We still may be stuck
1494 // with an extra search engine entry in the edge case (due to storing the
1495 // deletion in memory), but it's better than most alternatives.
Tommy Li9c7646a2020-10-28 00:47:491496 //
1497 // In the past, we tried re-creating the deleted TemplateURL, but it was
1498 // likely a source of duplicate search engine entries. crbug.com/1022775
1499 if (existing_turl != GetDefaultSearchProvider()) {
1500 Remove(existing_turl);
Rushan Suleymanovb25693f2023-09-22 15:21:321501 } else {
1502 postponed_deleted_default_engine_guid_ = existing_turl->sync_guid();
Tommy Li9c7646a2020-10-28 00:47:491503 }
Peter Kastingebaac2972020-04-10 23:54:411504 continue;
1505 }
1506
Tommy Li81a1da02020-11-30 22:14:071507 // Because TemplateURLService sometimes ignores remote Sync changes which
1508 // we cannot cleanly apply, we need to handle ADD and UPDATE together.
1509 // Ignore what the other Sync layers THINK the change type is. Instead:
1510 // If we have an existing engine, treat as an update.
1511 DCHECK(iter->change_type() == syncer::SyncChange::ACTION_ADD ||
1512 iter->change_type() == syncer::SyncChange::ACTION_UPDATE);
stevet@chromium.orgc50d76c2012-07-10 23:17:581513
Tommy Li81a1da02020-11-30 22:14:071514 if (!existing_turl) {
Peter Kastingbef7bc522021-09-08 17:53:301515 base::AutoReset<DefaultSearchChangeOrigin> change_origin_add(
erikwright@chromium.orga80ec962014-05-12 00:55:381516 &dsp_change_origin_, DSP_CHANGE_SYNC_ADD);
stevet@chromium.org7d574f0b2012-08-14 21:32:511517 // Force the local ID to kInvalidTemplateURLID so we can add it.
1518 TemplateURLData data(turl->data());
1519 data.id = kInvalidTemplateURLID;
Peter Kastingebaac2972020-04-10 23:54:411520
Tommy Li81a1da02020-11-30 22:14:071521 TemplateURL* added = Add(std::make_unique<TemplateURL>(data));
1522 if (added) {
1523 MaybeUpdateDSEViaPrefs(added);
Tommy Li81a1da02020-11-30 22:14:071524 }
1525 } else {
Tommy Li81a1da02020-11-30 22:14:071526 // Since we've already found |existing_turl| by GUID, this Update() should
1527 // always return true, but we still don't want to crash if it fails.
1528 DCHECK(existing_turl);
1529 bool update_success = Update(existing_turl, *turl);
1530 DCHECK(update_success);
1531
Peter Kastingebaac2972020-04-10 23:54:411532 MaybeUpdateDSEViaPrefs(existing_turl);
Tommy Li81a1da02020-11-30 22:14:071533 }
stevet@chromium.orgd88cb202011-08-17 20:03:011534 }
1535
stevet@chromium.orgdeac42902011-09-03 23:05:561536 // If something went wrong, we want to prematurely exit to avoid pushing
1537 // inconsistent data to Sync. We return the last error we received.
Marc Treib7bf627d2023-01-18 18:17:561538 if (error) {
1539 return error;
1540 }
stevet@chromium.orgd88cb202011-08-17 20:03:011541
Victor Hugo Vianna Silvad591a082020-04-16 10:56:361542 return sync_processor_->ProcessSyncChanges(from_here, new_changes);
stevet@chromium.orgd88cb202011-08-17 20:03:011543}
1544
David Bertoni724339a802024-01-04 23:50:281545base::WeakPtr<syncer::SyncableService> TemplateURLService::AsWeakPtr() {
1546 return weak_ptr_factory_.GetWeakPtr();
1547}
1548
Arthur Sonzognic571efb2024-01-26 20:26:181549std::optional<syncer::ModelError> TemplateURLService::MergeDataAndStartSyncing(
akalin@chromium.orga4a147652012-07-03 23:41:321550 syncer::ModelType type,
akalin@chromium.org65f173552012-06-28 22:43:581551 const syncer::SyncDataList& initial_sync_data,
Marc Treib7bf627d2023-01-18 18:17:561552 std::unique_ptr<syncer::SyncChangeProcessor> sync_processor) {
pkasting@chromium.org7b596f62012-05-08 01:34:251553 DCHECK(loaded_);
akalin@chromium.orga4a147652012-07-03 23:41:321554 DCHECK_EQ(type, syncer::SEARCH_ENGINES);
Zinovy Nis701103b2018-05-10 22:59:381555 DCHECK(!sync_processor_);
1556 DCHECK(sync_processor);
erikwright@chromium.orgc4b2af22014-05-11 19:48:531557
1558 // Disable sync if we failed to load.
1559 if (load_failed_) {
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:421560 return syncer::ModelError(FROM_HERE, "Local database load failed.");
erikwright@chromium.orgc4b2af22014-05-11 19:48:531561 }
1562
dcheng51ace48a2015-12-26 22:45:171563 sync_processor_ = std::move(sync_processor);
stevet@chromium.orgd88cb202011-08-17 20:03:011564
1565 // We do a lot of calls to Add/Remove/ResetTemplateURL here, so ensure we
1566 // don't step on our own toes.
dbeam@chromium.org997ec9f2012-11-21 04:44:141567 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
Esmael El-Moslimanyb4306e02018-04-18 21:24:331568
1569 Scoper scoper(this);
stevet@chromium.orgd88cb202011-08-17 20:03:011570
stevet@chromium.org7124eee2012-06-13 22:04:081571 // We've started syncing, so set our origin member to the base Sync value.
1572 // As we move through Sync Code, we may set this to increasingly specific
1573 // origins so we can tell what exactly caused a DSP change.
dbeam@chromium.org997ec9f2012-11-21 04:44:141574 base::AutoReset<DefaultSearchChangeOrigin> change_origin(&dsp_change_origin_,
stevet@chromium.org7124eee2012-06-13 22:04:081575 DSP_CHANGE_SYNC_UNINTENTIONAL);
1576
akalin@chromium.org65f173552012-06-28 22:43:581577 syncer::SyncChangeList new_changes;
stevet@chromium.orgd88cb202011-08-17 20:03:011578
akalin@chromium.org65f173552012-06-28 22:43:581579 // Build maps of our sync GUIDs to syncer::SyncData.
stevet@chromium.orgd88cb202011-08-17 20:03:011580 SyncDataMap local_data_map = CreateGUIDToSyncDataMap(
akalin@chromium.orga4a147652012-07-03 23:41:321581 GetAllSyncData(syncer::SEARCH_ENGINES));
stevet@chromium.orgd88cb202011-08-17 20:03:011582 SyncDataMap sync_data_map = CreateGUIDToSyncDataMap(initial_sync_data);
1583
1584 for (SyncDataMap::const_iterator iter = sync_data_map.begin();
1585 iter != sync_data_map.end(); ++iter) {
pkasting@chromium.org3613347d2012-04-27 20:27:371586 TemplateURL* local_turl = GetTemplateURLForGUID(iter->first);
dchengd967d9502016-04-21 22:36:511587 std::unique_ptr<TemplateURL> sync_turl(
hashimoto@chromium.org20c0fb302014-06-28 02:04:121588 CreateTemplateURLFromTemplateURLAndSyncData(
Boris Sazonov2b77815a2024-01-17 15:35:041589 client_.get(), prefs_, search_engine_choice_service_,
1590 search_terms_data(), local_turl, iter->second, &new_changes));
Zinovy Nis701103b2018-05-10 22:59:381591 if (!sync_turl)
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081592 continue;
stevet@chromium.orgd88cb202011-08-17 20:03:011593
stevet@chromium.org2eff6b12012-05-16 20:07:051594 if (pre_sync_deletes_.find(sync_turl->sync_guid()) !=
1595 pre_sync_deletes_.end()) {
1596 // This entry was deleted before the initial sync began (possibly through
1597 // preprocessing in TemplateURLService's loading code). Ignore it and send
1598 // an ACTION_DELETE up to the server.
akalin@chromium.org65f173552012-06-28 22:43:581599 new_changes.push_back(
zea@chromium.orgb78170f2012-07-11 03:34:261600 syncer::SyncChange(FROM_HERE,
1601 syncer::SyncChange::ACTION_DELETE,
akalin@chromium.org65f173552012-06-28 22:43:581602 iter->second));
stevet@chromium.orgc3dd7892012-08-27 21:28:281603 UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName,
1604 DELETE_ENGINE_PRE_SYNC, DELETE_ENGINE_MAX);
stevet@chromium.org2eff6b12012-05-16 20:07:051605 continue;
1606 }
1607
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081608 if (local_turl) {
stevet@chromium.org7d574f0b2012-08-14 21:32:511609 DCHECK(IsFromSync(local_turl, sync_data_map));
stevet@chromium.orgd88cb202011-08-17 20:03:011610 // This local search engine is already synced. If the timestamp differs
1611 // from Sync, we need to update locally or to the cloud. Note that if the
1612 // timestamps are equal, we touch neither.
1613 if (sync_turl->last_modified() > local_turl->last_modified()) {
stevet@chromium.org5a0082632012-01-25 20:08:451614 // We've received an update from Sync. We should replace all synced
1615 // fields in the local TemplateURL. Note that this includes the
1616 // TemplateURLID and the TemplateURL may have to be reparsed. This
stevet@chromium.orgdeac42902011-09-03 23:05:561617 // also makes the local data's last_modified timestamp equal to Sync's,
1618 // avoiding an Update on the next MergeData call.
Esmael El-Moslimanyb4306e02018-04-18 21:24:331619 Update(local_turl, *sync_turl);
stevet@chromium.orgd88cb202011-08-17 20:03:011620 } else if (sync_turl->last_modified() < local_turl->last_modified()) {
stevet@chromium.orgdeac42902011-09-03 23:05:561621 // Otherwise, we know we have newer data, so update Sync with our
1622 // data fields.
akalin@chromium.orgcb02f612012-06-27 03:15:501623 new_changes.push_back(
zea@chromium.orgb78170f2012-07-11 03:34:261624 syncer::SyncChange(FROM_HERE,
1625 syncer::SyncChange::ACTION_UPDATE,
1626 local_data_map[local_turl->sync_guid()]));
stevet@chromium.orgd88cb202011-08-17 20:03:011627 }
1628 local_data_map.erase(iter->first);
1629 } else {
stevet@chromium.org7d574f0b2012-08-14 21:32:511630 // The search engine from the cloud has not been synced locally. Merge it
1631 // into our local model. This will handle any conflicts with local (and
1632 // already-synced) TemplateURLs. It will prefer to keep entries from Sync
1633 // over not-yet-synced TemplateURLs.
Esmael El-Moslimanyb4306e02018-04-18 21:24:331634 MergeInSyncTemplateURL(sync_turl.get(), sync_data_map, &new_changes,
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:421635 &local_data_map);
stevet@chromium.orgd88cb202011-08-17 20:03:011636 }
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081637 }
stevet@chromium.orgd88cb202011-08-17 20:03:011638
Sky Maliceab7ec092017-11-16 19:24:571639
stevet@chromium.orgd88cb202011-08-17 20:03:011640 // The remaining SyncData in local_data_map should be everything that needs to
1641 // be pushed as ADDs to sync.
1642 for (SyncDataMap::const_iterator iter = local_data_map.begin();
1643 iter != local_data_map.end(); ++iter) {
akalin@chromium.orgcb02f612012-06-27 03:15:501644 new_changes.push_back(
zea@chromium.orgb78170f2012-07-11 03:34:261645 syncer::SyncChange(FROM_HERE,
1646 syncer::SyncChange::ACTION_ADD,
1647 iter->second));
stevet@chromium.orgd88cb202011-08-17 20:03:011648 }
1649
stevet@chromium.org612ee4942012-05-14 23:55:541650 // Do some post-processing on the change list to ensure that we are sending
1651 // valid changes to sync_processor_.
1652 PruneSyncChanges(&sync_data_map, &new_changes);
pkasting@chromium.org7b596f62012-05-08 01:34:251653
stevet@chromium.org78a857c2012-08-21 20:31:481654 LogDuplicatesHistogram(GetTemplateURLs());
Arthur Sonzognic571efb2024-01-26 20:26:181655 std::optional<syncer::ModelError> error =
Victor Hugo Vianna Silvad591a082020-04-16 10:56:361656 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes);
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:421657 if (!error.has_value()) {
1658 // The ACTION_DELETEs from this set are processed. Empty it so we don't try
1659 // to reuse them on the next call to MergeDataAndStartSyncing.
1660 pre_sync_deletes_.clear();
stevet@chromium.orgd88cb202011-08-17 20:03:011661
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:421662 models_associated_ = true;
1663 }
stevet@chromium.org2eff6b12012-05-16 20:07:051664
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:421665 return error;
stevet@chromium.orgd88cb202011-08-17 20:03:011666}
1667
akalin@chromium.orga4a147652012-07-03 23:41:321668void TemplateURLService::StopSyncing(syncer::ModelType type) {
1669 DCHECK_EQ(type, syncer::SEARCH_ENGINES);
stevet@chromium.orgd88cb202011-08-17 20:03:011670 models_associated_ = false;
zea@chromium.org0677c6952012-03-23 21:59:001671 sync_processor_.reset();
stevet@chromium.orgd88cb202011-08-17 20:03:011672}
1673
1674void TemplateURLService::ProcessTemplateURLChange(
Brett Wilson190fd2ac2017-09-12 05:04:301675 const base::Location& from_here,
stevet@chromium.orgd88cb202011-08-17 20:03:011676 const TemplateURL* turl,
akalin@chromium.org65f173552012-06-28 22:43:581677 syncer::SyncChange::SyncChangeType type) {
stevet@chromium.orgd88cb202011-08-17 20:03:011678 DCHECK(turl);
1679
1680 if (!models_associated_)
1681 return; // Not syncing.
1682
1683 if (processing_syncer_changes_)
1684 return; // These are changes originating from us. Ignore.
1685
stevet@chromium.orgc4296fa2011-10-15 00:18:071686 // Avoid syncing keywords managed by policy.
Fabio Tirelo6e6df8bf2023-11-22 17:00:151687 if (turl->created_by_policy() !=
1688 TemplateURLData::CreatedByPolicy::kNoPolicy) {
stevet@chromium.orgc4296fa2011-10-15 00:18:071689 return;
Fabio Tirelo6e6df8bf2023-11-22 17:00:151690 }
stevet@chromium.orgc4296fa2011-10-15 00:18:071691
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251692 // Avoid syncing extension-controlled search engines.
ianwena27b1df2016-09-23 23:08:231693 if (turl->type() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION)
vasilii@chromium.orgbdcbcd82013-10-28 13:40:251694 return;
1695
akalin@chromium.org65f173552012-06-28 22:43:581696 syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(*turl);
Sky Maliceab7ec092017-11-16 19:24:571697 syncer::SyncChangeList changes = {
1698 syncer::SyncChange(from_here, type, sync_data)};
stevet@chromium.orgd88cb202011-08-17 20:03:011699 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
1700}
1701
Xi Han1f4c1c5e2023-10-20 00:29:511702bool TemplateURLService::IsEeaChoiceCountry() {
1703 return search_engines::IsEeaChoiceCountry(
Boris Sazonov2b77815a2024-01-17 15:35:041704 search_engine_choice_service_->GetCountryId());
Xi Han1f4c1c5e2023-10-20 00:29:511705}
1706
Nicolas Dossou-Gbete409217c2024-01-09 13:18:361707#if BUILDFLAG(IS_ANDROID)
1708bool TemplateURLService::ShouldShowUpdatedSettings() {
Boris Sazonov2b77815a2024-01-17 15:35:041709 return search_engine_choice_service_->ShouldShowUpdatedSettings();
Nicolas Dossou-Gbete409217c2024-01-09 13:18:361710}
1711#endif
1712
manukh873036392020-11-09 17:23:291713std::string TemplateURLService::GetSessionToken() {
1714 base::TimeTicks current_time(base::TimeTicks::Now());
1715 // Renew token if it expired.
1716 if (current_time > token_expiration_time_) {
1717 const size_t kTokenBytes = 12;
1718 std::string raw_data;
1719 base::RandBytes(base::WriteInto(&raw_data, kTokenBytes + 1), kTokenBytes);
1720 base::Base64UrlEncode(raw_data,
1721 base::Base64UrlEncodePolicy::INCLUDE_PADDING,
1722 &current_token_);
1723 }
1724
1725 // Extend expiration time another 60 seconds.
Peter Kastinge5a38ed2021-10-02 03:06:351726 token_expiration_time_ = current_time + base::Seconds(60);
manukh873036392020-11-09 17:23:291727 return current_token_;
1728}
1729
1730void TemplateURLService::ClearSessionToken() {
1731 token_expiration_time_ = base::TimeTicks();
1732}
1733
sky@chromium.org7e03e812010-11-15 23:01:011734// static
Angela Yoeurng7ff86ed52021-08-12 20:03:241735TemplateURLData::ActiveStatus TemplateURLService::ActiveStatusFromSync(
1736 sync_pb::SearchEngineSpecifics_ActiveStatus is_active) {
1737 switch (is_active) {
1738 case sync_pb::SearchEngineSpecifics_ActiveStatus::
1739 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_UNSPECIFIED:
1740 return TemplateURLData::ActiveStatus::kUnspecified;
1741 case sync_pb::SearchEngineSpecifics_ActiveStatus::
1742 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_TRUE:
1743 return TemplateURLData::ActiveStatus::kTrue;
1744 case sync_pb::SearchEngineSpecifics_ActiveStatus::
1745 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_FALSE:
1746 return TemplateURLData::ActiveStatus::kFalse;
1747 }
1748}
1749
1750// static
1751sync_pb::SearchEngineSpecifics_ActiveStatus
1752TemplateURLService::ActiveStatusToSync(
1753 TemplateURLData::ActiveStatus is_active) {
1754 switch (is_active) {
1755 case TemplateURLData::ActiveStatus::kUnspecified:
1756 return sync_pb::SearchEngineSpecifics_ActiveStatus::
1757 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_UNSPECIFIED;
1758 case TemplateURLData::ActiveStatus::kTrue:
1759 return sync_pb::SearchEngineSpecifics_ActiveStatus::
1760 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_TRUE;
1761 case TemplateURLData::ActiveStatus::kFalse:
1762 return sync_pb::SearchEngineSpecifics_ActiveStatus::
1763 SearchEngineSpecifics_ActiveStatus_ACTIVE_STATUS_FALSE;
1764 }
1765}
1766
1767// static
akalin@chromium.org65f173552012-06-28 22:43:581768syncer::SyncData TemplateURLService::CreateSyncDataFromTemplateURL(
stevet@chromium.orgd88cb202011-08-17 20:03:011769 const TemplateURL& turl) {
1770 sync_pb::EntitySpecifics specifics;
tim@chromium.org4557d222012-03-04 23:33:361771 sync_pb::SearchEngineSpecifics* se_specifics =
1772 specifics.mutable_search_engine();
avi@chromium.orgf911df52013-12-24 23:24:231773 se_specifics->set_short_name(base::UTF16ToUTF8(turl.short_name()));
1774 se_specifics->set_keyword(base::UTF16ToUTF8(turl.keyword()));
pkasting@chromium.org405aae22012-03-29 20:36:131775 se_specifics->set_favicon_url(turl.favicon_url().spec());
pkasting@chromium.org360ba052012-04-04 17:26:131776 se_specifics->set_url(turl.url());
stevet@chromium.orgd88cb202011-08-17 20:03:011777 se_specifics->set_safe_for_autoreplace(turl.safe_for_autoreplace());
1778 se_specifics->set_originating_url(turl.originating_url().spec());
1779 se_specifics->set_date_created(turl.date_created().ToInternalValue());
brettwd94a22142015-07-15 05:19:261780 se_specifics->set_input_encodings(
1781 base::JoinString(turl.input_encodings(), ";"));
pkasting@chromium.org360ba052012-04-04 17:26:131782 se_specifics->set_suggestions_url(turl.suggestions_url());
stevet@chromium.orgd88cb202011-08-17 20:03:011783 se_specifics->set_prepopulate_id(turl.prepopulate_id());
jnd@chromium.org39682d12013-07-31 23:02:051784 if (!turl.image_url().empty())
1785 se_specifics->set_image_url(turl.image_url());
jered@chromium.org041238dd2013-08-19 21:05:121786 se_specifics->set_new_tab_url(turl.new_tab_url());
jnd@chromium.org39682d12013-07-31 23:02:051787 if (!turl.search_url_post_params().empty())
1788 se_specifics->set_search_url_post_params(turl.search_url_post_params());
1789 if (!turl.suggestions_url_post_params().empty()) {
1790 se_specifics->set_suggestions_url_post_params(
1791 turl.suggestions_url_post_params());
1792 }
jnd@chromium.org39682d12013-07-31 23:02:051793 if (!turl.image_url_post_params().empty())
1794 se_specifics->set_image_url_post_params(turl.image_url_post_params());
stevet@chromium.orgd88cb202011-08-17 20:03:011795 se_specifics->set_last_modified(turl.last_modified().ToInternalValue());
1796 se_specifics->set_sync_guid(turl.sync_guid());
beaudoin@chromium.org5b3078752012-10-09 18:54:161797 for (size_t i = 0; i < turl.alternate_urls().size(); ++i)
1798 se_specifics->add_alternate_urls(turl.alternate_urls()[i]);
Angela Yoeurng7ff86ed52021-08-12 20:03:241799 se_specifics->set_is_active(ActiveStatusToSync(turl.is_active()));
Angela Yoeurng500c3102022-04-26 20:05:531800 se_specifics->set_starter_pack_id(turl.starter_pack_id());
beaudoin@chromium.org5b3078752012-10-09 18:54:161801
akalin@chromium.org65f173552012-06-28 22:43:581802 return syncer::SyncData::CreateLocalData(se_specifics->sync_guid(),
beaudoin@chromium.org008987b02013-01-03 21:22:431803 se_specifics->keyword(),
1804 specifics);
stevet@chromium.orgd88cb202011-08-17 20:03:011805}
1806
1807// static
dchengd967d9502016-04-21 22:36:511808std::unique_ptr<TemplateURL>
vasilii1fb84252014-11-06 11:59:091809TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData(
1810 TemplateURLServiceClient* client,
hashimoto@chromium.org20c0fb302014-06-28 02:04:121811 PrefService* prefs,
Boris Sazonov2b77815a2024-01-17 15:35:041812 search_engines::SearchEngineChoiceService* search_engine_choice_service,
hashimoto@chromium.org20c0fb302014-06-28 02:04:121813 const SearchTermsData& search_terms_data,
jeffschiller612137a2017-04-19 04:34:241814 const TemplateURL* existing_turl,
akalin@chromium.org65f173552012-06-28 22:43:581815 const syncer::SyncData& sync_data,
1816 syncer::SyncChangeList* change_list) {
pkasting@chromium.org16fca9b82012-04-23 18:40:261817 DCHECK(change_list);
1818
pkasting@chromium.org573889f22012-04-07 01:31:541819 sync_pb::SearchEngineSpecifics specifics =
1820 sync_data.GetSpecifics().search_engine();
1821
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081822 // Past bugs might have caused either of these fields to be empty. Just
1823 // delete this data off the server.
1824 if (specifics.url().empty() || specifics.sync_guid().empty()) {
akalin@chromium.orgcb02f612012-06-27 03:15:501825 change_list->push_back(
zea@chromium.orgb78170f2012-07-11 03:34:261826 syncer::SyncChange(FROM_HERE,
1827 syncer::SyncChange::ACTION_DELETE,
1828 sync_data));
stevet@chromium.orgc3dd7892012-08-27 21:28:281829 UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName,
1830 DELETE_ENGINE_EMPTY_FIELD, DELETE_ENGINE_MAX);
avi8a64b715b2016-09-02 17:30:041831 return nullptr;
pkasting@chromium.org4e40f59bb2012-04-19 01:07:081832 }
1833
Angela Yoeurng6d82e5d2024-02-14 22:43:111834 // Throw out anything from sync that has an invalid starter pack ID. This
1835 // might happen occasionally when the starter pack gets new entries that are
1836 // not yet supported in this version of Chrome.
1837 if (specifics.starter_pack_id() >=
1838 TemplateURLStarterPackData::kMaxStarterPackID) {
1839 return nullptr;
1840 }
1841
pkasting@chromium.org317dec72012-06-05 20:58:301842 TemplateURLData data(existing_turl ?
1843 existing_turl->data() : TemplateURLData());
mpearson3c6d7af2015-05-13 23:59:531844 data.SetShortName(base::UTF8ToUTF16(specifics.short_name()));
pkasting@chromium.org573889f22012-04-07 01:31:541845 data.originating_url = GURL(specifics.originating_url());
Jan Wilken Dörriefa241ba2021-03-11 17:57:011846 std::u16string keyword(base::UTF8ToUTF16(specifics.keyword()));
pkasting@chromium.org7b596f62012-05-08 01:34:251847 // NOTE: Once this code has shipped in a couple of stable releases, we can
1848 // probably remove the migration portion, comment out the
1849 // "autogenerate_keyword" field entirely in the .proto file, and fold the
1850 // empty keyword case into the "delete data" block above.
1851 bool reset_keyword =
1852 specifics.autogenerate_keyword() || specifics.keyword().empty();
1853 if (reset_keyword)
Jan Wilken Dörrie756999e2021-03-23 15:05:241854 keyword = u"dummy"; // Will be replaced below.
pkasting@chromium.org7b596f62012-05-08 01:34:251855 DCHECK(!keyword.empty());
1856 data.SetKeyword(keyword);
pkasting@chromium.org573889f22012-04-07 01:31:541857 data.SetURL(specifics.url());
1858 data.suggestions_url = specifics.suggestions_url();
jnd@chromium.org39682d12013-07-31 23:02:051859 data.image_url = specifics.image_url();
jered@chromium.org041238dd2013-08-19 21:05:121860 data.new_tab_url = specifics.new_tab_url();
jnd@chromium.org39682d12013-07-31 23:02:051861 data.search_url_post_params = specifics.search_url_post_params();
1862 data.suggestions_url_post_params = specifics.suggestions_url_post_params();
jnd@chromium.org39682d12013-07-31 23:02:051863 data.image_url_post_params = specifics.image_url_post_params();
pkasting@chromium.org573889f22012-04-07 01:31:541864 data.favicon_url = GURL(specifics.favicon_url());
pkasting@chromium.org573889f22012-04-07 01:31:541865 data.safe_for_autoreplace = specifics.safe_for_autoreplace();
brettw8be197d12015-07-23 23:23:311866 data.input_encodings = base::SplitString(
1867 specifics.input_encodings(), ";",
1868 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
pkasting@chromium.orga2622302012-06-27 03:36:071869 // If the server data has duplicate encodings, we'll want to push an update
1870 // below to correct it. Note that we also fix this in
1871 // GetSearchProvidersUsingKeywordResult(), since otherwise we'd never correct
1872 // local problems for clients which have disabled search engine sync.
1873 bool deduped = DeDupeEncodings(&data.input_encodings);
pkasting@chromium.org573889f22012-04-07 01:31:541874 data.date_created = base::Time::FromInternalValue(specifics.date_created());
1875 data.last_modified = base::Time::FromInternalValue(specifics.last_modified());
1876 data.prepopulate_id = specifics.prepopulate_id();
1877 data.sync_guid = specifics.sync_guid();
beaudoin@chromium.org5b3078752012-10-09 18:54:161878 data.alternate_urls.clear();
1879 for (int i = 0; i < specifics.alternate_urls_size(); ++i)
1880 data.alternate_urls.push_back(specifics.alternate_urls(i));
Angela Yoeurng7ff86ed52021-08-12 20:03:241881 data.is_active = ActiveStatusFromSync(specifics.is_active());
Angela Yoeurng500c3102022-04-26 20:05:531882 data.starter_pack_id = specifics.starter_pack_id();
pkasting@chromium.org3954c3a2012-04-10 20:17:551883
dchengd967d9502016-04-21 22:36:511884 std::unique_ptr<TemplateURL> turl(new TemplateURL(data));
beaudoin@chromium.org10aeaf12013-02-05 07:41:461885 // If this TemplateURL matches a built-in prepopulated template URL, it's
1886 // possible that sync is trying to modify fields that should not be touched.
1887 // Revert these fields to the built-in values.
Boris Sazonov2b77815a2024-01-17 15:35:041888 UpdateTemplateURLIfPrepopulated(turl.get(), prefs,
1889 search_engine_choice_service);
vasilii1fb84252014-11-06 11:59:091890
ianwena27b1df2016-09-23 23:08:231891 DCHECK_EQ(TemplateURL::NORMAL, turl->type());
pkasting@chromium.orga2622302012-06-27 03:36:071892 if (reset_keyword || deduped) {
1893 if (reset_keyword)
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:191894 turl->ResetKeywordIfNecessary(search_terms_data, true);
Peter Kastingbef7bc522021-09-08 17:53:301895 syncer::SyncData updated_sync_data = CreateSyncDataFromTemplateURL(*turl);
1896 change_list->push_back(syncer::SyncChange(
1897 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, updated_sync_data));
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:191898 } else if (turl->IsGoogleSearchURLWithReplaceableKeyword(search_terms_data)) {
pkasting@chromium.org317dec72012-06-05 20:58:301899 if (!existing_turl) {
1900 // We're adding a new TemplateURL that uses the Google base URL, so set
1901 // its keyword appropriately for the local environment.
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:191902 turl->ResetKeywordIfNecessary(search_terms_data, false);
1903 } else if (existing_turl->IsGoogleSearchURLWithReplaceableKeyword(
1904 search_terms_data)) {
pkasting@chromium.org317dec72012-06-05 20:58:301905 // Ignore keyword changes triggered by the Google base URL changing on
1906 // another client. If the base URL changes in this client as well, we'll
1907 // pick that up separately at the appropriate time. Otherwise, changing
1908 // the keyword here could result in having the wrong keyword for the local
1909 // environment.
1910 turl->data_.SetKeyword(existing_turl->keyword());
1911 }
pkasting@chromium.org7b596f62012-05-08 01:34:251912 }
pkasting@chromium.org317dec72012-06-05 20:58:301913
dcheng51ace48a2015-12-26 22:45:171914 return turl;
stevet@chromium.orgd88cb202011-08-17 20:03:011915}
1916
1917// static
1918SyncDataMap TemplateURLService::CreateGUIDToSyncDataMap(
akalin@chromium.org65f173552012-06-28 22:43:581919 const syncer::SyncDataList& sync_data) {
stevet@chromium.orgd88cb202011-08-17 20:03:011920 SyncDataMap data_map;
jdoerrie3feb1852018-10-05 12:16:441921 for (auto i(sync_data.begin()); i != sync_data.end(); ++i)
pkasting@chromium.org3954c3a2012-04-10 20:17:551922 data_map[i->GetSpecifics().search_engine().sync_guid()] = *i;
stevet@chromium.orgd88cb202011-08-17 20:03:011923 return data_map;
1924}
1925
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541926void TemplateURLService::Init() {
hashimoto@chromium.org7e310422014-07-03 12:44:451927 if (client_)
1928 client_->SetOwner(this);
blundell@chromium.orga594a012014-05-17 19:13:161929
hashimoto@chromium.org7e310422014-07-03 12:44:451930 if (prefs_) {
hashimoto@chromium.org20c0fb302014-06-28 02:04:121931 pref_change_registrar_.Init(prefs_);
Jens Mueller087e5c42023-10-05 11:36:051932 if (search_engines::IsChoiceScreenFlagEnabled(
1933 search_engines::ChoicePromo::kAny)) {
Jack Yammineabb0ba22023-07-20 10:46:341934 pref_change_registrar_.Add(
1935 prefs::kDefaultSearchProviderGUID,
1936 base::BindRepeating(
1937 &TemplateURLService::OnDefaultSearchProviderGUIDChanged,
1938 base::Unretained(this)));
1939 } else {
1940 pref_change_registrar_.Add(
1941 prefs::kSyncedDefaultSearchProviderGUID,
1942 base::BindRepeating(
1943 &TemplateURLService::OnDefaultSearchProviderGUIDChanged,
1944 base::Unretained(this)));
1945 }
levin@chromium.orgce75ec62010-08-21 00:34:461946 }
erikwright@chromium.orga80ec962014-05-12 00:55:381947
1948 DefaultSearchManager::Source source = DefaultSearchManager::FROM_USER;
a-v-y02d3f50b2017-02-16 12:03:551949 const TemplateURLData* dse =
erikwright@chromium.orga80ec962014-05-12 00:55:381950 default_search_manager_.GetDefaultSearchEngine(&source);
Esmael El-Moslimanyb4306e02018-04-18 21:24:331951
1952 Scoper scoper(this);
1953
erikwright@chromium.orga80ec962014-05-12 00:55:381954 ApplyDefaultSearchChange(dse, source);
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541955}
levin@chromium.orgce75ec62010-08-21 00:34:461956
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541957void TemplateURLService::ApplyInitializersForTesting(
1958 base::span<const TemplateURLService::Initializer> initializers) {
1959 // This path is only hit by test code and is used to simulate a loaded
1960 // TemplateURLService.
1961 CHECK_IS_TEST();
levin@chromium.orgce75ec62010-08-21 00:34:461962
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541963 if (initializers.empty()) {
1964 return;
1965 }
levin@chromium.orgce75ec62010-08-21 00:34:461966
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541967 ChangeToLoadedState();
pkasting@chromium.org56fa29592013-07-02 20:25:531968
Nicolas Dossou-Gbete0a72d0732024-01-04 16:21:541969 // Add specific initializers, if any.
1970 for (size_t i = 0; i < initializers.size(); ++i) {
1971 CHECK(initializers[i].keyword);
1972 CHECK(initializers[i].url);
1973 CHECK(initializers[i].content);
1974
1975 // TemplateURLService ends up owning the TemplateURL, don't try and free
1976 // it.
1977 TemplateURLData data;
1978 data.SetShortName(base::UTF8ToUTF16(initializers[i].content));
1979 data.SetKeyword(base::UTF8ToUTF16(initializers[i].keyword));
1980 data.SetURL(initializers[i].url);
1981 // Set all to active by default for testing purposes.
1982 data.is_active = TemplateURLData::ActiveStatus::kTrue;
1983 Add(std::make_unique<TemplateURL>(data));
1984
1985 // Set the first provided identifier to be the default.
1986 if (i == 0) {
Mihai Sardarescu27fe06b2024-03-12 10:07:461987 default_search_manager_.SetUserSelectedDefaultSearchEngine(
1988 data, search_engines::ChoiceMadeLocation::kOther);
levin@chromium.orgd8522742010-08-27 17:55:441989 }
levin@chromium.orgce75ec62010-08-21 00:34:461990 }
levin@chromium.orgce75ec62010-08-21 00:34:461991}
1992
Kevin Bailey499298a2018-03-16 14:54:431993void TemplateURLService::RemoveFromMaps(const TemplateURL* template_url) {
Jan Wilken Dörriefa241ba2021-03-11 17:57:011994 const std::u16string& keyword = template_url->keyword();
Tommy Li70e35282020-11-11 23:52:561995
Angela Yoeurngcec8a4642023-06-15 20:33:591996 // Remove from |keyword_to_turl_|. No need to find the best
Tommy Li70e35282020-11-11 23:52:561997 // fallback. We choose the best one as-needed from the multimap.
Angela Yoeurngcec8a4642023-06-15 20:33:591998 const auto match_range = keyword_to_turl_.equal_range(keyword);
Tommy Li70e35282020-11-11 23:52:561999 for (auto it = match_range.first; it != match_range.second;) {
Angela Yoeurngcec8a4642023-06-15 20:33:592000 if (it->second == template_url) {
2001 it = keyword_to_turl_.erase(it);
mpearson6456fb62015-11-13 06:44:282002 } else {
Tommy Li70e35282020-11-11 23:52:562003 ++it;
mpearson6456fb62015-11-13 06:44:282004 }
pkasting@chromium.org7b596f62012-05-08 01:34:252005 }
pkasting@chromium.org3954c3a2012-04-10 20:17:552006
ianwena27b1df2016-09-23 23:08:232007 if (template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION)
vasilii1fb84252014-11-06 11:59:092008 return;
2009
Rushan Suleymanovb25693f2023-09-22 15:21:322010 if (!template_url->sync_guid().empty()) {
mpearson6456fb62015-11-13 06:44:282011 guid_to_turl_.erase(template_url->sync_guid());
Rushan Suleymanovb25693f2023-09-22 15:21:322012 if (postponed_deleted_default_engine_guid_ == template_url->sync_guid()) {
2013 // `template_url` has been updated locally or removed, discard incoming
2014 // deletion.
2015 postponed_deleted_default_engine_guid_.clear();
2016 }
2017 }
2018
Fabio Tirelo07ea7dd2023-12-06 02:13:442019 if (template_url->created_by_policy() ==
2020 TemplateURLData::CreatedByPolicy::kSiteSearch) {
2021 enterprise_site_search_keyword_to_turl_.erase(keyword);
2022 }
2023
erikwright@chromium.org5f137652014-05-14 00:02:192024 // |provider_map_| is only initialized after loading has completed.
pkasting@chromium.org317dec72012-06-05 20:58:302025 if (loaded_) {
hashimoto@chromium.org20c0fb302014-06-28 02:04:122026 provider_map_->Remove(template_url);
pkasting@chromium.org317dec72012-06-05 20:58:302027 }
levin@chromium.orgce75ec62010-08-21 00:34:462028}
2029
pkasting@chromium.org3613347d2012-04-27 20:27:372030void TemplateURLService::AddToMaps(TemplateURL* template_url) {
Jan Wilken Dörriefa241ba2021-03-11 17:57:012031 const std::u16string& keyword = template_url->keyword();
Angela Yoeurngcec8a4642023-06-15 20:33:592032 keyword_to_turl_.insert(std::make_pair(keyword, template_url));
pkasting@chromium.org3954c3a2012-04-10 20:17:552033
Tommy Li70e35282020-11-11 23:52:562034 if (template_url->type() == TemplateURL::OMNIBOX_API_EXTENSION)
vasilii1fb84252014-11-06 11:59:092035 return;
2036
stevet@chromium.orgd88cb202011-08-17 20:03:012037 if (!template_url->sync_guid().empty())
mpearson6456fb62015-11-13 06:44:282038 guid_to_turl_[template_url->sync_guid()] = template_url;
Fabio Tirelo07ea7dd2023-12-06 02:13:442039
2040 if (template_url->created_by_policy() ==
2041 TemplateURLData::CreatedByPolicy::kSiteSearch) {
2042 enterprise_site_search_keyword_to_turl_[keyword] = template_url;
2043 }
2044
erikwright@chromium.org5f137652014-05-14 00:02:192045 // |provider_map_| is only initialized after loading has completed.
hashimoto@chromium.org20c0fb302014-06-28 02:04:122046 if (loaded_)
2047 provider_map_->Add(template_url, search_terms_data());
levin@chromium.orgce75ec62010-08-21 00:34:462048}
2049
avi8a64b715b2016-09-02 17:30:042050void TemplateURLService::SetTemplateURLs(
2051 std::unique_ptr<OwnedTemplateURLVector> urls) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:332052 Scoper scoper(this);
2053
pkasting@chromium.org224cfac2013-09-16 21:22:292054 // Partition the URLs first, instead of implementing the loops below by simply
2055 // scanning the input twice. While it's not supposed to happen normally, it's
2056 // possible for corrupt databases to return multiple entries with the same
2057 // keyword. In this case, the first loop may delete the first entry when
2058 // adding the second. If this happens, the second loop must not attempt to
2059 // access the deleted entry. Partitioning ensures this constraint.
avi8a64b715b2016-09-02 17:30:042060 auto first_invalid = std::partition(
2061 urls->begin(), urls->end(), [](const std::unique_ptr<TemplateURL>& turl) {
2062 return turl->id() != kInvalidTemplateURLID;
2063 });
pkasting@chromium.org224cfac2013-09-16 21:22:292064
2065 // First, add the items that already have id's, so that the next_id_ gets
2066 // properly set.
avi8a64b715b2016-09-02 17:30:042067 for (auto i = urls->begin(); i != first_invalid; ++i) {
pkasting@chromium.org224cfac2013-09-16 21:22:292068 next_id_ = std::max(next_id_, (*i)->id());
Esmael El-Moslimanyb4306e02018-04-18 21:24:332069 Add(std::move(*i), false);
levin@chromium.orgce75ec62010-08-21 00:34:462070 }
levin@chromium.orge1ddda02010-08-26 19:43:482071
2072 // Next add the new items that don't have id's.
avi8a64b715b2016-09-02 17:30:042073 for (auto i = first_invalid; i != urls->end(); ++i)
Esmael El-Moslimanyb4306e02018-04-18 21:24:332074 Add(std::move(*i));
levin@chromium.orgce75ec62010-08-21 00:34:462075}
2076
erg@google.com8e5c89a2011-06-07 18:13:332077void TemplateURLService::ChangeToLoadedState() {
levin@chromium.orgd8522742010-08-27 17:55:442078 DCHECK(!loaded_);
2079
hashimoto@chromium.org20c0fb302014-06-28 02:04:122080 provider_map_->Init(template_urls_, search_terms_data());
levin@chromium.orgd8522742010-08-27 17:55:442081 loaded_ = true;
levin@chromium.orgd8522742010-08-27 17:55:442082
erikwright@chromium.orgc2de25b2014-05-23 01:02:582083 ApplyDefaultSearchChangeNoMetrics(
Fabio Tirelo07ea7dd2023-12-06 02:13:442084 pre_loading_providers_->default_search_provider()
2085 ? &pre_loading_providers_->default_search_provider()->data()
avi8a64b715b2016-09-02 17:30:042086 : nullptr,
erikwright@chromium.orgc2de25b2014-05-23 01:02:582087 default_search_provider_source_);
Fabio Tirelo07ea7dd2023-12-06 02:13:442088 if (base::FeatureList::IsEnabled(omnibox::kSiteSearchSettingsPolicy)) {
2089 ApplyEnterpriseSiteSearchChanges(
2090 pre_loading_providers_->TakeSiteSearchEngines());
2091 }
2092 pre_loading_providers_.reset();
robliao66fe8b192015-03-13 01:29:182093
Mikel Astiz095de452019-03-11 06:29:582094 if (on_loaded_callback_for_sync_)
2095 std::move(on_loaded_callback_for_sync_).Run();
2096
erikwright@chromium.orga80ec962014-05-12 00:55:382097 on_loaded_callbacks_.Notify();
vasilii@chromium.org4a40facd2013-05-29 14:44:562098}
2099
mpearson511ce60b2015-05-23 23:47:092100bool TemplateURLService::CanAddAutogeneratedKeywordForHost(
jeffschiller9c4df022017-04-14 00:47:382101 const std::string& host) const {
pkasting@chromium.org3613347d2012-04-27 20:27:372102 const TemplateURLSet* urls = provider_map_->GetURLsForHost(host);
pkasting@chromium.org3954c3a2012-04-10 20:17:552103 if (!urls)
2104 return true;
Denis Yaroshevskiy442ae062018-03-12 12:35:422105
Peter Kasting913b9b852022-08-31 13:45:302106 return base::ranges::all_of(*urls, [](const TemplateURL* turl) {
Denis Yaroshevskiy442ae062018-03-12 12:35:422107 return turl->safe_for_autoreplace();
2108 });
ben@chromium.orgd82443b2009-01-15 19:54:562109}
2110
Esmael El-Moslimanyb4306e02018-04-18 21:24:332111bool TemplateURLService::Update(TemplateURL* existing_turl,
2112 const TemplateURL& new_values) {
levin@chromium.org98d0f9212010-08-24 05:12:552113 DCHECK(existing_turl);
avi8a64b715b2016-09-02 17:30:042114 if (!Contains(&template_urls_, existing_turl))
pkasting@chromium.org7b596f62012-05-08 01:34:252115 return false;
levin@chromium.org98d0f9212010-08-24 05:12:552116
Esmael El-Moslimanyb4306e02018-04-18 21:24:332117 Scoper scoper(this);
2118 model_mutated_notification_pending_ = true;
2119
pkasting@chromium.org573889f22012-04-07 01:31:542120 TemplateURLID previous_id = existing_turl->id();
a-v-y7084ddf2017-03-09 13:15:482121 RemoveFromMaps(existing_turl);
2122
Tommy Li276535cc2020-11-23 19:11:012123 // Update existing turl with new values and add back to the map.
2124 // We don't do any keyword conflict handling here, as TemplateURLService
2125 // already can pick the best engine out of duplicates. Replaceable duplicates
2126 // will be culled during next startup's Add() loop. We did this to keep
2127 // Update() simple: it never fails, and never deletes |existing_engine|.
Alexander Yashkina5bb8be2018-04-03 06:51:132128 existing_turl->CopyFrom(new_values);
2129 existing_turl->data_.id = previous_id;
levin@chromium.org98d0f9212010-08-24 05:12:552130
a-v-y7084ddf2017-03-09 13:15:482131 AddToMaps(existing_turl);
2132
2133 if (existing_turl->type() == TemplateURL::NORMAL) {
2134 if (web_data_service_)
2135 web_data_service_->UpdateKeyword(existing_turl->data());
2136
2137 // Inform sync of the update.
2138 ProcessTemplateURLChange(FROM_HERE, existing_turl,
2139 syncer::SyncChange::ACTION_UPDATE);
pkasting@chromium.org7b596f62012-05-08 01:34:252140 }
stevet@chromium.orgd88cb202011-08-17 20:03:012141
Vasilii Sukhanov9e32af72018-04-17 18:47:152142 // Even if the DSE is controlled by an extension or policy, update the user
2143 // preferences as they may take over later.
2144 if (default_search_provider_source_ != DefaultSearchManager::FROM_FALLBACK)
2145 MaybeUpdateDSEViaPrefs(existing_turl);
a-v-y9e4f54d2017-03-29 14:50:362146
pkasting@chromium.org7b596f62012-05-08 01:34:252147 return true;
levin@chromium.org98d0f9212010-08-24 05:12:552148}
2149
beaudoin@chromium.org10aeaf12013-02-05 07:41:462150// static
2151void TemplateURLService::UpdateTemplateURLIfPrepopulated(
2152 TemplateURL* template_url,
Boris Sazonov2b77815a2024-01-17 15:35:042153 PrefService* prefs,
2154 search_engines::SearchEngineChoiceService* search_engine_choice_service) {
beaudoin@chromium.org10aeaf12013-02-05 07:41:462155 int prepopulate_id = template_url->prepopulate_id();
2156 if (template_url->prepopulate_id() == 0)
2157 return;
2158
avie8828f22016-09-02 18:07:582159 std::vector<std::unique_ptr<TemplateURLData>> prepopulated_urls =
Boris Sazonov2b77815a2024-01-17 15:35:042160 TemplateURLPrepopulateData::GetPrepopulatedEngines(
2161 prefs, search_engine_choice_service, nullptr);
beaudoin@chromium.org10aeaf12013-02-05 07:41:462162
avie8828f22016-09-02 18:07:582163 for (const auto& url : prepopulated_urls) {
2164 if (url->prepopulate_id == prepopulate_id) {
Angela Yoeurng6e3d6b72022-04-18 21:26:172165 MergeIntoEngineData(template_url, url.get());
avie8828f22016-09-02 18:07:582166 template_url->CopyFrom(TemplateURL(*url));
beaudoin@chromium.org10aeaf12013-02-05 07:41:462167 }
2168 }
2169}
2170
Vasilii Sukhanov9e32af72018-04-17 18:47:152171void TemplateURLService::MaybeUpdateDSEViaPrefs(TemplateURL* synced_turl) {
Jack Yammineabb0ba22023-07-20 10:46:342172 if (prefs_ && (synced_turl->sync_guid() ==
2173 GetDefaultSearchProviderPrefValue(*prefs_))) {
erikwright@chromium.orga80ec962014-05-12 00:55:382174 default_search_manager_.SetUserSelectedDefaultSearchEngine(
Mihai Sardarescu27fe06b2024-03-12 10:07:462175 synced_turl->data(), search_engines::ChoiceMadeLocation::kOther);
erikwright@chromium.orga80ec962014-05-12 00:55:382176 }
2177}
2178
erg@google.com8e5c89a2011-06-07 18:13:332179void TemplateURLService::UpdateKeywordSearchTermsForURL(
hashimoto@chromium.org7e310422014-07-03 12:44:452180 const URLVisitedDetails& details) {
2181 if (!details.url.is_valid())
ben@chromium.orgd82443b2009-01-15 19:54:562182 return;
ben@chromium.orgd82443b2009-01-15 19:54:562183
levin@chromium.orgd8522742010-08-27 17:55:442184 const TemplateURLSet* urls_for_host =
hashimoto@chromium.org7e310422014-07-03 12:44:452185 provider_map_->GetURLsForHost(details.url.host());
levin@chromium.orgd8522742010-08-27 17:55:442186 if (!urls_for_host)
ben@chromium.orgd82443b2009-01-15 19:54:562187 return;
ben@chromium.orgd82443b2009-01-15 19:54:562188
ltian8b5fc832016-12-02 23:43:352189 TemplateURL* visited_url = nullptr;
jdoerrie3feb1852018-10-05 12:16:442190 for (auto i = urls_for_host->begin(); i != urls_for_host->end(); ++i) {
Jan Wilken Dörriefa241ba2021-03-11 17:57:012191 std::u16string search_terms;
hashimoto@chromium.org7e310422014-07-03 12:44:452192 if ((*i)->ExtractSearchTermsFromURL(details.url, search_terms_data(),
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:192193 &search_terms) &&
beaudoin@chromium.org67d8b752013-04-03 17:33:272194 !search_terms.empty()) {
hashimoto@chromium.org7e310422014-07-03 12:44:452195 if (details.is_keyword_transition) {
sky@chromium.org0bfc29a2009-04-27 16:15:442196 // The visit is the result of the user entering a keyword, generate a
2197 // KEYWORD_GENERATED visit for the KEYWORD so that the keyword typed
2198 // count is boosted.
2199 AddTabToSearchVisit(**i);
2200 }
hashimoto31135362014-08-29 09:19:452201 if (client_) {
2202 client_->SetKeywordSearchTermsForURL(
2203 details.url, (*i)->id(), search_terms);
2204 }
ltian8b5fc832016-12-02 23:43:352205 // Caches the matched TemplateURL so its last_visited could be updated
2206 // later after iteration.
Esmael El-Moslimanyb4306e02018-04-18 21:24:332207 // Note: Update() will replace the entry from the container of this
2208 // iterator, so update here directly will cause an error about it.
a-v-y7084ddf2017-03-09 13:15:482209 if (!IsCreatedByExtension(*i))
2210 visited_url = *i;
ben@chromium.orgd82443b2009-01-15 19:54:562211 }
2212 }
ltian8b5fc832016-12-02 23:43:352213 if (visited_url)
2214 UpdateTemplateURLVisitTime(visited_url);
ben@chromium.orgd82443b2009-01-15 19:54:562215}
2216
ltian035c5722017-01-10 00:35:392217void TemplateURLService::UpdateTemplateURLVisitTime(TemplateURL* url) {
2218 TemplateURLData data(url->data());
2219 data.last_visited = clock_->Now();
2220 Update(url, TemplateURL(data));
2221}
2222
erg@google.com8e5c89a2011-06-07 18:13:332223void TemplateURLService::AddTabToSearchVisit(const TemplateURL& t_url) {
sky@chromium.org0bfc29a2009-04-27 16:15:442224 // Only add visits for entries the user hasn't modified. If the user modified
2225 // the entry the keyword may no longer correspond to the host name. It may be
2226 // possible to do something more sophisticated here, but it's so rare as to
2227 // not be worth it.
2228 if (!t_url.safe_for_autoreplace())
2229 return;
2230
hashimoto@chromium.org7e310422014-07-03 12:44:452231 if (!client_)
sky@chromium.org0bfc29a2009-04-27 16:15:442232 return;
2233
rsleevi24f64dc22015-08-07 21:39:212234 GURL url(url_formatter::FixupURL(base::UTF16ToUTF8(t_url.keyword()),
2235 std::string()));
sky@chromium.org0bfc29a2009-04-27 16:15:442236 if (!url.is_valid())
2237 return;
2238
2239 // Synthesize a visit for the keyword. This ensures the url for the keyword is
2240 // autocompleted even if the user doesn't type the url in directly.
hashimoto@chromium.org7e310422014-07-03 12:44:452241 client_->AddKeywordGeneratedVisit(url);
2242}
2243
erikwright@chromium.orga80ec962014-05-12 00:55:382244void TemplateURLService::ApplyDefaultSearchChange(
2245 const TemplateURLData* data,
2246 DefaultSearchManager::Source source) {
erikwright@chromium.orgc2de25b2014-05-23 01:02:582247 if (!ApplyDefaultSearchChangeNoMetrics(data, source))
2248 return;
2249
erikwright@chromium.orgc2de25b2014-05-23 01:02:582250 if (GetDefaultSearchProvider() &&
hashimoto@chromium.org82fe07e2014-07-02 07:40:222251 GetDefaultSearchProvider()->HasGoogleBaseURLs(search_terms_data()) &&
2252 !dsp_change_callback_.is_null())
2253 dsp_change_callback_.Run();
erikwright@chromium.orgc2de25b2014-05-23 01:02:582254}
2255
Jack Yamminef973df622023-09-05 14:57:112256bool TemplateURLService::ApplyDefaultSearchChangeForTesting(
2257 const TemplateURLData* data,
2258 DefaultSearchManager::Source source) {
2259 CHECK_IS_TEST();
2260 return ApplyDefaultSearchChangeNoMetrics(data, source);
2261}
2262
erikwright@chromium.orgc2de25b2014-05-23 01:02:582263bool TemplateURLService::ApplyDefaultSearchChangeNoMetrics(
2264 const TemplateURLData* data,
2265 DefaultSearchManager::Source source) {
Tommy Li388759a2020-04-17 22:06:062266 // We do not want any sort of reentrancy while changing the default search
2267 // engine. This can occur when resolving conflicting entries. In those cases,
2268 // it's best to early exit and let the original process finish.
2269 if (applying_default_search_engine_change_)
2270 return false;
2271 base::AutoReset<bool> applying_change(&applying_default_search_engine_change_,
2272 true);
2273
erikwright@chromium.orga80ec962014-05-12 00:55:382274 if (!loaded_) {
Fabio Tirelo07ea7dd2023-12-06 02:13:442275 // Set pre-loading default search provider from the preferences. This is
erikwright@chromium.orga80ec962014-05-12 00:55:382276 // mainly so we can hold ownership until we get to the point where the list
2277 // of keywords from Web Data is the owner of everything including the
2278 // default.
a-v-yef5c7c72017-04-14 09:06:402279 bool changed = !TemplateURL::MatchesData(
Fabio Tirelo07ea7dd2023-12-06 02:13:442280 pre_loading_providers_->default_search_provider(), data,
2281 search_terms_data());
a-v-y858416e2017-03-01 16:42:452282 TemplateURL::Type initial_engine_type =
2283 (source == DefaultSearchManager::FROM_EXTENSION)
2284 ? TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION
2285 : TemplateURL::NORMAL;
Fabio Tirelo07ea7dd2023-12-06 02:13:442286 pre_loading_providers_->set_default_search_provider(
Jinho Bangfa6b5752018-01-03 20:52:192287 data ? std::make_unique<TemplateURL>(*data, initial_engine_type)
Fabio Tirelo07ea7dd2023-12-06 02:13:442288 : nullptr);
erikwright@chromium.orga80ec962014-05-12 00:55:382289 default_search_provider_source_ = source;
erikwright@chromium.orgc2de25b2014-05-23 01:02:582290 return changed;
erikwright@chromium.orga80ec962014-05-12 00:55:382291 }
hclam@chromium.orgd63488d2014-05-10 20:48:272292
erikwright@chromium.orgc2de25b2014-05-23 01:02:582293 // This may be deleted later. Use exclusively for pointer comparison to detect
2294 // a change.
2295 TemplateURL* previous_default_search_engine = default_search_provider_;
erikwright@chromium.orga80ec962014-05-12 00:55:382296
Esmael El-Moslimanyb4306e02018-04-18 21:24:332297 Scoper scoper(this);
2298
erikwright@chromium.orga80ec962014-05-12 00:55:382299 if (default_search_provider_source_ == DefaultSearchManager::FROM_POLICY ||
Igor Ruvinovf266d2a42022-06-24 17:52:162300 default_search_provider_source_ ==
2301 DefaultSearchManager::FROM_POLICY_RECOMMENDED ||
2302 source == DefaultSearchManager::FROM_POLICY ||
2303 source == DefaultSearchManager::FROM_POLICY_RECOMMENDED) {
erikwright@chromium.orga80ec962014-05-12 00:55:382304 // We do this both to remove any no-longer-applicable policy-defined DSE as
2305 // well as to add the new one, if appropriate.
Fabio Tirelo41316deb2023-11-24 21:33:112306 UpdateDefaultProvidersCreatedByPolicy(
erikwright@chromium.orga80ec962014-05-12 00:55:382307 &template_urls_,
Igor Ruvinovf266d2a42022-06-24 17:52:162308 source == DefaultSearchManager::FROM_POLICY ||
2309 source == DefaultSearchManager::FROM_POLICY_RECOMMENDED
2310 ? data
2311 : nullptr,
2312 /*is_mandatory=*/source == DefaultSearchManager::FROM_POLICY);
erikwright@chromium.orga80ec962014-05-12 00:55:382313 }
2314
Esmael El-Moslimanyb4306e02018-04-18 21:24:332315 // |default_search_provider_source_| must be set before calling Update(),
2316 // since that function needs to know the source of the update in question.
a-v-y02d3f50b2017-02-16 12:03:552317 default_search_provider_source_ = source;
2318
erikwright@chromium.orga80ec962014-05-12 00:55:382319 if (!data) {
avi8a64b715b2016-09-02 17:30:042320 default_search_provider_ = nullptr;
erikwright@chromium.orgc2de25b2014-05-23 01:02:582321 } else if (source == DefaultSearchManager::FROM_EXTENSION) {
a-v-yf4eb4352017-02-25 18:46:592322 default_search_provider_ = FindMatchingDefaultExtensionTemplateURL(*data);
erikwright@chromium.orgc2de25b2014-05-23 01:02:582323 } else if (source == DefaultSearchManager::FROM_FALLBACK) {
erikwright@chromium.orga80ec962014-05-12 00:55:382324 default_search_provider_ =
2325 FindPrepopulatedTemplateURL(data->prepopulate_id);
2326 if (default_search_provider_) {
2327 TemplateURLData update_data(*data);
2328 update_data.sync_guid = default_search_provider_->sync_guid();
Tommy C. Li482ddff2019-03-09 00:42:232329
2330 // Now that we are auto-updating the favicon_url as the user browses,
2331 // respect the favicon_url entry in the database, instead of falling back
2332 // to the one in the prepopulated list.
2333 update_data.favicon_url = default_search_provider_->favicon_url();
2334
erikwright@chromium.orga80ec962014-05-12 00:55:382335 if (!default_search_provider_->safe_for_autoreplace()) {
2336 update_data.safe_for_autoreplace = false;
2337 update_data.SetKeyword(default_search_provider_->keyword());
mpearson3c6d7af2015-05-13 23:59:532338 update_data.SetShortName(default_search_provider_->short_name());
erikwright@chromium.orga80ec962014-05-12 00:55:382339 }
Esmael El-Moslimanyb4306e02018-04-18 21:24:332340 Update(default_search_provider_, TemplateURL(update_data));
hclam@chromium.orgd63488d2014-05-10 20:48:272341 } else {
erikwright@chromium.orga80ec962014-05-12 00:55:382342 // Normally the prepopulated fallback should be present in
2343 // |template_urls_|, but in a few cases it might not be:
2344 // (1) Tests that initialize the TemplateURLService in peculiar ways.
2345 // (2) If the user deleted the pre-populated default and we subsequently
2346 // lost their user-selected value.
Esmael El-Moslimanyb4306e02018-04-18 21:24:332347 default_search_provider_ = Add(std::make_unique<TemplateURL>(*data));
Tommy Li2d7174dc2021-01-13 19:45:282348 DCHECK(default_search_provider_)
2349 << "Add() to repair the DSE must never fail.";
hclam@chromium.orgd63488d2014-05-10 20:48:272350 }
erikwright@chromium.orgc2de25b2014-05-23 01:02:582351 } else if (source == DefaultSearchManager::FROM_USER) {
erikwright@chromium.orga80ec962014-05-12 00:55:382352 default_search_provider_ = GetTemplateURLForGUID(data->sync_guid);
2353 if (!default_search_provider_ && data->prepopulate_id) {
2354 default_search_provider_ =
2355 FindPrepopulatedTemplateURL(data->prepopulate_id);
hclam@chromium.orgd63488d2014-05-10 20:48:272356 }
erikwright@chromium.orga80ec962014-05-12 00:55:382357 TemplateURLData new_data(*data);
erikwright@chromium.orga80ec962014-05-12 00:55:382358 if (default_search_provider_) {
Esmael El-Moslimanyb4306e02018-04-18 21:24:332359 Update(default_search_provider_, TemplateURL(new_data));
jeanluc@google.com14154f82010-10-07 00:22:462360 } else {
erikwright@chromium.orga80ec962014-05-12 00:55:382361 new_data.id = kInvalidTemplateURLID;
Esmael El-Moslimanyb4306e02018-04-18 21:24:332362 default_search_provider_ = Add(std::make_unique<TemplateURL>(new_data));
Tommy Li2d7174dc2021-01-13 19:45:282363 DCHECK(default_search_provider_)
2364 << "Add() to repair the DSE must never fail.";
jeanluc@google.comb6fd1fa72010-10-05 23:49:312365 }
hashimoto@chromium.org20c0fb302014-06-28 02:04:122366 if (default_search_provider_ && prefs_) {
Jack Yammineabb0ba22023-07-20 10:46:342367 SetDefaultSearchProviderPrefValue(*prefs_,
2368 default_search_provider_->sync_guid());
hclam@chromium.orgd63488d2014-05-10 20:48:272369 }
2370 }
jeanluc@google.comb6fd1fa72010-10-05 23:49:312371
Rushan Suleymanovb25693f2023-09-22 15:21:322372 if (default_search_provider_ == previous_default_search_engine) {
2373 // Default search engine hasn't changed.
2374 return false;
Esmael El-Moslimanyb4306e02018-04-18 21:24:332375 }
erikwright@chromium.orgc2de25b2014-05-23 01:02:582376
Rushan Suleymanovb25693f2023-09-22 15:21:322377 model_mutated_notification_pending_ = true;
2378 if (!postponed_deleted_default_engine_guid_.empty()) {
2379 // There was a postponed deletion for the previous default search engine,
2380 // remove it now.
2381 TemplateURL* existing_turl =
2382 GetTemplateURLForGUID(postponed_deleted_default_engine_guid_);
2383 if (existing_turl) {
2384 // Remove() below CHECKs that the current default search engine is not
2385 // deleted.
2386 Remove(existing_turl);
2387 }
2388 postponed_deleted_default_engine_guid_.clear();
2389 }
2390
2391 return true;
jeanluc@google.comb6fd1fa72010-10-05 23:49:312392}
2393
Fabio Tirelo07ea7dd2023-12-06 02:13:442394void TemplateURLService::ApplyEnterpriseSiteSearchChanges(
Fabio Tirelo5efc04da2024-01-04 21:31:372395 TemplateURLService::OwnedTemplateURLVector&& policy_site_search_engines) {
Fabio Tirelo07ea7dd2023-12-06 02:13:442396 CHECK(loaded_);
2397
2398 Scoper scoper(this);
2399
Fabio Tirelo5efc04da2024-01-04 21:31:372400 LogSiteSearchPolicyConflict(policy_site_search_engines);
2401
Fabio Tirelo07ea7dd2023-12-06 02:13:442402 base::flat_set<std::u16string> new_keywords;
2403 base::ranges::transform(
Fabio Tirelo5efc04da2024-01-04 21:31:372404 policy_site_search_engines,
2405 std::inserter(new_keywords, new_keywords.begin()),
Fabio Tirelo07ea7dd2023-12-06 02:13:442406 [](const std::unique_ptr<TemplateURL>& turl) { return turl->keyword(); });
2407
2408 // Remove old site search entries no longer present in the policy's list.
2409 //
2410 // Note: auxiliary `keywords_to_remove` is needed to avoid reentry issues
2411 // while removing elements from
2412 // `enterprise_site_search_keyword_to_turl_`.
2413 //
2414 // Note: This can be made more idiomatic once Chromium style allows
2415 // `std::views::keys`:
2416 // std::copy_if(
2417 // std::views::keys(enterprise_site_search_keyword_to_turl_),
2418 // std::inserter(keywords_to_remove, keywords_to_remove.begin()),
2419 // [new_keywords] (const std::u16string& keyword) {
2420 // new_keywords.find(keyword) == new_keywords.end()
2421 // });
2422 base::flat_set<std::u16string> keywords_to_remove;
2423 for (auto [keyword, _] : enterprise_site_search_keyword_to_turl_) {
2424 if (new_keywords.find(keyword) == new_keywords.end()) {
2425 keywords_to_remove.insert(keyword);
2426 }
2427 }
2428 base::ranges::for_each(
2429 keywords_to_remove, [this](const std::u16string& keyword) {
2430 Remove(enterprise_site_search_keyword_to_turl_[keyword]);
2431 });
2432
2433 // Either add new site search entries or update existing ones if necessary.
Fabio Tirelo5efc04da2024-01-04 21:31:372434 for (auto& site_search_engine : policy_site_search_engines) {
Fabio Tirelo07ea7dd2023-12-06 02:13:442435 const std::u16string& keyword = site_search_engine->keyword();
2436 auto it = enterprise_site_search_keyword_to_turl_.find(keyword);
2437 if (it == enterprise_site_search_keyword_to_turl_.end()) {
2438 Add(std::move(site_search_engine), /*newly_adding=*/true);
2439 } else if (ShouldMergeEnterpriseSiteSearchEngines(
2440 /*existing_turl=*/*it->second,
2441 /*new_values=*/*site_search_engine)) {
2442 Update(/*existing_turl=*/it->second,
2443 /*new_values=*/MergeEnterpriseSiteSearchEngines(
2444 /*existing_turl=*/*it->second,
2445 /*new_values=*/*site_search_engine));
2446 }
2447 }
2448
2449 // TODO(b/314162426): Check interaction with DSP not set by policy.
2450 // TODO(b/309456406): Override existing SE if keywords starts with "@" and
2451 // this is a featured site search entry.
Fabio Tirelo07ea7dd2023-12-06 02:13:442452}
2453
Fabio Tirelo78ca8262023-10-31 14:53:182454void TemplateURLService::EnterpriseSiteSearchChanged(
Fabio Tirelo5efc04da2024-01-04 21:31:372455 OwnedTemplateURLDataVector&& policy_site_search_engines) {
Fabio Tirelo07ea7dd2023-12-06 02:13:442456 OwnedTemplateURLVector turl_site_search_engines;
Fabio Tirelo5efc04da2024-01-04 21:31:372457 for (const std::unique_ptr<TemplateURLData>& it :
2458 policy_site_search_engines) {
Fabio Tirelo07ea7dd2023-12-06 02:13:442459 turl_site_search_engines.push_back(
2460 std::make_unique<TemplateURL>(*it, TemplateURL::NORMAL));
2461 }
2462
2463 if (loaded_) {
2464 ApplyEnterpriseSiteSearchChanges(std::move(turl_site_search_engines));
2465 } else {
2466 pre_loading_providers_->set_site_search_engines(
2467 std::move(turl_site_search_engines));
2468 }
Fabio Tirelo78ca8262023-10-31 14:53:182469}
2470
Esmael El-Moslimanyb4306e02018-04-18 21:24:332471TemplateURL* TemplateURLService::Add(std::unique_ptr<TemplateURL> template_url,
2472 bool newly_adding) {
jeanluc@google.comb6fd1fa72010-10-05 23:49:312473 DCHECK(template_url);
pkasting@chromium.org3954c3a2012-04-10 20:17:552474
Esmael El-Moslimanyb4306e02018-04-18 21:24:332475 Scoper scoper(this);
2476
pkasting@chromium.org3954c3a2012-04-10 20:17:552477 if (newly_adding) {
2478 DCHECK_EQ(kInvalidTemplateURLID, template_url->id());
avi8a64b715b2016-09-02 17:30:042479 DCHECK(!Contains(&template_urls_, template_url.get()));
pkasting@chromium.org3954c3a2012-04-10 20:17:552480 template_url->data_.id = ++next_id_;
2481 }
2482
Tommy Li726e59c42021-01-07 22:57:002483 template_url->ResetKeywordIfNecessary(search_terms_data(), false);
erikwright@chromium.org5f137652014-05-14 00:02:192484
Tommy Li276535cc2020-11-23 19:11:012485 // Early exit if the newly added TemplateURL was a replaceable duplicate.
2486 // No need to inform either Sync or flag on the model-mutated in that case.
2487 if (RemoveDuplicateReplaceableEnginesOf(template_url.get())) {
2488 return nullptr;
pkasting@chromium.org7b596f62012-05-08 01:34:252489 }
Tommy Li276535cc2020-11-23 19:11:012490
avi8a64b715b2016-09-02 17:30:042491 TemplateURL* template_url_ptr = template_url.get();
2492 template_urls_.push_back(std::move(template_url));
2493 AddToMaps(template_url_ptr);
jeanluc@google.comb6fd1fa72010-10-05 23:49:312494
ianwena27b1df2016-09-23 23:08:232495 if (newly_adding && (template_url_ptr->type() == TemplateURL::NORMAL)) {
Zinovy Nis701103b2018-05-10 22:59:382496 if (web_data_service_)
avi8a64b715b2016-09-02 17:30:042497 web_data_service_->AddKeyword(template_url_ptr->data());
stevet@chromium.orgd88cb202011-08-17 20:03:012498
pkasting@chromium.org3954c3a2012-04-10 20:17:552499 // Inform sync of the addition. Note that this will assign a GUID to
mpearson6456fb62015-11-13 06:44:282500 // template_url and add it to the guid_to_turl_.
avi8a64b715b2016-09-02 17:30:042501 ProcessTemplateURLChange(FROM_HERE, template_url_ptr,
zea@chromium.orgb78170f2012-07-11 03:34:262502 syncer::SyncChange::ACTION_ADD);
pkasting@chromium.org3954c3a2012-04-10 20:17:552503 }
Esmael El-Moslimanyb4306e02018-04-18 21:24:332504
2505 if (template_url_ptr)
2506 model_mutated_notification_pending_ = true;
2507
avi8a64b715b2016-09-02 17:30:042508 return template_url_ptr;
jeanluc@google.comb6fd1fa72010-10-05 23:49:312509}
2510
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452511// |template_urls| are the TemplateURLs loaded from the database.
erikwright@chromium.orga80ec962014-05-12 00:55:382512// |default_from_prefs| is the default search provider from the preferences, or
2513// NULL if the DSE is not policy-defined.
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452514//
2515// This function removes from the vector and the database all the TemplateURLs
Fabio Tirelo41316deb2023-11-24 21:33:112516// that were set by policy as default provider, unless it is the current default
2517// search provider, in which case it is updated with the data from prefs.
2518void TemplateURLService::UpdateDefaultProvidersCreatedByPolicy(
avi8a64b715b2016-09-02 17:30:042519 OwnedTemplateURLVector* template_urls,
Igor Ruvinovf266d2a42022-06-24 17:52:162520 const TemplateURLData* default_from_prefs,
2521 bool is_mandatory) {
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452522 DCHECK(template_urls);
erikwright@chromium.orga80ec962014-05-12 00:55:382523
Esmael El-Moslimanyb4306e02018-04-18 21:24:332524 Scoper scoper(this);
2525
avi8a64b715b2016-09-02 17:30:042526 for (auto i = template_urls->begin(); i != template_urls->end();) {
2527 TemplateURL* template_url = i->get();
Fabio Tirelo6e6df8bf2023-11-22 17:00:152528 if (template_url->created_by_policy() ==
2529 TemplateURLData::CreatedByPolicy::kDefaultSearchProvider) {
erikwright@chromium.orga80ec962014-05-12 00:55:382530 if (default_from_prefs &&
hashimoto@chromium.orgce7ee5f2014-06-16 23:41:192531 TemplateURL::MatchesData(template_url, default_from_prefs,
2532 search_terms_data())) {
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452533 // If the database specified a default search provider that was set
2534 // by policy, and the default search provider from the preferences
2535 // is also set by policy and they are the same, keep the entry in the
2536 // database and the |default_search_provider|.
erikwright@chromium.orga80ec962014-05-12 00:55:382537 default_search_provider_ = template_url;
2538 // Prevent us from saving any other entries, or creating a new one.
avi8a64b715b2016-09-02 17:30:042539 default_from_prefs = nullptr;
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452540 ++i;
2541 continue;
2542 }
2543
Igor Ruvinovac41f8d2023-04-21 14:09:282544 // If the previous default search provider was set as a recommended policy
2545 // and the new provider is not set by policy, keep the previous provider
2546 // in the database. This allows the recommended provider to remain in the
2547 // list if the user switches to a different provider.
2548 if (template_url->enforced_by_policy() || default_from_prefs) {
2549 TemplateURLID id = template_url->id();
2550 RemoveFromMaps(template_url);
2551 i = template_urls->erase(i);
2552 if (web_data_service_) {
2553 web_data_service_->RemoveKeyword(id);
2554 }
2555 } else {
2556 ++i;
2557 }
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452558 } else {
2559 ++i;
2560 }
2561 }
erikwright@chromium.orga80ec962014-05-12 00:55:382562
2563 if (default_from_prefs) {
avi8a64b715b2016-09-02 17:30:042564 default_search_provider_ = nullptr;
Igor Ruvinovf266d2a42022-06-24 17:52:162565 default_search_provider_source_ =
2566 is_mandatory ? DefaultSearchManager::FROM_POLICY
2567 : DefaultSearchManager::FROM_POLICY_RECOMMENDED;
erikwright@chromium.orga80ec962014-05-12 00:55:382568 TemplateURLData new_data(*default_from_prefs);
2569 if (new_data.sync_guid.empty())
Mikel Astizb1c1cac2019-04-18 12:51:482570 new_data.GenerateSyncGUID();
Fabio Tirelo6e6df8bf2023-11-22 17:00:152571 new_data.created_by_policy =
2572 TemplateURLData::CreatedByPolicy::kDefaultSearchProvider;
Igor Ruvinovac41f8d2023-04-21 14:09:282573 new_data.enforced_by_policy = is_mandatory;
Tommy Libf31dec2020-12-17 21:26:352574 new_data.safe_for_autoreplace = false;
Angela Yoeurngca315d62021-11-15 19:55:282575 new_data.is_active = TemplateURLData::ActiveStatus::kTrue;
avi8a64b715b2016-09-02 17:30:042576 std::unique_ptr<TemplateURL> new_dse_ptr =
Jinho Bangfa6b5752018-01-03 20:52:192577 std::make_unique<TemplateURL>(new_data);
avi8a64b715b2016-09-02 17:30:042578 TemplateURL* new_dse = new_dse_ptr.get();
Esmael El-Moslimanyb4306e02018-04-18 21:24:332579 if (Add(std::move(new_dse_ptr)))
erikwright@chromium.orga80ec962014-05-12 00:55:382580 default_search_provider_ = new_dse;
2581 }
joaodasilva@chromium.org18b5f6842011-05-23 11:51:452582}
stevet@chromium.orgd88cb202011-08-17 20:03:012583
Esmael El-Moslimanyb4306e02018-04-18 21:24:332584void TemplateURLService::ResetTemplateURLGUID(TemplateURL* url,
stevet@chromium.orgd88cb202011-08-17 20:03:012585 const std::string& guid) {
pkasting@chromium.org7b596f62012-05-08 01:34:252586 DCHECK(loaded_);
stevet@chromium.orgd88cb202011-08-17 20:03:012587 DCHECK(!guid.empty());
2588
pkasting@chromium.org573889f22012-04-07 01:31:542589 TemplateURLData data(url->data());
2590 data.sync_guid = guid;
Esmael El-Moslimanyb4306e02018-04-18 21:24:332591 Update(url, TemplateURL(data));
stevet@chromium.orgd88cb202011-08-17 20:03:012592}
2593
Esmael El-Moslimanyb4306e02018-04-18 21:24:332594void TemplateURLService::MergeInSyncTemplateURL(
stevet@chromium.orgd88cb202011-08-17 20:03:012595 TemplateURL* sync_turl,
stevet@chromium.org7d574f0b2012-08-14 21:32:512596 const SyncDataMap& sync_data,
2597 syncer::SyncChangeList* change_list,
Victor Hugo Vianna Silva8aad161a2020-04-16 10:52:422598 SyncDataMap* local_data) {
stevet@chromium.orgd88cb202011-08-17 20:03:012599 DCHECK(sync_turl);
stevet@chromium.org7d574f0b2012-08-14 21:32:512600 DCHECK(!GetTemplateURLForGUID(sync_turl->sync_guid()));
2601 DCHECK(IsFromSync(sync_turl, sync_data));
stevet@chromium.orgf024fd52011-12-02 20:43:092602
stevet@chromium.org7d574f0b2012-08-14 21:32:512603 bool should_add_sync_turl = true;
Esmael El-Moslimanyb4306e02018-04-18 21:24:332604
2605 Scoper scoper(this);
stevet@chromium.orgf024fd52011-12-02 20:43:092606
Tommy Li2f7daef2020-12-03 21:09:122607 // First resolve conflicts with local duplicate keyword NORMAL TemplateURLs,
2608 // working from best to worst.
2609 DCHECK(sync_turl->type() == TemplateURL::NORMAL);
2610 std::vector<TemplateURL*> local_duplicates;
Angela Yoeurngcec8a4642023-06-15 20:33:592611 const auto match_range = keyword_to_turl_.equal_range(sync_turl->keyword());
Tommy Li2f7daef2020-12-03 21:09:122612 for (auto it = match_range.first; it != match_range.second; ++it) {
Angela Yoeurngcec8a4642023-06-15 20:33:592613 TemplateURL* local_turl = it->second;
Tommy C. Li31563242023-02-16 16:59:492614 // The conflict resolution code below sometimes resets the TemplateURL's
2615 // GUID, which can trigger deleting any Policy-created engines. Avoid this
2616 // use-after-free bug by excluding any Policy-created engines. Also exclude
2617 // Play API created engines, as those also seem local-only and should not
2618 // be merged into Synced engines. crbug.com/1414224.
2619 if (local_turl->type() == TemplateURL::NORMAL &&
Fabio Tirelo6e6df8bf2023-11-22 17:00:152620 local_turl->created_by_policy() ==
2621 TemplateURLData::CreatedByPolicy::kNoPolicy &&
Tommy C. Li31563242023-02-16 16:59:492622 !local_turl->created_from_play_api()) {
Tommy Li2f7daef2020-12-03 21:09:122623 local_duplicates.push_back(local_turl);
2624 }
2625 }
2626 base::ranges::sort(local_duplicates, [&](const auto& a, const auto& b) {
Fabio Tireloc0de81c2024-01-10 18:07:152627 return a->IsBetterThanConflictingEngine(b);
Tommy Li2f7daef2020-12-03 21:09:122628 });
2629 for (TemplateURL* conflicting_turl : local_duplicates) {
stevet@chromium.org7d574f0b2012-08-14 21:32:512630 if (IsFromSync(conflicting_turl, sync_data)) {
2631 // |conflicting_turl| is already known to Sync, so we're not allowed to
Tommy Li2f7daef2020-12-03 21:09:122632 // remove it. Just leave it. TemplateURLService can tolerate duplicates.
2633 // TODO(tommycli): Eventually we should figure out a way to merge
2634 // substantively identical ones or somehow otherwise cull the herd.
2635 continue;
stevet@chromium.org7d574f0b2012-08-14 21:32:512636 }
Tommy Li2f7daef2020-12-03 21:09:122637
2638 // |conflicting_turl| is not yet known to Sync. If it is better, then we
2639 // want to transfer its values up to sync. Otherwise, we remove it and
2640 // allow the entry from Sync to overtake it in the model.
2641 const std::string guid = conflicting_turl->sync_guid();
2642 if (conflicting_turl == GetDefaultSearchProvider() ||
Fabio Tireloc0de81c2024-01-10 18:07:152643 conflicting_turl->IsBetterThanConflictingEngine(sync_turl)) {
Tommy Li2f7daef2020-12-03 21:09:122644 ResetTemplateURLGUID(conflicting_turl, sync_turl->sync_guid());
Peter Kastingbef7bc522021-09-08 17:53:302645 syncer::SyncData updated_sync_data =
Tommy Li2f7daef2020-12-03 21:09:122646 CreateSyncDataFromTemplateURL(*conflicting_turl);
2647 change_list->push_back(syncer::SyncChange(
Peter Kastingbef7bc522021-09-08 17:53:302648 FROM_HERE, syncer::SyncChange::ACTION_UPDATE, updated_sync_data));
Tommy Li2f7daef2020-12-03 21:09:122649 // Note that in this case we do not add the Sync TemplateURL to the
2650 // local model, since we've effectively "merged" it in by updating the
2651 // local conflicting entry with its sync_guid.
2652 should_add_sync_turl = false;
2653 } else {
2654 // We guarantee that this isn't the local search provider. Otherwise,
2655 // local would have won.
2656 DCHECK(conflicting_turl != GetDefaultSearchProvider());
2657 Remove(conflicting_turl);
2658 }
2659 // This TemplateURL was either removed or overwritten in the local model.
2660 // Remove the entry from the local data so it isn't pushed up to Sync.
2661 local_data->erase(guid);
2662 }
2663
Angela Yoeurng500c3102022-04-26 20:05:532664 // Try to take over a local built-in (prepopulated or starter pack) entry,
2665 // assuming we haven't already run into a keyword conflict.
2666 if (local_duplicates.empty() &&
2667 (sync_turl->prepopulate_id() != 0 || sync_turl->starter_pack_id() != 0)) {
pnoland8ec6ae82016-06-15 21:40:222668 // Check for a turl with a conflicting prepopulate_id. This detects the case
2669 // where the user changes a prepopulated engine's keyword on one client,
2670 // then begins syncing on another client. We want to reflect this keyword
2671 // change to that prepopulated URL on other clients instead of assuming that
2672 // the modified TemplateURL is a new entity.
Angela Yoeurng500c3102022-04-26 20:05:532673 TemplateURL* conflicting_built_in_turl =
2674 (sync_turl->prepopulate_id() != 0)
2675 ? FindPrepopulatedTemplateURL(sync_turl->prepopulate_id())
2676 : FindStarterPackTemplateURL(sync_turl->starter_pack_id());
pnoland8ec6ae82016-06-15 21:40:222677
2678 // If we found a conflict, and the sync entity is better, apply the remote
2679 // changes locally. We consider |sync_turl| better if it's been modified
2680 // more recently and the local TemplateURL isn't yet known to sync. We will
2681 // consider the sync entity better even if the local TemplateURL is the
2682 // current default, since in this case being default does not necessarily
2683 // mean the user explicitly set this TemplateURL as default. If we didn't do
2684 // this, changes to the keywords of prepopulated default engines would never
2685 // be applied to other clients.
2686 // If we can't safely replace the local entry with the synced one, or merge
2687 // the relevant changes in, we give up and leave both intact.
Angela Yoeurng500c3102022-04-26 20:05:532688 if (conflicting_built_in_turl &&
2689 !IsFromSync(conflicting_built_in_turl, sync_data) &&
Fabio Tireloc0de81c2024-01-10 18:07:152690 sync_turl->IsBetterThanConflictingEngine(conflicting_built_in_turl)) {
Angela Yoeurng500c3102022-04-26 20:05:532691 std::string guid = conflicting_built_in_turl->sync_guid();
2692 if (conflicting_built_in_turl == default_search_provider_) {
Jack Yammineabb0ba22023-07-20 10:46:342693 bool pref_matched = GetDefaultSearchProviderPrefValue(*prefs_) ==
2694 default_search_provider_->sync_guid();
Marc Treibdd87e8c2018-07-10 16:41:022695 // Update the existing engine in-place.
2696 Update(default_search_provider_, TemplateURL(sync_turl->data()));
2697 // If prefs::kSyncedDefaultSearchProviderGUID matched
2698 // |default_search_provider_|'s GUID before, then update it to match its
2699 // new GUID. If the pref didn't match before, then it probably refers to
2700 // a new search engine from Sync which just hasn't been added locally
2701 // yet, so leave it alone in that case.
2702 if (pref_matched) {
Jack Yammineabb0ba22023-07-20 10:46:342703 SetDefaultSearchProviderPrefValue(
2704 *prefs_, default_search_provider_->sync_guid());
Marc Treibdd87e8c2018-07-10 16:41:022705 }
2706
Marc Treib53671752018-07-10 09:55:252707 should_add_sync_turl = false;
pnoland8ec6ae82016-06-15 21:40:222708 } else {
Angela Yoeurng500c3102022-04-26 20:05:532709 Remove(conflicting_built_in_turl);
pnoland8ec6ae82016-06-15 21:40:222710 }
2711 // Remove the local data so it isn't written to sync.
2712 local_data->erase(guid);
2713 }
stevet@chromium.org7d574f0b2012-08-14 21:32:512714 }
2715
2716 if (should_add_sync_turl) {
pkasting@chromium.org573889f22012-04-07 01:31:542717 // Force the local ID to kInvalidTemplateURLID so we can add it.
stevet@chromium.org7d574f0b2012-08-14 21:32:512718 TemplateURLData data(sync_turl->data());
2719 data.id = kInvalidTemplateURLID;
avi8a64b715b2016-09-02 17:30:042720 std::unique_ptr<TemplateURL> added_ptr =
Jinho Bangfa6b5752018-01-03 20:52:192721 std::make_unique<TemplateURL>(data);
avi8a64b715b2016-09-02 17:30:042722 TemplateURL* added = added_ptr.get();
erikwright@chromium.orga80ec962014-05-12 00:55:382723 base::AutoReset<DefaultSearchChangeOrigin> change_origin(
2724 &dsp_change_origin_, DSP_CHANGE_SYNC_ADD);
Esmael El-Moslimanyb4306e02018-04-18 21:24:332725 if (Add(std::move(added_ptr)))
Vasilii Sukhanov9e32af72018-04-17 18:47:152726 MaybeUpdateDSEViaPrefs(added);
stevet@chromium.orgd88cb202011-08-17 20:03:012727 }
2728}
stevet@chromium.orga1eecd42011-11-08 19:52:142729
2730void TemplateURLService::PatchMissingSyncGUIDs(
avi8a64b715b2016-09-02 17:30:042731 OwnedTemplateURLVector* template_urls) {
stevet@chromium.orga1eecd42011-11-08 19:52:142732 DCHECK(template_urls);
avi8a64b715b2016-09-02 17:30:042733 for (auto& template_url : *template_urls) {
stevet@chromium.orga1eecd42011-11-08 19:52:142734 DCHECK(template_url);
vasilii@chromium.orgbdcbcd82013-10-28 13:40:252735 if (template_url->sync_guid().empty() &&
ianwena27b1df2016-09-23 23:08:232736 (template_url->type() == TemplateURL::NORMAL)) {
Mikel Astizb1c1cac2019-04-18 12:51:482737 template_url->data_.GenerateSyncGUID();
Zinovy Nis701103b2018-05-10 22:59:382738 if (web_data_service_)
hashimoto@chromium.org37b324602014-07-02 07:30:492739 web_data_service_->UpdateKeyword(template_url->data());
stevet@chromium.orga1eecd42011-11-08 19:52:142740 }
2741 }
2742}
vasilii@chromium.org4a40facd2013-05-29 14:44:562743
Jack Yammineabb0ba22023-07-20 10:46:342744void TemplateURLService::OnDefaultSearchProviderGUIDChanged() {
erikwright@chromium.orga80ec962014-05-12 00:55:382745 base::AutoReset<DefaultSearchChangeOrigin> change_origin(
2746 &dsp_change_origin_, DSP_CHANGE_SYNC_PREF);
vasilii@chromium.org4a40facd2013-05-29 14:44:562747
Jack Yammineabb0ba22023-07-20 10:46:342748 std::string new_guid = GetDefaultSearchProviderPrefValue(*prefs_);
erikwright@chromium.orga80ec962014-05-12 00:55:382749 if (new_guid.empty()) {
2750 default_search_manager_.ClearUserSelectedDefaultSearchEngine();
2751 return;
vasilii@chromium.org4a40facd2013-05-29 14:44:562752 }
erikwright@chromium.orga80ec962014-05-12 00:55:382753
jeffschiller612137a2017-04-19 04:34:242754 const TemplateURL* turl = GetTemplateURLForGUID(new_guid);
Mihai Sardarescu27fe06b2024-03-12 10:07:462755 if (turl) {
2756 default_search_manager_.SetUserSelectedDefaultSearchEngine(
2757 turl->data(), search_engines::ChoiceMadeLocation::kOther);
2758 }
vasilii@chromium.org4a40facd2013-05-29 14:44:562759}
2760
Angela Yoeurng7ff86ed52021-08-12 20:03:242761void TemplateURLService::MaybeSetIsActiveSearchEngines(
2762 OwnedTemplateURLVector* template_urls) {
2763 DCHECK(template_urls);
2764 for (auto& turl : *template_urls) {
2765 DCHECK(turl);
2766 // An turl is "active" if it has ever been used or manually added/modified.
2767 // |safe_for_autoreplace| is false if the entry has been modified.
2768 if (turl->is_active() == TemplateURLData::ActiveStatus::kUnspecified &&
2769 (!turl->safe_for_autoreplace() || turl->usage_count() > 0)) {
2770 turl->data_.is_active = TemplateURLData::ActiveStatus::kTrue;
Angela Yoeurng38806b42022-10-13 00:20:542771 turl->data_.safe_for_autoreplace = false;
Angela Yoeurng7ff86ed52021-08-12 20:03:242772 if (web_data_service_)
2773 web_data_service_->UpdateKeyword(turl->data());
2774 }
2775 }
2776}
2777
mpearson6456fb62015-11-13 06:44:282778template <typename Container>
2779void TemplateURLService::AddMatchingKeywordsHelper(
Angela Yoeurngcec8a4642023-06-15 20:33:592780 const Container& keyword_to_turl,
Jan Wilken Dörriefa241ba2021-03-11 17:57:012781 const std::u16string& prefix,
mpearson6456fb62015-11-13 06:44:282782 bool supports_replacement_only,
Angela Yoeurngcec8a4642023-06-15 20:33:592783 TemplateURLVector* matches) {
mpearson6456fb62015-11-13 06:44:282784 // Sanity check args.
2785 if (prefix.empty())
2786 return;
2787 DCHECK(matches);
2788
2789 // Find matching keyword range. Searches the element map for keywords
2790 // beginning with |prefix| and stores the endpoints of the resulting set in
2791 // |match_range|.
2792 const auto match_range(std::equal_range(
Angela Yoeurngcec8a4642023-06-15 20:33:592793 keyword_to_turl.begin(), keyword_to_turl.end(),
2794 typename Container::value_type(prefix, nullptr), LessWithPrefix()));
mpearson6456fb62015-11-13 06:44:282795
2796 // Add to vector of matching keywords.
2797 for (typename Container::const_iterator i(match_range.first);
2798 i != match_range.second; ++i) {
2799 if (!supports_replacement_only ||
Angela Yoeurngcec8a4642023-06-15 20:33:592800 i->second->url_ref().SupportsReplacement(search_terms_data())) {
mpearson6456fb62015-11-13 06:44:282801 matches->push_back(i->second);
Angela Yoeurngcec8a4642023-06-15 20:33:592802 }
mpearson6456fb62015-11-13 06:44:282803 }
2804}
2805
erikwright@chromium.orga80ec962014-05-12 00:55:382806TemplateURL* TemplateURLService::FindPrepopulatedTemplateURL(
2807 int prepopulated_id) {
Patrick Nolandc878eec2017-10-02 17:32:532808 DCHECK(prepopulated_id);
avi8a64b715b2016-09-02 17:30:042809 for (const auto& turl : template_urls_) {
2810 if (turl->prepopulate_id() == prepopulated_id)
2811 return turl.get();
vasilii@chromium.org4a40facd2013-05-29 14:44:562812 }
avi8a64b715b2016-09-02 17:30:042813 return nullptr;
vasilii@chromium.org4a40facd2013-05-29 14:44:562814}
vasilii@chromium.orga50e38f2013-07-22 16:47:002815
Angela Yoeurng500c3102022-04-26 20:05:532816TemplateURL* TemplateURLService::FindStarterPackTemplateURL(
2817 int starter_pack_id) {
2818 DCHECK(starter_pack_id);
2819 for (const auto& turl : template_urls_) {
2820 if (turl->starter_pack_id() == starter_pack_id)
2821 return turl.get();
2822 }
2823 return nullptr;
2824}
2825
vasilii@chromium.orgbdcbcd82013-10-28 13:40:252826TemplateURL* TemplateURLService::FindTemplateURLForExtension(
2827 const std::string& extension_id,
erikwright@chromium.org8f1f8ac2014-05-11 23:23:052828 TemplateURL::Type type) {
vasilii@chromium.orgbdcbcd82013-10-28 13:40:252829 DCHECK_NE(TemplateURL::NORMAL, type);
avi8a64b715b2016-09-02 17:30:042830 for (const auto& turl : template_urls_) {
ianwena27b1df2016-09-23 23:08:232831 if (turl->type() == type && turl->GetExtensionId() == extension_id)
avi8a64b715b2016-09-02 17:30:042832 return turl.get();
vasilii@chromium.orgbdcbcd82013-10-28 13:40:252833 }
avi8a64b715b2016-09-02 17:30:042834 return nullptr;
vasilii@chromium.orgbdcbcd82013-10-28 13:40:252835}
2836
a-v-yf4eb4352017-02-25 18:46:592837TemplateURL* TemplateURLService::FindMatchingDefaultExtensionTemplateURL(
2838 const TemplateURLData& data) {
avi8a64b715b2016-09-02 17:30:042839 for (const auto& turl : template_urls_) {
a-v-yf4eb4352017-02-25 18:46:592840 if (turl->type() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION &&
2841 turl->extension_info_->wants_to_be_default_engine &&
avi8a64b715b2016-09-02 17:30:042842 TemplateURL::MatchesData(turl.get(), &data, search_terms_data()))
2843 return turl.get();
erikwright@chromium.orga80ec962014-05-12 00:55:382844 }
avi8a64b715b2016-09-02 17:30:042845 return nullptr;
erikwright@chromium.orga80ec962014-05-12 00:55:382846}
Tommy Li276535cc2020-11-23 19:11:012847
2848bool TemplateURLService::RemoveDuplicateReplaceableEnginesOf(
2849 TemplateURL* candidate) {
2850 DCHECK(candidate);
Fabio Tirelob64e49b2023-12-15 01:41:022851
2852 // Do not replace existing search engines if `candidate` was created by the
2853 // `SiteSearchSettings` policy.
2854 if (candidate->created_by_policy() ==
2855 TemplateURLData::CreatedByPolicy::kSiteSearch) {
2856 return false;
2857 }
2858
Jan Wilken Dörriefa241ba2021-03-11 17:57:012859 const std::u16string& keyword = candidate->keyword();
Tommy Li276535cc2020-11-23 19:11:012860
2861 // If there's not at least one conflicting TemplateURL, there's nothing to do.
Angela Yoeurngcec8a4642023-06-15 20:33:592862 const auto match_range = keyword_to_turl_.equal_range(keyword);
Tommy Li276535cc2020-11-23 19:11:012863 if (match_range.first == match_range.second) {
2864 return false;
2865 }
2866
Tommy Lic773bad2020-12-17 23:51:152867 // Gather the replaceable TemplateURLs to be removed. We don't do it in-place,
2868 // because Remove() invalidates iterators.
Tommy Li276535cc2020-11-23 19:11:012869 std::vector<TemplateURL*> replaceable_turls;
2870 for (auto it = match_range.first; it != match_range.second; ++it) {
Angela Yoeurngcec8a4642023-06-15 20:33:592871 TemplateURL* turl = it->second;
Tommy Li276535cc2020-11-23 19:11:012872 DCHECK_NE(turl, candidate) << "This algorithm runs BEFORE |candidate| is "
2873 "added to the keyword map.";
2874
Angela Yoeurng791a9562022-07-22 02:51:402875 // Built-in engines are marked as safe_for_autoreplace(). But because
Tommy Li2d7174dc2021-01-13 19:45:282876 // they are shown in the Default Search Engines Settings UI, users would
2877 // find it confusing if they were ever automatically removed.
2878 // https://crbug.com/1164024
Angela Yoeurng791a9562022-07-22 02:51:402879 if (turl->safe_for_autoreplace() && turl->prepopulate_id() == 0 &&
2880 turl->starter_pack_id() == 0) {
Tommy Li276535cc2020-11-23 19:11:012881 replaceable_turls.push_back(turl);
Tommy Li276535cc2020-11-23 19:11:012882 }
2883 }
Tommy Lic773bad2020-12-17 23:51:152884
2885 // Find the BEST engine for |keyword| factoring in the new |candidate|.
2886 TemplateURL* best = GetTemplateURLForKeyword(keyword);
Fabio Tireloc0de81c2024-01-10 18:07:152887 if (!best || candidate->IsBetterThanConflictingEngine(best)) {
Tommy Lic773bad2020-12-17 23:51:152888 best = candidate;
Tommy Li276535cc2020-11-23 19:11:012889 }
2890
Tommy Lic773bad2020-12-17 23:51:152891 // Remove all the replaceable TemplateURLs that are not the best.
Tommy Li276535cc2020-11-23 19:11:012892 for (TemplateURL* turl : replaceable_turls) {
Tommy Lic773bad2020-12-17 23:51:152893 DCHECK_NE(turl, candidate);
2894
Tommy Libf31dec2020-12-17 21:26:352895 // Never actually remove the DSE during this phase. This handling defers
2896 // deleting the DSE until it's no longer set as the DSE, analagous to how
2897 // we handle ACTION_DELETE of the DSE in ProcessSyncChanges().
Tommy Libc7e63b2021-01-14 05:14:242898 if (turl != best && !MatchesDefaultSearchProvider(turl)) {
Tommy Li276535cc2020-11-23 19:11:012899 Remove(turl);
2900 }
2901 }
2902
2903 // Caller needs to know if |candidate| would have been deleted.
Tommy Li2d7174dc2021-01-13 19:45:282904 // Also always successfully add prepopulated engines, for two reasons:
2905 // 1. The DSE repair logic in ApplyDefaultSearchChangeNoMetrics() relies on
2906 // Add()ing back the DSE always succeeding. https://crbug.com/1164024
2907 // 2. If we don't do this, we have a weird order-dependence on the
2908 // replaceability of prepopulated engines, given that we refuse to add
2909 // prepopulated engines to the |replaceable_engines| vector.
2910 //
2911 // Given the necessary special casing of prepopulated engines, we may consider
2912 // marking prepopulated engines as NOT safe_for_autoreplace(), but there's a
2913 // few obstacles to this:
2914 // 1. Prepopulated engines are not user-created, and therefore meet the
2915 // definition of safe_for_autoreplace().
2916 // 2. If we mark them as NOT safe_for_autoreplace(), we can no longer
2917 // distinguish between prepopulated engines that user has edited, vs. not
2918 // edited.
2919 //
2920 // One more caveat: In 2019, we made prepopulated engines have a
2921 // deterministically generated Sync GUID in order to prevent duplicate
2922 // prepopulated engines when two clients start syncing at the same time.
2923 // When combined with the requirement that we can never fail to add a
2924 // prepopulated engine, this could leads to two engines having the same GUID.
2925 //
2926 // TODO(tommycli): After M89, we need to investigate solving the contradiction
2927 // above. Most probably: the solution is to stop Syncing prepopulated engines
2928 // and make the GUIDs actually globally unique again.
2929 return candidate != best && candidate->safe_for_autoreplace() &&
Angela Yoeurng791a9562022-07-22 02:51:402930 candidate->prepopulate_id() == 0 && candidate->starter_pack_id() == 0;
Tommy Li276535cc2020-11-23 19:11:012931}
Tommy Libc7e63b2021-01-14 05:14:242932
2933bool TemplateURLService::MatchesDefaultSearchProvider(TemplateURL* turl) const {
2934 DCHECK(turl);
2935 const TemplateURL* default_provider = GetDefaultSearchProvider();
2936 if (!default_provider)
2937 return false;
2938
2939 return turl->sync_guid() == default_provider->sync_guid();
2940}
Fabio Tirelo78ca8262023-10-31 14:53:182941
2942std::unique_ptr<EnterpriseSiteSearchManager>
2943TemplateURLService::GetEnterpriseSiteSearchManager(PrefService* prefs) {
2944#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
Fabio Tirelo9fc885b92024-01-25 17:19:442945 BUILDFLAG(IS_CHROMEOS)
Fabio Tirelo78ca8262023-10-31 14:53:182946 return base::FeatureList::IsEnabled(omnibox::kSiteSearchSettingsPolicy)
2947 ? std::make_unique<EnterpriseSiteSearchManager>(
2948 prefs, base::BindRepeating(
2949 &TemplateURLService::EnterpriseSiteSearchChanged,
2950 base::Unretained(this)))
2951 : nullptr;
2952#else
2953 return nullptr;
2954#endif
2955}
Fabio Tirelo5efc04da2024-01-04 21:31:372956
2957void TemplateURLService::LogSiteSearchPolicyConflict(
2958 const TemplateURLService::OwnedTemplateURLVector&
2959 policy_site_search_engines) {
2960 if (policy_site_search_engines.empty()) {
2961 // No need to record conflict histograms if the SiteSearchSettings policy
2962 // doesn't create any search engine.
2963 return;
2964 }
2965
2966 bool has_conflict_with_featured = false;
2967 bool has_conflict_with_non_featured = false;
2968 for (const auto& policy_turl : policy_site_search_engines) {
2969 const std::u16string& keyword = policy_turl->keyword();
2970 CHECK(!keyword.empty());
2971
2972 const auto match_range = keyword_to_turl_.equal_range(keyword);
2973 bool conflicts_with_active =
2974 std::any_of(match_range.first, match_range.second,
2975 [](const KeywordToTURL::value_type& entry) {
2976 return entry.second->created_by_policy() ==
2977 TemplateURLData::CreatedByPolicy::kNoPolicy &&
2978 !entry.second->safe_for_autoreplace();
2979 });
2980 SiteSearchPolicyConflictType type =
2981 conflicts_with_active
2982 ? (policy_turl->featured_by_policy()
2983 ? SiteSearchPolicyConflictType::kWithFeatured
2984 : SiteSearchPolicyConflictType::kWithNonFeatured)
2985 : SiteSearchPolicyConflictType::kNone;
2986 base::UmaHistogramEnumeration(kSiteSearchPolicyConflictCountHistogramName,
2987 type);
2988
2989 has_conflict_with_featured |=
2990 type == SiteSearchPolicyConflictType::kWithFeatured;
2991 has_conflict_with_non_featured |=
2992 type == SiteSearchPolicyConflictType::kWithNonFeatured;
2993 }
2994
2995 base::UmaHistogramBoolean(
2996 kSiteSearchPolicyHasConflictWithFeaturedHistogramName,
2997 has_conflict_with_featured);
2998 base::UmaHistogramBoolean(
2999 kSiteSearchPolicyHasConflictWithNonFeaturedHistogramName,
3000 has_conflict_with_non_featured);
3001}