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