autocomplete.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
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/autocomplete/autocomplete.h" 6 7#include <algorithm> 8 9#include "app/l10n_util.h" 10#include "base/basictypes.h" 11#include "base/command_line.h" 12#include "base/i18n/number_formatting.h" 13#include "base/string_number_conversions.h" 14#include "base/string_util.h" 15#include "base/utf_string_conversions.h" 16#include "chrome/browser/autocomplete/autocomplete_match.h" 17#include "chrome/browser/autocomplete/history_quick_provider.h" 18#include "chrome/browser/autocomplete/history_url_provider.h" 19#include "chrome/browser/autocomplete/history_contents_provider.h" 20#include "chrome/browser/autocomplete/keyword_provider.h" 21#include "chrome/browser/autocomplete/search_provider.h" 22#include "chrome/browser/bookmarks/bookmark_model.h" 23#include "chrome/browser/dom_ui/history_ui.h" 24#include "chrome/browser/external_protocol_handler.h" 25#include "chrome/browser/net/url_fixer_upper.h" 26#include "chrome/browser/prefs/pref_service.h" 27#include "chrome/browser/profile.h" 28#include "chrome/common/chrome_switches.h" 29#include "chrome/common/notification_service.h" 30#include "chrome/common/pref_names.h" 31#include "chrome/common/url_constants.h" 32#include "googleurl/src/gurl.h" 33#include "googleurl/src/url_canon_ip.h" 34#include "googleurl/src/url_util.h" 35#include "grit/generated_resources.h" 36#include "grit/theme_resources.h" 37#include "net/base/net_util.h" 38#include "net/base/registry_controlled_domain.h" 39#include "net/url_request/url_request.h" 40 41using base::TimeDelta; 42 43// AutocompleteInput ---------------------------------------------------------- 44 45AutocompleteInput::AutocompleteInput() 46 : type_(INVALID), 47 initial_prevent_inline_autocomplete_(false), 48 prevent_inline_autocomplete_(false), 49 prefer_keyword_(false), 50 allow_exact_keyword_match_(true), 51 synchronous_only_(false) { 52} 53 54AutocompleteInput::AutocompleteInput(const std::wstring& text, 55 const std::wstring& desired_tld, 56 bool prevent_inline_autocomplete, 57 bool prefer_keyword, 58 bool allow_exact_keyword_match, 59 bool synchronous_only) 60 : desired_tld_(desired_tld), 61 initial_prevent_inline_autocomplete_(prevent_inline_autocomplete), 62 prevent_inline_autocomplete_(prevent_inline_autocomplete), 63 prefer_keyword_(prefer_keyword), 64 allow_exact_keyword_match_(allow_exact_keyword_match), 65 synchronous_only_(synchronous_only) { 66 // Trim whitespace from edges of input; don't inline autocomplete if there 67 // was trailing whitespace. 68 if (TrimWhitespace(text, TRIM_ALL, &text_) & TRIM_TRAILING) 69 prevent_inline_autocomplete_ = true; 70 71 type_ = Parse(text_, desired_tld, &parts_, &scheme_); 72 73 if (type_ == INVALID) 74 return; 75 76 if ((type_ == UNKNOWN) || (type_ == REQUESTED_URL) || (type_ == URL)) { 77 GURL canonicalized_url(URLFixerUpper::FixupURL(WideToUTF8(text_), 78 WideToUTF8(desired_tld_))); 79 if (canonicalized_url.is_valid() && 80 (!canonicalized_url.IsStandard() || canonicalized_url.SchemeIsFile() || 81 !canonicalized_url.host().empty())) 82 canonicalized_url_ = canonicalized_url; 83 } 84 85 RemoveForcedQueryStringIfNecessary(type_, &text_); 86} 87 88AutocompleteInput::~AutocompleteInput() { 89} 90 91// static 92void AutocompleteInput::RemoveForcedQueryStringIfNecessary(Type type, 93 std::wstring* text) { 94 if (type == FORCED_QUERY && !text->empty() && (*text)[0] == L'?') 95 text->erase(0, 1); 96} 97 98// static 99std::string AutocompleteInput::TypeToString(Type type) { 100 switch (type) { 101 case INVALID: return "invalid"; 102 case UNKNOWN: return "unknown"; 103 case REQUESTED_URL: return "requested-url"; 104 case URL: return "url"; 105 case QUERY: return "query"; 106 case FORCED_QUERY: return "forced-query"; 107 108 default: 109 NOTREACHED(); 110 return std::string(); 111 } 112} 113 114// static 115AutocompleteInput::Type AutocompleteInput::Parse( 116 const std::wstring& text, 117 const std::wstring& desired_tld, 118 url_parse::Parsed* parts, 119 std::wstring* scheme) { 120 const size_t first_non_white = text.find_first_not_of(kWhitespaceWide, 0); 121 if (first_non_white == std::wstring::npos) 122 return INVALID; // All whitespace. 123 124 if (text.at(first_non_white) == L'?') { 125 // If the first non-whitespace character is a '?', we magically treat this 126 // as a query. 127 return FORCED_QUERY; 128 } 129 130 // Ask our parsing back-end to help us understand what the user typed. We 131 // use the URLFixerUpper here because we want to be smart about what we 132 // consider a scheme. For example, we shouldn't consider www.google.com:80 133 // to have a scheme. 134 url_parse::Parsed local_parts; 135 if (!parts) 136 parts = &local_parts; 137 const std::wstring parsed_scheme(URLFixerUpper::SegmentURL(text, parts)); 138 if (scheme) 139 *scheme = parsed_scheme; 140 141 if (parsed_scheme == L"file") { 142 // A user might or might not type a scheme when entering a file URL. In 143 // either case, |parsed_scheme| will tell us that this is a file URL, but 144 // |parts->scheme| might be empty, e.g. if the user typed "C:\foo". 145 return URL; 146 } 147 148 // If the user typed a scheme, and it's HTTP or HTTPS, we know how to parse it 149 // well enough that we can fall through to the heuristics below. If it's 150 // something else, we can just determine our action based on what we do with 151 // any input of this scheme. In theory we could do better with some schemes 152 // (e.g. "ftp" or "view-source") but I'll wait to spend the effort on that 153 // until I run into some cases that really need it. 154 if (parts->scheme.is_nonempty() && 155 (parsed_scheme != L"http") && (parsed_scheme != L"https")) { 156 // See if we know how to handle the URL internally. 157 if (URLRequest::IsHandledProtocol(WideToASCII(parsed_scheme))) 158 return URL; 159 160 // There are also some schemes that we convert to other things before they 161 // reach the renderer or else the renderer handles internally without 162 // reaching the URLRequest logic. We thus won't catch these above, but we 163 // should still claim to handle them. 164 if (LowerCaseEqualsASCII(parsed_scheme, chrome::kViewSourceScheme) || 165 LowerCaseEqualsASCII(parsed_scheme, chrome::kJavaScriptScheme) || 166 LowerCaseEqualsASCII(parsed_scheme, chrome::kDataScheme)) 167 return URL; 168 169 // Finally, check and see if the user has explicitly opened this scheme as 170 // a URL before. We need to do this last because some schemes may be in 171 // here as "blocked" (e.g. "javascript") because we don't want pages to open 172 // them, but users still can. 173 // TODO(viettrungluu): get rid of conversion. 174 switch (ExternalProtocolHandler::GetBlockState(WideToUTF8(parsed_scheme))) { 175 case ExternalProtocolHandler::DONT_BLOCK: 176 return URL; 177 178 case ExternalProtocolHandler::BLOCK: 179 // If we don't want the user to open the URL, don't let it be navigated 180 // to at all. 181 return QUERY; 182 183 default: 184 // We don't know about this scheme. It's likely to be a search operator 185 // like "site:" or "link:". We classify it as UNKNOWN so the user has 186 // the option of treating it as a URL if we're wrong. 187 // Note that SegmentURL() is smart so we aren't tricked by "c:\foo" or 188 // "www.example.com:81" in this case. 189 return UNKNOWN; 190 } 191 } 192 193 // Either the user didn't type a scheme, in which case we need to distinguish 194 // between an HTTP URL and a query, or the scheme is HTTP or HTTPS, in which 195 // case we should reject invalid formulations. 196 197 // If we have an empty host it can't be a URL. 198 if (!parts->host.is_nonempty()) 199 return QUERY; 200 201 // Likewise, the RCDS can reject certain obviously-invalid hosts. (We also 202 // use the registry length later below.) 203 const std::wstring host(text.substr(parts->host.begin, parts->host.len)); 204 const size_t registry_length = 205 net::RegistryControlledDomainService::GetRegistryLength(host, false); 206 if (registry_length == std::wstring::npos) { 207 // Try to append the desired_tld. 208 if (!desired_tld.empty()) { 209 std::wstring host_with_tld(host); 210 if (host[host.length() - 1] != '.') 211 host_with_tld += '.'; 212 host_with_tld += desired_tld; 213 if (net::RegistryControlledDomainService::GetRegistryLength( 214 host_with_tld, false) != std::wstring::npos) 215 return REQUESTED_URL; // Something like "99999999999" that looks like a 216 // bad IP address, but becomes valid on attaching 217 // a TLD. 218 } 219 return QUERY; // Could be a broken IP address, etc. 220 } 221 222 223 // See if the hostname is valid. While IE and GURL allow hostnames to contain 224 // many other characters (perhaps for weird intranet machines), it's extremely 225 // unlikely that a user would be trying to type those in for anything other 226 // than a search query. 227 url_canon::CanonHostInfo host_info; 228 const std::string canonicalized_host(net::CanonicalizeHost(host, &host_info)); 229 if ((host_info.family == url_canon::CanonHostInfo::NEUTRAL) && 230 !net::IsCanonicalizedHostCompliant(canonicalized_host, 231 WideToUTF8(desired_tld))) { 232 // Invalid hostname. There are several possible cases: 233 // * Our checker is too strict and the user pasted in a real-world URL 234 // that's "invalid" but resolves. To catch these, we return UNKNOWN when 235 // the user explicitly typed a scheme, so we'll still search by default 236 // but we'll show the accidental search infobar if necessary. 237 // * The user is typing a multi-word query. If we see a space anywhere in 238 // the hostname we assume this is a search and return QUERY. 239 // * Our checker is too strict and the user is typing a real-world hostname 240 // that's "invalid" but resolves. We return UNKNOWN if the TLD is known. 241 // Note that we explicitly excluded hosts with spaces above so that 242 // "toys at amazon.com" will be treated as a search. 243 // * The user is typing some garbage string. Return QUERY. 244 // 245 // Thus we fall down in the following cases: 246 // * Trying to navigate to a hostname with spaces 247 // * Trying to navigate to a hostname with invalid characters and an unknown 248 // TLD 249 // These are rare, though probably possible in intranets. 250 return (parts->scheme.is_nonempty() || 251 ((registry_length != 0) && (host.find(' ') == std::wstring::npos))) ? 252 UNKNOWN : QUERY; 253 } 254 255 // A port number is a good indicator that this is a URL. However, it might 256 // also be a query like "1.66:1" that looks kind of like an IP address and 257 // port number. So here we only check for "port numbers" that are illegal and 258 // thus mean this can't be navigated to (e.g. "1.2.3.4:garbage"), and we save 259 // handling legal port numbers until after the "IP address" determination 260 // below. 261 if (parts->port.is_nonempty()) { 262 int port; 263 if (!base::StringToInt(WideToUTF8( 264 text.substr(parts->port.begin, parts->port.len)), &port) || 265 (port < 0) || (port > 65535)) 266 return QUERY; 267 } 268 269 // Now that we've ruled out all schemes other than http or https and done a 270 // little more sanity checking, the presence of a scheme means this is likely 271 // a URL. 272 if (parts->scheme.is_nonempty()) 273 return URL; 274 275 // See if the host is an IP address. 276 if (host_info.family == url_canon::CanonHostInfo::IPV4) { 277 // If the user originally typed a host that looks like an IP address (a 278 // dotted quad), they probably want to open it. If the original input was 279 // something else (like a single number), they probably wanted to search for 280 // it, unless they explicitly typed a scheme. This is true even if the URL 281 // appears to have a path: "1.2/45" is more likely a search (for the answer 282 // to a math problem) than a URL. 283 if (host_info.num_ipv4_components == 4) 284 return URL; 285 return desired_tld.empty() ? UNKNOWN : REQUESTED_URL; 286 } 287 if (host_info.family == url_canon::CanonHostInfo::IPV6) 288 return URL; 289 290 // Now that we've ruled out invalid ports and queries that look like they have 291 // a port, the presence of a port means this is likely a URL. 292 if (parts->port.is_nonempty()) 293 return URL; 294 295 // Presence of a password means this is likely a URL. Note that unless the 296 // user has typed an explicit "http://" or similar, we'll probably think that 297 // the username is some unknown scheme, and bail out in the scheme-handling 298 // code above. 299 if (parts->password.is_nonempty()) 300 return URL; 301 302 // The host doesn't look like a number, so see if the user's given us a path. 303 if (parts->path.is_nonempty()) { 304 // Most inputs with paths are URLs, even ones without known registries (e.g. 305 // intranet URLs). However, if there's no known registry and the path has 306 // a space, this is more likely a query with a slash in the first term 307 // (e.g. "ps/2 games") than a URL. We can still open URLs with spaces in 308 // the path by escaping the space, and we will still inline autocomplete 309 // them if users have typed them in the past, but we default to searching 310 // since that's the common case. 311 return ((registry_length == 0) && 312 (text.substr(parts->path.begin, parts->path.len).find(' ') != 313 std::wstring::npos)) ? UNKNOWN : URL; 314 } 315 316 // If we reach here with a username, our input looks like "user@host". 317 // Because there is no scheme explicitly specified, we think this is more 318 // likely an email address than an HTTP auth attempt. Hence, we search by 319 // default and let users correct us on a case-by-case basis. 320 if (parts->username.is_nonempty()) 321 return UNKNOWN; 322 323 // We have a bare host string. If it has a known TLD, it's probably a URL. 324 if (registry_length != 0) 325 return URL; 326 327 // No TLD that we know about. This could be: 328 // * A string that the user wishes to add a desired_tld to to get a URL. If 329 // we reach this point, we know there's no known TLD on the string, so the 330 // fixup code will be willing to add one; thus this is a URL. 331 // * A single word "foo"; possibly an intranet site, but more likely a search. 332 // This is ideally an UNKNOWN, and we can let the Alternate Nav URL code 333 // catch our mistakes. 334 // * A URL with a valid TLD we don't know about yet. If e.g. a registrar adds 335 // "xxx" as a TLD, then until we add it to our data file, Chrome won't know 336 // "foo.xxx" is a real URL. So ideally this is a URL, but we can't really 337 // distinguish this case from: 338 // * A "URL-like" string that's not really a URL (like 339 // "browser.tabs.closeButtons" or "java.awt.event.*"). This is ideally a 340 // QUERY. Since the above case and this one are indistinguishable, and this 341 // case is likely to be much more common, just say these are both UNKNOWN, 342 // which should default to the right thing and let users correct us on a 343 // case-by-case basis. 344 return desired_tld.empty() ? UNKNOWN : REQUESTED_URL; 345} 346 347// static 348void AutocompleteInput::ParseForEmphasizeComponents( 349 const std::wstring& text, 350 const std::wstring& desired_tld, 351 url_parse::Component* scheme, 352 url_parse::Component* host) { 353 url_parse::Parsed parts; 354 std::wstring scheme_str; 355 Parse(text, desired_tld, &parts, &scheme_str); 356 357 *scheme = parts.scheme; 358 *host = parts.host; 359 360 int after_scheme_and_colon = parts.scheme.end() + 1; 361 // For the view-source scheme, we should emphasize the scheme and host of the 362 // URL qualified by the view-source prefix. 363 if (LowerCaseEqualsASCII(scheme_str, chrome::kViewSourceScheme) && 364 (static_cast<int>(text.length()) > after_scheme_and_colon)) { 365 // Obtain the URL prefixed by view-source and parse it. 366 std::wstring real_url(text.substr(after_scheme_and_colon)); 367 url_parse::Parsed real_parts; 368 AutocompleteInput::Parse(real_url, desired_tld, &real_parts, NULL); 369 if (real_parts.scheme.is_nonempty() || real_parts.host.is_nonempty()) { 370 if (real_parts.scheme.is_nonempty()) { 371 *scheme = url_parse::Component( 372 after_scheme_and_colon + real_parts.scheme.begin, 373 real_parts.scheme.len); 374 } else { 375 scheme->reset(); 376 } 377 if (real_parts.host.is_nonempty()) { 378 *host = url_parse::Component( 379 after_scheme_and_colon + real_parts.host.begin, 380 real_parts.host.len); 381 } else { 382 host->reset(); 383 } 384 } 385 } 386} 387 388// static 389std::wstring AutocompleteInput::FormattedStringWithEquivalentMeaning( 390 const GURL& url, 391 const std::wstring& formatted_url) { 392 if (!net::CanStripTrailingSlash(url)) 393 return formatted_url; 394 const std::wstring url_with_path(formatted_url + L"/"); 395 return (AutocompleteInput::Parse(formatted_url, std::wstring(), NULL, NULL) == 396 AutocompleteInput::Parse(url_with_path, std::wstring(), NULL, NULL)) ? 397 formatted_url : url_with_path; 398} 399 400 401bool AutocompleteInput::Equals(const AutocompleteInput& other) const { 402 return (text_ == other.text_) && 403 (type_ == other.type_) && 404 (desired_tld_ == other.desired_tld_) && 405 (scheme_ == other.scheme_) && 406 (prevent_inline_autocomplete_ == other.prevent_inline_autocomplete_) && 407 (prefer_keyword_ == other.prefer_keyword_) && 408 (synchronous_only_ == other.synchronous_only_); 409} 410 411void AutocompleteInput::Clear() { 412 text_.clear(); 413 type_ = INVALID; 414 parts_ = url_parse::Parsed(); 415 scheme_.clear(); 416 desired_tld_.clear(); 417 prevent_inline_autocomplete_ = false; 418 prefer_keyword_ = false; 419} 420 421// AutocompleteProvider ------------------------------------------------------- 422 423// static 424const size_t AutocompleteProvider::kMaxMatches = 3; 425 426AutocompleteProvider::ACProviderListener::~ACProviderListener() { 427} 428 429AutocompleteProvider::AutocompleteProvider(ACProviderListener* listener, 430 Profile* profile, 431 const char* name) 432 : profile_(profile), 433 listener_(listener), 434 done_(true), 435 name_(name) { 436} 437 438void AutocompleteProvider::SetProfile(Profile* profile) { 439 DCHECK(profile); 440 DCHECK(done_); // The controller should have already stopped us. 441 profile_ = profile; 442} 443 444void AutocompleteProvider::Stop() { 445 done_ = true; 446} 447 448void AutocompleteProvider::DeleteMatch(const AutocompleteMatch& match) { 449} 450 451AutocompleteProvider::~AutocompleteProvider() { 452 Stop(); 453} 454 455// static 456bool AutocompleteProvider::HasHTTPScheme(const std::wstring& input) { 457 std::string utf8_input(WideToUTF8(input)); 458 url_parse::Component scheme; 459 if (url_util::FindAndCompareScheme(utf8_input, chrome::kViewSourceScheme, 460 &scheme)) 461 utf8_input.erase(0, scheme.end() + 1); 462 return url_util::FindAndCompareScheme(utf8_input, chrome::kHttpScheme, NULL); 463} 464 465void AutocompleteProvider::UpdateStarredStateOfMatches() { 466 if (matches_.empty()) 467 return; 468 469 if (!profile_) 470 return; 471 BookmarkModel* bookmark_model = profile_->GetBookmarkModel(); 472 if (!bookmark_model || !bookmark_model->IsLoaded()) 473 return; 474 475 for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i) 476 i->starred = bookmark_model->IsBookmarked(GURL(i->destination_url)); 477} 478 479std::wstring AutocompleteProvider::StringForURLDisplay(const GURL& url, 480 bool check_accept_lang, 481 bool trim_http) const { 482 std::string languages = (check_accept_lang && profile_) ? 483 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : std::string(); 484 return UTF16ToWideHack(net::FormatUrl( 485 url, 486 languages, 487 net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP), 488 UnescapeRule::SPACES, NULL, NULL, NULL)); 489} 490 491// AutocompleteResult --------------------------------------------------------- 492 493// static 494const size_t AutocompleteResult::kMaxMatches = 6; 495 496void AutocompleteResult::Selection::Clear() { 497 destination_url = GURL(); 498 provider_affinity = NULL; 499 is_history_what_you_typed_match = false; 500} 501 502AutocompleteResult::AutocompleteResult() { 503 // Reserve space for the max number of matches we'll show. The +1 accounts 504 // for the history shortcut match as it isn't included in max_matches. 505 matches_.reserve(kMaxMatches + 1); 506 507 // It's probably safe to do this in the initializer list, but there's little 508 // penalty to doing it here and it ensures our object is fully constructed 509 // before calling member functions. 510 default_match_ = end(); 511} 512 513AutocompleteResult::~AutocompleteResult() {} 514 515void AutocompleteResult::CopyFrom(const AutocompleteResult& rhs) { 516 if (this == &rhs) 517 return; 518 519 matches_ = rhs.matches_; 520 // Careful! You can't just copy iterators from another container, you have to 521 // reconstruct them. 522 default_match_ = (rhs.default_match_ == rhs.end()) ? 523 end() : (begin() + (rhs.default_match_ - rhs.begin())); 524 525 alternate_nav_url_ = rhs.alternate_nav_url_; 526} 527 528void AutocompleteResult::AppendMatches(const ACMatches& matches) { 529 std::copy(matches.begin(), matches.end(), std::back_inserter(matches_)); 530 default_match_ = end(); 531 alternate_nav_url_ = GURL(); 532} 533 534void AutocompleteResult::AddMatch(const AutocompleteMatch& match) { 535 DCHECK(default_match_ != end()); 536 ACMatches::iterator insertion_point = 537 std::upper_bound(begin(), end(), match, &AutocompleteMatch::MoreRelevant); 538 ACMatches::iterator::difference_type default_offset = 539 default_match_ - begin(); 540 if ((insertion_point - begin()) <= default_offset) 541 ++default_offset; 542 matches_.insert(insertion_point, match); 543 default_match_ = begin() + default_offset; 544} 545 546void AutocompleteResult::SortAndCull(const AutocompleteInput& input) { 547 // Remove duplicates. 548 std::sort(matches_.begin(), matches_.end(), 549 &AutocompleteMatch::DestinationSortFunc); 550 matches_.erase(std::unique(matches_.begin(), matches_.end(), 551 &AutocompleteMatch::DestinationsEqual), 552 matches_.end()); 553 554 // Find the top |kMaxMatches| matches. 555 if (matches_.size() > kMaxMatches) { 556 std::partial_sort(matches_.begin(), matches_.begin() + kMaxMatches, 557 matches_.end(), &AutocompleteMatch::MoreRelevant); 558 matches_.erase(matches_.begin() + kMaxMatches, matches_.end()); 559 } 560 561 // HistoryContentsProvider uses a negative relevance as a way to avoid 562 // starving out other provider matches, yet we may end up using this match. To 563 // make sure such matches are sorted correctly we search for all 564 // relevances < 0 and negate them. If we change our relevance algorithm to 565 // properly mix different providers' matches, this can go away. 566 for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i) { 567 if (i->relevance < 0) 568 i->relevance = -i->relevance; 569 } 570 571 // Put the final result set in order. 572 std::sort(matches_.begin(), matches_.end(), &AutocompleteMatch::MoreRelevant); 573 default_match_ = begin(); 574 575 // Set the alternate nav URL. 576 alternate_nav_url_ = GURL(); 577 if (((input.type() == AutocompleteInput::UNKNOWN) || 578 (input.type() == AutocompleteInput::REQUESTED_URL)) && 579 (default_match_ != end()) && 580 (default_match_->transition != PageTransition::TYPED) && 581 (default_match_->transition != PageTransition::KEYWORD) && 582 (input.canonicalized_url() != default_match_->destination_url)) 583 alternate_nav_url_ = input.canonicalized_url(); 584} 585 586size_t AutocompleteResult::size() const { 587 return matches_.size(); 588} 589 590bool AutocompleteResult::empty() const { 591 return matches_.empty(); 592} 593 594AutocompleteResult::const_iterator AutocompleteResult::begin() const { 595 return matches_.begin(); 596} 597 598AutocompleteResult::iterator AutocompleteResult::begin() { 599 return matches_.begin(); 600} 601 602AutocompleteResult::const_iterator AutocompleteResult::end() const { 603 return matches_.end(); 604} 605 606AutocompleteResult::iterator AutocompleteResult::end() { 607 return matches_.end(); 608} 609 610// Returns the match at the given index. 611const AutocompleteMatch& AutocompleteResult::match_at(size_t index) const { 612 DCHECK(index < matches_.size()); 613 return matches_[index]; 614} 615 616void AutocompleteResult::Reset() { 617 matches_.clear(); 618 default_match_ = end(); 619} 620 621#ifndef NDEBUG 622void AutocompleteResult::Validate() const { 623 for (const_iterator i(begin()); i != end(); ++i) 624 i->Validate(); 625} 626#endif 627 628// AutocompleteController ----------------------------------------------------- 629 630const int AutocompleteController::kNoItemSelected = -1; 631 632namespace { 633// The time we'll wait between sending updates to our observers (balances 634// flicker against lag). 635const int kUpdateDelayMs = 350; 636}; 637 638AutocompleteController::AutocompleteController(Profile* profile) 639 : updated_latest_result_(false), 640 delay_interval_has_passed_(false), 641 have_committed_during_this_query_(false), 642 done_(true) { 643 search_provider_ = new SearchProvider(this, profile); 644 providers_.push_back(search_provider_); 645 if (!CommandLine::ForCurrentProcess()->HasSwitch( 646 switches::kDisableHistoryQuickProvider)) 647 providers_.push_back(new HistoryQuickProvider(this, profile)); 648 if (!CommandLine::ForCurrentProcess()->HasSwitch( 649 switches::kDisableHistoryURLProvider)) 650 providers_.push_back(new HistoryURLProvider(this, profile)); 651 providers_.push_back(new KeywordProvider(this, profile)); 652 history_contents_provider_ = new HistoryContentsProvider(this, profile); 653 providers_.push_back(history_contents_provider_); 654 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 655 (*i)->AddRef(); 656} 657 658AutocompleteController::~AutocompleteController() { 659 // The providers may have tasks outstanding that hold refs to them. We need 660 // to ensure they won't call us back if they outlive us. (Practically, 661 // calling Stop() should also cancel those tasks and make it so that we hold 662 // the only refs.) We also don't want to bother notifying anyone of our 663 // result changes here, because the notification observer is in the midst of 664 // shutdown too, so we don't ask Stop() to clear |result_| (and notify). 665 result_.Reset(); // Not really necessary. 666 Stop(false); 667 668 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 669 (*i)->Release(); 670 671 providers_.clear(); // Not really necessary. 672} 673 674void AutocompleteController::SetProfile(Profile* profile) { 675 Stop(true); 676 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 677 (*i)->SetProfile(profile); 678 input_.Clear(); // Ensure we don't try to do a "minimal_changes" query on a 679 // different profile. 680} 681 682void AutocompleteController::Start(const std::wstring& text, 683 const std::wstring& desired_tld, 684 bool prevent_inline_autocomplete, 685 bool prefer_keyword, 686 bool allow_exact_keyword_match, 687 bool synchronous_only) { 688 const std::wstring old_input_text(input_.text()); 689 const bool old_synchronous_only = input_.synchronous_only(); 690 input_ = AutocompleteInput(text, desired_tld, prevent_inline_autocomplete, 691 prefer_keyword, allow_exact_keyword_match, synchronous_only); 692 693 // See if we can avoid rerunning autocomplete when the query hasn't changed 694 // much. When the user presses or releases the ctrl key, the desired_tld 695 // changes, and when the user finishes an IME composition, inline autocomplete 696 // may no longer be prevented. In both these cases the text itself hasn't 697 // changed since the last query, and some providers can do much less work (and 698 // get matches back more quickly). Taking advantage of this reduces flicker. 699 // 700 // NOTE: This comes after constructing |input_| above since that construction 701 // can change the text string (e.g. by stripping off a leading '?'). 702 const bool minimal_changes = (input_.text() == old_input_text) && 703 (input_.synchronous_only() == old_synchronous_only); 704 705 // If we're interrupting an old query, and committing its result won't shrink 706 // the visible set (which would probably re-expand soon, thus looking very 707 // flickery), then go ahead and commit what we've got, in order to feel more 708 // responsive when the user is typing rapidly. In this case it's important 709 // that we don't update the edit, as the user has already changed its contents 710 // and anything we might do with it (e.g. inline autocomplete) likely no 711 // longer applies. 712 if (!minimal_changes && !done_ && (latest_result_.size() >= result_.size())) 713 CommitResult(false); 714 715 // If the timer is already running, it could fire shortly after starting this 716 // query, when we're likely to only have the synchronous results back, thus 717 // almost certainly causing flicker. Reset it, except when we haven't 718 // committed anything for the past query, in which case the user is typing 719 // quickly and we need to keep running the timer lest we lag too far behind. 720 if (have_committed_during_this_query_) { 721 update_delay_timer_.Stop(); 722 delay_interval_has_passed_ = false; 723 } 724 725 // Start the new query. 726 have_committed_during_this_query_ = false; 727 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); 728 ++i) { 729 (*i)->Start(input_, minimal_changes); 730 if (synchronous_only) 731 DCHECK((*i)->done()); 732 } 733 CheckIfDone(); 734 UpdateLatestResult(true); 735} 736 737void AutocompleteController::Stop(bool clear_result) { 738 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 739 ++i) { 740 (*i)->Stop(); 741 } 742 743 update_delay_timer_.Stop(); 744 updated_latest_result_ = false; 745 delay_interval_has_passed_ = false; 746 done_ = true; 747 if (clear_result && !result_.empty()) { 748 result_.Reset(); 749 NotificationService::current()->Notify( 750 NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED, 751 Source<AutocompleteController>(this), 752 Details<const AutocompleteResult>(&result_)); 753 // NOTE: We don't notify AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED since 754 // we're trying to only clear the popup, not touch the edit... this is all 755 // a mess and should be cleaned up :( 756 } 757 latest_result_.CopyFrom(result_); 758} 759 760void AutocompleteController::DeleteMatch(const AutocompleteMatch& match) { 761 DCHECK(match.deletable); 762 match.provider->DeleteMatch(match); // This may synchronously call back to 763 // OnProviderUpdate(). 764 CommitResult(true); // Ensure any new result gets committed immediately. If 765 // it was committed already or hasn't been modified, this 766 // is harmless. 767} 768 769void AutocompleteController::CommitIfQueryHasNeverBeenCommitted() { 770 if (!have_committed_during_this_query_) 771 CommitResult(true); 772} 773 774void AutocompleteController::OnProviderUpdate(bool updated_matches) { 775 CheckIfDone(); 776 if (updated_matches || done_) 777 UpdateLatestResult(false); 778} 779 780void AutocompleteController::UpdateLatestResult(bool is_synchronous_pass) { 781 // Add all providers' matches. 782 latest_result_.Reset(); 783 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 784 ++i) 785 latest_result_.AppendMatches((*i)->matches()); 786 updated_latest_result_ = true; 787 788 // Sort the matches and trim to a small number of "best" matches. 789 latest_result_.SortAndCull(input_); 790 791 if (history_contents_provider_) 792 AddHistoryContentsShortcut(); 793 794#ifndef NDEBUG 795 latest_result_.Validate(); 796#endif 797 798 if (is_synchronous_pass) { 799 if (!update_delay_timer_.IsRunning()) { 800 update_delay_timer_.Start( 801 TimeDelta::FromMilliseconds(kUpdateDelayMs), 802 this, &AutocompleteController::DelayTimerFired); 803 } 804 805 NotificationService::current()->Notify( 806 NotificationType::AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED, 807 Source<AutocompleteController>(this), 808 Details<const AutocompleteResult>(&latest_result_)); 809 } 810 811 // If nothing is visible, commit immediately so that the first character the 812 // user types produces an instant response. If the query has finished and we 813 // haven't ever committed a result set, commit immediately to minimize lag. 814 // Otherwise, only commit when it's been at least one delay interval since the 815 // last commit, to minimize flicker. 816 if (result_.empty() || (done_ && !have_committed_during_this_query_) || 817 delay_interval_has_passed_) 818 CommitResult(true); 819} 820 821void AutocompleteController::DelayTimerFired() { 822 delay_interval_has_passed_ = true; 823 CommitResult(true); 824} 825 826void AutocompleteController::CommitResult(bool notify_default_match) { 827 if (done_) { 828 update_delay_timer_.Stop(); 829 delay_interval_has_passed_ = false; 830 } 831 832 // Don't send update notifications when nothing's actually changed. 833 if (!updated_latest_result_) 834 return; 835 836 updated_latest_result_ = false; 837 delay_interval_has_passed_ = false; 838 have_committed_during_this_query_ = true; 839 result_.CopyFrom(latest_result_); 840 NotificationService::current()->Notify( 841 NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED, 842 Source<AutocompleteController>(this), 843 Details<const AutocompleteResult>(&result_)); 844 if (notify_default_match) { 845 // This notification must be sent after the other so the popup has time to 846 // update its state before the edit calls into it. 847 // TODO(pkasting): Eliminate this ordering requirement. 848 NotificationService::current()->Notify( 849 NotificationType::AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED, 850 Source<AutocompleteController>(this), 851 Details<const AutocompleteResult>(&result_)); 852 } 853 if (!done_) 854 update_delay_timer_.Reset(); 855} 856 857ACMatches AutocompleteController::GetMatchesNotInLatestResult( 858 const AutocompleteProvider* provider) const { 859 DCHECK(provider); 860 861 // Determine the set of destination URLs. 862 std::set<GURL> destination_urls; 863 for (AutocompleteResult::const_iterator i(latest_result_.begin()); 864 i != latest_result_.end(); ++i) 865 destination_urls.insert(i->destination_url); 866 867 ACMatches matches; 868 const ACMatches& provider_matches = provider->matches(); 869 for (ACMatches::const_iterator i = provider_matches.begin(); 870 i != provider_matches.end(); ++i) { 871 if (destination_urls.find(i->destination_url) == destination_urls.end()) 872 matches.push_back(*i); 873 } 874 875 return matches; 876} 877 878void AutocompleteController::AddHistoryContentsShortcut() { 879 DCHECK(history_contents_provider_); 880 // Only check the history contents provider if the history contents provider 881 // is done and has matches. 882 if (!history_contents_provider_->done() || 883 !history_contents_provider_->db_match_count()) { 884 return; 885 } 886 887 if ((history_contents_provider_->db_match_count() <= 888 (latest_result_.size() + 1)) || 889 (history_contents_provider_->db_match_count() == 1)) { 890 // We only want to add a shortcut if we're not already showing the matches. 891 ACMatches matches(GetMatchesNotInLatestResult(history_contents_provider_)); 892 if (matches.empty()) 893 return; 894 if (matches.size() == 1) { 895 // Only one match not shown, add it. The relevance may be negative, 896 // which means we need to negate it to get the true relevance. 897 AutocompleteMatch& match = matches.front(); 898 if (match.relevance < 0) 899 match.relevance = -match.relevance; 900 latest_result_.AddMatch(match); 901 return; 902 } // else, fall through and add item. 903 } 904 905 AutocompleteMatch match(NULL, 0, false, AutocompleteMatch::OPEN_HISTORY_PAGE); 906 match.fill_into_edit = input_.text(); 907 908 // Mark up the text such that the user input text is bold. 909 size_t keyword_offset = std::wstring::npos; // Offset into match.contents. 910 if (history_contents_provider_->db_match_count() == 911 history_contents_provider_->kMaxMatchCount) { 912 // History contents searcher has maxed out. 913 match.contents = l10n_util::GetStringF(IDS_OMNIBOX_RECENT_HISTORY_MANY, 914 input_.text(), 915 &keyword_offset); 916 } else { 917 // We can report exact matches when there aren't too many. 918 std::vector<size_t> content_param_offsets; 919 match.contents = l10n_util::GetStringF( 920 IDS_OMNIBOX_RECENT_HISTORY, 921 UTF16ToWide(base::FormatNumber(history_contents_provider_-> 922 db_match_count())), 923 input_.text(), 924 &content_param_offsets); 925 926 // content_param_offsets is ordered based on supplied params, we expect 927 // that the second one contains the query (first is the number). 928 if (content_param_offsets.size() == 2) { 929 keyword_offset = content_param_offsets[1]; 930 } else { 931 // See comments on an identical NOTREACHED() in search_provider.cc. 932 NOTREACHED(); 933 } 934 } 935 936 // NOTE: This comparison succeeds when keyword_offset == std::wstring::npos. 937 if (keyword_offset > 0) { 938 match.contents_class.push_back( 939 ACMatchClassification(0, ACMatchClassification::NONE)); 940 } 941 match.contents_class.push_back( 942 ACMatchClassification(keyword_offset, ACMatchClassification::MATCH)); 943 if (keyword_offset + input_.text().size() < match.contents.size()) { 944 match.contents_class.push_back( 945 ACMatchClassification(keyword_offset + input_.text().size(), 946 ACMatchClassification::NONE)); 947 } 948 match.destination_url = 949 HistoryUI::GetHistoryURLWithSearchText(WideToUTF16(input_.text())); 950 match.transition = PageTransition::AUTO_BOOKMARK; 951 match.provider = history_contents_provider_; 952 latest_result_.AddMatch(match); 953} 954 955void AutocompleteController::CheckIfDone() { 956 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 957 ++i) { 958 if (!(*i)->done()) { 959 done_ = false; 960 return; 961 } 962 } 963 done_ = true; 964} 965