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, ¤t_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