1 // Copyright 2012 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.
5 #include "components/omnibox/search_provider.h"
10 #include "base/base64.h"
11 #include "base/callback.h"
12 #include "base/i18n/break_iterator.h"
13 #include "base/i18n/case_conversion.h"
14 #include "base/json/json_string_value_serializer.h"
15 #include "base/metrics/histogram.h"
16 #include "base/metrics/user_metrics.h"
17 #include "base/rand_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "components/history/core/browser/in_memory_database.h"
21 #include "components/history/core/browser/keyword_search_term.h"
22 #include "components/metrics/proto/omnibox_input_type.pb.h"
23 #include "components/omnibox/autocomplete_provider_client.h"
24 #include "components/omnibox/autocomplete_provider_listener.h"
25 #include "components/omnibox/autocomplete_result.h"
26 #include "components/omnibox/keyword_provider.h"
27 #include "components/omnibox/omnibox_field_trial.h"
28 #include "components/omnibox/url_prefix.h"
29 #include "components/search/search.h"
30 #include "components/search_engines/template_url_prepopulate_data.h"
31 #include "components/search_engines/template_url_service.h"
32 #include "components/variations/variations_http_header_provider.h"
33 #include "grit/components_strings.h"
34 #include "net/base/escape.h"
35 #include "net/base/load_flags.h"
36 #include "net/base/net_util.h"
37 #include "net/http/http_request_headers.h"
38 #include "net/url_request/url_fetcher.h"
39 #include "net/url_request/url_request_status.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "url/url_constants.h"
42 #include "url/url_util.h"
44 // Helpers --------------------------------------------------------------------
48 // We keep track in a histogram how many suggest requests we send, how
49 // many suggest requests we invalidate (e.g., due to a user typing
50 // another character), and how many replies we receive.
51 // *** ADD NEW ENUMS AFTER ALL PREVIOUSLY DEFINED ONES! ***
52 // (excluding the end-of-list enum value)
53 // We do not want values of existing enums to change or else it screws
55 enum SuggestRequestsHistogramValue
{
59 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE
62 // The verbatim score for an input which is not an URL.
63 const int kNonURLVerbatimRelevance
= 1300;
65 // Increments the appropriate value in the histogram by one.
66 void LogOmniboxSuggestRequest(
67 SuggestRequestsHistogramValue request_value
) {
68 UMA_HISTOGRAM_ENUMERATION("Omnibox.SuggestRequests", request_value
,
69 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE
);
72 bool HasMultipleWords(const base::string16
& text
) {
73 base::i18n::BreakIterator
i(text
, base::i18n::BreakIterator::BREAK_WORD
);
74 bool found_word
= false;
89 // SearchProvider::Providers --------------------------------------------------
91 SearchProvider::Providers::Providers(TemplateURLService
* template_url_service
)
92 : template_url_service_(template_url_service
) {}
94 const TemplateURL
* SearchProvider::Providers::GetDefaultProviderURL() const {
95 return default_provider_
.empty() ? NULL
:
96 template_url_service_
->GetTemplateURLForKeyword(default_provider_
);
99 const TemplateURL
* SearchProvider::Providers::GetKeywordProviderURL() const {
100 return keyword_provider_
.empty() ? NULL
:
101 template_url_service_
->GetTemplateURLForKeyword(keyword_provider_
);
105 // SearchProvider::CompareScoredResults ---------------------------------------
107 class SearchProvider::CompareScoredResults
{
109 bool operator()(const SearchSuggestionParser::Result
& a
,
110 const SearchSuggestionParser::Result
& b
) {
111 // Sort in descending relevance order.
112 return a
.relevance() > b
.relevance();
117 // SearchProvider -------------------------------------------------------------
120 int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs
= 100;
122 SearchProvider::SearchProvider(
123 AutocompleteProviderListener
* listener
,
124 TemplateURLService
* template_url_service
,
125 scoped_ptr
<AutocompleteProviderClient
> client
)
126 : BaseSearchProvider(template_url_service
, client
.Pass(),
127 AutocompleteProvider::TYPE_SEARCH
),
129 suggest_results_pending_(0),
130 providers_(template_url_service
),
135 std::string
SearchProvider::GetSuggestMetadata(const AutocompleteMatch
& match
) {
136 return match
.GetAdditionalInfo(kSuggestMetadataKey
);
139 void SearchProvider::ResetSession() {
140 field_trial_triggered_in_session_
= false;
143 SearchProvider::~SearchProvider() {
147 int SearchProvider::CalculateRelevanceForKeywordVerbatim(
148 metrics::OmniboxInputType::Type type
,
149 bool prefer_keyword
) {
150 // This function is responsible for scoring verbatim query matches
151 // for non-extension keywords. KeywordProvider::CalculateRelevance()
152 // scores verbatim query matches for extension keywords, as well as
153 // for keyword matches (i.e., suggestions of a keyword itself, not a
154 // suggestion of a query on a keyword search engine). These two
155 // functions are currently in sync, but there's no reason we
156 // couldn't decide in the future to score verbatim matches
157 // differently for extension and non-extension keywords. If you
158 // make such a change, however, you should update this comment to
159 // describe it, so it's clear why the functions diverge.
162 return (type
== metrics::OmniboxInputType::QUERY
) ? 1450 : 1100;
165 void SearchProvider::Start(const AutocompleteInput
& input
,
166 bool minimal_changes
) {
167 // Do our best to load the model as early as possible. This will reduce
168 // odds of having the model not ready when really needed (a non-empty input).
169 TemplateURLService
* model
= providers_
.template_url_service();
174 field_trial_triggered_
= false;
176 // Can't return search/suggest results for bogus input.
177 if (input
.type() == metrics::OmniboxInputType::INVALID
) {
182 keyword_input_
= input
;
183 const TemplateURL
* keyword_provider
=
184 KeywordProvider::GetSubstitutingTemplateURLForInput(model
,
186 if (keyword_provider
== NULL
)
187 keyword_input_
.Clear();
188 else if (keyword_input_
.text().empty())
189 keyword_provider
= NULL
;
191 const TemplateURL
* default_provider
= model
->GetDefaultSearchProvider();
192 if (default_provider
&&
193 !default_provider
->SupportsReplacement(model
->search_terms_data()))
194 default_provider
= NULL
;
196 if (keyword_provider
== default_provider
)
197 default_provider
= NULL
; // No use in querying the same provider twice.
199 if (!default_provider
&& !keyword_provider
) {
200 // No valid providers.
205 // If we're still running an old query but have since changed the query text
206 // or the providers, abort the query.
207 base::string16
default_provider_keyword(default_provider
?
208 default_provider
->keyword() : base::string16());
209 base::string16
keyword_provider_keyword(keyword_provider
?
210 keyword_provider
->keyword() : base::string16());
211 if (!minimal_changes
||
212 !providers_
.equal(default_provider_keyword
, keyword_provider_keyword
)) {
213 // Cancel any in-flight suggest requests.
218 providers_
.set(default_provider_keyword
, keyword_provider_keyword
);
220 if (input
.text().empty()) {
221 // User typed "?" alone. Give them a placeholder result indicating what
223 if (default_provider
) {
224 AutocompleteMatch match
;
225 match
.provider
= this;
226 match
.contents
.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE
));
227 match
.contents_class
.push_back(
228 ACMatchClassification(0, ACMatchClassification::NONE
));
229 match
.keyword
= providers_
.default_provider();
230 match
.allowed_to_be_default_match
= true;
231 matches_
.push_back(match
);
239 DoHistoryQuery(minimal_changes
);
240 DoAnswersQuery(input
);
241 StartOrStopSuggestQuery(minimal_changes
);
245 void SearchProvider::Stop(bool clear_cached_results
) {
249 if (clear_cached_results
)
253 const TemplateURL
* SearchProvider::GetTemplateURL(bool is_keyword
) const {
254 return is_keyword
? providers_
.GetKeywordProviderURL()
255 : providers_
.GetDefaultProviderURL();
258 const AutocompleteInput
SearchProvider::GetInput(bool is_keyword
) const {
259 return is_keyword
? keyword_input_
: input_
;
262 bool SearchProvider::ShouldAppendExtraParams(
263 const SearchSuggestionParser::SuggestResult
& result
) const {
264 return !result
.from_keyword_provider() ||
265 providers_
.default_provider().empty();
268 void SearchProvider::RecordDeletionResult(bool success
) {
271 base::UserMetricsAction("Omnibox.ServerSuggestDelete.Success"));
274 base::UserMetricsAction("Omnibox.ServerSuggestDelete.Failure"));
278 void SearchProvider::OnURLFetchComplete(const net::URLFetcher
* source
) {
280 --suggest_results_pending_
;
281 DCHECK_GE(suggest_results_pending_
, 0); // Should never go negative.
283 const bool is_keyword
= source
== keyword_fetcher_
.get();
285 // Ensure the request succeeded and that the provider used is still available.
286 // A verbatim match cannot be generated without this provider, causing errors.
287 const bool request_succeeded
=
288 source
->GetStatus().is_success() && (source
->GetResponseCode() == 200) &&
289 GetTemplateURL(is_keyword
);
291 LogFetchComplete(request_succeeded
, is_keyword
);
293 bool results_updated
= false;
294 if (request_succeeded
) {
295 scoped_ptr
<base::Value
> data(SearchSuggestionParser::DeserializeJsonData(
296 SearchSuggestionParser::ExtractJsonData(source
)));
298 SearchSuggestionParser::Results
* results
=
299 is_keyword
? &keyword_results_
: &default_results_
;
300 results_updated
= ParseSuggestResults(*data
, -1, is_keyword
, results
);
302 SortResults(is_keyword
, results
);
306 if (done_
|| results_updated
)
307 listener_
->OnProviderUpdate(results_updated
);
310 void SearchProvider::StopSuggest() {
311 // Increment the appropriate field in the histogram by the number of
312 // pending requests that were invalidated.
313 for (int i
= 0; i
< suggest_results_pending_
; ++i
)
314 LogOmniboxSuggestRequest(REQUEST_INVALIDATED
);
315 suggest_results_pending_
= 0;
317 // Stop any in-progress URL fetches.
318 keyword_fetcher_
.reset();
319 default_fetcher_
.reset();
322 void SearchProvider::ClearAllResults() {
323 keyword_results_
.Clear();
324 default_results_
.Clear();
327 void SearchProvider::UpdateMatchContentsClass(
328 const base::string16
& input_text
,
329 SearchSuggestionParser::Results
* results
) {
330 for (SearchSuggestionParser::SuggestResults::iterator sug_it
=
331 results
->suggest_results
.begin();
332 sug_it
!= results
->suggest_results
.end(); ++sug_it
) {
333 sug_it
->ClassifyMatchContents(false, input_text
);
335 const std::string
languages(client_
->AcceptLanguages());
336 for (SearchSuggestionParser::NavigationResults::iterator nav_it
=
337 results
->navigation_results
.begin();
338 nav_it
!= results
->navigation_results
.end(); ++nav_it
) {
339 nav_it
->CalculateAndClassifyMatchContents(false, input_text
, languages
);
343 void SearchProvider::SortResults(bool is_keyword
,
344 SearchSuggestionParser::Results
* results
) {
345 // Ignore suggested scores for non-keyword matches in keyword mode; if the
346 // server is allowed to score these, it could interfere with the user's
347 // ability to get good keyword results.
348 const bool abandon_suggested_scores
=
349 !is_keyword
&& !providers_
.keyword_provider().empty();
350 // Apply calculated relevance scores to suggestions if valid relevances were
351 // not provided or we're abandoning suggested scores entirely.
352 if (!results
->relevances_from_server
|| abandon_suggested_scores
) {
353 ApplyCalculatedSuggestRelevance(&results
->suggest_results
);
354 ApplyCalculatedNavigationRelevance(&results
->navigation_results
);
355 // If abandoning scores entirely, also abandon the verbatim score.
356 if (abandon_suggested_scores
)
357 results
->verbatim_relevance
= -1;
360 // Keep the result lists sorted.
361 const CompareScoredResults comparator
= CompareScoredResults();
362 std::stable_sort(results
->suggest_results
.begin(),
363 results
->suggest_results
.end(),
365 std::stable_sort(results
->navigation_results
.begin(),
366 results
->navigation_results
.end(),
370 void SearchProvider::LogFetchComplete(bool success
, bool is_keyword
) {
371 LogOmniboxSuggestRequest(REPLY_RECEIVED
);
372 // Record response time for suggest requests sent to Google. We care
373 // only about the common case: the Google default provider used in
375 const TemplateURL
* default_url
= providers_
.GetDefaultProviderURL();
376 if (!is_keyword
&& default_url
&&
377 (TemplateURLPrepopulateData::GetEngineType(
379 providers_
.template_url_service()->search_terms_data()) ==
380 SEARCH_ENGINE_GOOGLE
)) {
381 const base::TimeDelta elapsed_time
=
382 base::TimeTicks::Now() - time_suggest_request_sent_
;
384 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Success.GoogleResponseTime",
387 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Failure.GoogleResponseTime",
393 void SearchProvider::UpdateMatches() {
394 ConvertResultsToAutocompleteMatches();
396 // Check constraints that may be violated by suggested relevances.
397 if (!matches_
.empty() &&
398 (default_results_
.HasServerProvidedScores() ||
399 keyword_results_
.HasServerProvidedScores())) {
400 // These blocks attempt to repair undesirable behavior by suggested
401 // relevances with minimal impact, preserving other suggested relevances.
403 const TemplateURL
* keyword_url
= providers_
.GetKeywordProviderURL();
404 const bool is_extension_keyword
= (keyword_url
!= NULL
) &&
405 (keyword_url
->GetType() == TemplateURL::OMNIBOX_API_EXTENSION
);
406 if ((keyword_url
!= NULL
) && !is_extension_keyword
&&
407 (FindTopMatch() == matches_
.end())) {
408 // In non-extension keyword mode, disregard the keyword verbatim suggested
409 // relevance if necessary, so at least one match is allowed to be default.
410 // (In extension keyword mode this is not necessary because the extension
411 // will return a default match.)
412 keyword_results_
.verbatim_relevance
= -1;
413 ConvertResultsToAutocompleteMatches();
415 if (IsTopMatchSearchWithURLInput()) {
416 // Disregard the suggested search and verbatim relevances if the input
417 // type is URL and the top match is a highly-ranked search suggestion.
418 // For example, prevent a search for "foo.com" from outranking another
419 // provider's navigation for "foo.com" or "foo.com/url_from_history".
420 ApplyCalculatedSuggestRelevance(&keyword_results_
.suggest_results
);
421 ApplyCalculatedSuggestRelevance(&default_results_
.suggest_results
);
422 default_results_
.verbatim_relevance
= -1;
423 keyword_results_
.verbatim_relevance
= -1;
424 ConvertResultsToAutocompleteMatches();
426 if (!is_extension_keyword
&& (FindTopMatch() == matches_
.end())) {
427 // Guarantee that SearchProvider returns a legal default match (except
428 // when in extension-based keyword mode). The omnibox always needs at
429 // least one legal default match, and it relies on SearchProvider in
430 // combination with KeywordProvider (for extension-based keywords) to
431 // always return one.
432 ApplyCalculatedRelevance();
433 ConvertResultsToAutocompleteMatches();
435 DCHECK(!IsTopMatchSearchWithURLInput());
436 DCHECK(is_extension_keyword
|| (FindTopMatch() != matches_
.end()));
438 UMA_HISTOGRAM_CUSTOM_COUNTS(
439 "Omnibox.SearchProviderMatches", matches_
.size(), 1, 6, 7);
443 void SearchProvider::Run() {
444 // Start a new request with the current input.
445 suggest_results_pending_
= 0;
446 time_suggest_request_sent_
= base::TimeTicks::Now();
448 default_fetcher_
.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID
,
449 providers_
.GetDefaultProviderURL(), input_
));
450 keyword_fetcher_
.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID
,
451 providers_
.GetKeywordProviderURL(), keyword_input_
));
453 // Both the above can fail if the providers have been modified or deleted
454 // since the query began.
455 if (suggest_results_pending_
== 0) {
457 // We only need to update the listener if we're actually done.
459 listener_
->OnProviderUpdate(false);
463 void SearchProvider::DoHistoryQuery(bool minimal_changes
) {
464 // The history query results are synchronous, so if minimal_changes is true,
465 // we still have the last results and don't need to do anything.
469 keyword_history_results_
.clear();
470 default_history_results_
.clear();
472 if (OmniboxFieldTrial::SearchHistoryDisable(
473 input_
.current_page_classification()))
476 history::URLDatabase
* url_db
= client_
->InMemoryDatabase();
480 // Request history for both the keyword and default provider. We grab many
481 // more matches than we'll ultimately clamp to so that if there are several
482 // recent multi-word matches who scores are lowered (see
483 // AddHistoryResultsToMap()), they won't crowd out older, higher-scoring
484 // matches. Note that this doesn't fix the problem entirely, but merely
485 // limits it to cases with a very large number of such multi-word matches; for
486 // now, this seems OK compared with the complexity of a real fix, which would
487 // require multiple searches and tracking of "single- vs. multi-word" in the
489 int num_matches
= kMaxMatches
* 5;
490 const TemplateURL
* default_url
= providers_
.GetDefaultProviderURL();
492 const base::TimeTicks start_time
= base::TimeTicks::Now();
493 url_db
->GetMostRecentKeywordSearchTerms(default_url
->id(), input_
.text(),
494 num_matches
, &default_history_results_
);
496 "Omnibox.SearchProvider.GetMostRecentKeywordTermsDefaultProviderTime",
497 base::TimeTicks::Now() - start_time
);
499 const TemplateURL
* keyword_url
= providers_
.GetKeywordProviderURL();
501 url_db
->GetMostRecentKeywordSearchTerms(keyword_url
->id(),
502 keyword_input_
.text(), num_matches
, &keyword_history_results_
);
506 void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes
) {
507 if (!IsQuerySuitableForSuggest()) {
513 // For the minimal_changes case, if we finished the previous query and still
514 // have its results, or are allowed to keep running it, just do that, rather
515 // than starting a new query.
516 if (minimal_changes
&&
517 (!default_results_
.suggest_results
.empty() ||
518 !default_results_
.navigation_results
.empty() ||
519 !keyword_results_
.suggest_results
.empty() ||
520 !keyword_results_
.navigation_results
.empty() ||
521 (!done_
&& input_
.want_asynchronous_matches())))
524 // We can't keep running any previous query, so halt it.
527 // Remove existing results that cannot inline autocomplete the new input.
528 RemoveAllStaleResults();
530 // Update the content classifications of remaining results so they look good
531 // against the current input.
532 UpdateMatchContentsClass(input_
.text(), &default_results_
);
533 if (!keyword_input_
.text().empty())
534 UpdateMatchContentsClass(keyword_input_
.text(), &keyword_results_
);
536 // We can't start a new query if we're only allowed synchronous results.
537 if (!input_
.want_asynchronous_matches())
540 // To avoid flooding the suggest server, don't send a query until at
541 // least 100 ms since the last query.
542 base::TimeTicks
next_suggest_time(time_suggest_request_sent_
+
543 base::TimeDelta::FromMilliseconds(kMinimumTimeBetweenSuggestQueriesMs
));
544 base::TimeTicks
now(base::TimeTicks::Now());
545 if (now
>= next_suggest_time
) {
549 timer_
.Start(FROM_HERE
, next_suggest_time
- now
, this, &SearchProvider::Run
);
552 bool SearchProvider::IsQuerySuitableForSuggest() const {
553 // Don't run Suggest in incognito mode, if the engine doesn't support it, or
554 // if the user has disabled it.
555 const TemplateURL
* default_url
= providers_
.GetDefaultProviderURL();
556 const TemplateURL
* keyword_url
= providers_
.GetKeywordProviderURL();
557 if (client_
->IsOffTheRecord() ||
558 ((!default_url
|| default_url
->suggestions_url().empty()) &&
559 (!keyword_url
|| keyword_url
->suggestions_url().empty())) ||
560 !client_
->SearchSuggestEnabled())
563 // If the input type might be a URL, we take extra care so that private data
564 // isn't sent to the server.
566 // FORCED_QUERY means the user is explicitly asking us to search for this, so
567 // we assume it isn't a URL and/or there isn't private data.
568 if (input_
.type() == metrics::OmniboxInputType::FORCED_QUERY
)
571 // Next we check the scheme. If this is UNKNOWN/URL with a scheme that isn't
572 // http/https/ftp, we shouldn't send it. Sending things like file: and data:
573 // is both a waste of time and a disclosure of potentially private, local
574 // data. Other "schemes" may actually be usernames, and we don't want to send
575 // passwords. If the scheme is OK, we still need to check other cases below.
576 // If this is QUERY, then the presence of these schemes means the user
577 // explicitly typed one, and thus this is probably a URL that's being entered
578 // and happens to currently be invalid -- in which case we again want to run
579 // our checks below. Other QUERY cases are less likely to be URLs and thus we
581 if (!LowerCaseEqualsASCII(input_
.scheme(), url::kHttpScheme
) &&
582 !LowerCaseEqualsASCII(input_
.scheme(), url::kHttpsScheme
) &&
583 !LowerCaseEqualsASCII(input_
.scheme(), url::kFtpScheme
))
584 return (input_
.type() == metrics::OmniboxInputType::QUERY
);
586 // Don't send URLs with usernames, queries or refs. Some of these are
587 // private, and the Suggest server is unlikely to have any useful results
588 // for any of them. Also don't send URLs with ports, as we may initially
589 // think that a username + password is a host + port (and we don't want to
590 // send usernames/passwords), and even if the port really is a port, the
591 // server is once again unlikely to have and useful results.
592 // Note that we only block based on refs if the input is URL-typed, as search
593 // queries can legitimately have #s in them which the URL parser
594 // overaggressively categorizes as a url with a ref.
595 const url::Parsed
& parts
= input_
.parts();
596 if (parts
.username
.is_nonempty() || parts
.port
.is_nonempty() ||
597 parts
.query
.is_nonempty() ||
598 (parts
.ref
.is_nonempty() &&
599 (input_
.type() == metrics::OmniboxInputType::URL
)))
602 // Don't send anything for https except the hostname. Hostnames are OK
603 // because they are visible when the TCP connection is established, but the
604 // specific path may reveal private information.
605 if (LowerCaseEqualsASCII(input_
.scheme(), url::kHttpsScheme
) &&
606 parts
.path
.is_nonempty())
612 void SearchProvider::RemoveAllStaleResults() {
613 if (keyword_input_
.text().empty()) {
614 // User is either in keyword mode with a blank input or out of
615 // keyword mode entirely.
616 keyword_results_
.Clear();
620 void SearchProvider::ApplyCalculatedRelevance() {
621 ApplyCalculatedSuggestRelevance(&keyword_results_
.suggest_results
);
622 ApplyCalculatedSuggestRelevance(&default_results_
.suggest_results
);
623 ApplyCalculatedNavigationRelevance(&keyword_results_
.navigation_results
);
624 ApplyCalculatedNavigationRelevance(&default_results_
.navigation_results
);
625 default_results_
.verbatim_relevance
= -1;
626 keyword_results_
.verbatim_relevance
= -1;
629 void SearchProvider::ApplyCalculatedSuggestRelevance(
630 SearchSuggestionParser::SuggestResults
* list
) {
631 for (size_t i
= 0; i
< list
->size(); ++i
) {
632 SearchSuggestionParser::SuggestResult
& result
= (*list
)[i
];
633 result
.set_relevance(
634 result
.CalculateRelevance(input_
, providers_
.has_keyword_provider()) +
635 (list
->size() - i
- 1));
636 result
.set_relevance_from_server(false);
640 void SearchProvider::ApplyCalculatedNavigationRelevance(
641 SearchSuggestionParser::NavigationResults
* list
) {
642 for (size_t i
= 0; i
< list
->size(); ++i
) {
643 SearchSuggestionParser::NavigationResult
& result
= (*list
)[i
];
644 result
.set_relevance(
645 result
.CalculateRelevance(input_
, providers_
.has_keyword_provider()) +
646 (list
->size() - i
- 1));
647 result
.set_relevance_from_server(false);
651 net::URLFetcher
* SearchProvider::CreateSuggestFetcher(
653 const TemplateURL
* template_url
,
654 const AutocompleteInput
& input
) {
655 if (!template_url
|| template_url
->suggestions_url().empty())
658 // Bail if the suggestion URL is invalid with the given replacements.
659 TemplateURLRef::SearchTermsArgs
search_term_args(input
.text());
660 search_term_args
.input_type
= input
.type();
661 search_term_args
.cursor_position
= input
.cursor_position();
662 search_term_args
.page_classification
= input
.current_page_classification();
663 if (OmniboxFieldTrial::EnableAnswersInSuggest()) {
664 search_term_args
.session_token
= GetSessionToken();
665 if (!prefetch_data_
.full_query_text
.empty()) {
666 search_term_args
.prefetch_query
=
667 base::UTF16ToUTF8(prefetch_data_
.full_query_text
);
668 search_term_args
.prefetch_query_type
=
669 base::UTF16ToUTF8(prefetch_data_
.query_type
);
672 GURL
suggest_url(template_url
->suggestions_url_ref().ReplaceSearchTerms(
674 providers_
.template_url_service()->search_terms_data()));
675 if (!suggest_url
.is_valid())
677 // Send the current page URL if user setting and URL requirements are met and
678 // the user is in the field trial.
679 if (CanSendURL(current_page_url_
, suggest_url
, template_url
,
680 input
.current_page_classification(),
681 template_url_service_
->search_terms_data(), client_
.get()) &&
682 OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial()) {
683 search_term_args
.current_page_url
= current_page_url_
.spec();
684 // Create the suggest URL again with the current page URL.
685 suggest_url
= GURL(template_url
->suggestions_url_ref().ReplaceSearchTerms(
687 providers_
.template_url_service()->search_terms_data()));
690 suggest_results_pending_
++;
691 LogOmniboxSuggestRequest(REQUEST_SENT
);
693 net::URLFetcher
* fetcher
=
694 net::URLFetcher::Create(id
, suggest_url
, net::URLFetcher::GET
, this);
695 fetcher
->SetRequestContext(client_
->RequestContext());
696 fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES
);
697 // Add Chrome experiment state to the request headers.
698 net::HttpRequestHeaders headers
;
699 variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
700 fetcher
->GetOriginalURL(), client_
->IsOffTheRecord(), false, &headers
);
701 fetcher
->SetExtraRequestHeaders(headers
.ToString());
706 void SearchProvider::ConvertResultsToAutocompleteMatches() {
707 // Convert all the results to matches and add them to a map, so we can keep
708 // the most relevant match for each result.
709 base::TimeTicks
start_time(base::TimeTicks::Now());
711 const base::Time no_time
;
712 int did_not_accept_keyword_suggestion
=
713 keyword_results_
.suggest_results
.empty() ?
714 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE
:
715 TemplateURLRef::NO_SUGGESTION_CHOSEN
;
717 bool relevance_from_server
;
718 int verbatim_relevance
= GetVerbatimRelevance(&relevance_from_server
);
719 int did_not_accept_default_suggestion
=
720 default_results_
.suggest_results
.empty() ?
721 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE
:
722 TemplateURLRef::NO_SUGGESTION_CHOSEN
;
723 const TemplateURL
* keyword_url
= providers_
.GetKeywordProviderURL();
724 if (verbatim_relevance
> 0) {
725 const base::string16
& trimmed_verbatim
=
726 base::CollapseWhitespace(input_
.text(), false);
728 // Verbatim results don't get suggestions and hence, answers.
729 // Scan previous matches if the last answer-bearing suggestion matches
730 // verbatim, and if so, copy over answer contents.
731 base::string16 answer_contents
;
732 base::string16 answer_type
;
733 for (ACMatches::iterator it
= matches_
.begin(); it
!= matches_
.end();
735 if (!it
->answer_contents
.empty() &&
736 it
->fill_into_edit
== trimmed_verbatim
) {
737 answer_contents
= it
->answer_contents
;
738 answer_type
= it
->answer_type
;
743 SearchSuggestionParser::SuggestResult
verbatim(
744 trimmed_verbatim
, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
,
745 trimmed_verbatim
, base::string16(), base::string16(), answer_contents
,
746 answer_type
, std::string(), std::string(), false, verbatim_relevance
,
747 relevance_from_server
, false, trimmed_verbatim
);
748 AddMatchToMap(verbatim
, std::string(), did_not_accept_default_suggestion
,
749 false, keyword_url
!= NULL
, &map
);
751 if (!keyword_input_
.text().empty()) {
752 // We only create the verbatim search query match for a keyword
753 // if it's not an extension keyword. Extension keywords are handled
754 // in KeywordProvider::Start(). (Extensions are complicated...)
755 // Note: in this provider, SEARCH_OTHER_ENGINE must correspond
756 // to the keyword verbatim search query. Do not create other matches
757 // of type SEARCH_OTHER_ENGINE.
759 (keyword_url
->GetType() != TemplateURL::OMNIBOX_API_EXTENSION
)) {
760 bool keyword_relevance_from_server
;
761 const int keyword_verbatim_relevance
=
762 GetKeywordVerbatimRelevance(&keyword_relevance_from_server
);
763 if (keyword_verbatim_relevance
> 0) {
764 const base::string16
& trimmed_verbatim
=
765 base::CollapseWhitespace(keyword_input_
.text(), false);
766 SearchSuggestionParser::SuggestResult
verbatim(
767 trimmed_verbatim
, AutocompleteMatchType::SEARCH_OTHER_ENGINE
,
768 trimmed_verbatim
, base::string16(), base::string16(),
769 base::string16(), base::string16(), std::string(), std::string(),
770 true, keyword_verbatim_relevance
, keyword_relevance_from_server
,
771 false, trimmed_verbatim
);
772 AddMatchToMap(verbatim
, std::string(),
773 did_not_accept_keyword_suggestion
, false, true, &map
);
777 AddHistoryResultsToMap(keyword_history_results_
, true,
778 did_not_accept_keyword_suggestion
, &map
);
779 AddHistoryResultsToMap(default_history_results_
, false,
780 did_not_accept_default_suggestion
, &map
);
782 AddSuggestResultsToMap(keyword_results_
.suggest_results
,
783 keyword_results_
.metadata
, &map
);
784 AddSuggestResultsToMap(default_results_
.suggest_results
,
785 default_results_
.metadata
, &map
);
788 for (MatchMap::const_iterator
i(map
.begin()); i
!= map
.end(); ++i
)
789 matches
.push_back(i
->second
);
791 AddNavigationResultsToMatches(keyword_results_
.navigation_results
, &matches
);
792 AddNavigationResultsToMatches(default_results_
.navigation_results
, &matches
);
794 // Now add the most relevant matches to |matches_|. We take up to kMaxMatches
795 // suggest/navsuggest matches, regardless of origin. If Instant Extended is
796 // enabled and we have server-provided (and thus hopefully more accurate)
797 // scores for some suggestions, we allow more of those, until we reach
798 // AutocompleteResult::kMaxMatches total matches (that is, enough to fill the
801 // We will always return any verbatim matches, no matter how we obtained their
802 // scores, unless we have already accepted AutocompleteResult::kMaxMatches
803 // higher-scoring matches under the conditions above.
804 std::sort(matches
.begin(), matches
.end(), &AutocompleteMatch::MoreRelevant
);
807 size_t num_suggestions
= 0;
808 for (ACMatches::const_iterator
i(matches
.begin());
809 (i
!= matches
.end()) &&
810 (matches_
.size() < AutocompleteResult::kMaxMatches
);
812 // SEARCH_OTHER_ENGINE is only used in the SearchProvider for the keyword
813 // verbatim result, so this condition basically means "if this match is a
814 // suggestion of some sort".
815 if ((i
->type
!= AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED
) &&
816 (i
->type
!= AutocompleteMatchType::SEARCH_OTHER_ENGINE
)) {
817 // If we've already hit the limit on non-server-scored suggestions, and
818 // this isn't a server-scored suggestion we can add, skip it.
819 if ((num_suggestions
>= kMaxMatches
) &&
820 (!chrome::IsInstantExtendedAPIEnabled() ||
821 (i
->GetAdditionalInfo(kRelevanceFromServerKey
) != kTrue
))) {
828 matches_
.push_back(*i
);
830 UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.ConvertResultsTime",
831 base::TimeTicks::Now() - start_time
);
834 ACMatches::const_iterator
SearchProvider::FindTopMatch() const {
835 ACMatches::const_iterator it
= matches_
.begin();
836 while ((it
!= matches_
.end()) && !it
->allowed_to_be_default_match
)
841 bool SearchProvider::IsTopMatchSearchWithURLInput() const {
842 ACMatches::const_iterator first_match
= FindTopMatch();
843 return (input_
.type() == metrics::OmniboxInputType::URL
) &&
844 (first_match
!= matches_
.end()) &&
845 (first_match
->relevance
> CalculateRelevanceForVerbatim()) &&
846 (first_match
->type
!= AutocompleteMatchType::NAVSUGGEST
) &&
847 (first_match
->type
!= AutocompleteMatchType::NAVSUGGEST_PERSONALIZED
);
850 void SearchProvider::AddNavigationResultsToMatches(
851 const SearchSuggestionParser::NavigationResults
& navigation_results
,
852 ACMatches
* matches
) {
853 for (SearchSuggestionParser::NavigationResults::const_iterator it
=
854 navigation_results
.begin(); it
!= navigation_results
.end(); ++it
) {
855 matches
->push_back(NavigationToMatch(*it
));
856 // In the absence of suggested relevance scores, use only the single
857 // highest-scoring result. (The results are already sorted by relevance.)
858 if (!it
->relevance_from_server())
863 void SearchProvider::AddHistoryResultsToMap(const HistoryResults
& results
,
865 int did_not_accept_suggestion
,
870 base::TimeTicks
start_time(base::TimeTicks::Now());
871 bool prevent_inline_autocomplete
= input_
.prevent_inline_autocomplete() ||
872 (input_
.type() == metrics::OmniboxInputType::URL
);
873 const base::string16
& input_text
=
874 is_keyword
? keyword_input_
.text() : input_
.text();
875 bool input_multiple_words
= HasMultipleWords(input_text
);
877 SearchSuggestionParser::SuggestResults scored_results
;
878 if (!prevent_inline_autocomplete
&& input_multiple_words
) {
879 // ScoreHistoryResults() allows autocompletion of multi-word, 1-visit
880 // queries if the input also has multiple words. But if we were already
881 // scoring a multi-word, multi-visit query aggressively, and the current
882 // input is still a prefix of it, then changing the suggestion suddenly
883 // feels wrong. To detect this case, first score as if only one word has
884 // been typed, then check if the best result came from aggressive search
885 // history scoring. If it did, then just keep that score set. This
886 // 1200 the lowest possible score in CalculateRelevanceForHistory()'s
887 // aggressive-scoring curve.
888 scored_results
= ScoreHistoryResults(results
, prevent_inline_autocomplete
,
889 false, input_text
, is_keyword
);
890 if ((scored_results
.front().relevance() < 1200) ||
891 !HasMultipleWords(scored_results
.front().suggestion()))
892 scored_results
.clear(); // Didn't detect the case above, score normally.
894 if (scored_results
.empty())
895 scored_results
= ScoreHistoryResults(results
, prevent_inline_autocomplete
,
896 input_multiple_words
, input_text
,
898 for (SearchSuggestionParser::SuggestResults::const_iterator
i(
899 scored_results
.begin()); i
!= scored_results
.end(); ++i
) {
900 AddMatchToMap(*i
, std::string(), did_not_accept_suggestion
, true,
901 providers_
.GetKeywordProviderURL() != NULL
, map
);
903 UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.AddHistoryResultsTime",
904 base::TimeTicks::Now() - start_time
);
907 SearchSuggestionParser::SuggestResults
SearchProvider::ScoreHistoryResults(
908 const HistoryResults
& results
,
909 bool base_prevent_inline_autocomplete
,
910 bool input_multiple_words
,
911 const base::string16
& input_text
,
913 SearchSuggestionParser::SuggestResults scored_results
;
914 // True if the user has asked this exact query previously.
915 bool found_what_you_typed_match
= false;
916 const bool prevent_search_history_inlining
=
917 OmniboxFieldTrial::SearchHistoryPreventInlining(
918 input_
.current_page_classification());
919 const base::string16
& trimmed_input
=
920 base::CollapseWhitespace(input_text
, false);
921 for (HistoryResults::const_iterator
i(results
.begin()); i
!= results
.end();
923 const base::string16
& trimmed_suggestion
=
924 base::CollapseWhitespace(i
->term
, false);
926 // Don't autocomplete multi-word queries that have only been seen once
927 // unless the user has typed more than one word.
928 bool prevent_inline_autocomplete
= base_prevent_inline_autocomplete
||
929 (!input_multiple_words
&& (i
->visits
< 2) &&
930 HasMultipleWords(trimmed_suggestion
));
932 int relevance
= CalculateRelevanceForHistory(
933 i
->time
, is_keyword
, !prevent_inline_autocomplete
,
934 prevent_search_history_inlining
);
935 // Add the match to |scored_results| by putting the what-you-typed match
936 // on the front and appending all other matches. We want the what-you-
937 // typed match to always be first.
938 SearchSuggestionParser::SuggestResults::iterator insertion_position
=
939 scored_results
.end();
940 if (trimmed_suggestion
== trimmed_input
) {
941 found_what_you_typed_match
= true;
942 insertion_position
= scored_results
.begin();
944 scored_results
.insert(
946 SearchSuggestionParser::SuggestResult(
947 trimmed_suggestion
, AutocompleteMatchType::SEARCH_HISTORY
,
948 trimmed_suggestion
, base::string16(), base::string16(),
949 base::string16(), base::string16(), std::string(), std::string(),
950 is_keyword
, relevance
, false, false, trimmed_input
));
953 // History returns results sorted for us. However, we may have docked some
954 // results' scores, so things are no longer in order. While keeping the
955 // what-you-typed match at the front (if it exists), do a stable sort to get
956 // things back in order without otherwise disturbing results with equal
957 // scores, then force the scores to be unique, so that the order in which
958 // they're shown is deterministic.
959 std::stable_sort(scored_results
.begin() +
960 (found_what_you_typed_match
? 1 : 0),
961 scored_results
.end(),
962 CompareScoredResults());
964 // Don't autocomplete to search terms that would normally be treated as URLs
965 // when typed. For example, if the user searched for "google.com" and types
966 // "goog", don't autocomplete to the search term "google.com". Otherwise,
967 // the input will look like a URL but act like a search, which is confusing.
968 // The 1200 relevance score threshold in the test below is the lowest
969 // possible score in CalculateRelevanceForHistory()'s aggressive-scoring
970 // curve. This is an appropriate threshold to use to decide if we're overly
971 // aggressively inlining because, if we decide the answer is yes, the
972 // way we resolve it it to not use the aggressive-scoring curve.
973 // NOTE: We don't check for autocompleting to URLs in the following cases:
974 // * When inline autocomplete is disabled, we won't be inline autocompleting
975 // this term, so we don't need to worry about confusion as much. This
976 // also prevents calling Classify() again from inside the classifier
977 // (which will corrupt state and likely crash), since the classifier
978 // always disables inline autocomplete.
979 // * When the user has typed the whole string before as a query, then it's
980 // likely the user has no expectation that term should be interpreted as
981 // as a URL, so we need not do anything special to preserve user
983 int last_relevance
= 0;
984 if (!base_prevent_inline_autocomplete
&& !found_what_you_typed_match
&&
985 scored_results
.front().relevance() >= 1200) {
986 AutocompleteMatch match
;
987 client_
->Classify(scored_results
.front().suggestion(), false, false,
988 input_
.current_page_classification(), &match
, NULL
);
989 // Demote this match that would normally be interpreted as a URL to have
990 // the highest score a previously-issued search query could have when
991 // scoring with the non-aggressive method. A consequence of demoting
992 // by revising |last_relevance| is that this match and all following
993 // matches get demoted; the relative order of matches is preserved.
994 // One could imagine demoting only those matches that might cause
995 // confusion (which, by the way, might change the relative order of
996 // matches. We have decided to go with the simple demote-all approach
997 // because selective demotion requires multiple Classify() calls and
998 // such calls can be expensive (as expensive as running the whole
999 // autocomplete system).
1000 if (!AutocompleteMatch::IsSearchType(match
.type
)) {
1001 last_relevance
= CalculateRelevanceForHistory(
1002 base::Time::Now(), is_keyword
, false,
1003 prevent_search_history_inlining
);
1007 for (SearchSuggestionParser::SuggestResults::iterator
i(
1008 scored_results
.begin()); i
!= scored_results
.end(); ++i
) {
1009 if ((last_relevance
!= 0) && (i
->relevance() >= last_relevance
))
1010 i
->set_relevance(last_relevance
- 1);
1011 last_relevance
= i
->relevance();
1014 return scored_results
;
1017 void SearchProvider::AddSuggestResultsToMap(
1018 const SearchSuggestionParser::SuggestResults
& results
,
1019 const std::string
& metadata
,
1021 for (size_t i
= 0; i
< results
.size(); ++i
) {
1022 AddMatchToMap(results
[i
], metadata
, i
, false,
1023 providers_
.GetKeywordProviderURL() != NULL
, map
);
1027 int SearchProvider::GetVerbatimRelevance(bool* relevance_from_server
) const {
1028 // Use the suggested verbatim relevance score if it is non-negative (valid),
1029 // if inline autocomplete isn't prevented (always show verbatim on backspace),
1030 // and if it won't suppress verbatim, leaving no default provider matches.
1031 // Otherwise, if the default provider returned no matches and was still able
1032 // to suppress verbatim, the user would have no search/nav matches and may be
1033 // left unable to search using their default provider from the omnibox.
1034 // Check for results on each verbatim calculation, as results from older
1035 // queries (on previous input) may be trimmed for failing to inline new input.
1036 bool use_server_relevance
=
1037 (default_results_
.verbatim_relevance
>= 0) &&
1038 !input_
.prevent_inline_autocomplete() &&
1039 ((default_results_
.verbatim_relevance
> 0) ||
1040 !default_results_
.suggest_results
.empty() ||
1041 !default_results_
.navigation_results
.empty());
1042 if (relevance_from_server
)
1043 *relevance_from_server
= use_server_relevance
;
1044 return use_server_relevance
?
1045 default_results_
.verbatim_relevance
: CalculateRelevanceForVerbatim();
1048 int SearchProvider::CalculateRelevanceForVerbatim() const {
1049 if (!providers_
.keyword_provider().empty())
1051 return CalculateRelevanceForVerbatimIgnoringKeywordModeState();
1054 int SearchProvider::
1055 CalculateRelevanceForVerbatimIgnoringKeywordModeState() const {
1056 switch (input_
.type()) {
1057 case metrics::OmniboxInputType::UNKNOWN
:
1058 case metrics::OmniboxInputType::QUERY
:
1059 case metrics::OmniboxInputType::FORCED_QUERY
:
1060 return kNonURLVerbatimRelevance
;
1062 case metrics::OmniboxInputType::URL
:
1071 int SearchProvider::GetKeywordVerbatimRelevance(
1072 bool* relevance_from_server
) const {
1073 // Use the suggested verbatim relevance score if it is non-negative (valid),
1074 // if inline autocomplete isn't prevented (always show verbatim on backspace),
1075 // and if it won't suppress verbatim, leaving no keyword provider matches.
1076 // Otherwise, if the keyword provider returned no matches and was still able
1077 // to suppress verbatim, the user would have no search/nav matches and may be
1078 // left unable to search using their keyword provider from the omnibox.
1079 // Check for results on each verbatim calculation, as results from older
1080 // queries (on previous input) may be trimmed for failing to inline new input.
1081 bool use_server_relevance
=
1082 (keyword_results_
.verbatim_relevance
>= 0) &&
1083 !input_
.prevent_inline_autocomplete() &&
1084 ((keyword_results_
.verbatim_relevance
> 0) ||
1085 !keyword_results_
.suggest_results
.empty() ||
1086 !keyword_results_
.navigation_results
.empty());
1087 if (relevance_from_server
)
1088 *relevance_from_server
= use_server_relevance
;
1089 return use_server_relevance
?
1090 keyword_results_
.verbatim_relevance
:
1091 CalculateRelevanceForKeywordVerbatim(keyword_input_
.type(),
1092 keyword_input_
.prefer_keyword());
1095 int SearchProvider::CalculateRelevanceForHistory(
1096 const base::Time
& time
,
1098 bool use_aggressive_method
,
1099 bool prevent_search_history_inlining
) const {
1100 // The relevance of past searches falls off over time. There are two distinct
1101 // equations used. If the first equation is used (searches to the primary
1102 // provider that we want to score aggressively), the score is in the range
1103 // 1300-1599 (unless |prevent_search_history_inlining|, in which case
1104 // it's in the range 1200-1299). If the second equation is used the
1105 // relevance of a search 15 minutes ago is discounted 50 points, while the
1106 // relevance of a search two weeks ago is discounted 450 points.
1107 double elapsed_time
= std::max((base::Time::Now() - time
).InSecondsF(), 0.0);
1108 bool is_primary_provider
= is_keyword
|| !providers_
.has_keyword_provider();
1109 if (is_primary_provider
&& use_aggressive_method
) {
1110 // Searches with the past two days get a different curve.
1111 const double autocomplete_time
= 2 * 24 * 60 * 60;
1112 if (elapsed_time
< autocomplete_time
) {
1113 int max_score
= is_keyword
? 1599 : 1399;
1114 if (prevent_search_history_inlining
)
1116 return max_score
- static_cast<int>(99 *
1117 std::pow(elapsed_time
/ autocomplete_time
, 2.5));
1119 elapsed_time
-= autocomplete_time
;
1122 const int score_discount
=
1123 static_cast<int>(6.5 * std::pow(elapsed_time
, 0.3));
1125 // Don't let scores go below 0. Negative relevance scores are meaningful in
1128 if (is_primary_provider
)
1129 base_score
= (input_
.type() == metrics::OmniboxInputType::URL
) ? 750 : 1050;
1132 return std::max(0, base_score
- score_discount
);
1135 AutocompleteMatch
SearchProvider::NavigationToMatch(
1136 const SearchSuggestionParser::NavigationResult
& navigation
) {
1137 base::string16 input
;
1138 const bool trimmed_whitespace
= base::TrimWhitespace(
1139 navigation
.from_keyword_provider() ?
1140 keyword_input_
.text() : input_
.text(),
1141 base::TRIM_TRAILING
, &input
) != base::TRIM_NONE
;
1142 AutocompleteMatch
match(this, navigation
.relevance(), false,
1144 match
.destination_url
= navigation
.url();
1145 BaseSearchProvider::SetDeletionURL(navigation
.deletion_url(), &match
);
1146 // First look for the user's input inside the formatted url as it would be
1147 // without trimming the scheme, so we can find matches at the beginning of the
1149 const URLPrefix
* prefix
=
1150 URLPrefix::BestURLPrefix(navigation
.formatted_url(), input
);
1151 size_t match_start
= (prefix
== NULL
) ?
1152 navigation
.formatted_url().find(input
) : prefix
->prefix
.length();
1153 bool trim_http
= !AutocompleteInput::HasHTTPScheme(input
) &&
1154 (!prefix
|| (match_start
!= 0));
1155 const net::FormatUrlTypes format_types
=
1156 net::kFormatUrlOmitAll
& ~(trim_http
? 0 : net::kFormatUrlOmitHTTP
);
1158 const std::string
languages(client_
->AcceptLanguages());
1159 size_t inline_autocomplete_offset
= (prefix
== NULL
) ?
1160 base::string16::npos
: (match_start
+ input
.length());
1161 match
.fill_into_edit
+=
1162 AutocompleteInput::FormattedStringWithEquivalentMeaning(
1164 net::FormatUrl(navigation
.url(), languages
, format_types
,
1165 net::UnescapeRule::SPACES
, NULL
, NULL
,
1166 &inline_autocomplete_offset
),
1167 client_
->SchemeClassifier());
1168 // Preserve the forced query '?' prefix in |match.fill_into_edit|.
1169 // Otherwise, user edits to a suggestion would show non-Search results.
1170 if (input_
.type() == metrics::OmniboxInputType::FORCED_QUERY
) {
1171 match
.fill_into_edit
.insert(0, base::ASCIIToUTF16("?"));
1172 if (inline_autocomplete_offset
!= base::string16::npos
)
1173 ++inline_autocomplete_offset
;
1175 if (inline_autocomplete_offset
!= base::string16::npos
) {
1176 DCHECK(inline_autocomplete_offset
<= match
.fill_into_edit
.length());
1177 match
.inline_autocompletion
=
1178 match
.fill_into_edit
.substr(inline_autocomplete_offset
);
1180 // An inlineable navsuggestion can only be the default match when there
1181 // is no keyword provider active, lest it appear first and break the user
1182 // out of keyword mode. It can also only be default if either the inline
1183 // autocompletion is empty or we're not preventing inline autocompletion.
1184 // Finally, if we have an inlineable navsuggestion with an inline completion
1185 // that we're not preventing, make sure we didn't trim any whitespace.
1186 // We don't want to claim http://foo.com/bar is inlineable against the
1187 // input "foo.com/b ".
1188 match
.allowed_to_be_default_match
= (prefix
!= NULL
) &&
1189 (providers_
.GetKeywordProviderURL() == NULL
) &&
1190 (match
.inline_autocompletion
.empty() ||
1191 (!input_
.prevent_inline_autocomplete() && !trimmed_whitespace
));
1192 match
.EnsureUWYTIsAllowedToBeDefault(
1193 input_
.canonicalized_url(), providers_
.template_url_service());
1195 match
.contents
= navigation
.match_contents();
1196 match
.contents_class
= navigation
.match_contents_class();
1197 match
.description
= navigation
.description();
1198 AutocompleteMatch::ClassifyMatchInString(input
, match
.description
,
1199 ACMatchClassification::NONE
, &match
.description_class
);
1201 match
.RecordAdditionalInfo(
1202 kRelevanceFromServerKey
,
1203 navigation
.relevance_from_server() ? kTrue
: kFalse
);
1204 match
.RecordAdditionalInfo(kShouldPrefetchKey
, kFalse
);
1209 void SearchProvider::UpdateDone() {
1210 // We're done when the timer isn't running, there are no suggest queries
1211 // pending, and we're not waiting on Instant.
1212 done_
= !timer_
.IsRunning() && (suggest_results_pending_
== 0);
1215 std::string
SearchProvider::GetSessionToken() {
1216 base::TimeTicks
current_time(base::TimeTicks::Now());
1217 // Renew token if it expired.
1218 if (current_time
> token_expiration_time_
) {
1219 const size_t kTokenBytes
= 12;
1220 std::string raw_data
;
1221 base::RandBytes(WriteInto(&raw_data
, kTokenBytes
+ 1), kTokenBytes
);
1222 base::Base64Encode(raw_data
, ¤t_token_
);
1224 // Make the base64 encoded value URL and filename safe(see RFC 3548).
1225 std::replace(current_token_
.begin(), current_token_
.end(), '+', '-');
1226 std::replace(current_token_
.begin(), current_token_
.end(), '/', '_');
1229 // Extend expiration time another 60 seconds.
1230 token_expiration_time_
= current_time
+ base::TimeDelta::FromSeconds(60);
1232 return current_token_
;
1235 void SearchProvider::RegisterDisplayedAnswers(
1236 const AutocompleteResult
& result
) {
1240 // The answer must be in the first or second slot to be considered. It should
1241 // only be in the second slot if AutocompleteController ranked a local search
1242 // history or a verbatim item higher than the answer.
1243 AutocompleteResult::const_iterator match
= result
.begin();
1244 if (match
->answer_contents
.empty() && result
.size() > 1)
1246 if (match
->answer_contents
.empty() || match
->answer_type
.empty() ||
1247 match
->fill_into_edit
.empty())
1250 // Valid answer encountered, cache it for further queries.
1251 answers_cache_
.UpdateRecentAnswers(match
->fill_into_edit
, match
->answer_type
);
1254 void SearchProvider::DoAnswersQuery(const AutocompleteInput
& input
) {
1255 prefetch_data_
= answers_cache_
.GetTopAnswerEntry(input
.text());