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