blob: f48be95769cb8cca10c593f8cc1df2c938620507 [file] [log] [blame]
[email protected]e41982a72012-11-20 07:16:511// Copyright 2012 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294
hashimoto5f7db4b2014-08-27 02:46:205#include "components/omnibox/search_provider.h"
initial.commit09911bf2008-07-26 23:55:296
[email protected]1cb2dac2010-03-08 21:49:157#include <algorithm>
[email protected]c3a4bd992010-08-18 20:25:018#include <cmath>
[email protected]1cb2dac2010-03-08 21:49:159
[email protected]20184242014-05-14 02:57:4210#include "base/base64.h"
[email protected]2041cf342010-02-19 03:15:5911#include "base/callback.h"
[email protected]51124552011-07-16 01:37:1012#include "base/i18n/break_iterator.h"
[email protected]503d03872011-05-06 08:36:2613#include "base/i18n/case_conversion.h"
[email protected]ffbec692012-02-26 20:26:4214#include "base/json/json_string_value_serializer.h"
[email protected]f5b95ba92012-03-27 14:05:1915#include "base/metrics/histogram.h"
[email protected]f7f41c0e2014-08-11 04:22:2316#include "base/metrics/user_metrics.h"
[email protected]20184242014-05-14 02:57:4217#include "base/rand_util.h"
[email protected]5889bfb2014-03-19 00:26:4818#include "base/strings/string_util.h"
[email protected]135cb802013-06-09 16:44:2019#include "base/strings/utf_string_conversions.h"
[email protected]e3ce70ac2014-06-26 18:34:5620#include "components/history/core/browser/in_memory_database.h"
[email protected]73b2d1e72014-06-25 23:45:3621#include "components/history/core/browser/keyword_search_term.h"
[email protected]3dc75b12014-06-08 00:02:2222#include "components/metrics/proto/omnibox_input_type.pb.h"
Ryo Hashimoto884ad192014-08-28 05:54:3023#include "components/omnibox/autocomplete_provider_client.h"
[email protected]b1c5ab682014-08-07 11:53:1724#include "components/omnibox/autocomplete_provider_listener.h"
[email protected]4b56c602014-08-14 17:02:3125#include "components/omnibox/autocomplete_result.h"
[email protected]02f5e32d2014-08-22 07:44:4626#include "components/omnibox/keyword_provider.h"
[email protected]4c583b62014-08-08 10:37:2327#include "components/omnibox/omnibox_field_trial.h"
jdonnelly7393cee2014-10-31 01:52:5628#include "components/omnibox/suggestion_answer.h"
[email protected]b1c5ab682014-08-07 11:53:1729#include "components/omnibox/url_prefix.h"
[email protected]720b10492014-07-23 08:48:4030#include "components/search/search.h"
[email protected]0915b352014-06-25 19:58:1431#include "components/search_engines/template_url_prepopulate_data.h"
[email protected]bf5c532d2014-07-05 00:29:5332#include "components/search_engines/template_url_service.h"
isherman3be67db2014-10-24 05:57:4433#include "components/variations/net/variations_http_header_provider.h"
[email protected]53f0cab2014-08-18 09:52:2734#include "grit/components_strings.h"
initial.commit09911bf2008-07-26 23:55:2935#include "net/base/escape.h"
[email protected]d3cf8682f02012-02-29 23:29:3436#include "net/base/load_flags.h"
[email protected]371dab12012-06-01 03:23:5537#include "net/base/net_util.h"
[email protected]bd3b4712012-12-18 17:01:3038#include "net/http/http_request_headers.h"
[email protected]3dc1bc42012-06-19 08:20:5339#include "net/url_request/url_fetcher.h"
[email protected]319d9e6f2009-02-18 19:47:2140#include "net/url_request/url_request_status.h"
[email protected]c051a1b2011-01-21 23:30:1741#include "ui/base/l10n/l10n_util.h"
[email protected]cca6f392014-05-28 21:32:2642#include "url/url_constants.h"
[email protected]761fa4702013-07-02 15:25:1543#include "url/url_util.h"
initial.commit09911bf2008-07-26 23:55:2944
[email protected]bc8bb0cd2013-06-24 21:50:2345// Helpers --------------------------------------------------------------------
[email protected]e1acf6f2008-10-27 20:43:3346
[email protected]51124552011-07-16 01:37:1047namespace {
48
[email protected]7706a522012-08-16 17:42:2549// We keep track in a histogram how many suggest requests we send, how
50// many suggest requests we invalidate (e.g., due to a user typing
51// another character), and how many replies we receive.
52// *** ADD NEW ENUMS AFTER ALL PREVIOUSLY DEFINED ONES! ***
53// (excluding the end-of-list enum value)
54// We do not want values of existing enums to change or else it screws
55// up the statistics.
56enum SuggestRequestsHistogramValue {
57 REQUEST_SENT = 1,
58 REQUEST_INVALIDATED,
59 REPLY_RECEIVED,
60 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE
61};
62
[email protected]90fe2bb2013-01-15 03:42:1363// The verbatim score for an input which is not an URL.
64const int kNonURLVerbatimRelevance = 1300;
65
[email protected]7706a522012-08-16 17:42:2566// Increments the appropriate value in the histogram by one.
67void LogOmniboxSuggestRequest(
68 SuggestRequestsHistogramValue request_value) {
69 UMA_HISTOGRAM_ENUMERATION("Omnibox.SuggestRequests", request_value,
70 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE);
71}
72
[email protected]0085863a2013-12-06 21:19:0373bool HasMultipleWords(const base::string16& text) {
[email protected]51124552011-07-16 01:37:1074 base::i18n::BreakIterator i(text, base::i18n::BreakIterator::BREAK_WORD);
75 bool found_word = false;
76 if (i.Init()) {
77 while (i.Advance()) {
78 if (i.IsWord()) {
79 if (found_word)
80 return true;
81 found_word = true;
82 }
83 }
84 }
85 return false;
86}
87
[email protected]d1f0a7f2012-06-05 10:26:4288} // namespace
[email protected]51124552011-07-16 01:37:1089
[email protected]3954c3a2012-04-10 20:17:5590// SearchProvider::Providers --------------------------------------------------
[email protected]b547666d2009-04-23 16:37:5891
[email protected]85b8d6f2012-05-08 20:53:4792SearchProvider::Providers::Providers(TemplateURLService* template_url_service)
[email protected]02346202014-02-05 05:18:3093 : template_url_service_(template_url_service) {}
[email protected]85b8d6f2012-05-08 20:53:4794
95const TemplateURL* SearchProvider::Providers::GetDefaultProviderURL() const {
96 return default_provider_.empty() ? NULL :
97 template_url_service_->GetTemplateURLForKeyword(default_provider_);
98}
99
100const TemplateURL* SearchProvider::Providers::GetKeywordProviderURL() const {
101 return keyword_provider_.empty() ? NULL :
102 template_url_service_->GetTemplateURLForKeyword(keyword_provider_);
[email protected]257ab712009-04-14 17:16:24103}
104
[email protected]3954c3a2012-04-10 20:17:55105
[email protected]bc8bb0cd2013-06-24 21:50:23106// SearchProvider::CompareScoredResults ---------------------------------------
107
108class SearchProvider::CompareScoredResults {
109 public:
[email protected]0b9575f2014-07-30 11:58:37110 bool operator()(const SearchSuggestionParser::Result& a,
111 const SearchSuggestionParser::Result& b) {
[email protected]bc8bb0cd2013-06-24 21:50:23112 // Sort in descending relevance order.
113 return a.relevance() > b.relevance();
114 }
115};
116
117
[email protected]3954c3a2012-04-10 20:17:55118// SearchProvider -------------------------------------------------------------
119
hashimoto663b9f42014-08-26 04:29:20120SearchProvider::SearchProvider(
121 AutocompleteProviderListener* listener,
122 TemplateURLService* template_url_service,
Ryo Hashimoto884ad192014-08-28 05:54:30123 scoped_ptr<AutocompleteProviderClient> client)
124 : BaseSearchProvider(template_url_service, client.Pass(),
[email protected]e6477f12014-08-05 07:59:54125 AutocompleteProvider::TYPE_SEARCH),
[email protected]776ee5902014-08-11 09:15:19126 listener_(listener),
127 suggest_results_pending_(0),
[email protected]ebbac63e2014-08-22 01:43:06128 providers_(template_url_service),
groby1dbb8e22014-09-23 21:50:26129 answers_cache_(10) {
[email protected]bc8bb0cd2013-06-24 21:50:23130}
131
[email protected]cb86ee6f2013-04-28 16:58:15132// static
[email protected]987fad782013-08-28 06:23:18133std::string SearchProvider::GetSuggestMetadata(const AutocompleteMatch& match) {
134 return match.GetAdditionalInfo(kSuggestMetadataKey);
135}
136
[email protected]bc8bb0cd2013-06-24 21:50:23137void SearchProvider::ResetSession() {
138 field_trial_triggered_in_session_ = false;
[email protected]4ab4c7c2010-11-24 04:49:34139}
140
[email protected]bc8bb0cd2013-06-24 21:50:23141SearchProvider::~SearchProvider() {
142}
143
[email protected]ee6110b2014-01-09 22:26:31144// static
[email protected]bc8bb0cd2013-06-24 21:50:23145int SearchProvider::CalculateRelevanceForKeywordVerbatim(
[email protected]332d17d22014-06-20 16:56:03146 metrics::OmniboxInputType::Type type,
[email protected]bc8bb0cd2013-06-24 21:50:23147 bool prefer_keyword) {
148 // This function is responsible for scoring verbatim query matches
149 // for non-extension keywords. KeywordProvider::CalculateRelevance()
150 // scores verbatim query matches for extension keywords, as well as
151 // for keyword matches (i.e., suggestions of a keyword itself, not a
152 // suggestion of a query on a keyword search engine). These two
153 // functions are currently in sync, but there's no reason we
154 // couldn't decide in the future to score verbatim matches
155 // differently for extension and non-extension keywords. If you
156 // make such a change, however, you should update this comment to
157 // describe it, so it's clear why the functions diverge.
158 if (prefer_keyword)
159 return 1500;
[email protected]3dc75b12014-06-08 00:02:22160 return (type == metrics::OmniboxInputType::QUERY) ? 1450 : 1100;
[email protected]bc8bb0cd2013-06-24 21:50:23161}
162
mpearson6c183672014-09-03 02:09:42163// static
164void SearchProvider::UpdateOldResults(
165 bool minimal_changes,
166 SearchSuggestionParser::Results* results) {
167 // When called without |minimal_changes|, it likely means the user has
168 // pressed a key. Revise the cached results appropriately.
169 if (!minimal_changes) {
170 for (SearchSuggestionParser::SuggestResults::iterator sug_it =
171 results->suggest_results.begin();
172 sug_it != results->suggest_results.end(); ++sug_it) {
173 sug_it->set_received_after_last_keystroke(false);
174 }
175 for (SearchSuggestionParser::NavigationResults::iterator nav_it =
176 results->navigation_results.begin();
177 nav_it != results->navigation_results.end(); ++nav_it) {
178 nav_it->set_received_after_last_keystroke(false);
179 }
180 }
181}
182
183// static
184ACMatches::iterator SearchProvider::FindTopMatch(ACMatches* matches) {
185 ACMatches::iterator it = matches->begin();
186 while ((it != matches->end()) && !it->allowed_to_be_default_match)
187 ++it;
188 return it;
189}
190
initial.commit09911bf2008-07-26 23:55:29191void SearchProvider::Start(const AutocompleteInput& input,
mariakhomenko3ef531d72015-01-10 00:03:43192 bool minimal_changes,
193 bool called_due_to_focus) {
[email protected]04504c242013-01-22 21:08:55194 // Do our best to load the model as early as possible. This will reduce
195 // odds of having the model not ready when really needed (a non-empty input).
196 TemplateURLService* model = providers_.template_url_service();
197 DCHECK(model);
198 model->Load();
199
initial.commit09911bf2008-07-26 23:55:29200 matches_.clear();
[email protected]618d6e62012-12-16 05:55:57201 field_trial_triggered_ = false;
initial.commit09911bf2008-07-26 23:55:29202
hashimoto663b9f42014-08-26 04:29:20203 // Can't return search/suggest results for bogus input.
mariakhomenko3ef531d72015-01-10 00:03:43204 if (called_due_to_focus ||
205 input.type() == metrics::OmniboxInputType::INVALID) {
[email protected]9950e5d52014-02-25 23:34:19206 Stop(true);
initial.commit09911bf2008-07-26 23:55:29207 return;
208 }
209
[email protected]14710852013-02-05 23:45:41210 keyword_input_ = input;
[email protected]257ab712009-04-14 17:16:24211 const TemplateURL* keyword_provider =
[email protected]14710852013-02-05 23:45:41212 KeywordProvider::GetSubstitutingTemplateURLForInput(model,
213 &keyword_input_);
214 if (keyword_provider == NULL)
215 keyword_input_.Clear();
216 else if (keyword_input_.text().empty())
[email protected]257ab712009-04-14 17:16:24217 keyword_provider = NULL;
[email protected]257ab712009-04-14 17:16:24218
[email protected]85b8d6f2012-05-08 20:53:47219 const TemplateURL* default_provider = model->GetDefaultSearchProvider();
[email protected]ce7ee5f2014-06-16 23:41:19220 if (default_provider &&
221 !default_provider->SupportsReplacement(model->search_terms_data()))
[email protected]257ab712009-04-14 17:16:24222 default_provider = NULL;
223
224 if (keyword_provider == default_provider)
[email protected]e17511f2011-07-13 14:09:18225 default_provider = NULL; // No use in querying the same provider twice.
[email protected]257ab712009-04-14 17:16:24226
227 if (!default_provider && !keyword_provider) {
228 // No valid providers.
[email protected]9950e5d52014-02-25 23:34:19229 Stop(true);
initial.commit09911bf2008-07-26 23:55:29230 return;
231 }
232
233 // If we're still running an old query but have since changed the query text
[email protected]257ab712009-04-14 17:16:24234 // or the providers, abort the query.
[email protected]0085863a2013-12-06 21:19:03235 base::string16 default_provider_keyword(default_provider ?
236 default_provider->keyword() : base::string16());
237 base::string16 keyword_provider_keyword(keyword_provider ?
238 keyword_provider->keyword() : base::string16());
[email protected]9e789742011-01-10 23:27:32239 if (!minimal_changes ||
[email protected]85b8d6f2012-05-08 20:53:47240 !providers_.equal(default_provider_keyword, keyword_provider_keyword)) {
[email protected]bb900e02013-03-14 14:15:29241 // Cancel any in-flight suggest requests.
[email protected]e1290ee62013-06-26 18:31:15242 if (!done_)
[email protected]e29249dc52012-07-19 17:33:50243 Stop(false);
[email protected]257ab712009-04-14 17:16:24244 }
initial.commit09911bf2008-07-26 23:55:29245
[email protected]85b8d6f2012-05-08 20:53:47246 providers_.set(default_provider_keyword, keyword_provider_keyword);
initial.commit09911bf2008-07-26 23:55:29247
248 if (input.text().empty()) {
249 // User typed "?" alone. Give them a placeholder result indicating what
250 // this syntax does.
[email protected]257ab712009-04-14 17:16:24251 if (default_provider) {
[email protected]69c579e2010-04-23 20:01:00252 AutocompleteMatch match;
253 match.provider = this;
[email protected]a2fedb1e2011-01-25 15:23:36254 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
[email protected]257ab712009-04-14 17:16:24255 match.contents_class.push_back(
[email protected]2c33dd22010-02-11 21:46:35256 ACMatchClassification(0, ACMatchClassification::NONE));
[email protected]85b8d6f2012-05-08 20:53:47257 match.keyword = providers_.default_provider();
[email protected]45f89a92013-08-12 13:41:36258 match.allowed_to_be_default_match = true;
[email protected]257ab712009-04-14 17:16:24259 matches_.push_back(match);
260 }
[email protected]9950e5d52014-02-25 23:34:19261 Stop(true);
initial.commit09911bf2008-07-26 23:55:29262 return;
263 }
264
265 input_ = input;
266
[email protected]e1290ee62013-06-26 18:31:15267 DoHistoryQuery(minimal_changes);
grobye5fcee42014-09-26 03:36:46268 // Answers needs scored history results before any suggest query has been
269 // started, since the query for answer-bearing results needs additional
270 // prefetch information based on the highest-scored local history result.
271 if (OmniboxFieldTrial::EnableAnswersInSuggest()) {
272 ScoreHistoryResults(raw_default_history_results_,
273 false,
274 &transformed_default_history_results_);
275 ScoreHistoryResults(raw_keyword_history_results_,
276 true,
277 &transformed_keyword_history_results_);
278 prefetch_data_ = FindAnswersPrefetchData();
279
280 // Raw results are not needed any more.
281 raw_default_history_results_.clear();
282 raw_keyword_history_results_.clear();
283 } else {
284 transformed_default_history_results_.clear();
285 transformed_keyword_history_results_.clear();
286 }
287
[email protected]e1290ee62013-06-26 18:31:15288 StartOrStopSuggestQuery(minimal_changes);
[email protected]344946a12012-12-20 12:03:42289 UpdateMatches();
initial.commit09911bf2008-07-26 23:55:29290}
291
[email protected]ec3f679b2014-08-18 07:45:13292void SearchProvider::Stop(bool clear_cached_results) {
293 StopSuggest();
294 done_ = true;
295
296 if (clear_cached_results)
297 ClearAllResults();
298}
299
[email protected]776ee5902014-08-11 09:15:19300const TemplateURL* SearchProvider::GetTemplateURL(bool is_keyword) const {
301 return is_keyword ? providers_.GetKeywordProviderURL()
302 : providers_.GetDefaultProviderURL();
303}
304
305const AutocompleteInput SearchProvider::GetInput(bool is_keyword) const {
306 return is_keyword ? keyword_input_ : input_;
307}
308
309bool SearchProvider::ShouldAppendExtraParams(
310 const SearchSuggestionParser::SuggestResult& result) const {
311 return !result.from_keyword_provider() ||
312 providers_.default_provider().empty();
313}
314
[email protected]776ee5902014-08-11 09:15:19315void SearchProvider::RecordDeletionResult(bool success) {
316 if (success) {
317 base::RecordAction(
318 base::UserMetricsAction("Omnibox.ServerSuggestDelete.Success"));
319 } else {
320 base::RecordAction(
321 base::UserMetricsAction("Omnibox.ServerSuggestDelete.Failure"));
322 }
323}
324
325void SearchProvider::OnURLFetchComplete(const net::URLFetcher* source) {
326 DCHECK(!done_);
327 --suggest_results_pending_;
328 DCHECK_GE(suggest_results_pending_, 0); // Should never go negative.
329
330 const bool is_keyword = source == keyword_fetcher_.get();
331
332 // Ensure the request succeeded and that the provider used is still available.
333 // A verbatim match cannot be generated without this provider, causing errors.
334 const bool request_succeeded =
335 source->GetStatus().is_success() && (source->GetResponseCode() == 200) &&
336 GetTemplateURL(is_keyword);
337
338 LogFetchComplete(request_succeeded, is_keyword);
339
340 bool results_updated = false;
341 if (request_succeeded) {
342 scoped_ptr<base::Value> data(SearchSuggestionParser::DeserializeJsonData(
343 SearchSuggestionParser::ExtractJsonData(source)));
344 if (data) {
345 SearchSuggestionParser::Results* results =
346 is_keyword ? &keyword_results_ : &default_results_;
347 results_updated = ParseSuggestResults(*data, -1, is_keyword, results);
348 if (results_updated)
349 SortResults(is_keyword, results);
350 }
351 }
352 UpdateMatches();
353 if (done_ || results_updated)
354 listener_->OnProviderUpdate(results_updated);
355}
356
[email protected]ec3f679b2014-08-18 07:45:13357void SearchProvider::StopSuggest() {
358 // Increment the appropriate field in the histogram by the number of
359 // pending requests that were invalidated.
360 for (int i = 0; i < suggest_results_pending_; ++i)
361 LogOmniboxSuggestRequest(REQUEST_INVALIDATED);
362 suggest_results_pending_ = 0;
363 timer_.Stop();
364 // Stop any in-progress URL fetches.
365 keyword_fetcher_.reset();
366 default_fetcher_.reset();
367}
368
369void SearchProvider::ClearAllResults() {
370 keyword_results_.Clear();
371 default_results_.Clear();
372}
373
[email protected]776ee5902014-08-11 09:15:19374void SearchProvider::UpdateMatchContentsClass(
375 const base::string16& input_text,
376 SearchSuggestionParser::Results* results) {
377 for (SearchSuggestionParser::SuggestResults::iterator sug_it =
378 results->suggest_results.begin();
379 sug_it != results->suggest_results.end(); ++sug_it) {
380 sug_it->ClassifyMatchContents(false, input_text);
381 }
Ryo Hashimoto884ad192014-08-28 05:54:30382 const std::string languages(client_->AcceptLanguages());
[email protected]776ee5902014-08-11 09:15:19383 for (SearchSuggestionParser::NavigationResults::iterator nav_it =
384 results->navigation_results.begin();
385 nav_it != results->navigation_results.end(); ++nav_it) {
386 nav_it->CalculateAndClassifyMatchContents(false, input_text, languages);
387 }
388}
389
[email protected]d4a94b92014-03-04 01:35:22390void SearchProvider::SortResults(bool is_keyword,
[email protected]0b9575f2014-07-30 11:58:37391 SearchSuggestionParser::Results* results) {
[email protected]d4a94b92014-03-04 01:35:22392 // Ignore suggested scores for non-keyword matches in keyword mode; if the
393 // server is allowed to score these, it could interfere with the user's
394 // ability to get good keyword results.
395 const bool abandon_suggested_scores =
396 !is_keyword && !providers_.keyword_provider().empty();
[email protected]0b9575f2014-07-30 11:58:37397 // Apply calculated relevance scores to suggestions if valid relevances were
[email protected]d4a94b92014-03-04 01:35:22398 // not provided or we're abandoning suggested scores entirely.
[email protected]2c802d12014-07-31 12:57:14399 if (!results->relevances_from_server || abandon_suggested_scores) {
[email protected]d4a94b92014-03-04 01:35:22400 ApplyCalculatedSuggestRelevance(&results->suggest_results);
401 ApplyCalculatedNavigationRelevance(&results->navigation_results);
402 // If abandoning scores entirely, also abandon the verbatim score.
403 if (abandon_suggested_scores)
404 results->verbatim_relevance = -1;
405 }
406
407 // Keep the result lists sorted.
408 const CompareScoredResults comparator = CompareScoredResults();
409 std::stable_sort(results->suggest_results.begin(),
410 results->suggest_results.end(),
411 comparator);
412 std::stable_sort(results->navigation_results.begin(),
413 results->navigation_results.end(),
414 comparator);
415}
416
[email protected]cfa164bf2014-03-19 11:51:15417void SearchProvider::LogFetchComplete(bool success, bool is_keyword) {
418 LogOmniboxSuggestRequest(REPLY_RECEIVED);
419 // Record response time for suggest requests sent to Google. We care
420 // only about the common case: the Google default provider used in
421 // non-keyword mode.
422 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
423 if (!is_keyword && default_url &&
[email protected]ce7ee5f2014-06-16 23:41:19424 (TemplateURLPrepopulateData::GetEngineType(
425 *default_url,
426 providers_.template_url_service()->search_terms_data()) ==
[email protected]cfa164bf2014-03-19 11:51:15427 SEARCH_ENGINE_GOOGLE)) {
428 const base::TimeDelta elapsed_time =
429 base::TimeTicks::Now() - time_suggest_request_sent_;
430 if (success) {
431 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Success.GoogleResponseTime",
432 elapsed_time);
433 } else {
434 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Failure.GoogleResponseTime",
435 elapsed_time);
436 }
437 }
438}
439
[email protected]cfa164bf2014-03-19 11:51:15440void SearchProvider::UpdateMatches() {
mpearson6c183672014-09-03 02:09:42441 PersistTopSuggestions(&default_results_);
442 PersistTopSuggestions(&keyword_results_);
[email protected]cfa164bf2014-03-19 11:51:15443 ConvertResultsToAutocompleteMatches();
444
445 // Check constraints that may be violated by suggested relevances.
446 if (!matches_.empty() &&
447 (default_results_.HasServerProvidedScores() ||
448 keyword_results_.HasServerProvidedScores())) {
449 // These blocks attempt to repair undesirable behavior by suggested
450 // relevances with minimal impact, preserving other suggested relevances.
451
[email protected]d0e4ad02014-08-22 18:58:33452 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
453 const bool is_extension_keyword = (keyword_url != NULL) &&
454 (keyword_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION);
455 if ((keyword_url != NULL) && !is_extension_keyword &&
[email protected]7bc5e162014-08-15 19:41:11456 (FindTopMatch() == matches_.end())) {
[email protected]d0e4ad02014-08-22 18:58:33457 // In non-extension keyword mode, disregard the keyword verbatim suggested
458 // relevance if necessary, so at least one match is allowed to be default.
459 // (In extension keyword mode this is not necessary because the extension
mpearson6c183672014-09-03 02:09:42460 // will return a default match.) Give keyword verbatim the lowest
461 // non-zero score to best reflect what the server desired.
462 DCHECK_EQ(0, keyword_results_.verbatim_relevance);
463 keyword_results_.verbatim_relevance = 1;
[email protected]cfa164bf2014-03-19 11:51:15464 ConvertResultsToAutocompleteMatches();
465 }
[email protected]89bd27d12014-04-12 17:36:23466 if (IsTopMatchSearchWithURLInput()) {
[email protected]cfa164bf2014-03-19 11:51:15467 // Disregard the suggested search and verbatim relevances if the input
468 // type is URL and the top match is a highly-ranked search suggestion.
469 // For example, prevent a search for "foo.com" from outranking another
470 // provider's navigation for "foo.com" or "foo.com/url_from_history".
471 ApplyCalculatedSuggestRelevance(&keyword_results_.suggest_results);
472 ApplyCalculatedSuggestRelevance(&default_results_.suggest_results);
473 default_results_.verbatim_relevance = -1;
474 keyword_results_.verbatim_relevance = -1;
475 ConvertResultsToAutocompleteMatches();
476 }
[email protected]d0e4ad02014-08-22 18:58:33477 if (!is_extension_keyword && (FindTopMatch() == matches_.end())) {
478 // Guarantee that SearchProvider returns a legal default match (except
479 // when in extension-based keyword mode). The omnibox always needs at
480 // least one legal default match, and it relies on SearchProvider in
481 // combination with KeywordProvider (for extension-based keywords) to
mpearson6c183672014-09-03 02:09:42482 // always return one. Give the verbatim suggestion the lowest non-zero
483 // scores to best reflect what the server desired.
484 DCHECK_EQ(0, default_results_.verbatim_relevance);
485 default_results_.verbatim_relevance = 1;
486 // We do not have to alter keyword_results_.verbatim_relevance here.
487 // If the user is in keyword mode, we already reverted (earlier in this
488 // function) the instructions to suppress keyword verbatim.
[email protected]cfa164bf2014-03-19 11:51:15489 ConvertResultsToAutocompleteMatches();
490 }
[email protected]89bd27d12014-04-12 17:36:23491 DCHECK(!IsTopMatchSearchWithURLInput());
[email protected]d0e4ad02014-08-22 18:58:33492 DCHECK(is_extension_keyword || (FindTopMatch() != matches_.end()));
[email protected]cfa164bf2014-03-19 11:51:15493 }
494 UMA_HISTOGRAM_CUSTOM_COUNTS(
495 "Omnibox.SearchProviderMatches", matches_.size(), 1, 6, 7);
mpearson6c183672014-09-03 02:09:42496
497 // Record the top suggestion (if any) for future use.
498 top_query_suggestion_match_contents_ = base::string16();
499 top_navigation_suggestion_ = GURL();
500 ACMatches::const_iterator first_match = FindTopMatch();
501 if ((first_match != matches_.end()) &&
502 !first_match->inline_autocompletion.empty()) {
503 // Identify if this match came from a query suggestion or a navsuggestion.
504 // In either case, extracts the identifying feature of the suggestion
505 // (query string or navigation url).
506 if (AutocompleteMatch::IsSearchType(first_match->type))
507 top_query_suggestion_match_contents_ = first_match->contents;
508 else
509 top_navigation_suggestion_ = first_match->destination_url;
510 }
511
[email protected]cfa164bf2014-03-19 11:51:15512 UpdateDone();
[email protected]cfa164bf2014-03-19 11:51:15513}
514
[email protected]bc8bb0cd2013-06-24 21:50:23515void SearchProvider::Run() {
516 // Start a new request with the current input.
517 suggest_results_pending_ = 0;
518 time_suggest_request_sent_ = base::TimeTicks::Now();
[email protected]abe441e2013-05-06 12:35:05519
[email protected]bc8bb0cd2013-06-24 21:50:23520 default_fetcher_.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID,
521 providers_.GetDefaultProviderURL(), input_));
522 keyword_fetcher_.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID,
523 providers_.GetKeywordProviderURL(), keyword_input_));
524
525 // Both the above can fail if the providers have been modified or deleted
526 // since the query began.
527 if (suggest_results_pending_ == 0) {
528 UpdateDone();
529 // We only need to update the listener if we're actually done.
530 if (done_)
531 listener_->OnProviderUpdate(false);
532 }
[email protected]601858c02010-09-01 17:08:20533}
534
[email protected]8d457132010-11-04 18:13:40535void SearchProvider::DoHistoryQuery(bool minimal_changes) {
536 // The history query results are synchronous, so if minimal_changes is true,
537 // we still have the last results and don't need to do anything.
538 if (minimal_changes)
initial.commit09911bf2008-07-26 23:55:29539 return;
540
grobye5fcee42014-09-26 03:36:46541 raw_keyword_history_results_.clear();
542 raw_default_history_results_.clear();
initial.commit09911bf2008-07-26 23:55:29543
[email protected]78e5e432013-08-03 02:10:10544 if (OmniboxFieldTrial::SearchHistoryDisable(
545 input_.current_page_classification()))
[email protected]d8cd76b2013-07-10 09:46:16546 return;
547
Ryo Hashimoto884ad192014-08-28 05:54:30548 history::URLDatabase* url_db = client_->InMemoryDatabase();
[email protected]8d457132010-11-04 18:13:40549 if (!url_db)
initial.commit09911bf2008-07-26 23:55:29550 return;
551
[email protected]51124552011-07-16 01:37:10552 // Request history for both the keyword and default provider. We grab many
553 // more matches than we'll ultimately clamp to so that if there are several
554 // recent multi-word matches who scores are lowered (see
grobye5fcee42014-09-26 03:36:46555 // ScoreHistoryResults()), they won't crowd out older, higher-scoring
[email protected]51124552011-07-16 01:37:10556 // matches. Note that this doesn't fix the problem entirely, but merely
557 // limits it to cases with a very large number of such multi-word matches; for
558 // now, this seems OK compared with the complexity of a real fix, which would
559 // require multiple searches and tracking of "single- vs. multi-word" in the
560 // database.
561 int num_matches = kMaxMatches * 5;
[email protected]85b8d6f2012-05-08 20:53:47562 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
563 if (default_url) {
[email protected]b4bec972014-04-05 18:07:15564 const base::TimeTicks start_time = base::TimeTicks::Now();
grobye5fcee42014-09-26 03:36:46565 url_db->GetMostRecentKeywordSearchTerms(default_url->id(),
566 input_.text(),
567 num_matches,
568 &raw_default_history_results_);
[email protected]31afdf72013-09-26 04:29:36569 UMA_HISTOGRAM_TIMES(
570 "Omnibox.SearchProvider.GetMostRecentKeywordTermsDefaultProviderTime",
571 base::TimeTicks::Now() - start_time);
[email protected]257ab712009-04-14 17:16:24572 }
[email protected]85b8d6f2012-05-08 20:53:47573 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
574 if (keyword_url) {
575 url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
grobye5fcee42014-09-26 03:36:46576 keyword_input_.text(),
577 num_matches,
578 &raw_keyword_history_results_);
[email protected]3954c3a2012-04-10 20:17:55579 }
initial.commit09911bf2008-07-26 23:55:29580}
581
bartn1c07e722014-10-27 19:34:24582base::TimeDelta SearchProvider::GetSuggestQueryDelay() const {
583 bool from_last_keystroke;
584 int polling_delay_ms;
585 OmniboxFieldTrial::GetSuggestPollingStrategy(&from_last_keystroke,
586 &polling_delay_ms);
587
588 base::TimeDelta delay(base::TimeDelta::FromMilliseconds(polling_delay_ms));
589 if (from_last_keystroke)
590 return delay;
591
592 base::TimeDelta time_since_last_suggest_request =
593 base::TimeTicks::Now() - time_suggest_request_sent_;
594 return std::max(base::TimeDelta(), delay - time_since_last_suggest_request);
595}
596
[email protected]6dc950f2012-07-16 19:49:08597void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]83c726482008-09-10 06:36:34598 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29599 StopSuggest();
[email protected]71b46152013-05-03 16:39:20600 ClearAllResults();
initial.commit09911bf2008-07-26 23:55:29601 return;
602 }
603
bartn1c07e722014-10-27 19:34:24604 if (OmniboxFieldTrial::DisableResultsCaching())
605 ClearAllResults();
606
initial.commit09911bf2008-07-26 23:55:29607 // For the minimal_changes case, if we finished the previous query and still
608 // have its results, or are allowed to keep running it, just do that, rather
609 // than starting a new query.
610 if (minimal_changes &&
[email protected]cc1526e2013-05-17 04:04:24611 (!default_results_.suggest_results.empty() ||
612 !default_results_.navigation_results.empty() ||
613 !keyword_results_.suggest_results.empty() ||
614 !keyword_results_.navigation_results.empty() ||
[email protected]a2770a7d2014-04-22 19:33:35615 (!done_ && input_.want_asynchronous_matches())))
initial.commit09911bf2008-07-26 23:55:29616 return;
617
618 // We can't keep running any previous query, so halt it.
619 StopSuggest();
[email protected]d1f0a7f2012-06-05 10:26:42620
mpearson6c183672014-09-03 02:09:42621 UpdateAllOldResults(minimal_changes);
initial.commit09911bf2008-07-26 23:55:29622
[email protected]ee6110b2014-01-09 22:26:31623 // Update the content classifications of remaining results so they look good
624 // against the current input.
[email protected]23db6492014-01-16 02:35:30625 UpdateMatchContentsClass(input_.text(), &default_results_);
626 if (!keyword_input_.text().empty())
627 UpdateMatchContentsClass(keyword_input_.text(), &keyword_results_);
[email protected]ee6110b2014-01-09 22:26:31628
initial.commit09911bf2008-07-26 23:55:29629 // We can't start a new query if we're only allowed synchronous results.
[email protected]a2770a7d2014-04-22 19:33:35630 if (!input_.want_asynchronous_matches())
initial.commit09911bf2008-07-26 23:55:29631 return;
632
bartn1c07e722014-10-27 19:34:24633 // Kick off a timer that will start the URL fetch if it completes before
634 // the user types another character. Requests may be delayed to avoid
635 // flooding the server with requests that are likely to be thrown away later
636 // anyway.
637 const base::TimeDelta delay = GetSuggestQueryDelay();
638 if (delay <= base::TimeDelta()) {
[email protected]515ffa942012-11-27 20:18:24639 Run();
640 return;
641 }
bartn1c07e722014-10-27 19:34:24642 timer_.Start(FROM_HERE, delay, this, &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29643}
644
[email protected]83c726482008-09-10 06:36:34645bool SearchProvider::IsQuerySuitableForSuggest() const {
[email protected]3954c3a2012-04-10 20:17:55646 // Don't run Suggest in incognito mode, if the engine doesn't support it, or
647 // if the user has disabled it.
[email protected]85b8d6f2012-05-08 20:53:47648 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
649 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
Ryo Hashimoto884ad192014-08-28 05:54:30650 if (client_->IsOffTheRecord() ||
[email protected]85b8d6f2012-05-08 20:53:47651 ((!default_url || default_url->suggestions_url().empty()) &&
652 (!keyword_url || keyword_url->suggestions_url().empty())) ||
Ryo Hashimoto884ad192014-08-28 05:54:30653 !client_->SearchSuggestEnabled())
[email protected]83c726482008-09-10 06:36:34654 return false;
655
[email protected]cac59d32010-08-09 23:23:14656 // If the input type might be a URL, we take extra care so that private data
[email protected]83c726482008-09-10 06:36:34657 // isn't sent to the server.
[email protected]83c726482008-09-10 06:36:34658
[email protected]cac59d32010-08-09 23:23:14659 // FORCED_QUERY means the user is explicitly asking us to search for this, so
660 // we assume it isn't a URL and/or there isn't private data.
[email protected]3dc75b12014-06-08 00:02:22661 if (input_.type() == metrics::OmniboxInputType::FORCED_QUERY)
[email protected]cac59d32010-08-09 23:23:14662 return true;
[email protected]83c726482008-09-10 06:36:34663
[email protected]f608ea102013-03-18 15:08:09664 // Next we check the scheme. If this is UNKNOWN/URL with a scheme that isn't
665 // http/https/ftp, we shouldn't send it. Sending things like file: and data:
666 // is both a waste of time and a disclosure of potentially private, local
667 // data. Other "schemes" may actually be usernames, and we don't want to send
668 // passwords. If the scheme is OK, we still need to check other cases below.
669 // If this is QUERY, then the presence of these schemes means the user
670 // explicitly typed one, and thus this is probably a URL that's being entered
671 // and happens to currently be invalid -- in which case we again want to run
672 // our checks below. Other QUERY cases are less likely to be URLs and thus we
673 // assume we're OK.
[email protected]df807042014-08-13 16:48:41674 if (!LowerCaseEqualsASCII(input_.scheme(), url::kHttpScheme) &&
675 !LowerCaseEqualsASCII(input_.scheme(), url::kHttpsScheme) &&
676 !LowerCaseEqualsASCII(input_.scheme(), url::kFtpScheme))
[email protected]3dc75b12014-06-08 00:02:22677 return (input_.type() == metrics::OmniboxInputType::QUERY);
[email protected]cac59d32010-08-09 23:23:14678
679 // Don't send URLs with usernames, queries or refs. Some of these are
680 // private, and the Suggest server is unlikely to have any useful results
681 // for any of them. Also don't send URLs with ports, as we may initially
682 // think that a username + password is a host + port (and we don't want to
683 // send usernames/passwords), and even if the port really is a port, the
684 // server is once again unlikely to have and useful results.
[email protected]825e16f2013-09-30 23:52:58685 // Note that we only block based on refs if the input is URL-typed, as search
686 // queries can legitimately have #s in them which the URL parser
687 // overaggressively categorizes as a url with a ref.
[email protected]b45334502014-04-30 19:44:05688 const url::Parsed& parts = input_.parts();
[email protected]cac59d32010-08-09 23:23:14689 if (parts.username.is_nonempty() || parts.port.is_nonempty() ||
[email protected]825e16f2013-09-30 23:52:58690 parts.query.is_nonempty() ||
[email protected]3dc75b12014-06-08 00:02:22691 (parts.ref.is_nonempty() &&
692 (input_.type() == metrics::OmniboxInputType::URL)))
[email protected]cac59d32010-08-09 23:23:14693 return false;
694
695 // Don't send anything for https except the hostname. Hostnames are OK
696 // because they are visible when the TCP connection is established, but the
697 // specific path may reveal private information.
[email protected]df807042014-08-13 16:48:41698 if (LowerCaseEqualsASCII(input_.scheme(), url::kHttpsScheme) &&
[email protected]a2fedb1e2011-01-25 15:23:36699 parts.path.is_nonempty())
[email protected]cac59d32010-08-09 23:23:14700 return false;
[email protected]83c726482008-09-10 06:36:34701
702 return true;
703}
704
mpearson6c183672014-09-03 02:09:42705void SearchProvider::UpdateAllOldResults(bool minimal_changes) {
[email protected]dc735c02013-11-12 23:23:41706 if (keyword_input_.text().empty()) {
[email protected]1e1550e2013-05-02 17:37:51707 // User is either in keyword mode with a blank input or out of
708 // keyword mode entirely.
[email protected]cc1526e2013-05-17 04:04:24709 keyword_results_.Clear();
[email protected]1e1550e2013-05-02 17:37:51710 }
mpearson6c183672014-09-03 02:09:42711 UpdateOldResults(minimal_changes, &default_results_);
712 UpdateOldResults(minimal_changes, &keyword_results_);
[email protected]d1f0a7f2012-06-05 10:26:42713}
714
mpearson6c183672014-09-03 02:09:42715void SearchProvider::PersistTopSuggestions(
716 SearchSuggestionParser::Results* results) {
717 // Mark any results matching the current top results as having been received
718 // prior to the last keystroke. That prevents asynchronous updates from
719 // clobbering top results, which may be used for inline autocompletion.
720 // Other results don't need similar changes, because they shouldn't be
721 // displayed asynchronously anyway.
722 if (!top_query_suggestion_match_contents_.empty()) {
723 for (SearchSuggestionParser::SuggestResults::iterator sug_it =
724 results->suggest_results.begin();
725 sug_it != results->suggest_results.end(); ++sug_it) {
726 if (sug_it->match_contents() == top_query_suggestion_match_contents_)
727 sug_it->set_received_after_last_keystroke(false);
728 }
729 }
730 if (top_navigation_suggestion_.is_valid()) {
731 for (SearchSuggestionParser::NavigationResults::iterator nav_it =
732 results->navigation_results.begin();
733 nav_it != results->navigation_results.end(); ++nav_it) {
734 if (nav_it->url() == top_navigation_suggestion_)
735 nav_it->set_received_after_last_keystroke(false);
736 }
737 }
[email protected]d1f0a7f2012-06-05 10:26:42738}
739
[email protected]0b9575f2014-07-30 11:58:37740void SearchProvider::ApplyCalculatedSuggestRelevance(
741 SearchSuggestionParser::SuggestResults* list) {
[email protected]d1f0a7f2012-06-05 10:26:42742 for (size_t i = 0; i < list->size(); ++i) {
[email protected]0b9575f2014-07-30 11:58:37743 SearchSuggestionParser::SuggestResult& result = (*list)[i];
[email protected]188b50c2013-03-28 07:19:42744 result.set_relevance(
745 result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
746 (list->size() - i - 1));
[email protected]d30268a2013-06-25 22:31:07747 result.set_relevance_from_server(false);
[email protected]d1f0a7f2012-06-05 10:26:42748 }
749}
750
[email protected]188b50c2013-03-28 07:19:42751void SearchProvider::ApplyCalculatedNavigationRelevance(
[email protected]0b9575f2014-07-30 11:58:37752 SearchSuggestionParser::NavigationResults* list) {
[email protected]d1f0a7f2012-06-05 10:26:42753 for (size_t i = 0; i < list->size(); ++i) {
[email protected]0b9575f2014-07-30 11:58:37754 SearchSuggestionParser::NavigationResult& result = (*list)[i];
[email protected]188b50c2013-03-28 07:19:42755 result.set_relevance(
756 result.CalculateRelevance(input_, providers_.has_keyword_provider()) +
757 (list->size() - i - 1));
[email protected]d30268a2013-06-25 22:31:07758 result.set_relevance_from_server(false);
[email protected]d1f0a7f2012-06-05 10:26:42759 }
760}
761
[email protected]15fb2aa2012-05-22 22:52:59762net::URLFetcher* SearchProvider::CreateSuggestFetcher(
[email protected]7cc6e5632011-10-25 17:56:12763 int id,
[email protected]9ff91722012-09-07 05:29:12764 const TemplateURL* template_url,
[email protected]14710852013-02-05 23:45:41765 const AutocompleteInput& input) {
[email protected]9ff91722012-09-07 05:29:12766 if (!template_url || template_url->suggestions_url().empty())
767 return NULL;
768
769 // Bail if the suggestion URL is invalid with the given replacements.
[email protected]14710852013-02-05 23:45:41770 TemplateURLRef::SearchTermsArgs search_term_args(input.text());
[email protected]420472b22014-06-10 13:34:43771 search_term_args.input_type = input.type();
[email protected]14710852013-02-05 23:45:41772 search_term_args.cursor_position = input.cursor_position();
[email protected]d5015ca2013-08-08 22:04:18773 search_term_args.page_classification = input.current_page_classification();
[email protected]2ef2a6642014-07-30 05:50:29774 if (OmniboxFieldTrial::EnableAnswersInSuggest()) {
[email protected]20184242014-05-14 02:57:42775 search_term_args.session_token = GetSessionToken();
[email protected]2ef2a6642014-07-30 05:50:29776 if (!prefetch_data_.full_query_text.empty()) {
777 search_term_args.prefetch_query =
[email protected]ebbac63e2014-08-22 01:43:06778 base::UTF16ToUTF8(prefetch_data_.full_query_text);
[email protected]2ef2a6642014-07-30 05:50:29779 search_term_args.prefetch_query_type =
[email protected]ebbac63e2014-08-22 01:43:06780 base::UTF16ToUTF8(prefetch_data_.query_type);
[email protected]2ef2a6642014-07-30 05:50:29781 }
782 }
[email protected]9ff91722012-09-07 05:29:12783 GURL suggest_url(template_url->suggestions_url_ref().ReplaceSearchTerms(
[email protected]ce7ee5f2014-06-16 23:41:19784 search_term_args,
785 providers_.template_url_service()->search_terms_data()));
[email protected]9ff91722012-09-07 05:29:12786 if (!suggest_url.is_valid())
787 return NULL;
[email protected]9b9fa672013-11-07 06:04:52788 // Send the current page URL if user setting and URL requirements are met and
789 // the user is in the field trial.
790 if (CanSendURL(current_page_url_, suggest_url, template_url,
[email protected]e6477f12014-08-05 07:59:54791 input.current_page_classification(),
Ryo Hashimoto884ad192014-08-28 05:54:30792 template_url_service_->search_terms_data(), client_.get()) &&
[email protected]9b9fa672013-11-07 06:04:52793 OmniboxFieldTrial::InZeroSuggestAfterTypingFieldTrial()) {
794 search_term_args.current_page_url = current_page_url_.spec();
795 // Create the suggest URL again with the current page URL.
796 suggest_url = GURL(template_url->suggestions_url_ref().ReplaceSearchTerms(
[email protected]ce7ee5f2014-06-16 23:41:19797 search_term_args,
798 providers_.template_url_service()->search_terms_data()));
[email protected]9b9fa672013-11-07 06:04:52799 }
[email protected]9ff91722012-09-07 05:29:12800
801 suggest_results_pending_++;
802 LogOmniboxSuggestRequest(REQUEST_SENT);
803
804 net::URLFetcher* fetcher =
805 net::URLFetcher::Create(id, suggest_url, net::URLFetcher::GET, this);
Ryo Hashimoto884ad192014-08-28 05:54:30806 fetcher->SetRequestContext(client_->RequestContext());
[email protected]d3cf8682f02012-02-29 23:29:34807 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
[email protected]bd3b4712012-12-18 17:01:30808 // Add Chrome experiment state to the request headers.
809 net::HttpRequestHeaders headers;
[email protected]71011c1682014-07-09 17:19:16810 variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
Ryo Hashimoto884ad192014-08-28 05:54:30811 fetcher->GetOriginalURL(), client_->IsOffTheRecord(), false, &headers);
[email protected]bd3b4712012-12-18 17:01:30812 fetcher->SetExtraRequestHeaders(headers.ToString());
[email protected]257ab712009-04-14 17:16:24813 fetcher->Start();
814 return fetcher;
815}
816
[email protected]344946a12012-12-20 12:03:42817void SearchProvider::ConvertResultsToAutocompleteMatches() {
initial.commit09911bf2008-07-26 23:55:29818 // Convert all the results to matches and add them to a map, so we can keep
819 // the most relevant match for each result.
[email protected]31afdf72013-09-26 04:29:36820 base::TimeTicks start_time(base::TimeTicks::Now());
initial.commit09911bf2008-07-26 23:55:29821 MatchMap map;
[email protected]bc8bb0cd2013-06-24 21:50:23822 const base::Time no_time;
[email protected]cc1526e2013-05-17 04:04:24823 int did_not_accept_keyword_suggestion =
824 keyword_results_.suggest_results.empty() ?
initial.commit09911bf2008-07-26 23:55:29825 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
826 TemplateURLRef::NO_SUGGESTION_CHOSEN;
initial.commit09911bf2008-07-26 23:55:29827
[email protected]d30268a2013-06-25 22:31:07828 bool relevance_from_server;
829 int verbatim_relevance = GetVerbatimRelevance(&relevance_from_server);
[email protected]cc1526e2013-05-17 04:04:24830 int did_not_accept_default_suggestion =
831 default_results_.suggest_results.empty() ?
[email protected]55ce8f12012-05-09 04:44:08832 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
833 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]7bc5e162014-08-15 19:41:11834 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
[email protected]d1f0a7f2012-06-05 10:26:42835 if (verbatim_relevance > 0) {
[email protected]c2ca3fd2014-03-22 03:07:44836 const base::string16& trimmed_verbatim =
837 base::CollapseWhitespace(input_.text(), false);
[email protected]716cd372014-08-15 18:56:03838
839 // Verbatim results don't get suggestions and hence, answers.
840 // Scan previous matches if the last answer-bearing suggestion matches
841 // verbatim, and if so, copy over answer contents.
842 base::string16 answer_contents;
843 base::string16 answer_type;
jdonnelly7393cee2014-10-31 01:52:56844 scoped_ptr<SuggestionAnswer> answer;
[email protected]716cd372014-08-15 18:56:03845 for (ACMatches::iterator it = matches_.begin(); it != matches_.end();
846 ++it) {
jdonnelly7393cee2014-10-31 01:52:56847 if (it->answer && it->fill_into_edit == trimmed_verbatim) {
[email protected]716cd372014-08-15 18:56:03848 answer_contents = it->answer_contents;
849 answer_type = it->answer_type;
jdonnelly7393cee2014-10-31 01:52:56850 answer = SuggestionAnswer::copy(it->answer.get());
[email protected]716cd372014-08-15 18:56:03851 break;
852 }
853 }
854
[email protected]0b9575f2014-07-30 11:58:37855 SearchSuggestionParser::SuggestResult verbatim(
[email protected]c2ca3fd2014-03-22 03:07:44856 trimmed_verbatim, AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
[email protected]716cd372014-08-15 18:56:03857 trimmed_verbatim, base::string16(), base::string16(), answer_contents,
jdonnelly7393cee2014-10-31 01:52:56858 answer_type, answer.Pass(), std::string(), std::string(), false,
859 verbatim_relevance, relevance_from_server, false, trimmed_verbatim);
[email protected]57482a72014-03-14 22:27:37860 AddMatchToMap(verbatim, std::string(), did_not_accept_default_suggestion,
[email protected]7bc5e162014-08-15 19:41:11861 false, keyword_url != NULL, &map);
[email protected]d1f0a7f2012-06-05 10:26:42862 }
[email protected]5423e562013-02-07 03:58:45863 if (!keyword_input_.text().empty()) {
[email protected]5423e562013-02-07 03:58:45864 // We only create the verbatim search query match for a keyword
865 // if it's not an extension keyword. Extension keywords are handled
866 // in KeywordProvider::Start(). (Extensions are complicated...)
867 // Note: in this provider, SEARCH_OTHER_ENGINE must correspond
868 // to the keyword verbatim search query. Do not create other matches
869 // of type SEARCH_OTHER_ENGINE.
[email protected]bdcbcd82013-10-28 13:40:25870 if (keyword_url &&
871 (keyword_url->GetType() != TemplateURL::OMNIBOX_API_EXTENSION)) {
[email protected]d30268a2013-06-25 22:31:07872 bool keyword_relevance_from_server;
873 const int keyword_verbatim_relevance =
874 GetKeywordVerbatimRelevance(&keyword_relevance_from_server);
[email protected]dab8d52d2013-03-05 07:35:28875 if (keyword_verbatim_relevance > 0) {
[email protected]c2ca3fd2014-03-22 03:07:44876 const base::string16& trimmed_verbatim =
877 base::CollapseWhitespace(keyword_input_.text(), false);
[email protected]0b9575f2014-07-30 11:58:37878 SearchSuggestionParser::SuggestResult verbatim(
[email protected]c2ca3fd2014-03-22 03:07:44879 trimmed_verbatim, AutocompleteMatchType::SEARCH_OTHER_ENGINE,
880 trimmed_verbatim, base::string16(), base::string16(),
jdonnelly7393cee2014-10-31 01:52:56881 base::string16(), base::string16(), nullptr, std::string(),
882 std::string(), true, keyword_verbatim_relevance,
883 keyword_relevance_from_server, false, trimmed_verbatim);
[email protected]57482a72014-03-14 22:27:37884 AddMatchToMap(verbatim, std::string(),
[email protected]7bc5e162014-08-15 19:41:11885 did_not_accept_keyword_suggestion, false, true, &map);
[email protected]dab8d52d2013-03-05 07:35:28886 }
[email protected]5423e562013-02-07 03:58:45887 }
888 }
grobye5fcee42014-09-26 03:36:46889 AddRawHistoryResultsToMap(true, did_not_accept_keyword_suggestion, &map);
890 AddRawHistoryResultsToMap(false, did_not_accept_default_suggestion, &map);
[email protected]257ab712009-04-14 17:16:24891
[email protected]d1cb6a822013-09-18 19:43:00892 AddSuggestResultsToMap(keyword_results_.suggest_results,
893 keyword_results_.metadata, &map);
[email protected]987fad782013-08-28 06:23:18894 AddSuggestResultsToMap(default_results_.suggest_results,
895 default_results_.metadata, &map);
initial.commit09911bf2008-07-26 23:55:29896
[email protected]d30268a2013-06-25 22:31:07897 ACMatches matches;
initial.commit09911bf2008-07-26 23:55:29898 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
[email protected]d30268a2013-06-25 22:31:07899 matches.push_back(i->second);
initial.commit09911bf2008-07-26 23:55:29900
[email protected]d30268a2013-06-25 22:31:07901 AddNavigationResultsToMatches(keyword_results_.navigation_results, &matches);
902 AddNavigationResultsToMatches(default_results_.navigation_results, &matches);
initial.commit09911bf2008-07-26 23:55:29903
[email protected]d30268a2013-06-25 22:31:07904 // Now add the most relevant matches to |matches_|. We take up to kMaxMatches
mpearson6c183672014-09-03 02:09:42905 // suggest/navsuggest matches, regardless of origin. We always include in
906 // that set a legal default match if possible. If Instant Extended is enabled
907 // and we have server-provided (and thus hopefully more accurate) scores for
908 // some suggestions, we allow more of those, until we reach
[email protected]d30268a2013-06-25 22:31:07909 // AutocompleteResult::kMaxMatches total matches (that is, enough to fill the
910 // whole popup).
911 //
912 // We will always return any verbatim matches, no matter how we obtained their
913 // scores, unless we have already accepted AutocompleteResult::kMaxMatches
914 // higher-scoring matches under the conditions above.
915 std::sort(matches.begin(), matches.end(), &AutocompleteMatch::MoreRelevant);
jdonnelly55f66142014-09-25 23:08:11916
mpearson6c183672014-09-03 02:09:42917 // Guarantee that if there's a legal default match anywhere in the result
918 // set that it'll get returned. The rotate() call does this by moving the
919 // default match to the front of the list.
920 ACMatches::iterator default_match = FindTopMatch(&matches);
921 if (default_match != matches.end())
922 std::rotate(matches.begin(), default_match, default_match + 1);
[email protected]3723e6e2012-06-11 21:06:56923
jdonnelly55f66142014-09-25 23:08:11924 // It's possible to get a copy of an answer from previous matches and get the
925 // same or a different answer to another server-provided suggestion. In the
926 // future we may decide that we want to have answers attached to multiple
927 // suggestions, but the current assumption is that there should only ever be
928 // one suggestion with an answer. To maintain this assumption, remove any
929 // answers after the first.
930 RemoveExtraAnswers(&matches);
931
932 matches_.clear();
[email protected]d30268a2013-06-25 22:31:07933 size_t num_suggestions = 0;
934 for (ACMatches::const_iterator i(matches.begin());
935 (i != matches.end()) &&
936 (matches_.size() < AutocompleteResult::kMaxMatches);
937 ++i) {
938 // SEARCH_OTHER_ENGINE is only used in the SearchProvider for the keyword
939 // verbatim result, so this condition basically means "if this match is a
940 // suggestion of some sort".
941 if ((i->type != AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED) &&
942 (i->type != AutocompleteMatchType::SEARCH_OTHER_ENGINE)) {
943 // If we've already hit the limit on non-server-scored suggestions, and
944 // this isn't a server-scored suggestion we can add, skip it.
945 if ((num_suggestions >= kMaxMatches) &&
946 (!chrome::IsInstantExtendedAPIEnabled() ||
947 (i->GetAdditionalInfo(kRelevanceFromServerKey) != kTrue))) {
948 continue;
949 }
950
951 ++num_suggestions;
952 }
953
954 matches_.push_back(*i);
955 }
[email protected]31afdf72013-09-26 04:29:36956 UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.ConvertResultsTime",
957 base::TimeTicks::Now() - start_time);
[email protected]344946a12012-12-20 12:03:42958}
959
jdonnelly55f66142014-09-25 23:08:11960void SearchProvider::RemoveExtraAnswers(ACMatches* matches) {
961 bool answer_seen = false;
962 for (ACMatches::iterator it = matches->begin(); it != matches->end(); ++it) {
jdonnelly7393cee2014-10-31 01:52:56963 if (it->answer) {
jdonnelly55f66142014-09-25 23:08:11964 if (!answer_seen) {
965 answer_seen = true;
966 } else {
967 it->answer_contents.clear();
968 it->answer_type.clear();
jdonnelly7393cee2014-10-31 01:52:56969 it->answer.reset();
jdonnelly55f66142014-09-25 23:08:11970 }
971 }
972 }
973}
974
[email protected]89bd27d12014-04-12 17:36:23975ACMatches::const_iterator SearchProvider::FindTopMatch() const {
[email protected]0a8718b12013-11-13 18:41:31976 ACMatches::const_iterator it = matches_.begin();
977 while ((it != matches_.end()) && !it->allowed_to_be_default_match)
978 ++it;
979 return it;
[email protected]9dfb4d362013-04-05 02:15:12980}
981
[email protected]89bd27d12014-04-12 17:36:23982bool SearchProvider::IsTopMatchSearchWithURLInput() const {
983 ACMatches::const_iterator first_match = FindTopMatch();
[email protected]3dc75b12014-06-08 00:02:22984 return (input_.type() == metrics::OmniboxInputType::URL) &&
[email protected]0a8718b12013-11-13 18:41:31985 (first_match != matches_.end()) &&
986 (first_match->relevance > CalculateRelevanceForVerbatim()) &&
[email protected]78981d8c2014-05-09 15:05:47987 (first_match->type != AutocompleteMatchType::NAVSUGGEST) &&
988 (first_match->type != AutocompleteMatchType::NAVSUGGEST_PERSONALIZED);
[email protected]344946a12012-12-20 12:03:42989}
990
[email protected]257ab712009-04-14 17:16:24991void SearchProvider::AddNavigationResultsToMatches(
[email protected]0b9575f2014-07-30 11:58:37992 const SearchSuggestionParser::NavigationResults& navigation_results,
[email protected]d30268a2013-06-25 22:31:07993 ACMatches* matches) {
[email protected]0b9575f2014-07-30 11:58:37994 for (SearchSuggestionParser::NavigationResults::const_iterator it =
995 navigation_results.begin(); it != navigation_results.end(); ++it) {
[email protected]d30268a2013-06-25 22:31:07996 matches->push_back(NavigationToMatch(*it));
[email protected]bc8bb0cd2013-06-24 21:50:23997 // In the absence of suggested relevance scores, use only the single
998 // highest-scoring result. (The results are already sorted by relevance.)
[email protected]d30268a2013-06-25 22:31:07999 if (!it->relevance_from_server())
[email protected]bc8bb0cd2013-06-24 21:50:231000 return;
[email protected]257ab712009-04-14 17:16:241001 }
1002}
1003
grobye5fcee42014-09-26 03:36:461004void SearchProvider::AddRawHistoryResultsToMap(bool is_keyword,
1005 int did_not_accept_suggestion,
1006 MatchMap* map) {
1007 const HistoryResults& raw_results =
1008 is_keyword ? raw_keyword_history_results_ : raw_default_history_results_;
1009 if (!OmniboxFieldTrial::EnableAnswersInSuggest() && raw_results.empty())
[email protected]51124552011-07-16 01:37:101010 return;
1011
[email protected]31afdf72013-09-26 04:29:361012 base::TimeTicks start_time(base::TimeTicks::Now());
[email protected]51124552011-07-16 01:37:101013
grobye5fcee42014-09-26 03:36:461014 // Until Answers becomes default, scoring of history results will still happen
1015 // here for non-Answers Chrome, to prevent scoring performance regressions
1016 // resulting from moving the scoring code before the suggest request is sent.
1017 // For users with Answers enabled, the history results have already been
1018 // scored earlier, right after calling DoHistoryQuery().
1019 SearchSuggestionParser::SuggestResults local_transformed_results;
1020 const SearchSuggestionParser::SuggestResults* transformed_results = NULL;
1021 if (!OmniboxFieldTrial::EnableAnswersInSuggest()) {
1022 ScoreHistoryResults(raw_results, is_keyword, &local_transformed_results);
1023 transformed_results = &local_transformed_results;
1024 } else {
1025 transformed_results = is_keyword ? &transformed_keyword_history_results_
1026 : &transformed_default_history_results_;
[email protected]51124552011-07-16 01:37:101027 }
grobye5fcee42014-09-26 03:36:461028 DCHECK(transformed_results);
1029 AddTransformedHistoryResultsToMap(
1030 *transformed_results, did_not_accept_suggestion, map);
[email protected]31afdf72013-09-26 04:29:361031 UMA_HISTOGRAM_TIMES("Omnibox.SearchProvider.AddHistoryResultsTime",
1032 base::TimeTicks::Now() - start_time);
[email protected]51124552011-07-16 01:37:101033}
1034
grobye5fcee42014-09-26 03:36:461035void SearchProvider::AddTransformedHistoryResultsToMap(
1036 const SearchSuggestionParser::SuggestResults& transformed_results,
1037 int did_not_accept_suggestion,
1038 MatchMap* map) {
1039 for (SearchSuggestionParser::SuggestResults::const_iterator i(
1040 transformed_results.begin());
1041 i != transformed_results.end();
1042 ++i) {
1043 AddMatchToMap(*i, std::string(), did_not_accept_suggestion, true,
1044 providers_.GetKeywordProviderURL() != NULL, map);
1045 }
1046}
1047
1048SearchSuggestionParser::SuggestResults
1049SearchProvider::ScoreHistoryResultsHelper(const HistoryResults& results,
1050 bool base_prevent_inline_autocomplete,
1051 bool input_multiple_words,
1052 const base::string16& input_text,
1053 bool is_keyword) {
[email protected]0b9575f2014-07-30 11:58:371054 SearchSuggestionParser::SuggestResults scored_results;
[email protected]ab5fd2f2014-07-17 19:18:521055 // True if the user has asked this exact query previously.
1056 bool found_what_you_typed_match = false;
[email protected]78e5e432013-08-03 02:10:101057 const bool prevent_search_history_inlining =
1058 OmniboxFieldTrial::SearchHistoryPreventInlining(
1059 input_.current_page_classification());
[email protected]c2ca3fd2014-03-22 03:07:441060 const base::string16& trimmed_input =
1061 base::CollapseWhitespace(input_text, false);
[email protected]257ab712009-04-14 17:16:241062 for (HistoryResults::const_iterator i(results.begin()); i != results.end();
1063 ++i) {
[email protected]c2ca3fd2014-03-22 03:07:441064 const base::string16& trimmed_suggestion =
1065 base::CollapseWhitespace(i->term, false);
1066
[email protected]51124552011-07-16 01:37:101067 // Don't autocomplete multi-word queries that have only been seen once
1068 // unless the user has typed more than one word.
1069 bool prevent_inline_autocomplete = base_prevent_inline_autocomplete ||
[email protected]c2ca3fd2014-03-22 03:07:441070 (!input_multiple_words && (i->visits < 2) &&
1071 HasMultipleWords(trimmed_suggestion));
[email protected]51124552011-07-16 01:37:101072
[email protected]78e5e432013-08-03 02:10:101073 int relevance = CalculateRelevanceForHistory(
1074 i->time, is_keyword, !prevent_inline_autocomplete,
1075 prevent_search_history_inlining);
[email protected]ab5fd2f2014-07-17 19:18:521076 // Add the match to |scored_results| by putting the what-you-typed match
1077 // on the front and appending all other matches. We want the what-you-
1078 // typed match to always be first.
[email protected]0b9575f2014-07-30 11:58:371079 SearchSuggestionParser::SuggestResults::iterator insertion_position =
1080 scored_results.end();
[email protected]ab5fd2f2014-07-17 19:18:521081 if (trimmed_suggestion == trimmed_input) {
1082 found_what_you_typed_match = true;
1083 insertion_position = scored_results.begin();
1084 }
mpearson6c183672014-09-03 02:09:421085 SearchSuggestionParser::SuggestResult history_suggestion(
1086 trimmed_suggestion, AutocompleteMatchType::SEARCH_HISTORY,
1087 trimmed_suggestion, base::string16(), base::string16(),
jdonnelly7393cee2014-10-31 01:52:561088 base::string16(), base::string16(), nullptr, std::string(),
1089 std::string(), is_keyword, relevance, false, false, trimmed_input);
mpearson6c183672014-09-03 02:09:421090 // History results are synchronous; they are received on the last keystroke.
1091 history_suggestion.set_received_after_last_keystroke(false);
1092 scored_results.insert(insertion_position, history_suggestion);
[email protected]257ab712009-04-14 17:16:241093 }
[email protected]51124552011-07-16 01:37:101094
1095 // History returns results sorted for us. However, we may have docked some
[email protected]ab5fd2f2014-07-17 19:18:521096 // results' scores, so things are no longer in order. While keeping the
1097 // what-you-typed match at the front (if it exists), do a stable sort to get
[email protected]51124552011-07-16 01:37:101098 // things back in order without otherwise disturbing results with equal
1099 // scores, then force the scores to be unique, so that the order in which
1100 // they're shown is deterministic.
[email protected]ab5fd2f2014-07-17 19:18:521101 std::stable_sort(scored_results.begin() +
1102 (found_what_you_typed_match ? 1 : 0),
1103 scored_results.end(),
[email protected]55ce8f12012-05-09 04:44:081104 CompareScoredResults());
[email protected]7e3b77f2014-07-25 02:29:441105
1106 // Don't autocomplete to search terms that would normally be treated as URLs
1107 // when typed. For example, if the user searched for "google.com" and types
1108 // "goog", don't autocomplete to the search term "google.com". Otherwise,
1109 // the input will look like a URL but act like a search, which is confusing.
1110 // The 1200 relevance score threshold in the test below is the lowest
1111 // possible score in CalculateRelevanceForHistory()'s aggressive-scoring
1112 // curve. This is an appropriate threshold to use to decide if we're overly
1113 // aggressively inlining because, if we decide the answer is yes, the
1114 // way we resolve it it to not use the aggressive-scoring curve.
1115 // NOTE: We don't check for autocompleting to URLs in the following cases:
1116 // * When inline autocomplete is disabled, we won't be inline autocompleting
1117 // this term, so we don't need to worry about confusion as much. This
1118 // also prevents calling Classify() again from inside the classifier
1119 // (which will corrupt state and likely crash), since the classifier
1120 // always disables inline autocomplete.
1121 // * When the user has typed the whole string before as a query, then it's
1122 // likely the user has no expectation that term should be interpreted as
1123 // as a URL, so we need not do anything special to preserve user
1124 // expectation.
[email protected]51124552011-07-16 01:37:101125 int last_relevance = 0;
[email protected]7e3b77f2014-07-25 02:29:441126 if (!base_prevent_inline_autocomplete && !found_what_you_typed_match &&
hashimoto663b9f42014-08-26 04:29:201127 scored_results.front().relevance() >= 1200) {
[email protected]7e3b77f2014-07-25 02:29:441128 AutocompleteMatch match;
Ryo Hashimoto884ad192014-08-28 05:54:301129 client_->Classify(scored_results.front().suggestion(), false, false,
1130 input_.current_page_classification(), &match, NULL);
[email protected]7e3b77f2014-07-25 02:29:441131 // Demote this match that would normally be interpreted as a URL to have
1132 // the highest score a previously-issued search query could have when
1133 // scoring with the non-aggressive method. A consequence of demoting
1134 // by revising |last_relevance| is that this match and all following
1135 // matches get demoted; the relative order of matches is preserved.
1136 // One could imagine demoting only those matches that might cause
1137 // confusion (which, by the way, might change the relative order of
1138 // matches. We have decided to go with the simple demote-all approach
1139 // because selective demotion requires multiple Classify() calls and
1140 // such calls can be expensive (as expensive as running the whole
1141 // autocomplete system).
1142 if (!AutocompleteMatch::IsSearchType(match.type)) {
1143 last_relevance = CalculateRelevanceForHistory(
1144 base::Time::Now(), is_keyword, false,
1145 prevent_search_history_inlining);
1146 }
1147 }
1148
[email protected]0b9575f2014-07-30 11:58:371149 for (SearchSuggestionParser::SuggestResults::iterator i(
1150 scored_results.begin()); i != scored_results.end(); ++i) {
[email protected]7e3b77f2014-07-25 02:29:441151 if ((last_relevance != 0) && (i->relevance() >= last_relevance))
[email protected]55ce8f12012-05-09 04:44:081152 i->set_relevance(last_relevance - 1);
1153 last_relevance = i->relevance();
[email protected]51124552011-07-16 01:37:101154 }
1155
[email protected]55ce8f12012-05-09 04:44:081156 return scored_results;
[email protected]257ab712009-04-14 17:16:241157}
1158
grobye5fcee42014-09-26 03:36:461159void SearchProvider::ScoreHistoryResults(
1160 const HistoryResults& results,
1161 bool is_keyword,
1162 SearchSuggestionParser::SuggestResults* scored_results) {
1163 DCHECK(scored_results);
1164 if (results.empty()) {
1165 scored_results->clear();
1166 return;
1167 }
1168
1169 bool prevent_inline_autocomplete = input_.prevent_inline_autocomplete() ||
1170 (input_.type() == metrics::OmniboxInputType::URL);
1171 const base::string16 input_text = GetInput(is_keyword).text();
1172 bool input_multiple_words = HasMultipleWords(input_text);
1173
1174 if (!prevent_inline_autocomplete && input_multiple_words) {
1175 // ScoreHistoryResultsHelper() allows autocompletion of multi-word, 1-visit
1176 // queries if the input also has multiple words. But if we were already
1177 // scoring a multi-word, multi-visit query aggressively, and the current
1178 // input is still a prefix of it, then changing the suggestion suddenly
1179 // feels wrong. To detect this case, first score as if only one word has
1180 // been typed, then check if the best result came from aggressive search
1181 // history scoring. If it did, then just keep that score set. This
1182 // 1200 the lowest possible score in CalculateRelevanceForHistory()'s
1183 // aggressive-scoring curve.
1184 *scored_results = ScoreHistoryResultsHelper(
1185 results, prevent_inline_autocomplete, false, input_text, is_keyword);
1186 if ((scored_results->front().relevance() < 1200) ||
1187 !HasMultipleWords(scored_results->front().suggestion()))
1188 scored_results->clear(); // Didn't detect the case above, score normally.
1189 }
1190 if (scored_results->empty()) {
1191 *scored_results = ScoreHistoryResultsHelper(results,
1192 prevent_inline_autocomplete,
1193 input_multiple_words,
1194 input_text,
1195 is_keyword);
1196 }
1197}
1198
[email protected]0b9575f2014-07-30 11:58:371199void SearchProvider::AddSuggestResultsToMap(
1200 const SearchSuggestionParser::SuggestResults& results,
1201 const std::string& metadata,
1202 MatchMap* map) {
[email protected]7bc5e162014-08-15 19:41:111203 for (size_t i = 0; i < results.size(); ++i) {
1204 AddMatchToMap(results[i], metadata, i, false,
1205 providers_.GetKeywordProviderURL() != NULL, map);
1206 }
initial.commit09911bf2008-07-26 23:55:291207}
1208
[email protected]d30268a2013-06-25 22:31:071209int SearchProvider::GetVerbatimRelevance(bool* relevance_from_server) const {
[email protected]dc6943b2012-06-19 06:39:561210 // Use the suggested verbatim relevance score if it is non-negative (valid),
1211 // if inline autocomplete isn't prevented (always show verbatim on backspace),
[email protected]1beee342012-06-19 22:22:281212 // and if it won't suppress verbatim, leaving no default provider matches.
1213 // Otherwise, if the default provider returned no matches and was still able
[email protected]dc6943b2012-06-19 06:39:561214 // to suppress verbatim, the user would have no search/nav matches and may be
[email protected]1beee342012-06-19 22:22:281215 // left unable to search using their default provider from the omnibox.
[email protected]dc6943b2012-06-19 06:39:561216 // Check for results on each verbatim calculation, as results from older
1217 // queries (on previous input) may be trimmed for failing to inline new input.
[email protected]bc8bb0cd2013-06-24 21:50:231218 bool use_server_relevance =
1219 (default_results_.verbatim_relevance >= 0) &&
[email protected]dab8d52d2013-03-05 07:35:281220 !input_.prevent_inline_autocomplete() &&
[email protected]bc8bb0cd2013-06-24 21:50:231221 ((default_results_.verbatim_relevance > 0) ||
[email protected]cc1526e2013-05-17 04:04:241222 !default_results_.suggest_results.empty() ||
[email protected]bc8bb0cd2013-06-24 21:50:231223 !default_results_.navigation_results.empty());
[email protected]d30268a2013-06-25 22:31:071224 if (relevance_from_server)
1225 *relevance_from_server = use_server_relevance;
[email protected]bc8bb0cd2013-06-24 21:50:231226 return use_server_relevance ?
1227 default_results_.verbatim_relevance : CalculateRelevanceForVerbatim();
[email protected]382a0642012-06-06 06:13:521228}
[email protected]d1f0a7f2012-06-05 10:26:421229
[email protected]382a0642012-06-06 06:13:521230int SearchProvider::CalculateRelevanceForVerbatim() const {
[email protected]85b8d6f2012-05-08 20:53:471231 if (!providers_.keyword_provider().empty())
[email protected]52d08b12009-10-19 18:42:361232 return 250;
[email protected]dab8d52d2013-03-05 07:35:281233 return CalculateRelevanceForVerbatimIgnoringKeywordModeState();
1234}
[email protected]52d08b12009-10-19 18:42:361235
[email protected]dab8d52d2013-03-05 07:35:281236int SearchProvider::
1237 CalculateRelevanceForVerbatimIgnoringKeywordModeState() const {
initial.commit09911bf2008-07-26 23:55:291238 switch (input_.type()) {
[email protected]3dc75b12014-06-08 00:02:221239 case metrics::OmniboxInputType::UNKNOWN:
1240 case metrics::OmniboxInputType::QUERY:
1241 case metrics::OmniboxInputType::FORCED_QUERY:
[email protected]90fe2bb2013-01-15 03:42:131242 return kNonURLVerbatimRelevance;
initial.commit09911bf2008-07-26 23:55:291243
[email protected]3dc75b12014-06-08 00:02:221244 case metrics::OmniboxInputType::URL:
[email protected]52d08b12009-10-19 18:42:361245 return 850;
initial.commit09911bf2008-07-26 23:55:291246
1247 default:
1248 NOTREACHED();
1249 return 0;
1250 }
1251}
1252
[email protected]d30268a2013-06-25 22:31:071253int SearchProvider::GetKeywordVerbatimRelevance(
1254 bool* relevance_from_server) const {
[email protected]dab8d52d2013-03-05 07:35:281255 // Use the suggested verbatim relevance score if it is non-negative (valid),
1256 // if inline autocomplete isn't prevented (always show verbatim on backspace),
1257 // and if it won't suppress verbatim, leaving no keyword provider matches.
1258 // Otherwise, if the keyword provider returned no matches and was still able
1259 // to suppress verbatim, the user would have no search/nav matches and may be
1260 // left unable to search using their keyword provider from the omnibox.
1261 // Check for results on each verbatim calculation, as results from older
1262 // queries (on previous input) may be trimmed for failing to inline new input.
[email protected]bc8bb0cd2013-06-24 21:50:231263 bool use_server_relevance =
1264 (keyword_results_.verbatim_relevance >= 0) &&
[email protected]dab8d52d2013-03-05 07:35:281265 !input_.prevent_inline_autocomplete() &&
[email protected]bc8bb0cd2013-06-24 21:50:231266 ((keyword_results_.verbatim_relevance > 0) ||
[email protected]cc1526e2013-05-17 04:04:241267 !keyword_results_.suggest_results.empty() ||
[email protected]bc8bb0cd2013-06-24 21:50:231268 !keyword_results_.navigation_results.empty());
[email protected]d30268a2013-06-25 22:31:071269 if (relevance_from_server)
1270 *relevance_from_server = use_server_relevance;
[email protected]bc8bb0cd2013-06-24 21:50:231271 return use_server_relevance ?
1272 keyword_results_.verbatim_relevance :
1273 CalculateRelevanceForKeywordVerbatim(keyword_input_.type(),
1274 keyword_input_.prefer_keyword());
[email protected]5423e562013-02-07 03:58:451275}
1276
[email protected]51124552011-07-16 01:37:101277int SearchProvider::CalculateRelevanceForHistory(
[email protected]bc8bb0cd2013-06-24 21:50:231278 const base::Time& time,
[email protected]51124552011-07-16 01:37:101279 bool is_keyword,
[email protected]78e5e432013-08-03 02:10:101280 bool use_aggressive_method,
1281 bool prevent_search_history_inlining) const {
[email protected]aa613d62010-11-09 20:40:181282 // The relevance of past searches falls off over time. There are two distinct
1283 // equations used. If the first equation is used (searches to the primary
[email protected]78e5e432013-08-03 02:10:101284 // provider that we want to score aggressively), the score is in the range
1285 // 1300-1599 (unless |prevent_search_history_inlining|, in which case
[email protected]d8cd76b2013-07-10 09:46:161286 // it's in the range 1200-1299). If the second equation is used the
1287 // relevance of a search 15 minutes ago is discounted 50 points, while the
1288 // relevance of a search two weeks ago is discounted 450 points.
[email protected]bc8bb0cd2013-06-24 21:50:231289 double elapsed_time = std::max((base::Time::Now() - time).InSecondsF(), 0.0);
[email protected]188b50c2013-03-28 07:19:421290 bool is_primary_provider = is_keyword || !providers_.has_keyword_provider();
[email protected]78e5e432013-08-03 02:10:101291 if (is_primary_provider && use_aggressive_method) {
[email protected]aa613d62010-11-09 20:40:181292 // Searches with the past two days get a different curve.
[email protected]51124552011-07-16 01:37:101293 const double autocomplete_time = 2 * 24 * 60 * 60;
[email protected]aa613d62010-11-09 20:40:181294 if (elapsed_time < autocomplete_time) {
[email protected]d8cd76b2013-07-10 09:46:161295 int max_score = is_keyword ? 1599 : 1399;
[email protected]78e5e432013-08-03 02:10:101296 if (prevent_search_history_inlining)
[email protected]d8cd76b2013-07-10 09:46:161297 max_score = 1299;
1298 return max_score - static_cast<int>(99 *
[email protected]aa613d62010-11-09 20:40:181299 std::pow(elapsed_time / autocomplete_time, 2.5));
1300 }
1301 elapsed_time -= autocomplete_time;
1302 }
1303
[email protected]c3a4bd992010-08-18 20:25:011304 const int score_discount =
1305 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3));
initial.commit09911bf2008-07-26 23:55:291306
[email protected]6c85aa02009-02-27 12:08:091307 // Don't let scores go below 0. Negative relevance scores are meaningful in
1308 // a different way.
initial.commit09911bf2008-07-26 23:55:291309 int base_score;
[email protected]51124552011-07-16 01:37:101310 if (is_primary_provider)
[email protected]3dc75b12014-06-08 00:02:221311 base_score = (input_.type() == metrics::OmniboxInputType::URL) ? 750 : 1050;
[email protected]51124552011-07-16 01:37:101312 else
1313 base_score = 200;
initial.commit09911bf2008-07-26 23:55:291314 return std::max(0, base_score - score_discount);
1315}
1316
initial.commit09911bf2008-07-26 23:55:291317AutocompleteMatch SearchProvider::NavigationToMatch(
[email protected]0b9575f2014-07-30 11:58:371318 const SearchSuggestionParser::NavigationResult& navigation) {
[email protected]5889bfb2014-03-19 00:26:481319 base::string16 input;
1320 const bool trimmed_whitespace = base::TrimWhitespace(
1321 navigation.from_keyword_provider() ?
1322 keyword_input_.text() : input_.text(),
1323 base::TRIM_TRAILING, &input) != base::TRIM_NONE;
[email protected]55ce8f12012-05-09 04:44:081324 AutocompleteMatch match(this, navigation.relevance(), false,
[email protected]78981d8c2014-05-09 15:05:471325 navigation.type());
[email protected]55ce8f12012-05-09 04:44:081326 match.destination_url = navigation.url();
[email protected]78981d8c2014-05-09 15:05:471327 BaseSearchProvider::SetDeletionURL(navigation.deletion_url(), &match);
[email protected]23db6492014-01-16 02:35:301328 // First look for the user's input inside the formatted url as it would be
[email protected]371dab12012-06-01 03:23:551329 // without trimming the scheme, so we can find matches at the beginning of the
1330 // scheme.
[email protected]371dab12012-06-01 03:23:551331 const URLPrefix* prefix =
[email protected]23db6492014-01-16 02:35:301332 URLPrefix::BestURLPrefix(navigation.formatted_url(), input);
[email protected]371dab12012-06-01 03:23:551333 size_t match_start = (prefix == NULL) ?
[email protected]23db6492014-01-16 02:35:301334 navigation.formatted_url().find(input) : prefix->prefix.length();
[email protected]d2445c82013-11-04 22:28:351335 bool trim_http = !AutocompleteInput::HasHTTPScheme(input) &&
1336 (!prefix || (match_start != 0));
[email protected]23db6492014-01-16 02:35:301337 const net::FormatUrlTypes format_types =
1338 net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP);
[email protected]371dab12012-06-01 03:23:551339
Ryo Hashimoto884ad192014-08-28 05:54:301340 const std::string languages(client_->AcceptLanguages());
[email protected]23db6492014-01-16 02:35:301341 size_t inline_autocomplete_offset = (prefix == NULL) ?
1342 base::string16::npos : (match_start + input.length());
[email protected]371dab12012-06-01 03:23:551343 match.fill_into_edit +=
[email protected]5655ea32014-06-21 05:28:081344 AutocompleteInput::FormattedStringWithEquivalentMeaning(
1345 navigation.url(),
[email protected]371dab12012-06-01 03:23:551346 net::FormatUrl(navigation.url(), languages, format_types,
1347 net::UnescapeRule::SPACES, NULL, NULL,
[email protected]5655ea32014-06-21 05:28:081348 &inline_autocomplete_offset),
Ryo Hashimoto884ad192014-08-28 05:54:301349 client_->SchemeClassifier());
[email protected]14119032013-11-07 08:14:261350 // Preserve the forced query '?' prefix in |match.fill_into_edit|.
1351 // Otherwise, user edits to a suggestion would show non-Search results.
[email protected]3dc75b12014-06-08 00:02:221352 if (input_.type() == metrics::OmniboxInputType::FORCED_QUERY) {
[email protected]670d3232013-12-24 17:58:581353 match.fill_into_edit.insert(0, base::ASCIIToUTF16("?"));
[email protected]0085863a2013-12-06 21:19:031354 if (inline_autocomplete_offset != base::string16::npos)
[email protected]14119032013-11-07 08:14:261355 ++inline_autocomplete_offset;
1356 }
[email protected]6c94a1022014-02-21 03:48:041357 if (inline_autocomplete_offset != base::string16::npos) {
[email protected]518024c2013-07-19 23:40:251358 DCHECK(inline_autocomplete_offset <= match.fill_into_edit.length());
1359 match.inline_autocompletion =
1360 match.fill_into_edit.substr(inline_autocomplete_offset);
1361 }
[email protected]6c94a1022014-02-21 03:48:041362 // An inlineable navsuggestion can only be the default match when there
1363 // is no keyword provider active, lest it appear first and break the user
mpearson6c183672014-09-03 02:09:421364 // out of keyword mode. We also must have received the navsuggestion before
1365 // the last keystroke, to prevent asynchronous inline autocompletions changes.
1366 // The navsuggestion can also only be default if either the inline
[email protected]5889bfb2014-03-19 00:26:481367 // autocompletion is empty or we're not preventing inline autocompletion.
1368 // Finally, if we have an inlineable navsuggestion with an inline completion
1369 // that we're not preventing, make sure we didn't trim any whitespace.
1370 // We don't want to claim http://foo.com/bar is inlineable against the
1371 // input "foo.com/b ".
mpearson6c183672014-09-03 02:09:421372 match.allowed_to_be_default_match =
1373 (prefix != NULL) &&
[email protected]6c94a1022014-02-21 03:48:041374 (providers_.GetKeywordProviderURL() == NULL) &&
mpearson6c183672014-09-03 02:09:421375 !navigation.received_after_last_keystroke() &&
[email protected]5889bfb2014-03-19 00:26:481376 (match.inline_autocompletion.empty() ||
[email protected]78981d8c2014-05-09 15:05:471377 (!input_.prevent_inline_autocomplete() && !trimmed_whitespace));
[email protected]c7b8be02014-07-11 19:46:341378 match.EnsureUWYTIsAllowedToBeDefault(
1379 input_.canonicalized_url(), providers_.template_url_service());
[email protected]371dab12012-06-01 03:23:551380
[email protected]23db6492014-01-16 02:35:301381 match.contents = navigation.match_contents();
1382 match.contents_class = navigation.match_contents_class();
[email protected]55ce8f12012-05-09 04:44:081383 match.description = navigation.description();
[email protected]371dab12012-06-01 03:23:551384 AutocompleteMatch::ClassifyMatchInString(input, match.description,
1385 ACMatchClassification::NONE, &match.description_class);
[email protected]d30268a2013-06-25 22:31:071386
1387 match.RecordAdditionalInfo(
1388 kRelevanceFromServerKey,
1389 navigation.relevance_from_server() ? kTrue : kFalse);
[email protected]987fad782013-08-28 06:23:181390 match.RecordAdditionalInfo(kShouldPrefetchKey, kFalse);
[email protected]d30268a2013-06-25 22:31:071391
initial.commit09911bf2008-07-26 23:55:291392 return match;
1393}
[email protected]4ab4c7c2010-11-24 04:49:341394
1395void SearchProvider::UpdateDone() {
[email protected]2cdf1172012-08-26 12:21:331396 // We're done when the timer isn't running, there are no suggest queries
[email protected]5fac3782013-03-06 09:32:311397 // pending, and we're not waiting on Instant.
[email protected]e1290ee62013-06-26 18:31:151398 done_ = !timer_.IsRunning() && (suggest_results_pending_ == 0);
[email protected]4ab4c7c2010-11-24 04:49:341399}
[email protected]20184242014-05-14 02:57:421400
1401std::string SearchProvider::GetSessionToken() {
1402 base::TimeTicks current_time(base::TimeTicks::Now());
1403 // Renew token if it expired.
1404 if (current_time > token_expiration_time_) {
1405 const size_t kTokenBytes = 12;
1406 std::string raw_data;
1407 base::RandBytes(WriteInto(&raw_data, kTokenBytes + 1), kTokenBytes);
1408 base::Base64Encode(raw_data, &current_token_);
[email protected]ab2c31f72014-05-17 17:03:531409
1410 // Make the base64 encoded value URL and filename safe(see RFC 3548).
1411 std::replace(current_token_.begin(), current_token_.end(), '+', '-');
1412 std::replace(current_token_.begin(), current_token_.end(), '/', '_');
[email protected]20184242014-05-14 02:57:421413 }
1414
1415 // Extend expiration time another 60 seconds.
1416 token_expiration_time_ = current_time + base::TimeDelta::FromSeconds(60);
1417
1418 return current_token_;
1419}
[email protected]2ef2a6642014-07-30 05:50:291420
1421void SearchProvider::RegisterDisplayedAnswers(
1422 const AutocompleteResult& result) {
1423 if (result.empty())
1424 return;
1425
1426 // The answer must be in the first or second slot to be considered. It should
1427 // only be in the second slot if AutocompleteController ranked a local search
1428 // history or a verbatim item higher than the answer.
1429 AutocompleteResult::const_iterator match = result.begin();
1430 if (match->answer_contents.empty() && result.size() > 1)
1431 ++match;
1432 if (match->answer_contents.empty() || match->answer_type.empty() ||
1433 match->fill_into_edit.empty())
1434 return;
1435
1436 // Valid answer encountered, cache it for further queries.
[email protected]ebbac63e2014-08-22 01:43:061437 answers_cache_.UpdateRecentAnswers(match->fill_into_edit, match->answer_type);
[email protected]2ef2a6642014-07-30 05:50:291438}
1439
grobye5fcee42014-09-26 03:36:461440AnswersQueryData SearchProvider::FindAnswersPrefetchData() {
1441 // Retrieve the top entry from scored history results.
1442 MatchMap map;
1443 AddTransformedHistoryResultsToMap(transformed_keyword_history_results_,
1444 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
1445 &map);
1446 AddTransformedHistoryResultsToMap(transformed_default_history_results_,
1447 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
1448 &map);
1449
1450 ACMatches matches;
1451 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
1452 matches.push_back(i->second);
1453 std::sort(matches.begin(), matches.end(), &AutocompleteMatch::MoreRelevant);
1454
1455 // If there is a top scoring entry, find the corresponding answer.
1456 if (!matches.empty())
1457 return answers_cache_.GetTopAnswerEntry(matches[0].contents);
1458
1459 return AnswersQueryData();
[email protected]2ef2a6642014-07-30 05:50:291460}