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