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