1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/search_engines/util.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_vector.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/time/time.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search_engines/template_url.h"
18 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
19 #include "chrome/browser/search_engines/template_url_service.h"
20 #include "chrome/browser/search_engines/template_url_service_factory.h"
21 #include "content/public/browser/browser_thread.h"
23 using content::BrowserThread
;
25 base::string16
GetDefaultSearchEngineName(Profile
* profile
) {
28 return base::string16();
30 const TemplateURL
* const default_provider
=
31 TemplateURLServiceFactory::GetForProfile(profile
)->
32 GetDefaultSearchProvider();
33 if (!default_provider
) {
34 // TODO(cpu): bug 1187517. It is possible to have no default provider.
35 // returning an empty string is a stopgap measure for the crash
36 // http://code.google.com/p/chromium/issues/detail?id=2573
37 return base::string16();
39 return default_provider
->short_name();
42 GURL
GetDefaultSearchURLForSearchTerms(Profile
* profile
,
43 const base::string16
& terms
) {
45 const TemplateURL
* default_provider
=
46 TemplateURLServiceFactory::GetForProfile(profile
)->
47 GetDefaultSearchProvider();
48 if (!default_provider
)
50 const TemplateURLRef
& search_url
= default_provider
->url_ref();
51 DCHECK(search_url
.SupportsReplacement());
52 TemplateURLRef::SearchTermsArgs
search_terms_args(terms
);
53 search_terms_args
.append_extra_query_params
= true;
54 return GURL(search_url
.ReplaceSearchTerms(search_terms_args
));
57 void RemoveDuplicatePrepopulateIDs(
58 WebDataService
* service
,
59 const ScopedVector
<TemplateURLData
>& prepopulated_urls
,
60 TemplateURL
* default_search_provider
,
61 TemplateURLService::TemplateURLVector
* template_urls
,
62 std::set
<std::string
>* removed_keyword_guids
) {
63 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
64 DCHECK(template_urls
);
66 // For convenience construct an ID->TemplateURL* map from |prepopulated_urls|.
67 typedef std::map
<int, TemplateURLData
*> PrepopulatedURLMap
;
68 PrepopulatedURLMap prepopulated_url_map
;
69 for (std::vector
<TemplateURLData
*>::const_iterator
i(
70 prepopulated_urls
.begin());
71 i
!= prepopulated_urls
.end();
73 prepopulated_url_map
[(*i
)->prepopulate_id
] = *i
;
75 // Separate |template_urls| into prepopulated and non-prepopulated groups.
76 typedef std::multimap
<int, TemplateURL
*> UncheckedURLMap
;
77 UncheckedURLMap unchecked_urls
;
78 TemplateURLService::TemplateURLVector checked_urls
;
79 for (TemplateURLService::TemplateURLVector::iterator
i(
80 template_urls
->begin()); i
!= template_urls
->end(); ++i
) {
81 TemplateURL
* turl
= *i
;
82 int prepopulate_id
= turl
->prepopulate_id();
84 unchecked_urls
.insert(std::make_pair(prepopulate_id
, turl
));
86 checked_urls
.push_back(turl
);
89 // For each group of prepopulated URLs with one ID, find the best URL to use
90 // and add it to the (initially all non-prepopulated) URLs we've already OKed.
91 // Delete the others from the service and from memory.
92 while (!unchecked_urls
.empty()) {
94 int prepopulate_id
= unchecked_urls
.begin()->first
;
95 PrepopulatedURLMap::const_iterator prepopulated_url
=
96 prepopulated_url_map
.find(prepopulate_id
);
97 UncheckedURLMap::iterator end
= unchecked_urls
.upper_bound(prepopulate_id
);
98 UncheckedURLMap::iterator best
= unchecked_urls
.begin();
99 bool matched_keyword
= false;
100 for (UncheckedURLMap::iterator i
= unchecked_urls
.begin(); i
!= end
; ++i
) {
101 // A URL is automatically the best if it's the default search engine.
102 if (i
->second
== default_search_provider
) {
107 // Otherwise, a URL is best if it matches the prepopulated data's keyword;
108 // if none match, just fall back to using the one with the lowest ID.
111 if ((prepopulated_url
!= prepopulated_url_map
.end()) &&
112 i
->second
->HasSameKeywordAs(*prepopulated_url
->second
)) {
114 matched_keyword
= true;
115 } else if (i
->second
->id() < best
->second
->id()) {
120 // Add the best URL to the checked group and delete the rest.
121 checked_urls
.push_back(best
->second
);
122 for (UncheckedURLMap::iterator i
= unchecked_urls
.begin(); i
!= end
; ++i
) {
126 service
->RemoveKeyword(i
->second
->id());
127 if (removed_keyword_guids
)
128 removed_keyword_guids
->insert(i
->second
->sync_guid());
133 // Done with this group.
134 unchecked_urls
.erase(unchecked_urls
.begin(), end
);
137 // Return the checked URLs.
138 template_urls
->swap(checked_urls
);
141 // Returns the TemplateURL with id specified from the list of TemplateURLs.
142 // If not found, returns NULL.
143 TemplateURL
* GetTemplateURLByID(
144 const TemplateURLService::TemplateURLVector
& template_urls
,
146 for (TemplateURLService::TemplateURLVector::const_iterator
i(
147 template_urls
.begin()); i
!= template_urls
.end(); ++i
) {
148 if ((*i
)->id() == id
) {
155 TemplateURL
* FindURLByPrepopulateID(
156 const TemplateURLService::TemplateURLVector
& template_urls
,
157 int prepopulate_id
) {
158 for (std::vector
<TemplateURL
*>::const_iterator i
= template_urls
.begin();
159 i
< template_urls
.end(); ++i
) {
160 if ((*i
)->prepopulate_id() == prepopulate_id
)
166 void MergeIntoPrepopulatedEngineData(const TemplateURL
* original_turl
,
167 TemplateURLData
* prepopulated_url
) {
168 DCHECK_EQ(original_turl
->prepopulate_id(), prepopulated_url
->prepopulate_id
);
169 if (!original_turl
->safe_for_autoreplace()) {
170 prepopulated_url
->safe_for_autoreplace
= false;
171 prepopulated_url
->SetKeyword(original_turl
->keyword());
172 prepopulated_url
->short_name
= original_turl
->short_name();
174 prepopulated_url
->id
= original_turl
->id();
175 prepopulated_url
->sync_guid
= original_turl
->sync_guid();
176 prepopulated_url
->date_created
= original_turl
->date_created();
177 prepopulated_url
->last_modified
= original_turl
->last_modified();
180 ActionsFromPrepopulateData::ActionsFromPrepopulateData() {}
182 ActionsFromPrepopulateData::~ActionsFromPrepopulateData() {}
184 // This is invoked when the version of the prepopulate data changes.
185 // If |removed_keyword_guids| is not NULL, the Sync GUID of each item removed
186 // from the DB will be added to it. Note that this function will take
187 // ownership of |prepopulated_urls| and will clear the vector.
188 void MergeEnginesFromPrepopulateData(
190 WebDataService
* service
,
191 ScopedVector
<TemplateURLData
>* prepopulated_urls
,
192 size_t default_search_index
,
193 TemplateURLService::TemplateURLVector
* template_urls
,
194 TemplateURL
** default_search_provider
,
195 std::set
<std::string
>* removed_keyword_guids
) {
196 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
197 DCHECK(prepopulated_urls
);
198 DCHECK(template_urls
);
199 DCHECK(default_search_provider
);
201 int default_prepopulated_id
=
202 (*prepopulated_urls
)[default_search_index
]->prepopulate_id
;
203 ActionsFromPrepopulateData
actions(CreateActionsFromCurrentPrepopulateData(
204 prepopulated_urls
, *template_urls
, *default_search_provider
));
207 for (std::vector
<TemplateURL
*>::iterator i
= actions
.removed_engines
.begin();
208 i
< actions
.removed_engines
.end(); ++i
) {
209 scoped_ptr
<TemplateURL
> template_url(*i
);
210 TemplateURLService::TemplateURLVector::iterator j
=
211 std::find(template_urls
->begin(), template_urls
->end(), template_url
);
212 DCHECK(j
!= template_urls
->end());
213 DCHECK(*j
!= *default_search_provider
);
214 template_urls
->erase(j
);
216 service
->RemoveKeyword(template_url
->id());
217 if (removed_keyword_guids
)
218 removed_keyword_guids
->insert(template_url
->sync_guid());
223 for (EditedEngines::iterator
i(actions
.edited_engines
.begin());
224 i
< actions
.edited_engines
.end(); ++i
) {
225 TemplateURLData
& data
= i
->second
;
226 scoped_ptr
<TemplateURL
> existing_url(i
->first
);
228 service
->UpdateKeyword(data
);
230 // Replace the entry in |template_urls| with the updated one.
231 TemplateURLService::TemplateURLVector::iterator j
= std::find(
232 template_urls
->begin(), template_urls
->end(), existing_url
.get());
233 *j
= new TemplateURL(profile
, data
);
234 if (*default_search_provider
== existing_url
.get())
235 *default_search_provider
= *j
;
239 for (std::vector
<TemplateURLData
>::const_iterator it
=
240 actions
.added_engines
.begin();
241 it
!= actions
.added_engines
.end();
243 template_urls
->push_back(new TemplateURL(profile
, *it
));
246 if (!*default_search_provider
) {
247 // The user had no existing default search provider, so set the
248 // default to the default prepopulated engine.
249 *default_search_provider
= FindURLByPrepopulateID(*template_urls
,
250 default_prepopulated_id
);
254 ActionsFromPrepopulateData
CreateActionsFromCurrentPrepopulateData(
255 ScopedVector
<TemplateURLData
>* prepopulated_urls
,
256 const TemplateURLService::TemplateURLVector
& existing_urls
,
257 const TemplateURL
* default_search_provider
) {
258 // Create a map to hold all provided |template_urls| that originally came from
259 // prepopulate data (i.e. have a non-zero prepopulate_id()).
260 typedef std::map
<int, TemplateURL
*> IDMap
;
262 for (TemplateURLService::TemplateURLVector::const_iterator
i(
263 existing_urls
.begin()); i
!= existing_urls
.end(); ++i
) {
264 int prepopulate_id
= (*i
)->prepopulate_id();
265 if (prepopulate_id
> 0)
266 id_to_turl
[prepopulate_id
] = *i
;
269 // For each current prepopulated URL, check whether |template_urls| contained
270 // a matching prepopulated URL. If so, update the passed-in URL to match the
271 // current data. (If the passed-in URL was user-edited, we persist the user's
272 // name and keyword.) If not, add the prepopulated URL.
273 ActionsFromPrepopulateData actions
;
274 for (size_t i
= 0; i
< prepopulated_urls
->size(); ++i
) {
275 // We take ownership of |prepopulated_urls[i]|.
276 scoped_ptr
<TemplateURLData
> prepopulated_url((*prepopulated_urls
)[i
]);
277 const int prepopulated_id
= prepopulated_url
->prepopulate_id
;
278 DCHECK_NE(0, prepopulated_id
);
280 IDMap::iterator
existing_url_iter(id_to_turl
.find(prepopulated_id
));
281 if (existing_url_iter
!= id_to_turl
.end()) {
282 // Update the data store with the new prepopulated data. Preserve user
283 // edits to the name and keyword.
284 TemplateURL
* existing_url(existing_url_iter
->second
);
285 id_to_turl
.erase(existing_url_iter
);
286 MergeIntoPrepopulatedEngineData(existing_url
, prepopulated_url
.get());
287 // Update last_modified to ensure that if this entry is later merged with
288 // entries from Sync, the conflict resolution logic knows that this was
289 // updated and propagates the new values to the server.
290 prepopulated_url
->last_modified
= base::Time::Now();
291 actions
.edited_engines
.push_back(
292 std::make_pair(existing_url
, *prepopulated_url
));
294 actions
.added_engines
.push_back(*prepopulated_url
);
297 // The above loop takes ownership of all the contents of prepopulated_urls.
298 // Clear the pointers.
299 prepopulated_urls
->weak_erase(prepopulated_urls
->begin(),
300 prepopulated_urls
->end());
302 // The block above removed all the URLs from the |id_to_turl| map that were
303 // found in the prepopulate data. Any remaining URLs that haven't been
304 // user-edited or made default can be removed from the data store.
305 for (IDMap::iterator
i(id_to_turl
.begin()); i
!= id_to_turl
.end(); ++i
) {
306 TemplateURL
* template_url
= i
->second
;
307 if ((template_url
->safe_for_autoreplace()) &&
308 (template_url
!= default_search_provider
))
309 actions
.removed_engines
.push_back(template_url
);
315 void GetSearchProvidersUsingKeywordResult(
316 const WDTypedResult
& result
,
317 WebDataService
* service
,
319 TemplateURLService::TemplateURLVector
* template_urls
,
320 TemplateURL
** default_search_provider
,
321 int* new_resource_keyword_version
,
322 std::set
<std::string
>* removed_keyword_guids
) {
323 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
324 DCHECK(template_urls
);
325 DCHECK(template_urls
->empty());
326 DCHECK(default_search_provider
);
327 DCHECK(*default_search_provider
== NULL
);
328 DCHECK_EQ(KEYWORDS_RESULT
, result
.GetType());
329 DCHECK(new_resource_keyword_version
);
331 WDKeywordsResult keyword_result
= reinterpret_cast<
332 const WDResult
<WDKeywordsResult
>*>(&result
)->GetValue();
334 for (KeywordTable::Keywords::iterator
i(keyword_result
.keywords
.begin());
335 i
!= keyword_result
.keywords
.end(); ++i
) {
336 // Fix any duplicate encodings in the local database. Note that we don't
337 // adjust the last_modified time of this keyword; this way, we won't later
338 // overwrite any changes on the sync server that happened to this keyword
339 // since the last time we synced. Instead, we also run a de-duping pass on
340 // the server-provided data in
341 // TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData() and
342 // update the server with the merged, de-duped results at that time. We
343 // still fix here, though, to correct problems in clients that have disabled
344 // search engine sync, since in that case that code will never be reached.
345 if (DeDupeEncodings(&i
->input_encodings
) && service
)
346 service
->UpdateKeyword(*i
);
347 template_urls
->push_back(new TemplateURL(profile
, *i
));
350 int64 default_search_provider_id
= keyword_result
.default_search_provider_id
;
351 if (default_search_provider_id
) {
352 *default_search_provider
=
353 GetTemplateURLByID(*template_urls
, default_search_provider_id
);
356 *new_resource_keyword_version
= keyword_result
.builtin_keyword_version
;
357 GetSearchProvidersUsingLoadedEngines(service
, profile
, template_urls
,
358 default_search_provider
,
359 new_resource_keyword_version
,
360 removed_keyword_guids
);
363 void GetSearchProvidersUsingLoadedEngines(
364 WebDataService
* service
,
366 TemplateURLService::TemplateURLVector
* template_urls
,
367 TemplateURL
** default_search_provider
,
368 int* resource_keyword_version
,
369 std::set
<std::string
>* removed_keyword_guids
) {
370 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
371 DCHECK(template_urls
);
372 DCHECK(default_search_provider
);
373 DCHECK(resource_keyword_version
);
374 PrefService
* prefs
= profile
? profile
->GetPrefs() : NULL
;
375 size_t default_search_index
;
376 ScopedVector
<TemplateURLData
> prepopulated_urls
=
377 TemplateURLPrepopulateData::GetPrepopulatedEngines(prefs
,
378 &default_search_index
);
379 RemoveDuplicatePrepopulateIDs(service
, prepopulated_urls
,
380 *default_search_provider
, template_urls
,
381 removed_keyword_guids
);
383 const int prepopulate_resource_keyword_version
=
384 TemplateURLPrepopulateData::GetDataVersion(prefs
);
385 if (*resource_keyword_version
< prepopulate_resource_keyword_version
) {
386 MergeEnginesFromPrepopulateData(profile
, service
, &prepopulated_urls
,
387 default_search_index
, template_urls
, default_search_provider
,
388 removed_keyword_guids
);
389 *resource_keyword_version
= prepopulate_resource_keyword_version
;
391 *resource_keyword_version
= 0;
395 bool DeDupeEncodings(std::vector
<std::string
>* encodings
) {
396 std::vector
<std::string
> deduped_encodings
;
397 std::set
<std::string
> encoding_set
;
398 for (std::vector
<std::string
>::const_iterator
i(encodings
->begin());
399 i
!= encodings
->end(); ++i
) {
400 if (encoding_set
.insert(*i
).second
)
401 deduped_encodings
.push_back(*i
);
403 encodings
->swap(deduped_encodings
);
404 return encodings
->size() != deduped_encodings
.size();