autocomplete.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/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 682AutocompleteResult::~AutocompleteResult() {} 683 684void AutocompleteResult::CopyFrom(const AutocompleteResult& rhs) { 685 if (this == &rhs) 686 return; 687 688 matches_ = rhs.matches_; 689 // Careful! You can't just copy iterators from another container, you have to 690 // reconstruct them. 691 default_match_ = (rhs.default_match_ == rhs.end()) ? 692 end() : (begin() + (rhs.default_match_ - rhs.begin())); 693 694 alternate_nav_url_ = rhs.alternate_nav_url_; 695} 696 697void AutocompleteResult::AppendMatches(const ACMatches& matches) { 698 std::copy(matches.begin(), matches.end(), std::back_inserter(matches_)); 699 default_match_ = end(); 700 alternate_nav_url_ = GURL(); 701} 702 703void AutocompleteResult::AddMatch(const AutocompleteMatch& match) { 704 DCHECK(default_match_ != end()); 705 ACMatches::iterator insertion_point = 706 std::upper_bound(begin(), end(), match, &AutocompleteMatch::MoreRelevant); 707 ACMatches::iterator::difference_type default_offset = 708 default_match_ - begin(); 709 if ((insertion_point - begin()) <= default_offset) 710 ++default_offset; 711 matches_.insert(insertion_point, match); 712 default_match_ = begin() + default_offset; 713} 714 715void AutocompleteResult::SortAndCull(const AutocompleteInput& input) { 716 // Remove duplicates. 717 std::sort(matches_.begin(), matches_.end(), 718 &AutocompleteMatch::DestinationSortFunc); 719 matches_.erase(std::unique(matches_.begin(), matches_.end(), 720 &AutocompleteMatch::DestinationsEqual), 721 matches_.end()); 722 723 // Find the top |kMaxMatches| matches. 724 if (matches_.size() > kMaxMatches) { 725 std::partial_sort(matches_.begin(), matches_.begin() + kMaxMatches, 726 matches_.end(), &AutocompleteMatch::MoreRelevant); 727 matches_.erase(matches_.begin() + kMaxMatches, matches_.end()); 728 } 729 730 // HistoryContentsProvider uses a negative relevance as a way to avoid 731 // starving out other provider matches, yet we may end up using this match. To 732 // make sure such matches are sorted correctly we search for all 733 // relevances < 0 and negate them. If we change our relevance algorithm to 734 // properly mix different providers' matches, this can go away. 735 for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i) { 736 if (i->relevance < 0) 737 i->relevance = -i->relevance; 738 } 739 740 // Put the final result set in order. 741 std::sort(matches_.begin(), matches_.end(), &AutocompleteMatch::MoreRelevant); 742 default_match_ = begin(); 743 744 // Set the alternate nav URL. 745 alternate_nav_url_ = GURL(); 746 if (((input.type() == AutocompleteInput::UNKNOWN) || 747 (input.type() == AutocompleteInput::REQUESTED_URL)) && 748 (default_match_ != end()) && 749 (default_match_->transition != PageTransition::TYPED) && 750 (default_match_->transition != PageTransition::KEYWORD) && 751 (input.canonicalized_url() != default_match_->destination_url)) 752 alternate_nav_url_ = input.canonicalized_url(); 753} 754 755void AutocompleteResult::Reset() { 756 matches_.clear(); 757 default_match_ = end(); 758} 759 760#ifndef NDEBUG 761void AutocompleteResult::Validate() const { 762 for (const_iterator i(begin()); i != end(); ++i) 763 i->Validate(); 764} 765#endif 766 767// AutocompleteController ----------------------------------------------------- 768 769const int AutocompleteController::kNoItemSelected = -1; 770 771namespace { 772// The time we'll wait between sending updates to our observers (balances 773// flicker against lag). 774const int kUpdateDelayMs = 350; 775}; 776 777AutocompleteController::AutocompleteController(Profile* profile) 778 : updated_latest_result_(false), 779 delay_interval_has_passed_(false), 780 have_committed_during_this_query_(false), 781 done_(true) { 782 providers_.push_back(new SearchProvider(this, profile)); 783 if (!CommandLine::ForCurrentProcess()->HasSwitch( 784 switches::kDisableHistoryQuickProvider)) 785 providers_.push_back(new HistoryQuickProvider(this, profile)); 786 if (!CommandLine::ForCurrentProcess()->HasSwitch( 787 switches::kDisableHistoryURLProvider)) 788 providers_.push_back(new HistoryURLProvider(this, profile)); 789 providers_.push_back(new KeywordProvider(this, profile)); 790 history_contents_provider_ = new HistoryContentsProvider(this, profile); 791 providers_.push_back(history_contents_provider_); 792 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 793 (*i)->AddRef(); 794} 795 796AutocompleteController::~AutocompleteController() { 797 // The providers may have tasks outstanding that hold refs to them. We need 798 // to ensure they won't call us back if they outlive us. (Practically, 799 // calling Stop() should also cancel those tasks and make it so that we hold 800 // the only refs.) We also don't want to bother notifying anyone of our 801 // result changes here, because the notification observer is in the midst of 802 // shutdown too, so we don't ask Stop() to clear |result_| (and notify). 803 result_.Reset(); // Not really necessary. 804 Stop(false); 805 806 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 807 (*i)->Release(); 808 809 providers_.clear(); // Not really necessary. 810} 811 812void AutocompleteController::SetProfile(Profile* profile) { 813 Stop(true); 814 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i) 815 (*i)->SetProfile(profile); 816 input_.Clear(); // Ensure we don't try to do a "minimal_changes" query on a 817 // different profile. 818} 819 820void AutocompleteController::Start(const std::wstring& text, 821 const std::wstring& desired_tld, 822 bool prevent_inline_autocomplete, 823 bool prefer_keyword, 824 bool synchronous_only) { 825 const std::wstring old_input_text(input_.text()); 826 const bool old_synchronous_only = input_.synchronous_only(); 827 input_ = AutocompleteInput(text, desired_tld, prevent_inline_autocomplete, 828 prefer_keyword, synchronous_only); 829 830 // See if we can avoid rerunning autocomplete when the query hasn't changed 831 // much. When the user presses or releases the ctrl key, the desired_tld 832 // changes, and when the user finishes an IME composition, inline autocomplete 833 // may no longer be prevented. In both these cases the text itself hasn't 834 // changed since the last query, and some providers can do much less work (and 835 // get matches back more quickly). Taking advantage of this reduces flicker. 836 // 837 // NOTE: This comes after constructing |input_| above since that construction 838 // can change the text string (e.g. by stripping off a leading '?'). 839 const bool minimal_changes = (input_.text() == old_input_text) && 840 (input_.synchronous_only() == old_synchronous_only); 841 842 // If we're interrupting an old query, and committing its result won't shrink 843 // the visible set (which would probably re-expand soon, thus looking very 844 // flickery), then go ahead and commit what we've got, in order to feel more 845 // responsive when the user is typing rapidly. In this case it's important 846 // that we don't update the edit, as the user has already changed its contents 847 // and anything we might do with it (e.g. inline autocomplete) likely no 848 // longer applies. 849 if (!minimal_changes && !done_ && (latest_result_.size() >= result_.size())) 850 CommitResult(false); 851 852 // If the timer is already running, it could fire shortly after starting this 853 // query, when we're likely to only have the synchronous results back, thus 854 // almost certainly causing flicker. Reset it, except when we haven't 855 // committed anything for the past query, in which case the user is typing 856 // quickly and we need to keep running the timer lest we lag too far behind. 857 if (have_committed_during_this_query_) { 858 update_delay_timer_.Stop(); 859 delay_interval_has_passed_ = false; 860 } 861 862 // Start the new query. 863 have_committed_during_this_query_ = false; 864 for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); 865 ++i) { 866 (*i)->Start(input_, minimal_changes); 867 if (synchronous_only) 868 DCHECK((*i)->done()); 869 } 870 CheckIfDone(); 871 UpdateLatestResult(true); 872} 873 874void AutocompleteController::Stop(bool clear_result) { 875 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 876 ++i) { 877 (*i)->Stop(); 878 } 879 880 update_delay_timer_.Stop(); 881 updated_latest_result_ = false; 882 delay_interval_has_passed_ = false; 883 done_ = true; 884 if (clear_result && !result_.empty()) { 885 result_.Reset(); 886 NotificationService::current()->Notify( 887 NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED, 888 Source<AutocompleteController>(this), 889 Details<const AutocompleteResult>(&result_)); 890 // NOTE: We don't notify AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED since 891 // we're trying to only clear the popup, not touch the edit... this is all 892 // a mess and should be cleaned up :( 893 } 894 latest_result_.CopyFrom(result_); 895} 896 897void AutocompleteController::DeleteMatch(const AutocompleteMatch& match) { 898 DCHECK(match.deletable); 899 match.provider->DeleteMatch(match); // This may synchronously call back to 900 // OnProviderUpdate(). 901 CommitResult(true); // Ensure any new result gets committed immediately. If 902 // it was committed already or hasn't been modified, this 903 // is harmless. 904} 905 906void AutocompleteController::CommitIfQueryHasNeverBeenCommitted() { 907 if (!have_committed_during_this_query_) 908 CommitResult(true); 909} 910 911void AutocompleteController::OnProviderUpdate(bool updated_matches) { 912 CheckIfDone(); 913 if (updated_matches || done_) 914 UpdateLatestResult(false); 915} 916 917void AutocompleteController::UpdateLatestResult(bool is_synchronous_pass) { 918 // Add all providers' matches. 919 latest_result_.Reset(); 920 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 921 ++i) 922 latest_result_.AppendMatches((*i)->matches()); 923 updated_latest_result_ = true; 924 925 // Sort the matches and trim to a small number of "best" matches. 926 latest_result_.SortAndCull(input_); 927 928 if (history_contents_provider_) 929 AddHistoryContentsShortcut(); 930 931#ifndef NDEBUG 932 latest_result_.Validate(); 933#endif 934 935 if (is_synchronous_pass) { 936 if (!update_delay_timer_.IsRunning()) { 937 update_delay_timer_.Start( 938 TimeDelta::FromMilliseconds(kUpdateDelayMs), 939 this, &AutocompleteController::DelayTimerFired); 940 } 941 942 NotificationService::current()->Notify( 943 NotificationType::AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED, 944 Source<AutocompleteController>(this), 945 Details<const AutocompleteResult>(&latest_result_)); 946 } 947 948 // If nothing is visible, commit immediately so that the first character the 949 // user types produces an instant response. If the query has finished and we 950 // haven't ever committed a result set, commit immediately to minimize lag. 951 // Otherwise, only commit when it's been at least one delay interval since the 952 // last commit, to minimize flicker. 953 if (result_.empty() || (done_ && !have_committed_during_this_query_) || 954 delay_interval_has_passed_) 955 CommitResult(true); 956} 957 958void AutocompleteController::DelayTimerFired() { 959 delay_interval_has_passed_ = true; 960 CommitResult(true); 961} 962 963void AutocompleteController::CommitResult(bool notify_default_match) { 964 if (done_) { 965 update_delay_timer_.Stop(); 966 delay_interval_has_passed_ = false; 967 } 968 969 // Don't send update notifications when nothing's actually changed. 970 if (!updated_latest_result_) 971 return; 972 973 updated_latest_result_ = false; 974 delay_interval_has_passed_ = false; 975 have_committed_during_this_query_ = true; 976 result_.CopyFrom(latest_result_); 977 NotificationService::current()->Notify( 978 NotificationType::AUTOCOMPLETE_CONTROLLER_RESULT_UPDATED, 979 Source<AutocompleteController>(this), 980 Details<const AutocompleteResult>(&result_)); 981 if (notify_default_match) { 982 // This notification must be sent after the other so the popup has time to 983 // update its state before the edit calls into it. 984 // TODO(pkasting): Eliminate this ordering requirement. 985 NotificationService::current()->Notify( 986 NotificationType::AUTOCOMPLETE_CONTROLLER_DEFAULT_MATCH_UPDATED, 987 Source<AutocompleteController>(this), 988 Details<const AutocompleteResult>(&result_)); 989 } 990 if (!done_) 991 update_delay_timer_.Reset(); 992} 993 994ACMatches AutocompleteController::GetMatchesNotInLatestResult( 995 const AutocompleteProvider* provider) const { 996 DCHECK(provider); 997 998 // Determine the set of destination URLs. 999 std::set<GURL> destination_urls; 1000 for (AutocompleteResult::const_iterator i(latest_result_.begin()); 1001 i != latest_result_.end(); ++i) 1002 destination_urls.insert(i->destination_url); 1003 1004 ACMatches matches; 1005 const ACMatches& provider_matches = provider->matches(); 1006 for (ACMatches::const_iterator i = provider_matches.begin(); 1007 i != provider_matches.end(); ++i) { 1008 if (destination_urls.find(i->destination_url) == destination_urls.end()) 1009 matches.push_back(*i); 1010 } 1011 1012 return matches; 1013} 1014 1015void AutocompleteController::AddHistoryContentsShortcut() { 1016 DCHECK(history_contents_provider_); 1017 // Only check the history contents provider if the history contents provider 1018 // is done and has matches. 1019 if (!history_contents_provider_->done() || 1020 !history_contents_provider_->db_match_count()) { 1021 return; 1022 } 1023 1024 if ((history_contents_provider_->db_match_count() <= 1025 (latest_result_.size() + 1)) || 1026 (history_contents_provider_->db_match_count() == 1)) { 1027 // We only want to add a shortcut if we're not already showing the matches. 1028 ACMatches matches(GetMatchesNotInLatestResult(history_contents_provider_)); 1029 if (matches.empty()) 1030 return; 1031 if (matches.size() == 1) { 1032 // Only one match not shown, add it. The relevance may be negative, 1033 // which means we need to negate it to get the true relevance. 1034 AutocompleteMatch& match = matches.front(); 1035 if (match.relevance < 0) 1036 match.relevance = -match.relevance; 1037 latest_result_.AddMatch(match); 1038 return; 1039 } // else, fall through and add item. 1040 } 1041 1042 AutocompleteMatch match(NULL, 0, false, AutocompleteMatch::OPEN_HISTORY_PAGE); 1043 match.fill_into_edit = input_.text(); 1044 1045 // Mark up the text such that the user input text is bold. 1046 size_t keyword_offset = std::wstring::npos; // Offset into match.contents. 1047 if (history_contents_provider_->db_match_count() == 1048 history_contents_provider_->kMaxMatchCount) { 1049 // History contents searcher has maxed out. 1050 match.contents = l10n_util::GetStringF(IDS_OMNIBOX_RECENT_HISTORY_MANY, 1051 input_.text(), 1052 &keyword_offset); 1053 } else { 1054 // We can report exact matches when there aren't too many. 1055 std::vector<size_t> content_param_offsets; 1056 match.contents = l10n_util::GetStringF( 1057 IDS_OMNIBOX_RECENT_HISTORY, 1058 UTF16ToWide(base::FormatNumber(history_contents_provider_-> 1059 db_match_count())), 1060 input_.text(), 1061 &content_param_offsets); 1062 1063 // content_param_offsets is ordered based on supplied params, we expect 1064 // that the second one contains the query (first is the number). 1065 if (content_param_offsets.size() == 2) { 1066 keyword_offset = content_param_offsets[1]; 1067 } else { 1068 // See comments on an identical NOTREACHED() in search_provider.cc. 1069 NOTREACHED(); 1070 } 1071 } 1072 1073 // NOTE: This comparison succeeds when keyword_offset == std::wstring::npos. 1074 if (keyword_offset > 0) { 1075 match.contents_class.push_back( 1076 ACMatchClassification(0, ACMatchClassification::NONE)); 1077 } 1078 match.contents_class.push_back( 1079 ACMatchClassification(keyword_offset, ACMatchClassification::MATCH)); 1080 if (keyword_offset + input_.text().size() < match.contents.size()) { 1081 match.contents_class.push_back( 1082 ACMatchClassification(keyword_offset + input_.text().size(), 1083 ACMatchClassification::NONE)); 1084 } 1085 match.destination_url = 1086 HistoryUI::GetHistoryURLWithSearchText(WideToUTF16(input_.text())); 1087 match.transition = PageTransition::AUTO_BOOKMARK; 1088 match.provider = history_contents_provider_; 1089 latest_result_.AddMatch(match); 1090} 1091 1092void AutocompleteController::CheckIfDone() { 1093 for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end(); 1094 ++i) { 1095 if (!(*i)->done()) { 1096 done_ = false; 1097 return; 1098 } 1099 } 1100 done_ = true; 1101} 1102