Home | History | Annotate | Download | only in autocomplete
      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.
      4 
      5 #include "chrome/browser/autocomplete/search_provider.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 
     10 #include "base/base64.h"
     11 #include "base/callback.h"
     12 #include "base/command_line.h"
     13 #include "base/i18n/break_iterator.h"
     14 #include "base/i18n/case_conversion.h"
     15 #include "base/json/json_string_value_serializer.h"
     16 #include "base/message_loop/message_loop.h"
     17 #include "base/metrics/histogram.h"
     18 #include "base/prefs/pref_service.h"
     19 #include "base/rand_util.h"
     20 #include "base/strings/string_util.h"
     21 #include "base/strings/utf_string_conversions.h"
     22 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
     23 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
     24 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
     25 #include "chrome/browser/autocomplete/autocomplete_result.h"
     26 #include "chrome/browser/autocomplete/keyword_provider.h"
     27 #include "chrome/browser/history/history_service.h"
     28 #include "chrome/browser/history/history_service_factory.h"
     29 #include "chrome/browser/history/in_memory_database.h"
     30 #include "chrome/browser/metrics/variations/variations_http_header_provider.h"
     31 #include "chrome/browser/omnibox/omnibox_field_trial.h"
     32 #include "chrome/browser/profiles/profile.h"
     33 #include "chrome/browser/search/search.h"
     34 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
     35 #include "chrome/browser/search_engines/template_url_service.h"
     36 #include "chrome/browser/search_engines/template_url_service_factory.h"
     37 #include "chrome/browser/ui/search/instant_controller.h"
     38 #include "chrome/common/chrome_switches.h"
     39 #include "chrome/common/pref_names.h"
     40 #include "components/autocomplete/url_prefix.h"
     41 #include "components/google/core/browser/google_util.h"
     42 #include "components/metrics/proto/omnibox_input_type.pb.h"
     43 #include "content/public/browser/user_metrics.h"
     44 #include "grit/generated_resources.h"
     45 #include "net/base/escape.h"
     46 #include "net/base/load_flags.h"
     47 #include "net/base/net_util.h"
     48 #include "net/http/http_request_headers.h"
     49 #include "net/url_request/url_fetcher.h"
     50 #include "net/url_request/url_request_status.h"
     51 #include "ui/base/l10n/l10n_util.h"
     52 #include "url/url_constants.h"
     53 #include "url/url_util.h"
     54 
     55 // Helpers --------------------------------------------------------------------
     56 
     57 namespace {
     58 
     59 // We keep track in a histogram how many suggest requests we send, how
     60 // many suggest requests we invalidate (e.g., due to a user typing
     61 // another character), and how many replies we receive.
     62 // *** ADD NEW ENUMS AFTER ALL PREVIOUSLY DEFINED ONES! ***
     63 //     (excluding the end-of-list enum value)
     64 // We do not want values of existing enums to change or else it screws
     65 // up the statistics.
     66 enum SuggestRequestsHistogramValue {
     67   REQUEST_SENT = 1,
     68   REQUEST_INVALIDATED,
     69   REPLY_RECEIVED,
     70   MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE
     71 };
     72 
     73 // The verbatim score for an input which is not an URL.
     74 const int kNonURLVerbatimRelevance = 1300;
     75 
     76 // Increments the appropriate value in the histogram by one.
     77 void LogOmniboxSuggestRequest(
     78     SuggestRequestsHistogramValue request_value) {
     79   UMA_HISTOGRAM_ENUMERATION("Omnibox.SuggestRequests", request_value,
     80                             MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE);
     81 }
     82 
     83 bool HasMultipleWords(const base::string16& text) {
     84   base::i18n::BreakIterator i(text, base::i18n::BreakIterator::BREAK_WORD);
     85   bool found_word = false;
     86   if (i.Init()) {
     87     while (i.Advance()) {
     88       if (i.IsWord()) {
     89         if (found_word)
     90           return true;
     91         found_word = true;
     92       }
     93     }
     94   }
     95   return false;
     96 }
     97 
     98 }  // namespace
     99 
    100 // SearchProvider::Providers --------------------------------------------------
    101 
    102 SearchProvider::Providers::Providers(TemplateURLService* template_url_service)
    103     : template_url_service_(template_url_service) {}
    104 
    105 const TemplateURL* SearchProvider::Providers::GetDefaultProviderURL() const {
    106   return default_provider_.empty() ? NULL :
    107       template_url_service_->GetTemplateURLForKeyword(default_provider_);
    108 }
    109 
    110 const TemplateURL* SearchProvider::Providers::GetKeywordProviderURL() const {
    111   return keyword_provider_.empty() ? NULL :
    112       template_url_service_->GetTemplateURLForKeyword(keyword_provider_);
    113 }
    114 
    115 
    116 // SearchProvider::CompareScoredResults ---------------------------------------
    117 
    118 class SearchProvider::CompareScoredResults {
    119  public:
    120   bool operator()(const Result& a, const Result& b) {
    121     // Sort in descending relevance order.
    122     return a.relevance() > b.relevance();
    123   }
    124 };
    125 
    126 
    127 // SearchProvider -------------------------------------------------------------
    128 
    129 // static
    130 int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs = 100;
    131 
    132 SearchProvider::SearchProvider(AutocompleteProviderListener* listener,
    133                                Profile* profile)
    134     : BaseSearchProvider(listener, profile, AutocompleteProvider::TYPE_SEARCH),
    135       providers_(TemplateURLServiceFactory::GetForProfile(profile)) {
    136 }
    137 
    138 // static
    139 std::string SearchProvider::GetSuggestMetadata(const AutocompleteMatch& match) {
    140   return match.GetAdditionalInfo(kSuggestMetadataKey);
    141 }
    142 
    143 void SearchProvider::ResetSession() {
    144   field_trial_triggered_in_session_ = false;
    145 }
    146 
    147 SearchProvider::~SearchProvider() {
    148 }
    149 
    150 void SearchProvider::UpdateMatchContentsClass(const base::string16& input_text,
    151                                               Results* results) {
    152   for (SuggestResults::iterator sug_it = results->suggest_results.begin();
    153        sug_it != results->suggest_results.end(); ++sug_it) {
    154     sug_it->ClassifyMatchContents(false, input_text);
    155   }
    156   const std::string languages(
    157       profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
    158   for (NavigationResults::iterator nav_it = results->navigation_results.begin();
    159        nav_it != results->navigation_results.end(); ++nav_it) {
    160     nav_it->CalculateAndClassifyMatchContents(false, input_text, languages);
    161   }
    162 }
    163 
    164 // static
    165 int SearchProvider::CalculateRelevanceForKeywordVerbatim(
    166     metrics::OmniboxInputType::Type type,
    167     bool prefer_keyword) {
    168   // This function is responsible for scoring verbatim query matches
    169   // for non-extension keywords.  KeywordProvider::CalculateRelevance()
    170   // scores verbatim query matches for extension keywords, as well as
    171   // for keyword matches (i.e., suggestions of a keyword itself, not a
    172   // suggestion of a query on a keyword search engine).  These two
    173   // functions are currently in sync, but there's no reason we
    174   // couldn't decide in the future to score verbatim matches
    175   // differently for extension and non-extension keywords.  If you
    176   // make such a change, however, you should update this comment to
    177   // describe it, so it's clear why the functions diverge.
    178   if (prefer_keyword)
    179     return 1500;
    180   return (type == metrics::OmniboxInputType::QUERY) ? 1450 : 1100;
    181 }
    182 
    183 void SearchProvider::Start(const AutocompleteInput& input,
    184                            bool minimal_changes) {
    185   // Do our best to load the model as early as possible.  This will reduce
    186   // odds of having the model not ready when really needed (a non-empty input).
    187   TemplateURLService* model = providers_.template_url_service();
    188   DCHECK(model);
    189   model->Load();
    190 
    191   matches_.clear();
    192   field_trial_triggered_ = false;
    193 
    194   // Can't return search/suggest results for bogus input or without a profile.
    195   if (!profile_ || (input.type() == metrics::OmniboxInputType::INVALID)) {
    196     Stop(true);
    197     return;
    198   }
    199 
    200   keyword_input_ = input;
    201   const TemplateURL* keyword_provider =
    202       KeywordProvider::GetSubstitutingTemplateURLForInput(model,
    203                                                           &keyword_input_);
    204   if (keyword_provider == NULL)
    205     keyword_input_.Clear();
    206   else if (keyword_input_.text().empty())
    207     keyword_provider = NULL;
    208 
    209   const TemplateURL* default_provider = model->GetDefaultSearchProvider();
    210   if (default_provider &&
    211       !default_provider->SupportsReplacement(model->search_terms_data()))
    212     default_provider = NULL;
    213 
    214   if (keyword_provider == default_provider)
    215     default_provider = NULL;  // No use in querying the same provider twice.
    216 
    217   if (!default_provider && !keyword_provider) {
    218     // No valid providers.
    219     Stop(true);
    220     return;
    221   }
    222 
    223   // If we're still running an old query but have since changed the query text
    224   // or the providers, abort the query.
    225   base::string16 default_provider_keyword(default_provider ?
    226       default_provider->keyword() : base::string16());
    227   base::string16 keyword_provider_keyword(keyword_provider ?
    228       keyword_provider->keyword() : base::string16());
    229   if (!minimal_changes ||
    230       !providers_.equal(default_provider_keyword, keyword_provider_keyword)) {
    231     // Cancel any in-flight suggest requests.
    232     if (!done_)
    233       Stop(false);
    234   }
    235 
    236   providers_.set(default_provider_keyword, keyword_provider_keyword);
    237 
    238   if (input.text().empty()) {
    239     // User typed "?" alone.  Give them a placeholder result indicating what
    240     // this syntax does.
    241     if (default_provider) {
    242       AutocompleteMatch match;
    243       match.provider = this;
    244       match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
    245       match.contents_class.push_back(
    246           ACMatchClassification(0, ACMatchClassification::NONE));
    247       match.keyword = providers_.default_provider();
    248       match.allowed_to_be_default_match = true;
    249       matches_.push_back(match);
    250     }
    251     Stop(true);
    252     return;
    253   }
    254 
    255   input_ = input;
    256 
    257   DoHistoryQuery(minimal_changes);
    258   StartOrStopSuggestQuery(minimal_changes);
    259   UpdateMatches();
    260 }
    261 
    262 void SearchProvider::SortResults(bool is_keyword,
    263                                  const base::ListValue* relevances,
    264                                  Results* results) {
    265   // Ignore suggested scores for non-keyword matches in keyword mode; if the
    266   // server is allowed to score these, it could interfere with the user's
    267   // ability to get good keyword results.
    268   const bool abandon_suggested_scores =
    269       !is_keyword && !providers_.keyword_provider().empty();
    270   // Apply calculated relevance scores to suggestions if a valid list was
    271   // not provided or we're abandoning suggested scores entirely.
    272   if ((relevances == NULL) || abandon_suggested_scores) {
    273     ApplyCalculatedSuggestRelevance(&results->suggest_results);
    274     ApplyCalculatedNavigationRelevance(&results->navigation_results);
    275     // If abandoning scores entirely, also abandon the verbatim score.
    276     if (abandon_suggested_scores)
    277       results->verbatim_relevance = -1;
    278   }
    279 
    280   // Keep the result lists sorted.
    281   const CompareScoredResults comparator = CompareScoredResults();
    282   std::stable_sort(results->suggest_results.begin(),
    283                    results->suggest_results.end(),
    284                    comparator);
    285   std::stable_sort(results->navigation_results.begin(),
    286                    results->navigation_results.end(),
    287                    comparator);
    288 }
    289 
    290 const TemplateURL* SearchProvider::GetTemplateURL(bool is_keyword) const {
    291   return is_keyword ? providers_.GetKeywordProviderURL()
    292                     : providers_.GetDefaultProviderURL();
    293 }
    294 
    295 const AutocompleteInput SearchProvider::GetInput(bool is_keyword) const {
    296   return is_keyword ? keyword_input_ : input_;
    297 }
    298 
    299 BaseSearchProvider::Results* SearchProvider::GetResultsToFill(bool is_keyword) {
    300   return is_keyword ? &keyword_results_ : &default_results_;
    301 }
    302 
    303 bool SearchProvider::ShouldAppendExtraParams(
    304     const SuggestResult& result) const {
    305   return !result.from_keyword_provider() ||
    306       providers_.default_provider().empty();
    307 }
    308 
    309 void SearchProvider::StopSuggest() {
    310   // Increment the appropriate field in the histogram by the number of
    311   // pending requests that were invalidated.
    312   for (int i = 0; i < suggest_results_pending_; ++i)
    313     LogOmniboxSuggestRequest(REQUEST_INVALIDATED);
    314   suggest_results_pending_ = 0;
    315   timer_.Stop();
    316   // Stop any in-progress URL fetches.
    317   keyword_fetcher_.reset();
    318   default_fetcher_.reset();
    319 }
    320 
    321 void SearchProvider::ClearAllResults() {
    322   keyword_results_.Clear();
    323   default_results_.Clear();
    324 }
    325 
    326 int SearchProvider::GetDefaultResultRelevance() const {
    327   return -1;
    328 }
    329 
    330 void SearchProvider::RecordDeletionResult(bool success) {
    331   if (success) {
    332     content::RecordAction(
    333         base::UserMetricsAction("Omnibox.ServerSuggestDelete.Success"));
    334   } else {
    335     content::RecordAction(
    336         base::UserMetricsAction("Omnibox.ServerSuggestDelete.Failure"));
    337   }
    338 }
    339 
    340 void SearchProvider::LogFetchComplete(bool success, bool is_keyword) {
    341   LogOmniboxSuggestRequest(REPLY_RECEIVED);
    342   // Record response time for suggest requests sent to Google.  We care
    343   // only about the common case: the Google default provider used in
    344   // non-keyword mode.
    345   const TemplateURL* default_url = providers_.GetDefaultProviderURL();
    346   if (!is_keyword && default_url &&
    347       (TemplateURLPrepopulateData::GetEngineType(
    348           *default_url,
    349           providers_.template_url_service()->search_terms_data()) ==
    350        SEARCH_ENGINE_GOOGLE)) {
    351     const base::TimeDelta elapsed_time =
    352         base::TimeTicks::Now() - time_suggest_request_sent_;
    353     if (success) {
    354       UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Success.GoogleResponseTime",
    355                           elapsed_time);
    356     } else {
    357       UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Failure.GoogleResponseTime",
    358                           elapsed_time);
    359     }
    360   }
    361 }
    362 
    363 bool SearchProvider::IsKeywordFetcher(const net::URLFetcher* fetcher) const {
    364   return fetcher == keyword_fetcher_.get();
    365 }
    366 
    367 void SearchProvider::UpdateMatches() {
    368   ConvertResultsToAutocompleteMatches();
    369 
    370   // Check constraints that may be violated by suggested relevances.
    371   if (!matches_.empty() &&
    372       (default_results_.HasServerProvidedScores() ||
    373        keyword_results_.HasServerProvidedScores())) {
    374     // These blocks attempt to repair undesirable behavior by suggested
    375     // relevances with minimal impact, preserving other suggested relevances.
    376 
    377     if (!HasKeywordDefaultMatchInKeywordMode()) {
    378       // In keyword mode, disregard the keyword verbatim suggested relevance
    379       // if necessary so there at least one keyword match that's allowed to
    380       // be the default match.
    381       keyword_results_.verbatim_relevance = -1;
    382       ConvertResultsToAutocompleteMatches();
    383     }
    384     if (IsTopMatchSearchWithURLInput()) {
    385       // Disregard the suggested search and verbatim relevances if the input
    386       // type is URL and the top match is a highly-ranked search suggestion.
    387       // For example, prevent a search for "foo.com" from outranking another
    388       // provider's navigation for "foo.com" or "foo.com/url_from_history".
    389       ApplyCalculatedSuggestRelevance(&keyword_results_.suggest_results);
    390       ApplyCalculatedSuggestRelevance(&default_results_.suggest_results);
    391       default_results_.verbatim_relevance = -1;
    392       keyword_results_.verbatim_relevance = -1;
    393       ConvertResultsToAutocompleteMatches();
    394     }
    395     if (FindTopMatch() == matches_.end()) {
    396       // Guarantee that SearchProvider returns a legal default match.  (The
    397       // omnibox always needs at least one legal default match, and it relies
    398       // on SearchProvider to always return one.)
    399       ApplyCalculatedRelevance();
    400       ConvertResultsToAutocompleteMatches();
    401     }
    402     DCHECK(HasKeywordDefaultMatchInKeywordMode());
    403     DCHECK(!IsTopMatchSearchWithURLInput());
    404     DCHECK(FindTopMatch() != matches_.end());
    405   }
    406   UMA_HISTOGRAM_CUSTOM_COUNTS(
    407       "Omnibox.SearchProviderMatches", matches_.size(), 1, 6, 7);
    408 
    409   const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
    410   if ((keyword_url != NULL) && HasKeywordDefaultMatchInKeywordMode()) {
    411     // If there is a keyword match that is allowed to be the default match,
    412     // then prohibit default provider matches from being the default match lest
    413     // such matches cause the user to break out of keyword mode.
    414     for (ACMatches::iterator it = matches_.begin(); it != matches_.end();
    415          ++it) {
    416       if (it->keyword != keyword_url->keyword())
    417         it->allowed_to_be_default_match = false;
    418     }
    419   }
    420 
    421   base::TimeTicks update_starred_start_time(base::TimeTicks::Now());
    422   UpdateStarredStateOfMatches();
    423   UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.UpdateStarredTime",
    424                       base::TimeTicks::Now() - update_starred_start_time);
    425   UpdateDone();
    426 }
    427 
    428 void SearchProvider::Run() {
    429   // Start a new request with the current input.
    430   suggest_results_pending_ = 0;
    431   time_suggest_request_sent_ = base::TimeTicks::Now();
    432 
    433   default_fetcher_.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID,
    434       providers_.GetDefaultProviderURL(), input_));
    435   keyword_fetcher_.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID,
    436       providers_.GetKeywordProviderURL(), keyword_input_));
    437 
    438   // Both the above can fail if the providers have been modified or deleted
    439   // since the query began.
    440   if (suggest_results_pending_ == 0) {
    441     UpdateDone();
    442     // We only need to update the listener if we're actually done.
    443     if (done_)
    444       listener_->OnProviderUpdate(false);
    445   }
    446 }
    447 
    448 void SearchProvider::DoHistoryQuery(bool minimal_changes) {
    449   // The history query results are synchronous, so if minimal_changes is true,
    450   // we still have the last results and don't need to do anything.
    451   if (minimal_changes)
    452     return;
    453 
    454   keyword_history_results_.clear();
    455   default_history_results_.clear();
    456 
    457   if (OmniboxFieldTrial::SearchHistoryDisable(
    458       input_.current_page_classification()))
    459     return;
    460 
    461   HistoryService* const history_service =
    462       HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
    463   history::URLDatabase* url_db = history_service ?
    464       history_service->InMemoryDatabase() : NULL;
    465   if (!url_db)
    466     return;
    467 
    468   // Request history for both the keyword and default provider.  We grab many
    469   // more matches than we'll ultimately clamp to so that if there are several
    470   // recent multi-word matches who scores are lowered (see
    471   // AddHistoryResultsToMap()), they won't crowd out older, higher-scoring
    472   // matches.  Note that this doesn't fix the problem entirely, but merely
    473   // limits it to cases with a very large number of such multi-word matches; for
    474   // now, this seems OK compared with the complexity of a real fix, which would
    475   // require multiple searches and tracking of "single- vs. multi-word" in the
    476   // database.
    477   int num_matches = kMaxMatches * 5;
    478   const TemplateURL* default_url = providers_.GetDefaultProviderURL();
    479   if (default_url) {
    480     const base::TimeTicks start_time = base::TimeTicks::Now();
    481     url_db->GetMostRecentKeywordSearchTerms(default_url->id(), input_.text(),
    482         num_matches, &default_history_results_);
    483     UMA_HISTOGRAM_TIMES(
    484         "Omnibox.SearchProvider.GetMostRecentKeywordTermsDefaultProviderTime",
    485         base::TimeTicks::Now() - start_time);
    486   }
    487   const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
    488   if (keyword_url) {
    489     url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
    490         keyword_input_.text(), num_matches, &keyword_history_results_);
    491   }
    492 }
    493 
    494 void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
    495   if (!IsQuerySuitableForSuggest()) {
    496     StopSuggest();
    497     ClearAllResults();
    498     return;
    499   }
    500 
    501   // For the minimal_changes case, if we finished the previous query and still
    502   // have its results, or are allowed to keep running it, just do that, rather
    503   // than starting a new query.
    504   if (minimal_changes &&
    505       (!default_results_.suggest_results.empty() ||
    506        !default_results_.navigation_results.empty() ||
    507        !keyword_results_.suggest_results.empty() ||
    508        !keyword_results_.navigation_results.empty() ||
    509        (!done_ && input_.want_asynchronous_matches())))
    510     return;
    511 
    512   // We can't keep running any previous query, so halt it.
    513   StopSuggest();
    514 
    515   // Remove existing results that cannot inline autocomplete the new input.
    516   RemoveAllStaleResults();
    517 
    518   // Update the content classifications of remaining results so they look good
    519   // against the current input.
    520   UpdateMatchContentsClass(input_.text(), &default_results_);
    521   if (!keyword_input_.text().empty())
    522     UpdateMatchContentsClass(keyword_input_.text(), &keyword_results_);
    523 
    524   // We can't start a new query if we're only allowed synchronous results.
    525   if (!input_.want_asynchronous_matches())
    526     return;
    527 
    528   // To avoid flooding the suggest server, don't send a query until at
    529   // least 100 ms since the last query.
    530   base::TimeTicks next_suggest_time(time_suggest_request_sent_ +
    531       base::TimeDelta::FromMilliseconds(kMinimumTimeBetweenSuggestQueriesMs));
    532   base::TimeTicks now(base::TimeTicks::Now());
    533   if (now >= next_suggest_time) {
    534     Run();
    535     return;
    536   }
    537   timer_.Start(FROM_HERE, next_suggest_time - now, this, &SearchProvider::Run);
    538 }
    539 
    540 bool SearchProvider::IsQuerySuitableForSuggest() const {
    541   // Don't run Suggest in incognito mode, if the engine doesn't support it, or
    542   // if the user has disabled it.
    543   const TemplateURL* default_url = providers_.GetDefaultProviderURL();
    544   const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
    545   if (profile_->IsOffTheRecord() ||
    546       ((!default_url || default_url->suggestions_url().empty()) &&
    547        (!keyword_url || keyword_url->suggestions_url().empty())) ||
    548       !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
    549     return false;
    550 
    551   // If the input type might be a URL, we take extra care so that private data
    552   // isn't sent to the server.
    553 
    554   // FORCED_QUERY means the user is explicitly asking us to search for this, so
    555   // we assume it isn't a URL and/or there isn't private data.
    556   if (input_.type() == metrics::OmniboxInputType::FORCED_QUERY)
    557     return true;
    558 
    559   // Next we check the scheme.  If this is UNKNOWN/URL with a scheme that isn't
    560   // http/https/ftp, we shouldn't send it.  Sending things like file: and data:
    561   // is both a waste of time and a disclosure of potentially private, local
    562   // data.  Other "schemes" may actually be usernames, and we don't want to send
    563   // passwords.  If the scheme is OK, we still need to check other cases below.
    564   // If this is QUERY, then the presence of these schemes means the user
    565   // explicitly typed one, and thus this is probably a URL that's being entered
    566   // and happens to currently be invalid -- in which case we again want to run
    567   // our checks below.  Other QUERY cases are less likely to be URLs and thus we
    568   // assume we're OK.
    569   if (!LowerCaseEqualsASCII(input_.scheme(), url::kHttpScheme) &&
    570       !LowerCaseEqualsASCII(input_.scheme(), url::kHttpsScheme) &&
    571       !LowerCaseEqualsASCII(input_.scheme(), url::kFtpScheme))
    572     return (input_.type() == metrics::OmniboxInputType::QUERY);
    573 
    574   // Don't send URLs with usernames, queries or refs.  Some of these are
    575   // private, and the Suggest server is unlikely to have any useful results
    576   // for any of them.  Also don't send URLs with ports, as we may initially
    577   // think that a username + password is a host + port (and we don't want to
    578   // send usernames/passwords), and even if the port really is a port, the
    579   // server is once again unlikely to have and useful results.
    580   // Note that we only block based on refs if the input is URL-typed, as search
    581   // queries can legitimately have #s in them which the URL parser
    582   // overaggressively categorizes as a url with a ref.
    583   const url::Parsed& parts = input_.parts();
    584   if (parts.username.is_nonempty() || parts.port.is_nonempty() ||
    585       parts.query.is_nonempty() ||
    586       (parts.ref.is_nonempty() &&
    587        (input_.type() == metrics::OmniboxInputType::URL)))
    588     return false;
    589 
    590   // Don't send anything for https except the hostname.  Hostnames are OK
    591   // because they are visible when the TCP connection is established, but the
    592   // specific path may reveal private information.
    593   if (LowerCaseEqualsASCII(input_.scheme(), url::kHttpsScheme) &&
    594       parts.path.is_nonempty())
    595     return false;
    596 
    597   return true;
    598 }
    599 
    600 void SearchProvider::RemoveAllStaleResults() {
    601   if (keyword_input_.text().empty()) {
    602     // User is either in keyword mode with a blank input or out of
    603     // keyword mode entirely.
    604     keyword_results_.Clear();
    605   }
    606 }
    607 
    608 void SearchProvider::ApplyCalculatedRelevance() {
    609   ApplyCalculatedSuggestRelevance(&keyword_results_.suggest_results);
    610   ApplyCalculatedSuggestRelevance(&default_results_.suggest_results);
    611   ApplyCalculatedNavigationRelevance(&keyword_results_.navigation_results);
    612   ApplyCalculatedNavigationRelevance(&default_results_.navigation_results);
    613   default_results_.verbatim_relevance = -1;
    614   keyword_results_.verbatim_relevance = -1;
    615 }
    616 
    617 void SearchProvider::ApplyCalculatedSuggestRelevance(SuggestResults* list) {
    618   for (size_t i = 0; i < list->size(); ++i) {
    619     SuggestResult& result = (*list)[i];
    620     result.set_relevance(
    621         result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
    622         (list->size() - i - 1));
    623     result.set_relevance_from_server(false);
    624   }
    625 }
    626 
    627 void SearchProvider::ApplyCalculatedNavigationRelevance(
    628     NavigationResults* list) {
    629   for (size_t i = 0; i < list->size(); ++i) {
    630     NavigationResult& result = (*list)[i];
    631     result.set_relevance(
    632         result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
    633         (list->size() - i - 1));
    634     result.set_relevance_from_server(false);
    635   }
    636 }
    637 
    638 net::URLFetcher* SearchProvider::CreateSuggestFetcher(
    639     int id,
    640     const TemplateURL* template_url,
    641     const AutocompleteInput& input) {
    642   if (!template_url || template_url->suggestions_url().empty())
    643     return NULL;
    644 
    645   // Bail if the suggestion URL is invalid with the given replacements.
    646   TemplateURLRef::SearchTermsArgs search_term_args(input.text());
    647   search_term_args.input_type = input.type();
    648   search_term_args.cursor_position = input.cursor_position();
    649   search_term_args.page_classification = input.current_page_classification();
    650   if (OmniboxFieldTrial::EnableAnswersInSuggest())
    651     search_term_args.session_token = GetSessionToken();
    652   GURL suggest_url(template_url->suggestions_url_ref().ReplaceSearchTerms(
    653       search_term_args,
    654       providers_.template_url_service()->search_terms_data()));
    655   if (!suggest_url.is_valid())
    656     return NULL;
    657   // Send the current page URL if user setting and URL requirements are met and
    658   // the user is in the field trial.
    659   if (CanSendURL(current_page_url_, suggest_url, template_url,
    660                  input.current_page_classification(), profile_) &&
    661       OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial()) {
    662     search_term_args.current_page_url = current_page_url_.spec();
    663     // Create the suggest URL again with the current page URL.
    664     suggest_url = GURL(template_url->suggestions_url_ref().ReplaceSearchTerms(
    665         search_term_args,
    666         providers_.template_url_service()->search_terms_data()));
    667   }
    668 
    669   suggest_results_pending_++;
    670   LogOmniboxSuggestRequest(REQUEST_SENT);
    671 
    672   net::URLFetcher* fetcher =
    673       net::URLFetcher::Create(id, suggest_url, net::URLFetcher::GET, this);
    674   fetcher->SetRequestContext(profile_->GetRequestContext());
    675   fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
    676   // Add Chrome experiment state to the request headers.
    677   net::HttpRequestHeaders headers;
    678   chrome_variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
    679       fetcher->GetOriginalURL(), profile_->IsOffTheRecord(), false, &headers);
    680   fetcher->SetExtraRequestHeaders(headers.ToString());
    681   fetcher->Start();
    682   return fetcher;
    683 }
    684 
    685 void SearchProvider::ConvertResultsToAutocompleteMatches() {
    686   // Convert all the results to matches and add them to a map, so we can keep
    687   // the most relevant match for each result.
    688   base::TimeTicks start_time(base::TimeTicks::Now());
    689   MatchMap map;
    690   const base::Time no_time;
    691   int did_not_accept_keyword_suggestion =
    692       keyword_results_.suggest_results.empty() ?
    693       TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
    694       TemplateURLRef::NO_SUGGESTION_CHOSEN;
    695 
    696   bool relevance_from_server;
    697   int verbatim_relevance = GetVerbatimRelevance(&relevance_from_server);
    698   int did_not_accept_default_suggestion =
    699       default_results_.suggest_results.empty() ?
    700       TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
    701       TemplateURLRef::NO_SUGGESTION_CHOSEN;
    702   if (verbatim_relevance > 0) {
    703     const base::string16& trimmed_verbatim =
    704         base::CollapseWhitespace(input_.text(), false);
    705     SuggestResult verbatim(
    706         trimmed_verbatim, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
    707         trimmed_verbatim, base::string16(), base::string16(), base::string16(),
    708         base::string16(), std::string(), std::string(), false,
    709         verbatim_relevance, relevance_from_server, false,
    710         trimmed_verbatim);
    711     AddMatchToMap(verbatim, std::string(), did_not_accept_default_suggestion,
    712                   false, &map);
    713   }
    714   if (!keyword_input_.text().empty()) {
    715     const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
    716     // We only create the verbatim search query match for a keyword
    717     // if it's not an extension keyword.  Extension keywords are handled
    718     // in KeywordProvider::Start().  (Extensions are complicated...)
    719     // Note: in this provider, SEARCH_OTHER_ENGINE must correspond
    720     // to the keyword verbatim search query.  Do not create other matches
    721     // of type SEARCH_OTHER_ENGINE.
    722     if (keyword_url &&
    723         (keyword_url->GetType() != TemplateURL::OMNIBOX_API_EXTENSION)) {
    724       bool keyword_relevance_from_server;
    725       const int keyword_verbatim_relevance =
    726           GetKeywordVerbatimRelevance(&keyword_relevance_from_server);
    727       if (keyword_verbatim_relevance > 0) {
    728         const base::string16& trimmed_verbatim =
    729             base::CollapseWhitespace(keyword_input_.text(), false);
    730         SuggestResult verbatim(
    731             trimmed_verbatim, AutocompleteMatchType::SEARCH_OTHER_ENGINE,
    732             trimmed_verbatim, base::string16(), base::string16(),
    733             base::string16(), base::string16(), std::string(), std::string(),
    734             true, keyword_verbatim_relevance, keyword_relevance_from_server,
    735             false, trimmed_verbatim);
    736         AddMatchToMap(verbatim, std::string(),
    737                       did_not_accept_keyword_suggestion, false, &map);
    738       }
    739     }
    740   }
    741   AddHistoryResultsToMap(keyword_history_results_, true,
    742                          did_not_accept_keyword_suggestion, &map);
    743   AddHistoryResultsToMap(default_history_results_, false,
    744                          did_not_accept_default_suggestion, &map);
    745 
    746   AddSuggestResultsToMap(keyword_results_.suggest_results,
    747                          keyword_results_.metadata, &map);
    748   AddSuggestResultsToMap(default_results_.suggest_results,
    749                          default_results_.metadata, &map);
    750 
    751   ACMatches matches;
    752   for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
    753     matches.push_back(i->second);
    754 
    755   AddNavigationResultsToMatches(keyword_results_.navigation_results, &matches);
    756   AddNavigationResultsToMatches(default_results_.navigation_results, &matches);
    757 
    758   // Now add the most relevant matches to |matches_|.  We take up to kMaxMatches
    759   // suggest/navsuggest matches, regardless of origin.  If Instant Extended is
    760   // enabled and we have server-provided (and thus hopefully more accurate)
    761   // scores for some suggestions, we allow more of those, until we reach
    762   // AutocompleteResult::kMaxMatches total matches (that is, enough to fill the
    763   // whole popup).
    764   //
    765   // We will always return any verbatim matches, no matter how we obtained their
    766   // scores, unless we have already accepted AutocompleteResult::kMaxMatches
    767   // higher-scoring matches under the conditions above.
    768   std::sort(matches.begin(), matches.end(), &AutocompleteMatch::MoreRelevant);
    769   matches_.clear();
    770 
    771   size_t num_suggestions = 0;
    772   for (ACMatches::const_iterator i(matches.begin());
    773        (i != matches.end()) &&
    774            (matches_.size() < AutocompleteResult::kMaxMatches);
    775        ++i) {
    776     // SEARCH_OTHER_ENGINE is only used in the SearchProvider for the keyword
    777     // verbatim result, so this condition basically means "if this match is a
    778     // suggestion of some sort".
    779     if ((i->type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED) &&
    780         (i->type != AutocompleteMatchType::SEARCH_OTHER_ENGINE)) {
    781       // If we've already hit the limit on non-server-scored suggestions, and
    782       // this isn't a server-scored suggestion we can add, skip it.
    783       if ((num_suggestions >= kMaxMatches) &&
    784           (!chrome::IsInstantExtendedAPIEnabled() ||
    785            (i->GetAdditionalInfo(kRelevanceFromServerKey) != kTrue))) {
    786         continue;
    787       }
    788 
    789       ++num_suggestions;
    790     }
    791 
    792     matches_.push_back(*i);
    793   }
    794   UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.ConvertResultsTime",
    795                       base::TimeTicks::Now() - start_time);
    796 }
    797 
    798 ACMatches::const_iterator SearchProvider::FindTopMatch() const {
    799   ACMatches::const_iterator it = matches_.begin();
    800   while ((it != matches_.end()) && !it->allowed_to_be_default_match)
    801     ++it;
    802   return it;
    803 }
    804 
    805 bool SearchProvider::HasKeywordDefaultMatchInKeywordMode() const {
    806   const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
    807   // If the user is not in keyword mode, return true to say that this
    808   // constraint is not violated.
    809   if (keyword_url == NULL)
    810     return true;
    811   for (ACMatches::const_iterator it = matches_.begin(); it != matches_.end();
    812        ++it) {
    813     if ((it->keyword == keyword_url->keyword()) &&
    814         it->allowed_to_be_default_match)
    815       return true;
    816   }
    817   return false;
    818 }
    819 
    820 bool SearchProvider::IsTopMatchSearchWithURLInput() const {
    821   ACMatches::const_iterator first_match = FindTopMatch();
    822   return (input_.type() == metrics::OmniboxInputType::URL) &&
    823       (first_match != matches_.end()) &&
    824       (first_match->relevance > CalculateRelevanceForVerbatim()) &&
    825       (first_match->type != AutocompleteMatchType::NAVSUGGEST) &&
    826       (first_match->type != AutocompleteMatchType::NAVSUGGEST_PERSONALIZED);
    827 }
    828 
    829 void SearchProvider::AddNavigationResultsToMatches(
    830     const NavigationResults& navigation_results,
    831     ACMatches* matches) {
    832   for (NavigationResults::const_iterator it = navigation_results.begin();
    833         it != navigation_results.end(); ++it) {
    834     matches->push_back(NavigationToMatch(*it));
    835     // In the absence of suggested relevance scores, use only the single
    836     // highest-scoring result.  (The results are already sorted by relevance.)
    837     if (!it->relevance_from_server())
    838       return;
    839   }
    840 }
    841 
    842 void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results,
    843                                             bool is_keyword,
    844                                             int did_not_accept_suggestion,
    845                                             MatchMap* map) {
    846   if (results.empty())
    847     return;
    848 
    849   base::TimeTicks start_time(base::TimeTicks::Now());
    850   bool prevent_inline_autocomplete = input_.prevent_inline_autocomplete() ||
    851       (input_.type() == metrics::OmniboxInputType::URL);
    852   const base::string16& input_text =
    853       is_keyword ? keyword_input_.text() : input_.text();
    854   bool input_multiple_words = HasMultipleWords(input_text);
    855 
    856   SuggestResults scored_results;
    857   if (!prevent_inline_autocomplete && input_multiple_words) {
    858     // ScoreHistoryResults() allows autocompletion of multi-word, 1-visit
    859     // queries if the input also has multiple words.  But if we were already
    860     // scoring a multi-word, multi-visit query aggressively, and the current
    861     // input is still a prefix of it, then changing the suggestion suddenly
    862     // feels wrong.  To detect this case, first score as if only one word has
    863     // been typed, then check if the best result came from aggressive search
    864     // history scoring.  If it did, then just keep that score set.  This
    865     // 1200 the lowest possible score in CalculateRelevanceForHistory()'s
    866     // aggressive-scoring curve.
    867     scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
    868                                          false, input_text, is_keyword);
    869     if ((scored_results.front().relevance() < 1200) ||
    870         !HasMultipleWords(scored_results.front().suggestion()))
    871       scored_results.clear();  // Didn't detect the case above, score normally.
    872   }
    873   if (scored_results.empty())
    874     scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
    875                                          input_multiple_words, input_text,
    876                                          is_keyword);
    877   for (SuggestResults::const_iterator i(scored_results.begin());
    878        i != scored_results.end(); ++i) {
    879     AddMatchToMap(*i, std::string(), did_not_accept_suggestion, true, map);
    880   }
    881   UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.AddHistoryResultsTime",
    882                       base::TimeTicks::Now() - start_time);
    883 }
    884 
    885 SearchProvider::SuggestResults SearchProvider::ScoreHistoryResults(
    886     const HistoryResults& results,
    887     bool base_prevent_inline_autocomplete,
    888     bool input_multiple_words,
    889     const base::string16& input_text,
    890     bool is_keyword) {
    891   AutocompleteClassifier* classifier =
    892       AutocompleteClassifierFactory::GetForProfile(profile_);
    893   SuggestResults scored_results;
    894   const bool prevent_search_history_inlining =
    895       OmniboxFieldTrial::SearchHistoryPreventInlining(
    896           input_.current_page_classification());
    897   const base::string16& trimmed_input =
    898       base::CollapseWhitespace(input_text, false);
    899   for (HistoryResults::const_iterator i(results.begin()); i != results.end();
    900        ++i) {
    901     const base::string16& trimmed_suggestion =
    902         base::CollapseWhitespace(i->term, false);
    903 
    904     // Don't autocomplete multi-word queries that have only been seen once
    905     // unless the user has typed more than one word.
    906     bool prevent_inline_autocomplete = base_prevent_inline_autocomplete ||
    907         (!input_multiple_words && (i->visits < 2) &&
    908          HasMultipleWords(trimmed_suggestion));
    909 
    910     // Don't autocomplete search terms that would normally be treated as URLs
    911     // when typed. For example, if the user searched for "google.com" and types
    912     // "goog", don't autocomplete to the search term "google.com". Otherwise,
    913     // the input will look like a URL but act like a search, which is confusing.
    914     // NOTE: We don't check this in the following cases:
    915     //  * When inline autocomplete is disabled, we won't be inline
    916     //    autocompleting this term, so we don't need to worry about confusion as
    917     //    much.  This also prevents calling Classify() again from inside the
    918     //    classifier (which will corrupt state and likely crash), since the
    919     //    classifier always disables inline autocomplete.
    920     //  * When the user has typed the whole term, the "what you typed" history
    921     //    match will outrank us for URL-like inputs anyway, so we need not do
    922     //    anything special.
    923     if (!prevent_inline_autocomplete && classifier &&
    924         (trimmed_suggestion != trimmed_input)) {
    925       AutocompleteMatch match;
    926       classifier->Classify(trimmed_suggestion, false, false,
    927                            input_.current_page_classification(), &match, NULL);
    928       prevent_inline_autocomplete =
    929           !AutocompleteMatch::IsSearchType(match.type);
    930     }
    931 
    932     int relevance = CalculateRelevanceForHistory(
    933         i->time, is_keyword, !prevent_inline_autocomplete,
    934         prevent_search_history_inlining);
    935     scored_results.push_back(SuggestResult(
    936         trimmed_suggestion, AutocompleteMatchType::SEARCH_HISTORY,
    937         trimmed_suggestion, base::string16(), base::string16(),
    938         base::string16(), base::string16(), std::string(), std::string(),
    939         is_keyword, relevance, false, false, trimmed_input));
    940   }
    941 
    942   // History returns results sorted for us.  However, we may have docked some
    943   // results' scores, so things are no longer in order.  Do a stable sort to get
    944   // things back in order without otherwise disturbing results with equal
    945   // scores, then force the scores to be unique, so that the order in which
    946   // they're shown is deterministic.
    947   std::stable_sort(scored_results.begin(), scored_results.end(),
    948                    CompareScoredResults());
    949   int last_relevance = 0;
    950   for (SuggestResults::iterator i(scored_results.begin());
    951        i != scored_results.end(); ++i) {
    952     if ((i != scored_results.begin()) && (i->relevance() >= last_relevance))
    953       i->set_relevance(last_relevance - 1);
    954     last_relevance = i->relevance();
    955   }
    956 
    957   return scored_results;
    958 }
    959 
    960 void SearchProvider::AddSuggestResultsToMap(const SuggestResults& results,
    961                                             const std::string& metadata,
    962                                             MatchMap* map) {
    963   for (size_t i = 0; i < results.size(); ++i)
    964     AddMatchToMap(results[i], metadata, i, false, map);
    965 }
    966 
    967 int SearchProvider::GetVerbatimRelevance(bool* relevance_from_server) const {
    968   // Use the suggested verbatim relevance score if it is non-negative (valid),
    969   // if inline autocomplete isn't prevented (always show verbatim on backspace),
    970   // and if it won't suppress verbatim, leaving no default provider matches.
    971   // Otherwise, if the default provider returned no matches and was still able
    972   // to suppress verbatim, the user would have no search/nav matches and may be
    973   // left unable to search using their default provider from the omnibox.
    974   // Check for results on each verbatim calculation, as results from older
    975   // queries (on previous input) may be trimmed for failing to inline new input.
    976   bool use_server_relevance =
    977       (default_results_.verbatim_relevance >= 0) &&
    978       !input_.prevent_inline_autocomplete() &&
    979       ((default_results_.verbatim_relevance > 0) ||
    980        !default_results_.suggest_results.empty() ||
    981        !default_results_.navigation_results.empty());
    982   if (relevance_from_server)
    983     *relevance_from_server = use_server_relevance;
    984   return use_server_relevance ?
    985       default_results_.verbatim_relevance : CalculateRelevanceForVerbatim();
    986 }
    987 
    988 int SearchProvider::CalculateRelevanceForVerbatim() const {
    989   if (!providers_.keyword_provider().empty())
    990     return 250;
    991   return CalculateRelevanceForVerbatimIgnoringKeywordModeState();
    992 }
    993 
    994 int SearchProvider::
    995     CalculateRelevanceForVerbatimIgnoringKeywordModeState() const {
    996   switch (input_.type()) {
    997     case metrics::OmniboxInputType::UNKNOWN:
    998     case metrics::OmniboxInputType::QUERY:
    999     case metrics::OmniboxInputType::FORCED_QUERY:
   1000       return kNonURLVerbatimRelevance;
   1001 
   1002     case metrics::OmniboxInputType::URL:
   1003       return 850;
   1004 
   1005     default:
   1006       NOTREACHED();
   1007       return 0;
   1008   }
   1009 }
   1010 
   1011 int SearchProvider::GetKeywordVerbatimRelevance(
   1012     bool* relevance_from_server) const {
   1013   // Use the suggested verbatim relevance score if it is non-negative (valid),
   1014   // if inline autocomplete isn't prevented (always show verbatim on backspace),
   1015   // and if it won't suppress verbatim, leaving no keyword provider matches.
   1016   // Otherwise, if the keyword provider returned no matches and was still able
   1017   // to suppress verbatim, the user would have no search/nav matches and may be
   1018   // left unable to search using their keyword provider from the omnibox.
   1019   // Check for results on each verbatim calculation, as results from older
   1020   // queries (on previous input) may be trimmed for failing to inline new input.
   1021   bool use_server_relevance =
   1022       (keyword_results_.verbatim_relevance >= 0) &&
   1023       !input_.prevent_inline_autocomplete() &&
   1024       ((keyword_results_.verbatim_relevance > 0) ||
   1025        !keyword_results_.suggest_results.empty() ||
   1026        !keyword_results_.navigation_results.empty());
   1027   if (relevance_from_server)
   1028     *relevance_from_server = use_server_relevance;
   1029   return use_server_relevance ?
   1030       keyword_results_.verbatim_relevance :
   1031       CalculateRelevanceForKeywordVerbatim(keyword_input_.type(),
   1032                                            keyword_input_.prefer_keyword());
   1033 }
   1034 
   1035 int SearchProvider::CalculateRelevanceForHistory(
   1036     const base::Time& time,
   1037     bool is_keyword,
   1038     bool use_aggressive_method,
   1039     bool prevent_search_history_inlining) const {
   1040   // The relevance of past searches falls off over time. There are two distinct
   1041   // equations used. If the first equation is used (searches to the primary
   1042   // provider that we want to score aggressively), the score is in the range
   1043   // 1300-1599 (unless |prevent_search_history_inlining|, in which case
   1044   // it's in the range 1200-1299). If the second equation is used the
   1045   // relevance of a search 15 minutes ago is discounted 50 points, while the
   1046   // relevance of a search two weeks ago is discounted 450 points.
   1047   double elapsed_time = std::max((base::Time::Now() - time).InSecondsF(), 0.0);
   1048   bool is_primary_provider = is_keyword || !providers_.has_keyword_provider();
   1049   if (is_primary_provider && use_aggressive_method) {
   1050     // Searches with the past two days get a different curve.
   1051     const double autocomplete_time = 2 * 24 * 60 * 60;
   1052     if (elapsed_time < autocomplete_time) {
   1053       int max_score = is_keyword ? 1599 : 1399;
   1054       if (prevent_search_history_inlining)
   1055         max_score = 1299;
   1056       return max_score - static_cast<int>(99 *
   1057           std::pow(elapsed_time / autocomplete_time, 2.5));
   1058     }
   1059     elapsed_time -= autocomplete_time;
   1060   }
   1061 
   1062   const int score_discount =
   1063       static_cast<int>(6.5 * std::pow(elapsed_time, 0.3));
   1064 
   1065   // Don't let scores go below 0.  Negative relevance scores are meaningful in
   1066   // a different way.
   1067   int base_score;
   1068   if (is_primary_provider)
   1069     base_score = (input_.type() == metrics::OmniboxInputType::URL) ? 750 : 1050;
   1070   else
   1071     base_score = 200;
   1072   return std::max(0, base_score - score_discount);
   1073 }
   1074 
   1075 AutocompleteMatch SearchProvider::NavigationToMatch(
   1076     const NavigationResult& navigation) {
   1077   base::string16 input;
   1078   const bool trimmed_whitespace = base::TrimWhitespace(
   1079       navigation.from_keyword_provider() ?
   1080           keyword_input_.text() : input_.text(),
   1081       base::TRIM_TRAILING, &input) != base::TRIM_NONE;
   1082   AutocompleteMatch match(this, navigation.relevance(), false,
   1083                           navigation.type());
   1084   match.destination_url = navigation.url();
   1085   BaseSearchProvider::SetDeletionURL(navigation.deletion_url(), &match);
   1086   // First look for the user's input inside the formatted url as it would be
   1087   // without trimming the scheme, so we can find matches at the beginning of the
   1088   // scheme.
   1089   const URLPrefix* prefix =
   1090       URLPrefix::BestURLPrefix(navigation.formatted_url(), input);
   1091   size_t match_start = (prefix == NULL) ?
   1092       navigation.formatted_url().find(input) : prefix->prefix.length();
   1093   bool trim_http = !AutocompleteInput::HasHTTPScheme(input) &&
   1094       (!prefix || (match_start != 0));
   1095   const net::FormatUrlTypes format_types =
   1096       net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP);
   1097 
   1098   const std::string languages(
   1099       profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
   1100   size_t inline_autocomplete_offset = (prefix == NULL) ?
   1101       base::string16::npos : (match_start + input.length());
   1102   match.fill_into_edit +=
   1103       AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
   1104           net::FormatUrl(navigation.url(), languages, format_types,
   1105                          net::UnescapeRule::SPACES, NULL, NULL,
   1106                          &inline_autocomplete_offset));
   1107   // Preserve the forced query '?' prefix in |match.fill_into_edit|.
   1108   // Otherwise, user edits to a suggestion would show non-Search results.
   1109   if (input_.type() == metrics::OmniboxInputType::FORCED_QUERY) {
   1110     match.fill_into_edit.insert(0, base::ASCIIToUTF16("?"));
   1111     if (inline_autocomplete_offset != base::string16::npos)
   1112       ++inline_autocomplete_offset;
   1113   }
   1114   if (inline_autocomplete_offset != base::string16::npos) {
   1115     DCHECK(inline_autocomplete_offset <= match.fill_into_edit.length());
   1116     match.inline_autocompletion =
   1117         match.fill_into_edit.substr(inline_autocomplete_offset);
   1118   }
   1119   // An inlineable navsuggestion can only be the default match when there
   1120   // is no keyword provider active, lest it appear first and break the user
   1121   // out of keyword mode.  It can also only be default if either the inline
   1122   // autocompletion is empty or we're not preventing inline autocompletion.
   1123   // Finally, if we have an inlineable navsuggestion with an inline completion
   1124   // that we're not preventing, make sure we didn't trim any whitespace.
   1125   // We don't want to claim http://foo.com/bar is inlineable against the
   1126   // input "foo.com/b ".
   1127   match.allowed_to_be_default_match = navigation.IsInlineable(input) &&
   1128       (providers_.GetKeywordProviderURL() == NULL) &&
   1129       (match.inline_autocompletion.empty() ||
   1130       (!input_.prevent_inline_autocomplete() && !trimmed_whitespace));
   1131 
   1132   match.contents = navigation.match_contents();
   1133   match.contents_class = navigation.match_contents_class();
   1134   match.description = navigation.description();
   1135   AutocompleteMatch::ClassifyMatchInString(input, match.description,
   1136       ACMatchClassification::NONE, &match.description_class);
   1137 
   1138   match.RecordAdditionalInfo(
   1139       kRelevanceFromServerKey,
   1140       navigation.relevance_from_server() ? kTrue : kFalse);
   1141   match.RecordAdditionalInfo(kShouldPrefetchKey, kFalse);
   1142 
   1143   return match;
   1144 }
   1145 
   1146 void SearchProvider::UpdateDone() {
   1147   // We're done when the timer isn't running, there are no suggest queries
   1148   // pending, and we're not waiting on Instant.
   1149   done_ = !timer_.IsRunning() && (suggest_results_pending_ == 0);
   1150 }
   1151 
   1152 std::string SearchProvider::GetSessionToken() {
   1153   base::TimeTicks current_time(base::TimeTicks::Now());
   1154   // Renew token if it expired.
   1155   if (current_time > token_expiration_time_) {
   1156     const size_t kTokenBytes = 12;
   1157     std::string raw_data;
   1158     base::RandBytes(WriteInto(&raw_data, kTokenBytes + 1), kTokenBytes);
   1159     base::Base64Encode(raw_data, &current_token_);
   1160 
   1161     // Make the base64 encoded value URL and filename safe(see RFC 3548).
   1162     std::replace(current_token_.begin(), current_token_.end(), '+', '-');
   1163     std::replace(current_token_.begin(), current_token_.end(), '/', '_');
   1164   }
   1165 
   1166   // Extend expiration time another 60 seconds.
   1167   token_expiration_time_ = current_time + base::TimeDelta::FromSeconds(60);
   1168 
   1169   return current_token_;
   1170 }
   1171