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 // If the user-selected DSE is a prepopulated engine its properties will
102 // either come from the prepopulation origin or from the user preferences
103 // file (see DefaultSearchManager). Those properties will end up
104 // overwriting whatever we load now anyway. If we are eliminating
105 // duplicates, then, we err on the side of keeping the thing that looks
106 // more like the value we will end up with in the end.
107 if (default_search_provider
&&
108 (default_search_provider
->prepopulate_id() ==
109 i
->second
->prepopulate_id()) &&
110 default_search_provider
->HasSameKeywordAs(i
->second
->data())) {
115 // Otherwise, a URL is best if it matches the prepopulated data's keyword;
116 // if none match, just fall back to using the one with the lowest ID.
119 if ((prepopulated_url
!= prepopulated_url_map
.end()) &&
120 i
->second
->HasSameKeywordAs(*prepopulated_url
->second
)) {
122 matched_keyword
= true;
123 } else if (i
->second
->id() < best
->second
->id()) {
128 // Add the best URL to the checked group and delete the rest.
129 checked_urls
.push_back(best
->second
);
130 for (UncheckedURLMap::iterator i
= unchecked_urls
.begin(); i
!= end
; ++i
) {
134 service
->RemoveKeyword(i
->second
->id());
135 if (removed_keyword_guids
)
136 removed_keyword_guids
->insert(i
->second
->sync_guid());
141 // Done with this group.
142 unchecked_urls
.erase(unchecked_urls
.begin(), end
);
145 // Return the checked URLs.
146 template_urls
->swap(checked_urls
);
149 // Returns the TemplateURL with id specified from the list of TemplateURLs.
150 // If not found, returns NULL.
151 TemplateURL
* GetTemplateURLByID(
152 const TemplateURLService::TemplateURLVector
& template_urls
,
154 for (TemplateURLService::TemplateURLVector::const_iterator
i(
155 template_urls
.begin()); i
!= template_urls
.end(); ++i
) {
156 if ((*i
)->id() == id
) {
163 TemplateURL
* FindURLByPrepopulateID(
164 const TemplateURLService::TemplateURLVector
& template_urls
,
165 int prepopulate_id
) {
166 for (std::vector
<TemplateURL
*>::const_iterator i
= template_urls
.begin();
167 i
< template_urls
.end(); ++i
) {
168 if ((*i
)->prepopulate_id() == prepopulate_id
)
174 void MergeIntoPrepopulatedEngineData(const TemplateURL
* original_turl
,
175 TemplateURLData
* prepopulated_url
) {
176 DCHECK_EQ(original_turl
->prepopulate_id(), prepopulated_url
->prepopulate_id
);
177 if (!original_turl
->safe_for_autoreplace()) {
178 prepopulated_url
->safe_for_autoreplace
= false;
179 prepopulated_url
->SetKeyword(original_turl
->keyword());
180 prepopulated_url
->short_name
= original_turl
->short_name();
182 prepopulated_url
->id
= original_turl
->id();
183 prepopulated_url
->sync_guid
= original_turl
->sync_guid();
184 prepopulated_url
->date_created
= original_turl
->date_created();
185 prepopulated_url
->last_modified
= original_turl
->last_modified();
188 ActionsFromPrepopulateData::ActionsFromPrepopulateData() {}
190 ActionsFromPrepopulateData::~ActionsFromPrepopulateData() {}
192 // This is invoked when the version of the prepopulate data changes.
193 // If |removed_keyword_guids| is not NULL, the Sync GUID of each item removed
194 // from the DB will be added to it. Note that this function will take
195 // ownership of |prepopulated_urls| and will clear the vector.
196 void MergeEnginesFromPrepopulateData(
198 WebDataService
* service
,
199 ScopedVector
<TemplateURLData
>* prepopulated_urls
,
200 size_t default_search_index
,
201 TemplateURLService::TemplateURLVector
* template_urls
,
202 TemplateURL
* default_search_provider
,
203 std::set
<std::string
>* removed_keyword_guids
) {
204 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
205 DCHECK(prepopulated_urls
);
206 DCHECK(template_urls
);
208 ActionsFromPrepopulateData
actions(CreateActionsFromCurrentPrepopulateData(
209 prepopulated_urls
, *template_urls
, default_search_provider
));
212 for (std::vector
<TemplateURL
*>::iterator i
= actions
.removed_engines
.begin();
213 i
< actions
.removed_engines
.end(); ++i
) {
214 scoped_ptr
<TemplateURL
> template_url(*i
);
215 TemplateURLService::TemplateURLVector::iterator j
=
216 std::find(template_urls
->begin(), template_urls
->end(), template_url
);
217 DCHECK(j
!= template_urls
->end());
218 DCHECK(!default_search_provider
||
219 (*j
)->prepopulate_id() != default_search_provider
->prepopulate_id());
220 template_urls
->erase(j
);
222 service
->RemoveKeyword(template_url
->id());
223 if (removed_keyword_guids
)
224 removed_keyword_guids
->insert(template_url
->sync_guid());
229 for (EditedEngines::iterator
i(actions
.edited_engines
.begin());
230 i
< actions
.edited_engines
.end(); ++i
) {
231 TemplateURLData
& data
= i
->second
;
232 scoped_ptr
<TemplateURL
> existing_url(i
->first
);
234 service
->UpdateKeyword(data
);
236 // Replace the entry in |template_urls| with the updated one.
237 TemplateURLService::TemplateURLVector::iterator j
= std::find(
238 template_urls
->begin(), template_urls
->end(), existing_url
.get());
239 *j
= new TemplateURL(profile
, data
);
243 for (std::vector
<TemplateURLData
>::const_iterator it
=
244 actions
.added_engines
.begin();
245 it
!= actions
.added_engines
.end();
247 template_urls
->push_back(new TemplateURL(profile
, *it
));
251 ActionsFromPrepopulateData
CreateActionsFromCurrentPrepopulateData(
252 ScopedVector
<TemplateURLData
>* prepopulated_urls
,
253 const TemplateURLService::TemplateURLVector
& existing_urls
,
254 const TemplateURL
* default_search_provider
) {
255 // Create a map to hold all provided |template_urls| that originally came from
256 // prepopulate data (i.e. have a non-zero prepopulate_id()).
257 typedef std::map
<int, TemplateURL
*> IDMap
;
259 for (TemplateURLService::TemplateURLVector::const_iterator
i(
260 existing_urls
.begin()); i
!= existing_urls
.end(); ++i
) {
261 int prepopulate_id
= (*i
)->prepopulate_id();
262 if (prepopulate_id
> 0)
263 id_to_turl
[prepopulate_id
] = *i
;
266 // For each current prepopulated URL, check whether |template_urls| contained
267 // a matching prepopulated URL. If so, update the passed-in URL to match the
268 // current data. (If the passed-in URL was user-edited, we persist the user's
269 // name and keyword.) If not, add the prepopulated URL.
270 ActionsFromPrepopulateData actions
;
271 for (size_t i
= 0; i
< prepopulated_urls
->size(); ++i
) {
272 // We take ownership of |prepopulated_urls[i]|.
273 scoped_ptr
<TemplateURLData
> prepopulated_url((*prepopulated_urls
)[i
]);
274 const int prepopulated_id
= prepopulated_url
->prepopulate_id
;
275 DCHECK_NE(0, prepopulated_id
);
277 IDMap::iterator
existing_url_iter(id_to_turl
.find(prepopulated_id
));
278 if (existing_url_iter
!= id_to_turl
.end()) {
279 // Update the data store with the new prepopulated data. Preserve user
280 // edits to the name and keyword.
281 TemplateURL
* existing_url(existing_url_iter
->second
);
282 id_to_turl
.erase(existing_url_iter
);
283 MergeIntoPrepopulatedEngineData(existing_url
, prepopulated_url
.get());
284 // Update last_modified to ensure that if this entry is later merged with
285 // entries from Sync, the conflict resolution logic knows that this was
286 // updated and propagates the new values to the server.
287 prepopulated_url
->last_modified
= base::Time::Now();
288 actions
.edited_engines
.push_back(
289 std::make_pair(existing_url
, *prepopulated_url
));
291 actions
.added_engines
.push_back(*prepopulated_url
);
294 // The above loop takes ownership of all the contents of prepopulated_urls.
295 // Clear the pointers.
296 prepopulated_urls
->weak_erase(prepopulated_urls
->begin(),
297 prepopulated_urls
->end());
299 // The block above removed all the URLs from the |id_to_turl| map that were
300 // found in the prepopulate data. Any remaining URLs that haven't been
301 // user-edited or made default can be removed from the data store.
302 // We assume that this entry is equivalent to the DSE if its prepopulate ID
303 // and keyword both match. If the prepopulate ID _does_ match all properties
304 // will be replaced with those from |default_search_provider| anyway.
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 (!default_search_provider
||
309 (template_url
->prepopulate_id() !=
310 default_search_provider
->prepopulate_id()) ||
311 (template_url
->keyword() != default_search_provider
->keyword())))
312 actions
.removed_engines
.push_back(template_url
);
318 void GetSearchProvidersUsingKeywordResult(
319 const WDTypedResult
& result
,
320 WebDataService
* service
,
322 TemplateURLService::TemplateURLVector
* template_urls
,
323 TemplateURL
* default_search_provider
,
324 int* new_resource_keyword_version
,
325 std::set
<std::string
>* removed_keyword_guids
) {
326 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
327 DCHECK(template_urls
);
328 DCHECK(template_urls
->empty());
329 DCHECK_EQ(KEYWORDS_RESULT
, result
.GetType());
330 DCHECK(new_resource_keyword_version
);
332 WDKeywordsResult keyword_result
= reinterpret_cast<
333 const WDResult
<WDKeywordsResult
>*>(&result
)->GetValue();
335 for (KeywordTable::Keywords::iterator
i(keyword_result
.keywords
.begin());
336 i
!= keyword_result
.keywords
.end(); ++i
) {
337 // Fix any duplicate encodings in the local database. Note that we don't
338 // adjust the last_modified time of this keyword; this way, we won't later
339 // overwrite any changes on the sync server that happened to this keyword
340 // since the last time we synced. Instead, we also run a de-duping pass on
341 // the server-provided data in
342 // TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData() and
343 // update the server with the merged, de-duped results at that time. We
344 // still fix here, though, to correct problems in clients that have disabled
345 // search engine sync, since in that case that code will never be reached.
346 if (DeDupeEncodings(&i
->input_encodings
) && service
)
347 service
->UpdateKeyword(*i
);
348 template_urls
->push_back(new TemplateURL(profile
, *i
));
351 *new_resource_keyword_version
= keyword_result
.builtin_keyword_version
;
352 GetSearchProvidersUsingLoadedEngines(service
, profile
, template_urls
,
353 default_search_provider
,
354 new_resource_keyword_version
,
355 removed_keyword_guids
);
358 void GetSearchProvidersUsingLoadedEngines(
359 WebDataService
* service
,
361 TemplateURLService::TemplateURLVector
* template_urls
,
362 TemplateURL
* default_search_provider
,
363 int* resource_keyword_version
,
364 std::set
<std::string
>* removed_keyword_guids
) {
365 DCHECK(service
== NULL
|| BrowserThread::CurrentlyOn(BrowserThread::UI
));
366 DCHECK(template_urls
);
367 DCHECK(resource_keyword_version
);
368 PrefService
* prefs
= profile
? profile
->GetPrefs() : NULL
;
369 size_t default_search_index
;
370 ScopedVector
<TemplateURLData
> prepopulated_urls
=
371 TemplateURLPrepopulateData::GetPrepopulatedEngines(prefs
,
372 &default_search_index
);
373 RemoveDuplicatePrepopulateIDs(service
, prepopulated_urls
,
374 default_search_provider
, template_urls
,
375 removed_keyword_guids
);
377 const int prepopulate_resource_keyword_version
=
378 TemplateURLPrepopulateData::GetDataVersion(prefs
);
379 if (*resource_keyword_version
< prepopulate_resource_keyword_version
) {
380 MergeEnginesFromPrepopulateData(profile
, service
, &prepopulated_urls
,
381 default_search_index
, template_urls
, default_search_provider
,
382 removed_keyword_guids
);
383 *resource_keyword_version
= prepopulate_resource_keyword_version
;
385 *resource_keyword_version
= 0;
389 bool DeDupeEncodings(std::vector
<std::string
>* encodings
) {
390 std::vector
<std::string
> deduped_encodings
;
391 std::set
<std::string
> encoding_set
;
392 for (std::vector
<std::string
>::const_iterator
i(encodings
->begin());
393 i
!= encodings
->end(); ++i
) {
394 if (encoding_set
.insert(*i
).second
)
395 deduped_encodings
.push_back(*i
);
397 encodings
->swap(deduped_encodings
);
398 return encodings
->size() != deduped_encodings
.size();