template_url_model.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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.
4
5#include "chrome/browser/search_engines/template_url_model.h"
6
7#include "app/l10n_util.h"
8#include "base/command_line.h"
9#include "base/environment.h"
10#include "base/stl_util-inl.h"
11#include "base/string_number_conversions.h"
12#include "base/string_split.h"
13#include "base/utf_string_conversions.h"
14#include "chrome/browser/extensions/extensions_service.h"
15#include "chrome/browser/google/google_url_tracker.h"
16#include "chrome/browser/history/history.h"
17#include "chrome/browser/history/history_notifications.h"
18#include "chrome/browser/net/url_fixer_upper.h"
19#include "chrome/browser/prefs/pref_service.h"
20#include "chrome/browser/prefs/pref_set_observer.h"
21#include "chrome/browser/profile.h"
22#include "chrome/browser/rlz/rlz.h"
23#include "chrome/browser/search_engines/search_host_to_urls_map.h"
24#include "chrome/browser/search_engines/search_terms_data.h"
25#include "chrome/browser/search_engines/template_url.h"
26#include "chrome/browser/search_engines/template_url_model_observer.h"
27#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
28#include "chrome/browser/search_engines/util.h"
29#include "chrome/common/chrome_switches.h"
30#include "chrome/common/env_vars.h"
31#include "chrome/common/extensions/extension.h"
32#include "chrome/common/notification_service.h"
33#include "chrome/common/pref_names.h"
34#include "chrome/common/url_constants.h"
35#include "net/base/net_util.h"
36
37using base::Time;
38typedef SearchHostToURLsMap::TemplateURLSet TemplateURLSet;
39
40// String in the URL that is replaced by the search term.
41static const char kSearchTermParameter[] = "{searchTerms}";
42
43// String in Initializer that is replaced with kSearchTermParameter.
44static const char kTemplateParameter[] = "%s";
45
46// Term used when generating a search url. Use something obscure so that on
47// the rare case the term replaces the URL it's unlikely another keyword would
48// have the same url.
49static const wchar_t kReplacementTerm[] = L"blah.blah.blah.blah.blah";
50
51
52// Removes from the vector any template URL that was created because of
53// policy.  These TemplateURLs are freed.
54// Sets default_search_provider to NULL if it was one of them.
55static void RemoveProvidersCreatedByPolicy(
56    std::vector<TemplateURL*>* template_urls,
57    const TemplateURL** default_search_provider) {
58  DCHECK(template_urls);
59  DCHECK(default_search_provider);
60  for (std::vector<TemplateURL*>::iterator i = template_urls->begin();
61      i != template_urls->end(); ) {
62    TemplateURL* template_url = *i;
63    if (template_url->created_by_policy()) {
64      if (*default_search_provider &&
65          (*default_search_provider)->id() == template_url->id())
66        *default_search_provider = NULL;
67      i = template_urls->erase(i);
68      delete template_url;
69    } else {
70      ++i;
71    }
72  }
73}
74
75class TemplateURLModel::LessWithPrefix {
76 public:
77  // We want to find the set of keywords that begin with a prefix.  The STL
78  // algorithms will return the set of elements that are "equal to" the
79  // prefix, where "equal(x, y)" means "!(cmp(x, y) || cmp(y, x))".  When
80  // cmp() is the typical std::less<>, this results in lexicographic equality;
81  // we need to extend this to mark a prefix as "not less than" a keyword it
82  // begins, which will cause the desired elements to be considered "equal to"
83  // the prefix.  Note: this is still a strict weak ordering, as required by
84  // equal_range() (though I will not prove that here).
85  //
86  // Unfortunately the calling convention is not "prefix and element" but
87  // rather "two elements", so we pass the prefix as a fake "element" which has
88  // a NULL KeywordDataElement pointer.
89  bool operator()(const KeywordToTemplateMap::value_type& elem1,
90                  const KeywordToTemplateMap::value_type& elem2) const {
91    return (elem1.second == NULL) ?
92        (elem2.first.compare(0, elem1.first.length(), elem1.first) > 0) :
93        (elem1.first < elem2.first);
94  }
95};
96
97TemplateURLModel::TemplateURLModel(Profile* profile)
98    : profile_(profile),
99      loaded_(false),
100      load_failed_(false),
101      load_handle_(0),
102      default_search_provider_(NULL),
103      is_default_search_managed_(false),
104      next_id_(1) {
105  DCHECK(profile_);
106  Init(NULL, 0);
107}
108
109TemplateURLModel::TemplateURLModel(const Initializer* initializers,
110                                   const int count)
111    : profile_(NULL),
112      loaded_(false),
113      load_failed_(false),
114      load_handle_(0),
115      service_(NULL),
116      default_search_provider_(NULL),
117      is_default_search_managed_(false),
118      next_id_(1) {
119  Init(initializers, count);
120}
121
122TemplateURLModel::~TemplateURLModel() {
123  if (load_handle_) {
124    DCHECK(service_.get());
125    service_->CancelRequest(load_handle_);
126  }
127
128  STLDeleteElements(&template_urls_);
129}
130
131// static
132std::wstring TemplateURLModel::GenerateKeyword(const GURL& url,
133                                               bool autodetected) {
134  // Don't autogenerate keywords for referrers that are the result of a form
135  // submission (TODO: right now we approximate this by checking for the URL
136  // having a query, but we should replace this with a call to WebCore to see if
137  // the originating page was actually a form submission), anything other than
138  // http, or referrers with a path.
139  //
140  // If we relax the path constraint, we need to be sure to sanitize the path
141  // elements and update AutocompletePopup to look for keywords using the path.
142  // See http://b/issue?id=863583.
143  if (!url.is_valid() ||
144      (autodetected && (url.has_query() || !url.SchemeIs(chrome::kHttpScheme) ||
145                        ((url.path() != "") && (url.path() != "/")))))
146    return std::wstring();
147
148  // Strip "www." off the front of the keyword; otherwise the keyword won't work
149  // properly.  See http://code.google.com/p/chromium/issues/detail?id=6984 .
150  return UTF16ToWideHack(net::StripWWW(UTF8ToUTF16(url.host())));
151}
152
153// static
154std::wstring TemplateURLModel::CleanUserInputKeyword(
155    const std::wstring& keyword) {
156  // Remove the scheme.
157  std::wstring result(UTF16ToWide(l10n_util::ToLower(WideToUTF16(keyword))));
158  url_parse::Component scheme_component;
159  if (url_parse::ExtractScheme(WideToUTF8(keyword).c_str(),
160                               static_cast<int>(keyword.length()),
161                               &scheme_component)) {
162    // If the scheme isn't "http" or "https", bail.  The user isn't trying to
163    // type a web address, but rather an FTP, file:, or other scheme URL, or a
164    // search query with some sort of initial operator (e.g. "site:").
165    if (result.compare(0, scheme_component.end(),
166                       ASCIIToWide(chrome::kHttpScheme)) &&
167        result.compare(0, scheme_component.end(),
168                       ASCIIToWide(chrome::kHttpsScheme)))
169      return std::wstring();
170
171    // Include trailing ':'.
172    result.erase(0, scheme_component.end() + 1);
173    // Many schemes usually have "//" after them, so strip it too.
174    const std::wstring after_scheme(L"//");
175    if (result.compare(0, after_scheme.length(), after_scheme) == 0)
176      result.erase(0, after_scheme.length());
177  }
178
179  // Remove leading "www.".
180  result = UTF16ToWideHack(net::StripWWW(WideToUTF16(result)));
181
182  // Remove trailing "/".
183  return (result.length() > 0 && result[result.length() - 1] == L'/') ?
184      result.substr(0, result.length() - 1) : result;
185}
186
187// static
188GURL TemplateURLModel::GenerateSearchURL(const TemplateURL* t_url) {
189  DCHECK(t_url);
190  UIThreadSearchTermsData search_terms_data;
191  return GenerateSearchURLUsingTermsData(t_url, search_terms_data);
192}
193
194// static
195GURL TemplateURLModel::GenerateSearchURLUsingTermsData(
196    const TemplateURL* t_url,
197    const SearchTermsData& search_terms_data) {
198  DCHECK(t_url);
199  const TemplateURLRef* search_ref = t_url->url();
200  // Extension keywords don't have host-based search URLs.
201  if (!search_ref || !search_ref->IsValidUsingTermsData(search_terms_data) ||
202      t_url->IsExtensionKeyword())
203    return GURL();
204
205  if (!search_ref->SupportsReplacementUsingTermsData(search_terms_data))
206    return GURL(search_ref->url());
207
208  return GURL(search_ref->ReplaceSearchTermsUsingTermsData(
209      *t_url, kReplacementTerm, TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
210      std::wstring(), search_terms_data));
211}
212
213bool TemplateURLModel::CanReplaceKeyword(
214    const std::wstring& keyword,
215    const GURL& url,
216    const TemplateURL** template_url_to_replace) {
217  DCHECK(!keyword.empty());  // This should only be called for non-empty
218                             // keywords. If we need to support empty kewords
219                             // the code needs to change slightly.
220  const TemplateURL* existing_url = GetTemplateURLForKeyword(keyword);
221  if (existing_url) {
222    // We already have a TemplateURL for this keyword. Only allow it to be
223    // replaced if the TemplateURL can be replaced.
224    if (template_url_to_replace)
225      *template_url_to_replace = existing_url;
226    return CanReplace(existing_url);
227  }
228
229  // We don't have a TemplateURL with keyword. Only allow a new one if there
230  // isn't a TemplateURL for the specified host, or there is one but it can
231  // be replaced. We do this to ensure that if the user assigns a different
232  // keyword to a generated TemplateURL, we won't regenerate another keyword for
233  // the same host.
234  if (url.is_valid() && !url.host().empty())
235    return CanReplaceKeywordForHost(url.host(), template_url_to_replace);
236  return true;
237}
238
239void TemplateURLModel::FindMatchingKeywords(
240    const std::wstring& prefix,
241    bool support_replacement_only,
242    std::vector<std::wstring>* matches) const {
243  // Sanity check args.
244  if (prefix.empty())
245    return;
246  DCHECK(matches != NULL);
247  DCHECK(matches->empty());  // The code for exact matches assumes this.
248
249  // Find matching keyword range.  Searches the element map for keywords
250  // beginning with |prefix| and stores the endpoints of the resulting set in
251  // |match_range|.
252  const std::pair<KeywordToTemplateMap::const_iterator,
253                  KeywordToTemplateMap::const_iterator> match_range(
254      std::equal_range(
255          keyword_to_template_map_.begin(), keyword_to_template_map_.end(),
256          KeywordToTemplateMap::value_type(prefix, NULL), LessWithPrefix()));
257
258  // Return vector of matching keywords.
259  for (KeywordToTemplateMap::const_iterator i(match_range.first);
260       i != match_range.second; ++i) {
261    DCHECK(i->second->url());
262    if (!support_replacement_only || i->second->url()->SupportsReplacement())
263      matches->push_back(i->first);
264  }
265}
266
267const TemplateURL* TemplateURLModel::GetTemplateURLForKeyword(
268                                     const std::wstring& keyword) const {
269  KeywordToTemplateMap::const_iterator elem(
270      keyword_to_template_map_.find(keyword));
271  return (elem == keyword_to_template_map_.end()) ? NULL : elem->second;
272}
273
274const TemplateURL* TemplateURLModel::GetTemplateURLForHost(
275    const std::string& host) const {
276  return provider_map_.GetTemplateURLForHost(host);
277}
278
279void TemplateURLModel::Add(TemplateURL* template_url) {
280  AddNoNotify(template_url);
281  NotifyObservers();
282}
283
284void TemplateURLModel::Remove(const TemplateURL* template_url) {
285  RemoveNoNotify(template_url);
286  NotifyObservers();
287}
288
289void TemplateURLModel::RemoveAutoGeneratedBetween(Time created_after,
290                                                  Time created_before) {
291  bool should_notify = false;
292  for (size_t i = 0; i < template_urls_.size();) {
293    if (template_urls_[i]->date_created() >= created_after &&
294        (created_before.is_null() ||
295         template_urls_[i]->date_created() < created_before) &&
296        CanReplace(template_urls_[i])) {
297      RemoveNoNotify(template_urls_[i]);
298      should_notify = true;
299    } else {
300      ++i;
301    }
302  }
303  if (should_notify)
304    NotifyObservers();
305}
306
307void TemplateURLModel::RemoveAutoGeneratedSince(Time created_after) {
308  RemoveAutoGeneratedBetween(created_after, Time());
309}
310
311void TemplateURLModel::RegisterExtensionKeyword(Extension* extension) {
312  // TODO(mpcomplete): disable the keyword when the extension is disabled.
313  if (extension->omnibox_keyword().empty())
314    return;
315
316  Load();
317  if (!loaded_) {
318    pending_extension_ids_.push_back(extension->id());
319    return;
320  }
321
322  const TemplateURL* existing_url = GetTemplateURLForExtension(extension);
323  std::wstring keyword = UTF8ToWide(extension->omnibox_keyword());
324
325  scoped_ptr<TemplateURL> template_url(new TemplateURL);
326  template_url->set_short_name(UTF8ToWide(extension->name()));
327  template_url->set_keyword(keyword);
328  // This URL is not actually used for navigation. It holds the extension's
329  // ID, as well as forcing the TemplateURL to be treated as a search keyword.
330  template_url->SetURL(
331      std::string(chrome::kExtensionScheme) + "://" +
332      extension->id() + "/?q={searchTerms}", 0, 0);
333  template_url->set_safe_for_autoreplace(false);
334
335  if (existing_url) {
336    // TODO(mpcomplete): only replace if the user hasn't changed the keyword.
337    // (We don't have UI for that yet).
338    UpdateNoNotify(existing_url, *template_url);
339  } else {
340    AddNoNotify(template_url.release());
341  }
342  NotifyObservers();
343}
344
345void TemplateURLModel::UnregisterExtensionKeyword(Extension* extension) {
346  const TemplateURL* url = GetTemplateURLForExtension(extension);
347  if (url)
348    Remove(url);
349}
350
351const TemplateURL* TemplateURLModel::GetTemplateURLForExtension(
352    Extension* extension) const {
353  for (TemplateURLVector::const_iterator i = template_urls_.begin();
354       i != template_urls_.end(); ++i) {
355    if ((*i)->IsExtensionKeyword() && (*i)->url()->GetHost() == extension->id())
356      return *i;
357  }
358
359  return NULL;
360}
361
362std::vector<const TemplateURL*> TemplateURLModel::GetTemplateURLs() const {
363  return template_urls_;
364}
365
366void TemplateURLModel::IncrementUsageCount(const TemplateURL* url) {
367  DCHECK(url && find(template_urls_.begin(), template_urls_.end(), url) !=
368         template_urls_.end());
369  const_cast<TemplateURL*>(url)->set_usage_count(url->usage_count() + 1);
370  if (service_.get())
371    service_.get()->UpdateKeyword(*url);
372}
373
374void TemplateURLModel::ResetTemplateURL(const TemplateURL* url,
375                                        const std::wstring& title,
376                                        const std::wstring& keyword,
377                                        const std::string& search_url) {
378  TemplateURL new_url(*url);
379  new_url.set_short_name(title);
380  new_url.set_keyword(keyword);
381  if ((new_url.url() && search_url.empty()) ||
382      (!new_url.url() && !search_url.empty()) ||
383      (new_url.url() && new_url.url()->url() != search_url)) {
384    // The urls have changed, reset the favicon url.
385    new_url.SetFavIconURL(GURL());
386    new_url.SetURL(search_url, 0, 0);
387  }
388  new_url.set_safe_for_autoreplace(false);
389  UpdateNoNotify(url, new_url);
390  NotifyObservers();
391}
392
393bool TemplateURLModel::CanMakeDefault(const TemplateURL* url) {
394  return url != GetDefaultSearchProvider() &&
395      url->url() &&
396      url->url()->SupportsReplacement() &&
397      !is_default_search_managed();
398}
399
400void TemplateURLModel::SetDefaultSearchProvider(const TemplateURL* url) {
401  if (is_default_search_managed_) {
402    NOTREACHED();
403    return;
404  }
405  if (default_search_provider_ == url)
406    return;
407  SetDefaultSearchProviderNoNotify(url);
408  NotifyObservers();
409}
410
411const TemplateURL* TemplateURLModel::GetDefaultSearchProvider() {
412  if (loaded_ && !load_failed_)
413    return default_search_provider_;
414
415  // We're not loaded, rely on the default search provider stored in prefs.
416  return initial_default_search_provider_.get();
417}
418
419void TemplateURLModel::AddObserver(TemplateURLModelObserver* observer) {
420  model_observers_.AddObserver(observer);
421}
422
423void TemplateURLModel::RemoveObserver(TemplateURLModelObserver* observer) {
424  model_observers_.RemoveObserver(observer);
425}
426
427void TemplateURLModel::Load() {
428  if (loaded_ || load_handle_)
429    return;
430
431  if (!service_.get())
432    service_ = profile_->GetWebDataService(Profile::EXPLICIT_ACCESS);
433
434  if (service_.get()) {
435    load_handle_ = service_->GetKeywords(this);
436  } else {
437    ChangeToLoadedState();
438    NotifyLoaded();
439  }
440}
441
442void TemplateURLModel::OnWebDataServiceRequestDone(
443    WebDataService::Handle h,
444    const WDTypedResult* result) {
445  // Reset the load_handle so that we don't try and cancel the load in
446  // the destructor.
447  load_handle_ = 0;
448
449  if (!result) {
450    // Results are null if the database went away or (most likely) wasn't
451    // loaded.
452    load_failed_ = true;
453    ChangeToLoadedState();
454    NotifyLoaded();
455    return;
456  }
457
458  // initial_default_search_provider_ is only needed before we've finished
459  // loading. Now that we've loaded we can nuke it.
460  initial_default_search_provider_.reset();
461  is_default_search_managed_ = false;
462
463  std::vector<TemplateURL*> template_urls;
464  const TemplateURL* default_search_provider = NULL;
465  int new_resource_keyword_version = 0;
466  GetSearchProvidersUsingKeywordResult(*result,
467                                       service_.get(),
468                                       GetPrefs(),
469                                       &template_urls,
470                                       &default_search_provider,
471                                       &new_resource_keyword_version);
472
473  bool database_specified_a_default = NULL != default_search_provider;
474
475  // Remove entries that were created because of policy as they may have
476  // changed since the database was saved.
477  RemoveProvidersCreatedByPolicy(&template_urls, &default_search_provider);
478
479  // Check if default search provider is now managed.
480  scoped_ptr<TemplateURL> default_from_prefs;
481  LoadDefaultSearchProviderFromPrefs(&default_from_prefs,
482                                     &is_default_search_managed_);
483
484  if (is_default_search_managed_) {
485    SetTemplateURLs(template_urls);
486    // Set the default.  AddNoNotify will take ownership of default_from_prefs
487    // so it is safe to release.  If it's null, there's no ownership to worry
488    // about :-)
489    TemplateURL* managed_default = default_from_prefs.release();
490    if (managed_default) {
491      managed_default->set_created_by_policy(true);
492      managed_default->set_id(0);
493      AddNoNotify(managed_default);
494    }
495    // Note that this saves the default search provider to prefs.
496    SetDefaultSearchProviderNoNotify(managed_default);
497  } else {
498    // If we had a managed default, replace it with the first provider of
499    // the list.
500    if (database_specified_a_default &&
501        NULL == default_search_provider &&
502        !template_urls.empty())
503      default_search_provider = template_urls[0];
504
505    // If the default search provider existed previously, then just
506    // set the member variable. Otherwise, we'll set it using the method
507    // to ensure that it is saved properly after its id is set.
508    if (default_search_provider && default_search_provider->id() != 0) {
509      default_search_provider_ = default_search_provider;
510      default_search_provider = NULL;
511    }
512    SetTemplateURLs(template_urls);
513
514    if (default_search_provider) {
515      // Note that this saves the default search provider to prefs.
516      SetDefaultSearchProvider(default_search_provider);
517    } else {
518      // Always save the default search provider to prefs. That way we don't
519      // have to worry about it being out of sync.
520      if (default_search_provider_)
521        SaveDefaultSearchProviderToPrefs(default_search_provider_);
522    }
523  }
524
525  // This initializes provider_map_ which should be done before
526  // calling UpdateKeywordSearchTermsForURL.
527  ChangeToLoadedState();
528
529  // Index any visits that occurred before we finished loading.
530  for (size_t i = 0; i < visits_to_add_.size(); ++i)
531    UpdateKeywordSearchTermsForURL(visits_to_add_[i]);
532  visits_to_add_.clear();
533
534  if (new_resource_keyword_version && service_.get())
535    service_->SetBuiltinKeywordVersion(new_resource_keyword_version);
536
537  NotifyObservers();
538  NotifyLoaded();
539}
540
541std::wstring TemplateURLModel::GetKeywordShortName(const std::wstring& keyword,
542                                                   bool* is_extension_keyword) {
543  const TemplateURL* template_url = GetTemplateURLForKeyword(keyword);
544
545  // TODO(sky): Once LocationBarView adds a listener to the TemplateURLModel
546  // to track changes to the model, this should become a DCHECK.
547  if (template_url) {
548    *is_extension_keyword = template_url->IsExtensionKeyword();
549    return template_url->AdjustedShortNameForLocaleDirection();
550  }
551  *is_extension_keyword = false;
552  return std::wstring();
553}
554
555void TemplateURLModel::Observe(NotificationType type,
556                               const NotificationSource& source,
557                               const NotificationDetails& details) {
558  if (type == NotificationType::HISTORY_URL_VISITED) {
559    Details<history::URLVisitedDetails> visit_details(details);
560    if (!loaded())
561      visits_to_add_.push_back(*visit_details.ptr());
562    else
563      UpdateKeywordSearchTermsForURL(*visit_details.ptr());
564  } else if (type == NotificationType::GOOGLE_URL_UPDATED) {
565    if (loaded_)
566      GoogleBaseURLChanged();
567  } else if (type == NotificationType::PREF_CHANGED) {
568    const std::string* pref_name = Details<std::string>(details).ptr();
569    if (!pref_name || default_search_prefs_->IsObserved(*pref_name)) {
570      // A preference related to default search engine has changed.
571      // Update the model if needed.
572      UpdateDefaultSearch();
573    }
574  } else {
575    NOTREACHED();
576  }
577}
578
579void TemplateURLModel::RegisterUserPrefs(PrefService* prefs) {
580  prefs->RegisterBooleanPref(
581      prefs::kDefaultSearchProviderEnabled, true);
582  prefs->RegisterStringPref(
583      prefs::kDefaultSearchProviderName, std::string());
584  prefs->RegisterStringPref(
585      prefs::kDefaultSearchProviderID, std::string());
586  prefs->RegisterStringPref(
587      prefs::kDefaultSearchProviderPrepopulateID, std::string());
588  prefs->RegisterStringPref(
589      prefs::kDefaultSearchProviderSuggestURL, std::string());
590  prefs->RegisterStringPref(
591      prefs::kDefaultSearchProviderSearchURL, std::string());
592  prefs->RegisterStringPref(
593      prefs::kDefaultSearchProviderInstantURL, std::string());
594  prefs->RegisterStringPref(
595      prefs::kDefaultSearchProviderKeyword, std::string());
596  prefs->RegisterStringPref(
597      prefs::kDefaultSearchProviderIconURL, std::string());
598  prefs->RegisterStringPref(
599      prefs::kDefaultSearchProviderEncodings, std::string());
600}
601
602void TemplateURLModel::SetKeywordSearchTermsForURL(const TemplateURL* t_url,
603                                                   const GURL& url,
604                                                   const std::wstring& term) {
605  HistoryService* history = profile_  ?
606      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS) : NULL;
607  if (!history)
608    return;
609  history->SetKeywordSearchTermsForURL(url, t_url->id(),
610                                       WideToUTF16Hack(term));
611}
612
613void TemplateURLModel::Init(const Initializer* initializers,
614                            int num_initializers) {
615  // Register for notifications.
616  if (profile_) {
617    // TODO(sky): bug 1166191. The keywords should be moved into the history
618    // db, which will mean we no longer need this notification and the history
619    // backend can handle automatically adding the search terms as the user
620    // navigates.
621    registrar_.Add(this, NotificationType::HISTORY_URL_VISITED,
622                   Source<Profile>(profile_->GetOriginalProfile()));
623    PrefService* prefs = GetPrefs();
624    default_search_prefs_.reset(
625        PrefSetObserver::CreateDefaultSearchPrefSetObserver(prefs, this));
626  }
627  registrar_.Add(this, NotificationType::GOOGLE_URL_UPDATED,
628                 NotificationService::AllSources());
629
630  if (num_initializers > 0) {
631    // This path is only hit by test code and is used to simulate a loaded
632    // TemplateURLModel.
633    ChangeToLoadedState();
634
635    // Add specific initializers, if any.
636    for (int i(0); i < num_initializers; ++i) {
637      DCHECK(initializers[i].keyword);
638      DCHECK(initializers[i].url);
639      DCHECK(initializers[i].content);
640
641      size_t template_position =
642          std::string(initializers[i].url).find(kTemplateParameter);
643      DCHECK(template_position != std::wstring::npos);
644      std::string osd_url(initializers[i].url);
645      osd_url.replace(template_position, arraysize(kTemplateParameter) - 1,
646                      kSearchTermParameter);
647
648      // TemplateURLModel ends up owning the TemplateURL, don't try and free it.
649      TemplateURL* template_url = new TemplateURL();
650      template_url->set_keyword(initializers[i].keyword);
651      template_url->set_short_name(initializers[i].content);
652      template_url->SetURL(osd_url, 0, 0);
653      AddNoNotify(template_url);
654    }
655  }
656
657  // Initialize default search.
658  UpdateDefaultSearch();
659
660  // Request a server check for the correct Google URL if Google is the
661  // default search engine, not in headless mode and not in Chrome Frame.
662  if (initial_default_search_provider_.get()) {
663    const TemplateURLRef* default_provider_ref =
664        initial_default_search_provider_->url();
665    if (default_provider_ref && default_provider_ref->HasGoogleBaseURLs()) {
666      scoped_ptr<base::Environment> env(base::Environment::Create());
667      if (!env->HasVar(env_vars::kHeadless) &&
668          !CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame))
669        GoogleURLTracker::RequestServerCheck();
670    }
671  }
672}
673
674void TemplateURLModel::RemoveFromMaps(const TemplateURL* template_url) {
675  if (!template_url->keyword().empty())
676    keyword_to_template_map_.erase(template_url->keyword());
677  if (loaded_)
678    provider_map_.Remove(template_url);
679}
680
681void TemplateURLModel::RemoveFromKeywordMapByPointer(
682    const TemplateURL* template_url) {
683  DCHECK(template_url);
684  for (KeywordToTemplateMap::iterator i = keyword_to_template_map_.begin();
685       i != keyword_to_template_map_.end(); ++i) {
686    if (i->second == template_url) {
687      keyword_to_template_map_.erase(i);
688      // A given TemplateURL only occurs once in the map. As soon as we find the
689      // entry, stop.
690      break;
691    }
692  }
693}
694
695void TemplateURLModel::AddToMaps(const TemplateURL* template_url) {
696  if (!template_url->keyword().empty())
697    keyword_to_template_map_[template_url->keyword()] = template_url;
698  if (loaded_) {
699    UIThreadSearchTermsData search_terms_data;
700    provider_map_.Add(template_url, search_terms_data);
701  }
702}
703
704void TemplateURLModel::SetTemplateURLs(const std::vector<TemplateURL*>& urls) {
705  // Add mappings for the new items.
706
707  // First, add the items that already have id's, so that the next_id_
708  // gets properly set.
709  for (std::vector<TemplateURL*>::const_iterator i = urls.begin();
710       i != urls.end();
711       ++i) {
712    if ((*i)->id() == 0)
713      continue;
714    next_id_ = std::max(next_id_, (*i)->id());
715    AddToMaps(*i);
716    template_urls_.push_back(*i);
717  }
718
719  // Next add the new items that don't have id's.
720  for (std::vector<TemplateURL*>::const_iterator i = urls.begin();
721       i != urls.end();
722       ++i) {
723    if ((*i)->id() != 0)
724      continue;
725    AddNoNotify(*i);
726  }
727}
728
729void TemplateURLModel::ChangeToLoadedState() {
730  DCHECK(!loaded_);
731
732  UIThreadSearchTermsData search_terms_data;
733  provider_map_.Init(template_urls_, search_terms_data);
734  loaded_ = true;
735}
736
737void TemplateURLModel::NotifyLoaded() {
738  NotificationService::current()->Notify(
739      NotificationType::TEMPLATE_URL_MODEL_LOADED,
740      Source<TemplateURLModel>(this),
741      NotificationService::NoDetails());
742
743  for (size_t i = 0; i < pending_extension_ids_.size(); ++i) {
744    Extension* extension = profile_->GetExtensionsService()->
745        GetExtensionById(pending_extension_ids_[i], true);
746    if (extension)
747      RegisterExtensionKeyword(extension);
748  }
749  pending_extension_ids_.clear();
750}
751
752void TemplateURLModel::SaveDefaultSearchProviderToPrefs(
753    const TemplateURL* t_url) {
754  PrefService* prefs = GetPrefs();
755  if (!prefs)
756    return;
757
758  bool enabled = false;
759  std::string search_url;
760  std::string suggest_url;
761  std::string instant_url;
762  std::string icon_url;
763  std::string encodings;
764  std::string short_name;
765  std::string keyword;
766  std::string id_string;
767  std::string prepopulate_id;
768  if (t_url) {
769    enabled = true;
770    if (t_url->url())
771      search_url = t_url->url()->url();
772    if (t_url->suggestions_url())
773      suggest_url = t_url->suggestions_url()->url();
774    if (t_url->instant_url())
775      instant_url = t_url->instant_url()->url();
776    GURL icon_gurl = t_url->GetFavIconURL();
777    if (!icon_gurl.is_empty())
778      icon_url = icon_gurl.spec();
779    encodings = JoinString(t_url->input_encodings(), ';');
780    short_name = WideToUTF8(t_url->short_name());
781    keyword = WideToUTF8(t_url->keyword());
782    id_string = base::Int64ToString(t_url->id());
783    prepopulate_id = base::Int64ToString(t_url->prepopulate_id());
784  }
785  prefs->SetBoolean(prefs::kDefaultSearchProviderEnabled, enabled);
786  prefs->SetString(prefs::kDefaultSearchProviderSearchURL, search_url);
787  prefs->SetString(prefs::kDefaultSearchProviderSuggestURL, suggest_url);
788  prefs->SetString(prefs::kDefaultSearchProviderInstantURL, instant_url);
789  prefs->SetString(prefs::kDefaultSearchProviderIconURL, icon_url);
790  prefs->SetString(prefs::kDefaultSearchProviderEncodings, encodings);
791  prefs->SetString(prefs::kDefaultSearchProviderName, short_name);
792  prefs->SetString(prefs::kDefaultSearchProviderKeyword, keyword);
793  prefs->SetString(prefs::kDefaultSearchProviderID, id_string);
794  prefs->SetString(prefs::kDefaultSearchProviderPrepopulateID, prepopulate_id);
795
796  prefs->ScheduleSavePersistentPrefs();
797}
798
799bool TemplateURLModel::LoadDefaultSearchProviderFromPrefs(
800    scoped_ptr<TemplateURL>* default_provider,
801    bool* is_managed) {
802  PrefService* prefs = GetPrefs();
803  if (!prefs || !prefs->HasPrefPath(prefs::kDefaultSearchProviderSearchURL))
804    return false;
805
806  const PrefService::Preference* pref =
807      prefs->FindPreference(prefs::kDefaultSearchProviderSearchURL);
808  *is_managed = pref && pref->IsManaged();
809
810  bool enabled =
811      prefs->GetBoolean(prefs::kDefaultSearchProviderEnabled);
812  std::string suggest_url =
813      prefs->GetString(prefs::kDefaultSearchProviderSuggestURL);
814  std::string search_url =
815      prefs->GetString(prefs::kDefaultSearchProviderSearchURL);
816  std::string instant_url =
817      prefs->GetString(prefs::kDefaultSearchProviderInstantURL);
818
819  if (!enabled || (suggest_url.empty() && search_url.empty())) {
820    // The user doesn't want a default search provider.
821    default_provider->reset(NULL);
822    return true;
823  }
824
825  std::wstring name =
826      UTF8ToWide(prefs->GetString(prefs::kDefaultSearchProviderName));
827  std::wstring keyword =
828      UTF8ToWide(prefs->GetString(prefs::kDefaultSearchProviderKeyword));
829  std::string icon_url =
830      prefs->GetString(prefs::kDefaultSearchProviderIconURL);
831  std::string encodings =
832      prefs->GetString(prefs::kDefaultSearchProviderEncodings);
833  std::string id_string = prefs->GetString(prefs::kDefaultSearchProviderID);
834  std::string prepopulate_id =
835      prefs->GetString(prefs::kDefaultSearchProviderPrepopulateID);
836
837  default_provider->reset(new TemplateURL());
838  (*default_provider)->set_short_name(name);
839  (*default_provider)->SetURL(search_url, 0, 0);
840  (*default_provider)->SetSuggestionsURL(suggest_url, 0, 0);
841  (*default_provider)->SetInstantURL(instant_url, 0, 0);
842  (*default_provider)->set_keyword(keyword);
843  (*default_provider)->SetFavIconURL(GURL(icon_url));
844  std::vector<std::string> encodings_vector;
845  base::SplitString(encodings, ';', &encodings_vector);
846  (*default_provider)->set_input_encodings(encodings_vector);
847  if (!id_string.empty()) {
848    int64 value;
849    base::StringToInt64(id_string, &value);
850    (*default_provider)->set_id(value);
851  }
852  if (!prepopulate_id.empty()) {
853    int value;
854    base::StringToInt(prepopulate_id, &value);
855    (*default_provider)->set_prepopulate_id(value);
856  }
857  (*default_provider)->set_show_in_default_list(true);
858  return true;
859}
860
861static bool TemplateURLsHaveSamePrefs(const TemplateURL* url1,
862                                      const TemplateURL* url2) {
863  if (url1 == url2)
864    return true;
865  return NULL != url1 &&
866      NULL != url2 &&
867      url1->short_name() == url2->short_name() &&
868      url1->keyword() == url2->keyword() &&
869      TemplateURLRef::SameUrlRefs(url1->url(), url2->url()) &&
870      TemplateURLRef::SameUrlRefs(url1->suggestions_url(),
871                                  url2->suggestions_url()) &&
872      url1->GetFavIconURL() == url2->GetFavIconURL() &&
873      url1->safe_for_autoreplace() == url2->safe_for_autoreplace() &&
874      url1->show_in_default_list() == url2->show_in_default_list() &&
875      url1->input_encodings() == url2->input_encodings();
876}
877
878
879bool TemplateURLModel::CanReplaceKeywordForHost(
880    const std::string& host,
881    const TemplateURL** to_replace) {
882  const TemplateURLSet* urls = provider_map_.GetURLsForHost(host);
883  if (urls) {
884    for (TemplateURLSet::const_iterator i = urls->begin();
885         i != urls->end(); ++i) {
886      const TemplateURL* url = *i;
887      if (CanReplace(url)) {
888        if (to_replace)
889          *to_replace = url;
890        return true;
891      }
892    }
893  }
894
895  if (to_replace)
896    *to_replace = NULL;
897  return !urls;
898}
899
900bool TemplateURLModel::CanReplace(const TemplateURL* t_url) {
901  return (t_url != default_search_provider_ && !t_url->show_in_default_list() &&
902          t_url->safe_for_autoreplace());
903}
904
905void TemplateURLModel::UpdateNoNotify(const TemplateURL* existing_turl,
906                                      const TemplateURL& new_values) {
907  DCHECK(loaded_);
908  DCHECK(existing_turl);
909  DCHECK(find(template_urls_.begin(), template_urls_.end(), existing_turl) !=
910         template_urls_.end());
911
912  if (!existing_turl->keyword().empty())
913    keyword_to_template_map_.erase(existing_turl->keyword());
914
915  // This call handles copying over the values (while retaining the id).
916  UIThreadSearchTermsData search_terms_data;
917  provider_map_.Update(existing_turl, new_values, search_terms_data);
918
919  if (!existing_turl->keyword().empty())
920    keyword_to_template_map_[existing_turl->keyword()] = existing_turl;
921
922  if (service_.get())
923    service_->UpdateKeyword(*existing_turl);
924
925  if (default_search_provider_ == existing_turl)
926    SetDefaultSearchProviderNoNotify(existing_turl);
927}
928
929PrefService* TemplateURLModel::GetPrefs() {
930  return profile_ ? profile_->GetPrefs() : NULL;
931}
932
933void TemplateURLModel::UpdateKeywordSearchTermsForURL(
934    const history::URLVisitedDetails& details) {
935  const history::URLRow& row = details.row;
936  if (!row.url().is_valid() ||
937      !row.url().parsed_for_possibly_invalid_spec().query.is_nonempty()) {
938    return;
939  }
940
941  const TemplateURLSet* urls_for_host =
942      provider_map_.GetURLsForHost(row.url().host());
943  if (!urls_for_host)
944    return;
945
946  QueryTerms query_terms;
947  bool built_terms = false;  // Most URLs won't match a TemplateURLs host;
948                             // so we lazily build the query_terms.
949  const std::string path = row.url().path();
950
951  for (TemplateURLSet::const_iterator i = urls_for_host->begin();
952       i != urls_for_host->end(); ++i) {
953    const TemplateURLRef* search_ref = (*i)->url();
954
955    // Count the URL against a TemplateURL if the host and path of the
956    // visited URL match that of the TemplateURL as well as the search term's
957    // key of the TemplateURL occurring in the visited url.
958    //
959    // NOTE: Even though we're iterating over TemplateURLs indexed by the host
960    // of the URL we still need to call GetHost on the search_ref. In
961    // particular, GetHost returns an empty string if search_ref doesn't support
962    // replacement or isn't valid for use in keyword search terms.
963
964    if (search_ref && search_ref->GetHost() == row.url().host() &&
965        search_ref->GetPath() == path) {
966      if (!built_terms && !BuildQueryTerms(row.url(), &query_terms)) {
967        // No query terms. No need to continue with the rest of the
968        // TemplateURLs.
969        return;
970      }
971      built_terms = true;
972
973      if (PageTransition::StripQualifier(details.transition) ==
974          PageTransition::KEYWORD) {
975        // The visit is the result of the user entering a keyword, generate a
976        // KEYWORD_GENERATED visit for the KEYWORD so that the keyword typed
977        // count is boosted.
978        AddTabToSearchVisit(**i);
979      }
980
981      QueryTerms::iterator terms_iterator =
982          query_terms.find(search_ref->GetSearchTermKey());
983      if (terms_iterator != query_terms.end() &&
984          !terms_iterator->second.empty()) {
985        SetKeywordSearchTermsForURL(
986            *i, row.url(), search_ref->SearchTermToWide(*(*i),
987            terms_iterator->second));
988      }
989    }
990  }
991}
992
993void TemplateURLModel::AddTabToSearchVisit(const TemplateURL& t_url) {
994  // Only add visits for entries the user hasn't modified. If the user modified
995  // the entry the keyword may no longer correspond to the host name. It may be
996  // possible to do something more sophisticated here, but it's so rare as to
997  // not be worth it.
998  if (!t_url.safe_for_autoreplace())
999    return;
1000
1001  if (!profile_)
1002    return;
1003
1004  HistoryService* history =
1005      profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
1006  if (!history)
1007    return;
1008
1009  GURL url(URLFixerUpper::FixupURL(WideToUTF8(t_url.keyword()), std::string()));
1010  if (!url.is_valid())
1011    return;
1012
1013  // Synthesize a visit for the keyword. This ensures the url for the keyword is
1014  // autocompleted even if the user doesn't type the url in directly.
1015  history->AddPage(url, NULL, 0, GURL(),
1016                   PageTransition::KEYWORD_GENERATED,
1017                   history::RedirectList(), history::SOURCE_BROWSED, false);
1018}
1019
1020// static
1021bool TemplateURLModel::BuildQueryTerms(const GURL& url,
1022                                       QueryTerms* query_terms) {
1023  url_parse::Component query = url.parsed_for_possibly_invalid_spec().query;
1024  url_parse::Component key, value;
1025  size_t valid_term_count = 0;
1026  while (url_parse::ExtractQueryKeyValue(url.spec().c_str(), &query, &key,
1027                                         &value)) {
1028    if (key.is_nonempty() && value.is_nonempty()) {
1029      std::string key_string = url.spec().substr(key.begin, key.len);
1030      std::string value_string = url.spec().substr(value.begin, value.len);
1031      QueryTerms::iterator query_terms_iterator =
1032          query_terms->find(key_string);
1033      if (query_terms_iterator != query_terms->end()) {
1034        if (!query_terms_iterator->second.empty() &&
1035            query_terms_iterator->second != value_string) {
1036          // The term occurs in multiple places with different values. Treat
1037          // this as if the term doesn't occur by setting the value to an empty
1038          // string.
1039          (*query_terms)[key_string] = std::string();
1040          DCHECK(valid_term_count > 0);
1041          valid_term_count--;
1042        }
1043      } else {
1044        valid_term_count++;
1045        (*query_terms)[key_string] = value_string;
1046      }
1047    }
1048  }
1049  return (valid_term_count > 0);
1050}
1051
1052void TemplateURLModel::GoogleBaseURLChanged() {
1053  bool something_changed = false;
1054  for (size_t i = 0; i < template_urls_.size(); ++i) {
1055    const TemplateURL* t_url = template_urls_[i];
1056    if ((t_url->url() && t_url->url()->HasGoogleBaseURLs()) ||
1057        (t_url->suggestions_url() &&
1058         t_url->suggestions_url()->HasGoogleBaseURLs())) {
1059      RemoveFromKeywordMapByPointer(t_url);
1060      t_url->InvalidateCachedValues();
1061      if (!t_url->keyword().empty())
1062        keyword_to_template_map_[t_url->keyword()] = t_url;
1063      something_changed = true;
1064    }
1065  }
1066
1067  if (something_changed && loaded_) {
1068    UIThreadSearchTermsData search_terms_data;
1069    provider_map_.UpdateGoogleBaseURLs(search_terms_data);
1070    NotifyObservers();
1071  }
1072}
1073
1074void TemplateURLModel::UpdateDefaultSearch() {
1075  if (!loaded_) {
1076    // Set |initial_default_search_provider_| from the preferences.  We use this
1077    // value for default search provider until the database has been loaded.
1078    if (!LoadDefaultSearchProviderFromPrefs(&initial_default_search_provider_,
1079                                            &is_default_search_managed_)) {
1080      // Prefs does not specify, so rely on the prepopulated engines.  This
1081      // should happen only the first time Chrome is started.
1082      initial_default_search_provider_.reset(
1083          TemplateURLPrepopulateData::GetPrepopulatedDefaultSearch(GetPrefs()));
1084      is_default_search_managed_ = false;
1085    }
1086    return;
1087  }
1088  // Load the default search specified in prefs.
1089  scoped_ptr<TemplateURL> new_default_from_prefs;
1090  bool new_is_default_managed = false;
1091  // Load the default from prefs.  It's possible that it won't succeed
1092  // because we are in the middle of doing SaveDefaultSearchProviderToPrefs()
1093  // and all the preference items have not been saved.  In that case, we
1094  // don't have yet a default.  It would be much better if we could save
1095  // preferences in batches and trigger notifications at the end.
1096  LoadDefaultSearchProviderFromPrefs(&new_default_from_prefs,
1097                                     &new_is_default_managed);
1098  if (!is_default_search_managed_ && !new_is_default_managed) {
1099    // We're not interested in cases where the default was and remains
1100    // unmanaged.  In that case, preferences have no impact on the default.
1101    return;
1102  }
1103  if (is_default_search_managed_ && new_is_default_managed) {
1104    // The default was managed and remains managed.  Update the default only
1105    // if it has changed; we don't want to respond to changes triggered by
1106    // SaveDefaultSearchProviderToPrefs.
1107    if (TemplateURLsHaveSamePrefs(default_search_provider_,
1108                                  new_default_from_prefs.get()))
1109      return;
1110    if (new_default_from_prefs.get() == NULL) {
1111      // default_search_provider_ can't be NULL otherwise
1112      // TemplateURLsHaveSamePrefs would have returned true.  Remove this now
1113      // invalid value.
1114      const TemplateURL* old_default = default_search_provider_;
1115      SetDefaultSearchProviderNoNotify(NULL);
1116      RemoveNoNotify(old_default);
1117    } else if (default_search_provider_) {
1118      new_default_from_prefs->set_created_by_policy(true);
1119      UpdateNoNotify(default_search_provider_, *new_default_from_prefs.get());
1120    } else {
1121      // AddNoNotify will take ownership of new_template, so it's safe to
1122      // release.
1123      TemplateURL* new_template = new_default_from_prefs.release();
1124      if (new_template) {
1125        new_template->set_created_by_policy(true);
1126        AddNoNotify(new_template);
1127      }
1128      SetDefaultSearchProviderNoNotify(new_template);
1129    }
1130  } else if (!is_default_search_managed_ && new_is_default_managed) {
1131    // The default used to be unmanaged and is now managed.  Add the new
1132    // managed default to the list of URLs and set it as default.
1133    is_default_search_managed_ = new_is_default_managed;
1134    // AddNoNotify will take ownership of new_template, so it's safe to
1135    // release.
1136    TemplateURL* new_template = new_default_from_prefs.release();
1137    if (new_template) {
1138      new_template->set_created_by_policy(true);
1139      AddNoNotify(new_template);
1140    }
1141    SetDefaultSearchProviderNoNotify(new_template);
1142  } else {
1143    // The default was managed and is no longer.
1144    DCHECK(is_default_search_managed_ && !new_is_default_managed);
1145    is_default_search_managed_ = new_is_default_managed;
1146    // If we had a default, delete the previous default if created by policy
1147    // and set a likely default.
1148    if (NULL != default_search_provider_ &&
1149        default_search_provider_->created_by_policy()) {
1150      const TemplateURL* old_default = default_search_provider_;
1151      default_search_provider_ = NULL;
1152      RemoveNoNotify(old_default);
1153    }
1154    SetDefaultSearchProviderNoNotify(FindNewDefaultSearchProvider());
1155  }
1156  NotifyObservers();
1157}
1158
1159const TemplateURL* TemplateURLModel::FindNewDefaultSearchProvider() {
1160  // See if the prepoluated default still exists.
1161  scoped_ptr<TemplateURL> prepopulated_default(
1162      TemplateURLPrepopulateData::GetPrepopulatedDefaultSearch(GetPrefs()));
1163  for (TemplateURLVector::iterator i = template_urls_.begin();
1164      i != template_urls_.end(); ) {
1165    if ((*i)->prepopulate_id() == prepopulated_default->prepopulate_id())
1166      return *i;
1167  }
1168  // If not, use the first of the templates.
1169  if (!template_urls_.empty()) {
1170    return template_urls_[0];
1171  }
1172  return NULL;
1173}
1174
1175void TemplateURLModel::SetDefaultSearchProviderNoNotify(
1176    const TemplateURL* url) {
1177  DCHECK(!url || find(template_urls_.begin(), template_urls_.end(), url) !=
1178         template_urls_.end());
1179  default_search_provider_ = url;
1180
1181  if (url) {
1182    TemplateURL* modifiable_url = const_cast<TemplateURL*>(url);
1183    // Don't mark the url as edited, otherwise we won't be able to rev the
1184    // template urls we ship with.
1185    modifiable_url->set_show_in_default_list(true);
1186    if (service_.get())
1187      service_.get()->UpdateKeyword(*url);
1188
1189    const TemplateURLRef* url_ref = url->url();
1190    if (url_ref && url_ref->HasGoogleBaseURLs()) {
1191      GoogleURLTracker::RequestServerCheck();
1192#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
1193      RLZTracker::RecordProductEvent(rlz_lib::CHROME,
1194                                     rlz_lib::CHROME_OMNIBOX,
1195                                     rlz_lib::SET_TO_GOOGLE);
1196#endif
1197    }
1198  }
1199
1200  if (!is_default_search_managed_)
1201    SaveDefaultSearchProviderToPrefs(url);
1202
1203  if (service_.get())
1204    service_->SetDefaultSearchProvider(url);
1205}
1206
1207void TemplateURLModel::AddNoNotify(TemplateURL* template_url) {
1208  DCHECK(template_url);
1209  DCHECK(template_url->id() == 0);
1210  DCHECK(find(template_urls_.begin(), template_urls_.end(), template_url) ==
1211         template_urls_.end());
1212  template_url->set_id(++next_id_);
1213  template_urls_.push_back(template_url);
1214  AddToMaps(template_url);
1215
1216  if (service_.get())
1217    service_->AddKeyword(*template_url);
1218}
1219
1220void TemplateURLModel::RemoveNoNotify(const TemplateURL* template_url) {
1221  TemplateURLVector::iterator i = find(template_urls_.begin(),
1222                                       template_urls_.end(),
1223                                       template_url);
1224  if (i == template_urls_.end())
1225    return;
1226
1227  if (template_url == default_search_provider_) {
1228    // Should never delete the default search provider.
1229    NOTREACHED();
1230    return;
1231  }
1232
1233  RemoveFromMaps(template_url);
1234
1235  // Remove it from the vector containing all TemplateURLs.
1236  template_urls_.erase(i);
1237
1238  if (service_.get())
1239    service_->RemoveKeyword(*template_url);
1240
1241  if (profile_) {
1242    HistoryService* history =
1243        profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
1244    if (history)
1245      history->DeleteAllSearchTermsForKeyword(template_url->id());
1246  }
1247
1248  // We own the TemplateURL and need to delete it.
1249  delete template_url;
1250}
1251
1252void TemplateURLModel::NotifyObservers() {
1253  if (!loaded_)
1254    return;
1255
1256  FOR_EACH_OBSERVER(TemplateURLModelObserver, model_observers_,
1257                    OnTemplateURLModelChanged());
1258}
1259
1260