blob: 32e21473423dd3d1023a3902cfb9116c43a2d7ad [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
5#include "chrome/browser/autocomplete/search_provider.h"
6
[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]2041cf342010-02-19 03:15:5910#include "base/callback.h"
[email protected]51124552011-07-16 01:37:1011#include "base/i18n/break_iterator.h"
[email protected]503d03872011-05-06 08:36:2612#include "base/i18n/case_conversion.h"
[email protected]d6e58c6e2009-10-10 20:40:5013#include "base/i18n/icu_string_conversions.h"
[email protected]ffbec692012-02-26 20:26:4214#include "base/json/json_string_value_serializer.h"
initial.commit09911bf2008-07-26 23:55:2915#include "base/message_loop.h"
[email protected]f5b95ba92012-03-27 14:05:1916#include "base/metrics/histogram.h"
[email protected]dc9a6762010-08-16 07:13:5317#include "base/string16.h"
[email protected]371dab12012-06-01 03:23:5518#include "base/string_util.h"
[email protected]1cb2dac2010-03-08 21:49:1519#include "base/utf_string_conversions.h"
[email protected]ea3b9a502011-04-04 14:19:3720#include "chrome/browser/autocomplete/autocomplete_classifier.h"
[email protected]810ffba2012-06-12 01:07:4821#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
[email protected]f5b95ba92012-03-27 14:05:1922#include "chrome/browser/autocomplete/autocomplete_field_trial.h"
[email protected]9ac40092010-10-27 23:05:2623#include "chrome/browser/autocomplete/autocomplete_match.h"
[email protected]5af9bc82012-06-29 00:53:4824#include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
[email protected]73c2b1632012-07-02 22:51:3825#include "chrome/browser/autocomplete/autocomplete_result.h"
[email protected]3723e6e2012-06-11 21:06:5626#include "chrome/browser/autocomplete/history_url_provider.h"
[email protected]2c812ba02011-07-14 00:23:1527#include "chrome/browser/autocomplete/keyword_provider.h"
[email protected]371dab12012-06-01 03:23:5528#include "chrome/browser/autocomplete/url_prefix.h"
[email protected]ce560f82009-06-03 09:39:4429#include "chrome/browser/history/history.h"
[email protected]9d2db762012-06-19 00:01:1030#include "chrome/browser/history/history_service_factory.h"
[email protected]10c2d692012-05-11 05:32:2331#include "chrome/browser/history/in_memory_database.h"
[email protected]f870a322009-01-16 21:47:2732#include "chrome/browser/net/url_fixer_upper.h"
[email protected]37858e52010-08-26 00:22:0233#include "chrome/browser/prefs/pref_service.h"
[email protected]8ecad5e2010-12-02 21:18:3334#include "chrome/browser/profiles/profile.h"
[email protected]a0ad93ea2012-05-07 22:11:5335#include "chrome/browser/search_engines/search_engine_type.h"
[email protected]9899a612012-08-21 23:50:0436#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
[email protected]8e5c89a2011-06-07 18:13:3337#include "chrome/browser/search_engines/template_url_service.h"
38#include "chrome/browser/search_engines/template_url_service_factory.h"
[email protected]e41982a72012-11-20 07:16:5139#include "chrome/browser/ui/browser_instant_controller.h"
[email protected]5cd06c3e2012-09-20 03:25:2040#include "chrome/browser/ui/search/search.h"
initial.commit09911bf2008-07-26 23:55:2941#include "chrome/common/pref_names.h"
[email protected]dcf7d352009-02-26 01:56:0242#include "chrome/common/url_constants.h"
initial.commit09911bf2008-07-26 23:55:2943#include "googleurl/src/url_util.h"
[email protected]34ac8f32009-02-22 23:03:2744#include "grit/generated_resources.h"
initial.commit09911bf2008-07-26 23:55:2945#include "net/base/escape.h"
[email protected]d3cf8682f02012-02-29 23:29:3446#include "net/base/load_flags.h"
[email protected]371dab12012-06-01 03:23:5547#include "net/base/net_util.h"
[email protected]319d9e6f2009-02-18 19:47:2148#include "net/http/http_response_headers.h"
[email protected]3dc1bc42012-06-19 08:20:5349#include "net/url_request/url_fetcher.h"
[email protected]319d9e6f2009-02-18 19:47:2150#include "net/url_request/url_request_status.h"
[email protected]c051a1b2011-01-21 23:30:1751#include "ui/base/l10n/l10n_util.h"
initial.commit09911bf2008-07-26 23:55:2952
[email protected]e1acf6f2008-10-27 20:43:3353using base::Time;
54using base::TimeDelta;
55
[email protected]51124552011-07-16 01:37:1056namespace {
57
[email protected]7706a522012-08-16 17:42:2558// We keep track in a histogram how many suggest requests we send, how
59// many suggest requests we invalidate (e.g., due to a user typing
60// another character), and how many replies we receive.
61// *** ADD NEW ENUMS AFTER ALL PREVIOUSLY DEFINED ONES! ***
62// (excluding the end-of-list enum value)
63// We do not want values of existing enums to change or else it screws
64// up the statistics.
65enum SuggestRequestsHistogramValue {
66 REQUEST_SENT = 1,
67 REQUEST_INVALIDATED,
68 REPLY_RECEIVED,
69 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE
70};
71
72// Increments the appropriate value in the histogram by one.
73void LogOmniboxSuggestRequest(
74 SuggestRequestsHistogramValue request_value) {
75 UMA_HISTOGRAM_ENUMERATION("Omnibox.SuggestRequests", request_value,
76 MAX_SUGGEST_REQUEST_HISTOGRAM_VALUE);
77}
78
[email protected]51124552011-07-16 01:37:1079bool HasMultipleWords(const string16& text) {
80 base::i18n::BreakIterator i(text, base::i18n::BreakIterator::BREAK_WORD);
81 bool found_word = false;
82 if (i.Init()) {
83 while (i.Advance()) {
84 if (i.IsWord()) {
85 if (found_word)
86 return true;
87 found_word = true;
88 }
89 }
90 }
91 return false;
92}
93
[email protected]d1f0a7f2012-06-05 10:26:4294} // namespace
[email protected]51124552011-07-16 01:37:1095
[email protected]033f3422012-03-13 21:24:1896
[email protected]3954c3a2012-04-10 20:17:5597// SearchProvider::Providers --------------------------------------------------
[email protected]b547666d2009-04-23 16:37:5898
[email protected]85b8d6f2012-05-08 20:53:4799SearchProvider::Providers::Providers(TemplateURLService* template_url_service)
100 : template_url_service_(template_url_service) {
101}
102
103const TemplateURL* SearchProvider::Providers::GetDefaultProviderURL() const {
104 return default_provider_.empty() ? NULL :
105 template_url_service_->GetTemplateURLForKeyword(default_provider_);
106}
107
108const TemplateURL* SearchProvider::Providers::GetKeywordProviderURL() const {
109 return keyword_provider_.empty() ? NULL :
110 template_url_service_->GetTemplateURLForKeyword(keyword_provider_);
[email protected]257ab712009-04-14 17:16:24111}
112
[email protected]3954c3a2012-04-10 20:17:55113
114// SearchProvider -------------------------------------------------------------
115
116// static
117const int SearchProvider::kDefaultProviderURLFetcherID = 1;
118// static
119const int SearchProvider::kKeywordProviderURLFetcherID = 2;
120// static
121bool SearchProvider::query_suggest_immediately_ = false;
122
[email protected]30f5bc92012-06-26 04:14:55123SearchProvider::SearchProvider(AutocompleteProviderListener* listener,
124 Profile* profile)
[email protected]35f1f4f02012-09-11 13:17:00125 : AutocompleteProvider(listener, profile,
126 AutocompleteProvider::TYPE_SEARCH),
[email protected]85b8d6f2012-05-08 20:53:47127 providers_(TemplateURLServiceFactory::GetForProfile(profile)),
[email protected]601858c02010-09-01 17:08:20128 suggest_results_pending_(0),
[email protected]6dc950f2012-07-16 19:49:08129 suggest_field_trial_group_number_(
130 AutocompleteFieldTrial::GetSuggestNumberOfGroups()),
[email protected]d1f0a7f2012-06-05 10:26:42131 has_suggested_relevance_(false),
132 verbatim_relevance_(-1),
[email protected]8e5cc282010-12-05 18:11:39133 have_suggest_results_(false),
[email protected]4ab4c7c2010-11-24 04:49:34134 instant_finalized_(false) {
[email protected]6dc950f2012-07-16 19:49:08135 // Above, we default |suggest_field_trial_group_number_| to the number of
136 // groups to mean "not in field trial." Field trial groups run from 0 to
137 // GetSuggestNumberOfGroups() - 1 (inclusive).
[email protected]f5b95ba92012-03-27 14:05:19138 if (AutocompleteFieldTrial::InSuggestFieldTrial()) {
[email protected]6dc950f2012-07-16 19:49:08139 suggest_field_trial_group_number_ =
[email protected]f5b95ba92012-03-27 14:05:19140 AutocompleteFieldTrial::GetSuggestGroupNameAsNumber();
141 }
[email protected]4ab4c7c2010-11-24 04:49:34142}
143
[email protected]a2fedb1e2011-01-25 15:23:36144void SearchProvider::FinalizeInstantQuery(const string16& input_text,
[email protected]93b73832012-10-18 20:18:38145 const InstantSuggestion& suggestion) {
[email protected]4ab4c7c2010-11-24 04:49:34146 if (done_ || instant_finalized_)
147 return;
148
149 instant_finalized_ = true;
150 UpdateDone();
151
[email protected]e918c112010-12-08 23:03:49152 if (input_text.empty()) {
[email protected]4ab4c7c2010-11-24 04:49:34153 // We only need to update the listener if we're actually done.
154 if (done_)
155 listener_->OnProviderUpdate(false);
156 return;
157 }
158
[email protected]93b73832012-10-18 20:18:38159 default_provider_suggestion_ = suggestion;
[email protected]9e789742011-01-10 23:27:32160
[email protected]a2fedb1e2011-01-25 15:23:36161 string16 adjusted_input_text(input_text);
[email protected]e918c112010-12-08 23:03:49162 AutocompleteInput::RemoveForcedQueryStringIfNecessary(input_.type(),
163 &adjusted_input_text);
164
[email protected]93b73832012-10-18 20:18:38165 const string16 text = adjusted_input_text + suggestion.text;
[email protected]9acdcdc02012-05-21 20:58:36166 bool results_updated = false;
[email protected]4ab4c7c2010-11-24 04:49:34167 // Remove any matches that are identical to |text|. We don't use the
168 // destination_url for comparison as it varies depending upon the index passed
169 // to TemplateURL::ReplaceSearchTerms.
170 for (ACMatches::iterator i = matches_.begin(); i != matches_.end();) {
171 if (((i->type == AutocompleteMatch::SEARCH_HISTORY) ||
172 (i->type == AutocompleteMatch::SEARCH_SUGGEST)) &&
173 (i->fill_into_edit == text)) {
[email protected]e030de62010-11-24 05:41:19174 i = matches_.erase(i);
[email protected]9acdcdc02012-05-21 20:58:36175 results_updated = true;
[email protected]4ab4c7c2010-11-24 04:49:34176 } else {
177 ++i;
178 }
179 }
180
[email protected]55ce8f12012-05-09 04:44:08181 // Add the new instant suggest result. We give it a rank higher than
[email protected]4ab4c7c2010-11-24 04:49:34182 // SEARCH_WHAT_YOU_TYPED so that it gets autocompleted.
[email protected]93b73832012-10-18 20:18:38183 const int verbatim_relevance = GetVerbatimRelevance();
184 if (suggestion.type == INSTANT_SUGGESTION_SEARCH) {
185 // Instant has a query suggestion.
186 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
[email protected]4ab4c7c2010-11-24 04:49:34187 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
188 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]93b73832012-10-18 20:18:38189 MatchMap match_map;
190 AddMatchToMap(text, adjusted_input_text, verbatim_relevance + 1,
191 AutocompleteMatch::SEARCH_SUGGEST,
192 did_not_accept_default_suggestion, false, &match_map);
193 if (!match_map.empty()) {
194 matches_.push_back(match_map.begin()->second);
195 results_updated = true;
196 }
197 } else {
198 // Instant has an URL suggestion.
199 matches_.push_back(NavigationToMatch(
200 NavigationResult(GURL(UTF16ToUTF8(suggestion.text)),
201 string16(),
202 verbatim_relevance + 1),
203 false));
[email protected]9acdcdc02012-05-21 20:58:36204 results_updated = true;
205 }
[email protected]4ab4c7c2010-11-24 04:49:34206
[email protected]9acdcdc02012-05-21 20:58:36207 if (results_updated || done_)
208 listener_->OnProviderUpdate(results_updated);
[email protected]601858c02010-09-01 17:08:20209}
210
initial.commit09911bf2008-07-26 23:55:29211void SearchProvider::Start(const AutocompleteInput& input,
[email protected]8deeb952008-10-09 18:21:27212 bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:29213 matches_.clear();
214
[email protected]ea3b9a502011-04-04 14:19:37215 instant_finalized_ =
216 (input.matches_requested() != AutocompleteInput::ALL_MATCHES);
[email protected]4ab4c7c2010-11-24 04:49:34217
[email protected]6c85aa02009-02-27 12:08:09218 // Can't return search/suggest results for bogus input or without a profile.
initial.commit09911bf2008-07-26 23:55:29219 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
[email protected]e29249dc52012-07-19 17:33:50220 Stop(false);
initial.commit09911bf2008-07-26 23:55:29221 return;
222 }
223
[email protected]257ab712009-04-14 17:16:24224 keyword_input_text_.clear();
225 const TemplateURL* keyword_provider =
226 KeywordProvider::GetSubstitutingTemplateURLForInput(profile_, input,
227 &keyword_input_text_);
[email protected]8d457132010-11-04 18:13:40228 if (keyword_input_text_.empty())
[email protected]257ab712009-04-14 17:16:24229 keyword_provider = NULL;
[email protected]257ab712009-04-14 17:16:24230
[email protected]85b8d6f2012-05-08 20:53:47231 TemplateURLService* model = providers_.template_url_service();
232 DCHECK(model);
233 model->Load();
234 const TemplateURL* default_provider = model->GetDefaultSearchProvider();
[email protected]9b74ab52012-03-30 16:08:07235 if (default_provider && !default_provider->SupportsReplacement())
[email protected]257ab712009-04-14 17:16:24236 default_provider = NULL;
237
238 if (keyword_provider == default_provider)
[email protected]e17511f2011-07-13 14:09:18239 default_provider = NULL; // No use in querying the same provider twice.
[email protected]257ab712009-04-14 17:16:24240
241 if (!default_provider && !keyword_provider) {
242 // No valid providers.
[email protected]e29249dc52012-07-19 17:33:50243 Stop(false);
initial.commit09911bf2008-07-26 23:55:29244 return;
245 }
246
247 // If we're still running an old query but have since changed the query text
[email protected]257ab712009-04-14 17:16:24248 // or the providers, abort the query.
[email protected]85b8d6f2012-05-08 20:53:47249 string16 default_provider_keyword(default_provider ?
250 default_provider->keyword() : string16());
251 string16 keyword_provider_keyword(keyword_provider ?
252 keyword_provider->keyword() : string16());
[email protected]9e789742011-01-10 23:27:32253 if (!minimal_changes ||
[email protected]85b8d6f2012-05-08 20:53:47254 !providers_.equal(default_provider_keyword, keyword_provider_keyword)) {
[email protected]9e789742011-01-10 23:27:32255 if (done_)
[email protected]93b73832012-10-18 20:18:38256 default_provider_suggestion_ = InstantSuggestion();
[email protected]9e789742011-01-10 23:27:32257 else
[email protected]e29249dc52012-07-19 17:33:50258 Stop(false);
[email protected]257ab712009-04-14 17:16:24259 }
initial.commit09911bf2008-07-26 23:55:29260
[email protected]85b8d6f2012-05-08 20:53:47261 providers_.set(default_provider_keyword, keyword_provider_keyword);
initial.commit09911bf2008-07-26 23:55:29262
263 if (input.text().empty()) {
264 // User typed "?" alone. Give them a placeholder result indicating what
265 // this syntax does.
[email protected]257ab712009-04-14 17:16:24266 if (default_provider) {
[email protected]69c579e2010-04-23 20:01:00267 AutocompleteMatch match;
268 match.provider = this;
[email protected]a2fedb1e2011-01-25 15:23:36269 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
[email protected]257ab712009-04-14 17:16:24270 match.contents_class.push_back(
[email protected]2c33dd22010-02-11 21:46:35271 ACMatchClassification(0, ACMatchClassification::NONE));
[email protected]85b8d6f2012-05-08 20:53:47272 match.keyword = providers_.default_provider();
[email protected]257ab712009-04-14 17:16:24273 matches_.push_back(match);
274 }
[email protected]e29249dc52012-07-19 17:33:50275 Stop(false);
initial.commit09911bf2008-07-26 23:55:29276 return;
277 }
278
279 input_ = input;
280
[email protected]5cd06c3e2012-09-20 03:25:20281 // Don't run the normal provider flow when the Instant Extended API is
282 // enabled. (When the Extended API is enabled, the embedded page will handle
283 // all search suggestions itself.)
284 // TODO(dcblack): once we are done refactoring the omnibox so we don't need to
285 // use FinalizeInstantQuery anymore, we can take out this check and remove
286 // this provider from kInstantExtendedOmniboxProviders.
287 if (!chrome::search::IsInstantExtendedAPIEnabled(profile_)) {
288 DoHistoryQuery(minimal_changes);
289 StartOrStopSuggestQuery(minimal_changes);
290 }
initial.commit09911bf2008-07-26 23:55:29291 ConvertResultsToAutocompleteMatches();
292}
293
[email protected]55ce8f12012-05-09 04:44:08294SearchProvider::Result::Result(int relevance) : relevance_(relevance) {}
295SearchProvider::Result::~Result() {}
296
297SearchProvider::SuggestResult::SuggestResult(const string16& suggestion,
298 int relevance)
299 : Result(relevance),
300 suggestion_(suggestion) {
301}
302
303SearchProvider::SuggestResult::~SuggestResult() {}
304
305SearchProvider::NavigationResult::NavigationResult(const GURL& url,
306 const string16& description,
307 int relevance)
308 : Result(relevance),
309 url_(url),
310 description_(description) {
311 DCHECK(url_.is_valid());
312}
313
314SearchProvider::NavigationResult::~NavigationResult() {}
315
316class SearchProvider::CompareScoredResults {
[email protected]51124552011-07-16 01:37:10317 public:
[email protected]55ce8f12012-05-09 04:44:08318 bool operator()(const Result& a, const Result& b) {
[email protected]51124552011-07-16 01:37:10319 // Sort in descending relevance order.
[email protected]55ce8f12012-05-09 04:44:08320 return a.relevance() > b.relevance();
[email protected]51124552011-07-16 01:37:10321 }
322};
323
initial.commit09911bf2008-07-26 23:55:29324void SearchProvider::Run() {
325 // Start a new request with the current input.
326 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24327 suggest_results_pending_ = 0;
[email protected]a0ad93ea2012-05-07 22:11:53328 time_suggest_request_sent_ = base::TimeTicks::Now();
[email protected]9ff91722012-09-07 05:29:12329
330 default_fetcher_.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID,
331 providers_.GetDefaultProviderURL(), input_.text()));
332 keyword_fetcher_.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID,
333 providers_.GetKeywordProviderURL(), keyword_input_text_));
[email protected]85b8d6f2012-05-08 20:53:47334
335 // Both the above can fail if the providers have been modified or deleted
336 // since the query began.
337 if (suggest_results_pending_ == 0) {
338 UpdateDone();
339 // We only need to update the listener if we're actually done.
340 if (done_)
341 listener_->OnProviderUpdate(false);
342 }
initial.commit09911bf2008-07-26 23:55:29343}
344
[email protected]e29249dc52012-07-19 17:33:50345void SearchProvider::Stop(bool clear_cached_results) {
initial.commit09911bf2008-07-26 23:55:29346 StopSuggest();
347 done_ = true;
[email protected]93b73832012-10-18 20:18:38348 default_provider_suggestion_ = InstantSuggestion();
[email protected]e29249dc52012-07-19 17:33:50349
350 if (clear_cached_results)
351 ClearResults();
initial.commit09911bf2008-07-26 23:55:29352}
353
[email protected]0e9e8782012-05-15 23:01:51354void SearchProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
355 provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
356 metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
357 new_entry.set_provider(AsOmniboxEventProviderType());
358 new_entry.set_provider_done(done_);
359}
360
[email protected]10c2d692012-05-11 05:32:23361void SearchProvider::OnURLFetchComplete(const net::URLFetcher* source) {
initial.commit09911bf2008-07-26 23:55:29362 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24363 suggest_results_pending_--;
[email protected]7706a522012-08-16 17:42:25364 LogOmniboxSuggestRequest(REPLY_RECEIVED);
[email protected]1cb2dac2010-03-08 21:49:15365 DCHECK_GE(suggest_results_pending_, 0); // Should never go negative.
[email protected]ec9207d32008-09-26 00:51:06366 const net::HttpResponseHeaders* const response_headers =
[email protected]7cc6e5632011-10-25 17:56:12367 source->GetResponseHeaders();
[email protected]c530c852011-10-24 18:18:34368 std::string json_data;
369 source->GetResponseAsString(&json_data);
[email protected]6c85aa02009-02-27 12:08:09370 // JSON is supposed to be UTF-8, but some suggest service providers send JSON
371 // files in non-UTF-8 encodings. The actual encoding is usually specified in
372 // the Content-Type header field.
[email protected]ec9207d32008-09-26 00:51:06373 if (response_headers) {
374 std::string charset;
375 if (response_headers->GetCharset(&charset)) {
[email protected]a2fedb1e2011-01-25 15:23:36376 string16 data_16;
[email protected]ec9207d32008-09-26 00:51:06377 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
[email protected]c530c852011-10-24 18:18:34378 if (base::CodepageToUTF16(json_data, charset.c_str(),
[email protected]a2fedb1e2011-01-25 15:23:36379 base::OnStringConversionError::FAIL,
380 &data_16))
381 json_data = UTF16ToUTF8(data_16);
[email protected]ec9207d32008-09-26 00:51:06382 }
383 }
384
[email protected]d7ad4772012-06-01 03:12:54385 const bool is_keyword = (source == keyword_fetcher_.get());
[email protected]013e9a02012-05-18 20:27:10386 const bool request_succeeded =
387 source->GetStatus().is_success() && source->GetResponseCode() == 200;
[email protected]a0ad93ea2012-05-07 22:11:53388
389 // Record response time for suggest requests sent to Google. We care
390 // only about the common case: the Google default provider used in
391 // non-keyword mode.
[email protected]85b8d6f2012-05-08 20:53:47392 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
[email protected]55ce8f12012-05-09 04:44:08393 if (!is_keyword && default_url &&
[email protected]9899a612012-08-21 23:50:04394 (TemplateURLPrepopulateData::GetEngineType(default_url->url()) ==
395 SEARCH_ENGINE_GOOGLE)) {
[email protected]6dc950f2012-07-16 19:49:08396 const TimeDelta elapsed_time =
[email protected]013e9a02012-05-18 20:27:10397 base::TimeTicks::Now() - time_suggest_request_sent_;
398 if (request_succeeded) {
399 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Success.GoogleResponseTime",
400 elapsed_time);
401 } else {
402 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Failure.GoogleResponseTime",
403 elapsed_time);
404 }
[email protected]b4cebf82008-12-29 19:59:08405 }
406
[email protected]d7ad4772012-06-01 03:12:54407 bool results_updated = false;
408 if (request_succeeded) {
409 JSONStringValueSerializer deserializer(json_data);
410 deserializer.set_allow_trailing_comma(true);
411 scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL));
412 results_updated = data.get() && ParseSuggestResults(data.get(), is_keyword);
413 }
414
initial.commit09911bf2008-07-26 23:55:29415 ConvertResultsToAutocompleteMatches();
[email protected]d7ad4772012-06-01 03:12:54416 if (done_ || results_updated)
417 listener_->OnProviderUpdate(results_updated);
initial.commit09911bf2008-07-26 23:55:29418}
419
[email protected]601858c02010-09-01 17:08:20420SearchProvider::~SearchProvider() {
421}
422
[email protected]8d457132010-11-04 18:13:40423void SearchProvider::DoHistoryQuery(bool minimal_changes) {
424 // The history query results are synchronous, so if minimal_changes is true,
425 // we still have the last results and don't need to do anything.
426 if (minimal_changes)
initial.commit09911bf2008-07-26 23:55:29427 return;
428
[email protected]8d457132010-11-04 18:13:40429 keyword_history_results_.clear();
430 default_history_results_.clear();
initial.commit09911bf2008-07-26 23:55:29431
[email protected]8d457132010-11-04 18:13:40432 HistoryService* const history_service =
[email protected]9d2db762012-06-19 00:01:10433 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
[email protected]8d457132010-11-04 18:13:40434 history::URLDatabase* url_db = history_service ?
435 history_service->InMemoryDatabase() : NULL;
436 if (!url_db)
initial.commit09911bf2008-07-26 23:55:29437 return;
438
[email protected]51124552011-07-16 01:37:10439 // Request history for both the keyword and default provider. We grab many
440 // more matches than we'll ultimately clamp to so that if there are several
441 // recent multi-word matches who scores are lowered (see
442 // AddHistoryResultsToMap()), they won't crowd out older, higher-scoring
443 // matches. Note that this doesn't fix the problem entirely, but merely
444 // limits it to cases with a very large number of such multi-word matches; for
445 // now, this seems OK compared with the complexity of a real fix, which would
446 // require multiple searches and tracking of "single- vs. multi-word" in the
447 // database.
448 int num_matches = kMaxMatches * 5;
[email protected]85b8d6f2012-05-08 20:53:47449 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
450 if (default_url) {
451 url_db->GetMostRecentKeywordSearchTerms(default_url->id(), input_.text(),
452 num_matches, &default_history_results_);
[email protected]257ab712009-04-14 17:16:24453 }
[email protected]85b8d6f2012-05-08 20:53:47454 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
455 if (keyword_url) {
456 url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
[email protected]3954c3a2012-04-10 20:17:55457 keyword_input_text_, num_matches, &keyword_history_results_);
458 }
initial.commit09911bf2008-07-26 23:55:29459}
460
[email protected]6dc950f2012-07-16 19:49:08461base::TimeDelta SearchProvider::GetSuggestQueryDelay() {
462 if (query_suggest_immediately_)
463 return TimeDelta();
[email protected]6c85aa02009-02-27 12:08:09464
[email protected]6dc950f2012-07-16 19:49:08465 // By default, wait 200ms after the last keypress before sending the suggest
466 // request. However, in the following field trials, we test different
467 // behavior:
468 // 17 - Wait 200ms since the last suggest request
469 // 18 - Wait 100ms since the last keypress
470 // 19 - Wait 100ms since the last suggest request
471 TimeDelta delay(TimeDelta::FromMilliseconds(200));
472
473 // Set the delay to 100ms if we are in field trial 18 or 19.
474 if (suggest_field_trial_group_number_ == 18 ||
475 suggest_field_trial_group_number_ == 19)
476 delay = TimeDelta::FromMilliseconds(100);
477
478 if (suggest_field_trial_group_number_ != 17 &&
479 suggest_field_trial_group_number_ != 19)
480 return delay;
481
482 // Use the time since last suggest request if we are in field trial 17 or 19.
483 TimeDelta time_since_last_suggest_request =
484 base::TimeTicks::Now() - time_suggest_request_sent_;
485 return std::max(TimeDelta(), delay - time_since_last_suggest_request);
486}
487
488void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]83c726482008-09-10 06:36:34489 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29490 StopSuggest();
[email protected]55ce8f12012-05-09 04:44:08491 ClearResults();
initial.commit09911bf2008-07-26 23:55:29492 return;
493 }
494
495 // For the minimal_changes case, if we finished the previous query and still
496 // have its results, or are allowed to keep running it, just do that, rather
497 // than starting a new query.
498 if (minimal_changes &&
[email protected]ea3b9a502011-04-04 14:19:37499 (have_suggest_results_ ||
500 (!done_ &&
501 input_.matches_requested() == AutocompleteInput::ALL_MATCHES)))
initial.commit09911bf2008-07-26 23:55:29502 return;
503
504 // We can't keep running any previous query, so halt it.
505 StopSuggest();
[email protected]d1f0a7f2012-06-05 10:26:42506
507 // Remove existing results that cannot inline autocomplete the new input.
508 RemoveStaleResults();
initial.commit09911bf2008-07-26 23:55:29509
510 // We can't start a new query if we're only allowed synchronous results.
[email protected]ea3b9a502011-04-04 14:19:37511 if (input_.matches_requested() != AutocompleteInput::ALL_MATCHES)
initial.commit09911bf2008-07-26 23:55:29512 return;
513
514 // Kick off a timer that will start the URL fetch if it completes before
[email protected]6dc950f2012-07-16 19:49:08515 // the user types another character. Requests may be delayed to avoid
516 // flooding the server with requests that are likely to be thrown away later
517 // anyway.
518 timer_.Start(FROM_HERE, GetSuggestQueryDelay(), this, &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29519}
520
[email protected]83c726482008-09-10 06:36:34521bool SearchProvider::IsQuerySuitableForSuggest() const {
[email protected]3954c3a2012-04-10 20:17:55522 // Don't run Suggest in incognito mode, if the engine doesn't support it, or
523 // if the user has disabled it.
[email protected]85b8d6f2012-05-08 20:53:47524 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
525 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
[email protected]83c726482008-09-10 06:36:34526 if (profile_->IsOffTheRecord() ||
[email protected]85b8d6f2012-05-08 20:53:47527 ((!default_url || default_url->suggestions_url().empty()) &&
528 (!keyword_url || keyword_url->suggestions_url().empty())) ||
[email protected]83c726482008-09-10 06:36:34529 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
530 return false;
531
[email protected]cac59d32010-08-09 23:23:14532 // If the input type might be a URL, we take extra care so that private data
[email protected]83c726482008-09-10 06:36:34533 // isn't sent to the server.
[email protected]83c726482008-09-10 06:36:34534
[email protected]cac59d32010-08-09 23:23:14535 // FORCED_QUERY means the user is explicitly asking us to search for this, so
536 // we assume it isn't a URL and/or there isn't private data.
537 if (input_.type() == AutocompleteInput::FORCED_QUERY)
538 return true;
[email protected]83c726482008-09-10 06:36:34539
[email protected]cac59d32010-08-09 23:23:14540 // Next we check the scheme. If this is UNKNOWN/REQUESTED_URL/URL with a
541 // scheme that isn't http/https/ftp, we shouldn't send it. Sending things
542 // like file: and data: is both a waste of time and a disclosure of
543 // potentially private, local data. Other "schemes" may actually be
544 // usernames, and we don't want to send passwords. If the scheme is OK, we
545 // still need to check other cases below. If this is QUERY, then the presence
546 // of these schemes means the user explicitly typed one, and thus this is
547 // probably a URL that's being entered and happens to currently be invalid --
548 // in which case we again want to run our checks below. Other QUERY cases are
549 // less likely to be URLs and thus we assume we're OK.
[email protected]a2fedb1e2011-01-25 15:23:36550 if (!LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpScheme) &&
551 !LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
552 !LowerCaseEqualsASCII(input_.scheme(), chrome::kFtpScheme))
[email protected]cac59d32010-08-09 23:23:14553 return (input_.type() == AutocompleteInput::QUERY);
554
555 // Don't send URLs with usernames, queries or refs. Some of these are
556 // private, and the Suggest server is unlikely to have any useful results
557 // for any of them. Also don't send URLs with ports, as we may initially
558 // think that a username + password is a host + port (and we don't want to
559 // send usernames/passwords), and even if the port really is a port, the
560 // server is once again unlikely to have and useful results.
561 const url_parse::Parsed& parts = input_.parts();
562 if (parts.username.is_nonempty() || parts.port.is_nonempty() ||
563 parts.query.is_nonempty() || parts.ref.is_nonempty())
564 return false;
565
566 // Don't send anything for https except the hostname. Hostnames are OK
567 // because they are visible when the TCP connection is established, but the
568 // specific path may reveal private information.
[email protected]a2fedb1e2011-01-25 15:23:36569 if (LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
570 parts.path.is_nonempty())
[email protected]cac59d32010-08-09 23:23:14571 return false;
[email protected]83c726482008-09-10 06:36:34572
573 return true;
574}
575
initial.commit09911bf2008-07-26 23:55:29576void SearchProvider::StopSuggest() {
[email protected]7706a522012-08-16 17:42:25577 // Increment the appropriate field in the histogram by the number of
578 // pending requests that were invalidated.
579 for (int i = 0; i < suggest_results_pending_; i++)
580 LogOmniboxSuggestRequest(REQUEST_INVALIDATED);
[email protected]257ab712009-04-14 17:16:24581 suggest_results_pending_ = 0;
[email protected]2d316662008-09-03 18:18:14582 timer_.Stop();
[email protected]257ab712009-04-14 17:16:24583 // Stop any in-progress URL fetches.
584 keyword_fetcher_.reset();
585 default_fetcher_.reset();
[email protected]55ce8f12012-05-09 04:44:08586}
587
588void SearchProvider::ClearResults() {
[email protected]257ab712009-04-14 17:16:24589 keyword_suggest_results_.clear();
590 default_suggest_results_.clear();
591 keyword_navigation_results_.clear();
592 default_navigation_results_.clear();
[email protected]d1f0a7f2012-06-05 10:26:42593 has_suggested_relevance_ = false;
594 verbatim_relevance_ = -1;
initial.commit09911bf2008-07-26 23:55:29595 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29596}
597
[email protected]d1f0a7f2012-06-05 10:26:42598void SearchProvider::RemoveStaleResults() {
599 RemoveStaleSuggestResults(&keyword_suggest_results_, true);
600 RemoveStaleSuggestResults(&default_suggest_results_, false);
601 RemoveStaleNavigationResults(&keyword_navigation_results_, true);
602 RemoveStaleNavigationResults(&default_navigation_results_, false);
603}
604
605void SearchProvider::RemoveStaleSuggestResults(SuggestResults* list,
606 bool is_keyword) {
607 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
608 for (SuggestResults::iterator i = list->begin(); i < list->end();)
609 i = StartsWith(i->suggestion(), input, false) ? (i + 1) : list->erase(i);
610}
611
612void SearchProvider::RemoveStaleNavigationResults(NavigationResults* list,
613 bool is_keyword) {
614 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
615 for (NavigationResults::iterator i = list->begin(); i < list->end();) {
616 const string16 fill(AutocompleteInput::FormattedStringWithEquivalentMeaning(
617 i->url(), StringForURLDisplay(i->url(), true, false)));
618 i = URLPrefix::BestURLPrefix(fill, input) ? (i + 1) : list->erase(i);
619 }
620}
621
622void SearchProvider::ApplyCalculatedRelevance() {
623 ApplyCalculatedSuggestRelevance(&keyword_suggest_results_, true);
624 ApplyCalculatedSuggestRelevance(&default_suggest_results_, false);
625 ApplyCalculatedNavigationRelevance(&keyword_navigation_results_, true);
626 ApplyCalculatedNavigationRelevance(&default_navigation_results_, false);
627 has_suggested_relevance_ = false;
628 verbatim_relevance_ = -1;
629}
630
631void SearchProvider::ApplyCalculatedSuggestRelevance(SuggestResults* list,
632 bool is_keyword) {
633 for (size_t i = 0; i < list->size(); ++i) {
634 (*list)[i].set_relevance(CalculateRelevanceForSuggestion(is_keyword) +
635 (list->size() - i - 1));
636 }
637}
638
639void SearchProvider::ApplyCalculatedNavigationRelevance(NavigationResults* list,
640 bool is_keyword) {
641 for (size_t i = 0; i < list->size(); ++i) {
642 (*list)[i].set_relevance(CalculateRelevanceForNavigation(is_keyword) +
643 (list->size() - i - 1));
644 }
645}
646
[email protected]15fb2aa2012-05-22 22:52:59647net::URLFetcher* SearchProvider::CreateSuggestFetcher(
[email protected]7cc6e5632011-10-25 17:56:12648 int id,
[email protected]9ff91722012-09-07 05:29:12649 const TemplateURL* template_url,
[email protected]7cc6e5632011-10-25 17:56:12650 const string16& text) {
[email protected]9ff91722012-09-07 05:29:12651 if (!template_url || template_url->suggestions_url().empty())
652 return NULL;
653
654 // Bail if the suggestion URL is invalid with the given replacements.
655 GURL suggest_url(template_url->suggestions_url_ref().ReplaceSearchTerms(
656 TemplateURLRef::SearchTermsArgs(text)));
657 if (!suggest_url.is_valid())
658 return NULL;
659
660 suggest_results_pending_++;
661 LogOmniboxSuggestRequest(REQUEST_SENT);
662
663 net::URLFetcher* fetcher =
664 net::URLFetcher::Create(id, suggest_url, net::URLFetcher::GET, this);
[email protected]7cc6e5632011-10-25 17:56:12665 fetcher->SetRequestContext(profile_->GetRequestContext());
[email protected]d3cf8682f02012-02-29 23:29:34666 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
[email protected]257ab712009-04-14 17:16:24667 fetcher->Start();
668 return fetcher;
669}
670
[email protected]d7ad4772012-06-01 03:12:54671bool SearchProvider::ParseSuggestResults(Value* root_val, bool is_keyword) {
672 // TODO(pkasting): Fix |have_suggest_results_|; see http://crbug.com/130631
673 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29674
[email protected]d7ad4772012-06-01 03:12:54675 string16 query;
676 ListValue* root_list = NULL;
677 ListValue* results = NULL;
678 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
679 if (!root_val->GetAsList(&root_list) || !root_list->GetString(0, &query) ||
680 (query != input_text) || !root_list->GetList(1, &results))
initial.commit09911bf2008-07-26 23:55:29681 return false;
682
[email protected]55ce8f12012-05-09 04:44:08683 // 3rd element: Description list.
[email protected]d7ad4772012-06-01 03:12:54684 ListValue* descriptions = NULL;
685 root_list->GetList(2, &descriptions);
initial.commit09911bf2008-07-26 23:55:29686
[email protected]55ce8f12012-05-09 04:44:08687 // 4th element: Disregard the query URL list for now.
initial.commit09911bf2008-07-26 23:55:29688
[email protected]d1f0a7f2012-06-05 10:26:42689 // Reset suggested relevance information from the default provider.
690 if (!is_keyword) {
691 has_suggested_relevance_ = false;
692 verbatim_relevance_ = -1;
693 }
694
[email protected]55ce8f12012-05-09 04:44:08695 // 5th element: Optional key-value pairs from the Suggest server.
[email protected]d7ad4772012-06-01 03:12:54696 ListValue* types = NULL;
[email protected]d1f0a7f2012-06-05 10:26:42697 ListValue* relevances = NULL;
698 DictionaryValue* extras = NULL;
699 if (root_list->GetDictionary(4, &extras)) {
700 extras->GetList("google:suggesttype", &types);
701
702 // Only accept relevance suggestions if Instant is disabled.
[email protected]e41982a72012-11-20 07:16:51703 if (!is_keyword &&
704 !chrome::BrowserInstantController::IsInstantEnabled(profile_)) {
[email protected]d1f0a7f2012-06-05 10:26:42705 // Discard this list if its size does not match that of the suggestions.
706 if (extras->GetList("google:suggestrelevance", &relevances) &&
707 relevances->GetSize() != results->GetSize())
708 relevances = NULL;
709
710 extras->GetInteger("google:verbatimrelevance", &verbatim_relevance_);
711 }
initial.commit09911bf2008-07-26 23:55:29712 }
713
[email protected]d7ad4772012-06-01 03:12:54714 SuggestResults* suggest_results =
715 is_keyword ? &keyword_suggest_results_ : &default_suggest_results_;
716 NavigationResults* navigation_results =
717 is_keyword ? &keyword_navigation_results_ : &default_navigation_results_;
initial.commit09911bf2008-07-26 23:55:29718
[email protected]d1f0a7f2012-06-05 10:26:42719 // Clear the previous results now that new results are available.
720 suggest_results->clear();
721 navigation_results->clear();
722
723 string16 result, title;
724 std::string type;
725 int relevance = -1;
[email protected]d7ad4772012-06-01 03:12:54726 for (size_t index = 0; results->GetString(index, &result); ++index) {
[email protected]8e81f5092010-09-29 23:19:40727 // Google search may return empty suggestions for weird input characters,
[email protected]55ce8f12012-05-09 04:44:08728 // they make no sense at all and can cause problems in our code.
[email protected]d7ad4772012-06-01 03:12:54729 if (result.empty())
[email protected]8e81f5092010-09-29 23:19:40730 continue;
731
[email protected]d1f0a7f2012-06-05 10:26:42732 // Apply valid suggested relevance scores; discard invalid lists.
733 if (relevances != NULL && !relevances->GetInteger(index, &relevance))
734 relevances = NULL;
[email protected]d7ad4772012-06-01 03:12:54735 if (types && types->GetString(index, &type) && (type == "NAVIGATION")) {
[email protected]d1f0a7f2012-06-05 10:26:42736 // Do not blindly trust the URL coming from the server to be valid.
737 GURL url(URLFixerUpper::FixupURL(UTF16ToUTF8(result), std::string()));
738 if (url.is_valid()) {
739 if (descriptions != NULL)
740 descriptions->GetString(index, &title);
741 navigation_results->push_back(NavigationResult(url, title, relevance));
initial.commit09911bf2008-07-26 23:55:29742 }
743 } else {
[email protected]d1f0a7f2012-06-05 10:26:42744 // TODO(kochi): Improve calculator result presentation.
745 suggest_results->push_back(SuggestResult(result, relevance));
initial.commit09911bf2008-07-26 23:55:29746 }
747 }
748
[email protected]d1f0a7f2012-06-05 10:26:42749 // Apply calculated relevance scores if a valid list was not provided.
750 if (relevances == NULL) {
751 ApplyCalculatedSuggestRelevance(suggest_results, is_keyword);
752 ApplyCalculatedNavigationRelevance(navigation_results, is_keyword);
753 } else if (!is_keyword) {
754 has_suggested_relevance_ = true;
755 }
756
[email protected]d7ad4772012-06-01 03:12:54757 have_suggest_results_ = true;
[email protected]d1f0a7f2012-06-05 10:26:42758 return true;
initial.commit09911bf2008-07-26 23:55:29759}
760
761void SearchProvider::ConvertResultsToAutocompleteMatches() {
762 // Convert all the results to matches and add them to a map, so we can keep
763 // the most relevant match for each result.
764 MatchMap map;
[email protected]257ab712009-04-14 17:16:24765 const Time no_time;
766 int did_not_accept_keyword_suggestion = keyword_suggest_results_.empty() ?
initial.commit09911bf2008-07-26 23:55:29767 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
768 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]257ab712009-04-14 17:16:24769 // Keyword what you typed results are handled by the KeywordProvider.
initial.commit09911bf2008-07-26 23:55:29770
[email protected]382a0642012-06-06 06:13:52771 int verbatim_relevance = GetVerbatimRelevance();
[email protected]257ab712009-04-14 17:16:24772 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
[email protected]55ce8f12012-05-09 04:44:08773 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
774 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]d1f0a7f2012-06-05 10:26:42775 if (verbatim_relevance > 0) {
776 AddMatchToMap(input_.text(), input_.text(), verbatim_relevance,
777 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
778 did_not_accept_default_suggestion, false, &map);
779 }
[email protected]b9ce8532012-05-24 20:51:15780 const size_t what_you_typed_size = map.size();
[email protected]93b73832012-10-18 20:18:38781 if (!default_provider_suggestion_.text.empty() &&
782 default_provider_suggestion_.type == INSTANT_SUGGESTION_SEARCH)
783 AddMatchToMap(input_.text() + default_provider_suggestion_.text,
[email protected]55ce8f12012-05-09 04:44:08784 input_.text(), verbatim_relevance + 1,
[email protected]85b8d6f2012-05-08 20:53:47785 AutocompleteMatch::SEARCH_SUGGEST,
[email protected]55ce8f12012-05-09 04:44:08786 did_not_accept_default_suggestion, false, &map);
initial.commit09911bf2008-07-26 23:55:29787
[email protected]257ab712009-04-14 17:16:24788 AddHistoryResultsToMap(keyword_history_results_, true,
789 did_not_accept_keyword_suggestion, &map);
790 AddHistoryResultsToMap(default_history_results_, false,
791 did_not_accept_default_suggestion, &map);
792
[email protected]55ce8f12012-05-09 04:44:08793 AddSuggestResultsToMap(keyword_suggest_results_, true, &map);
794 AddSuggestResultsToMap(default_suggest_results_, false, &map);
initial.commit09911bf2008-07-26 23:55:29795
796 // Now add the most relevant matches from the map to |matches_|.
797 matches_.clear();
798 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
799 matches_.push_back(i->second);
800
[email protected]93b73832012-10-18 20:18:38801 if (!default_provider_suggestion_.text.empty() &&
802 default_provider_suggestion_.type == INSTANT_SUGGESTION_URL)
803 matches_.push_back(NavigationToMatch(
804 NavigationResult(GURL(UTF16ToUTF8(default_provider_suggestion_.text)),
805 string16(),
806 verbatim_relevance + 1),
807 false));
[email protected]257ab712009-04-14 17:16:24808 AddNavigationResultsToMatches(keyword_navigation_results_, true);
809 AddNavigationResultsToMatches(default_navigation_results_, false);
initial.commit09911bf2008-07-26 23:55:29810
[email protected]b9ce8532012-05-24 20:51:15811 // Allow an additional match for "what you typed" if it's present.
812 const size_t max_total_matches = kMaxMatches + what_you_typed_size;
initial.commit09911bf2008-07-26 23:55:29813 std::partial_sort(matches_.begin(),
814 matches_.begin() + std::min(max_total_matches, matches_.size()),
815 matches_.end(), &AutocompleteMatch::MoreRelevant);
[email protected]3723e6e2012-06-11 21:06:56816
817 // If the top match is effectively 'verbatim' but exceeds the calculated
818 // verbatim relevance, and REQUESTED_URL |input_| has a |desired_tld|
819 // (for example ".com" when the CTRL key is pressed for REQUESTED_URL input),
820 // promote a URL_WHAT_YOU_TYPED match to the top. Otherwise, these matches can
821 // stomp the HistoryURLProvider's similar transient URL_WHAT_YOU_TYPED match,
822 // and CTRL+ENTER will invoke the search instead of the expected navigation.
823 if ((has_suggested_relevance_ || verbatim_relevance_ >= 0) &&
824 input_.type() == AutocompleteInput::REQUESTED_URL &&
825 !input_.desired_tld().empty() && !matches_.empty() &&
826 matches_.front().relevance > CalculateRelevanceForVerbatim() &&
827 matches_.front().fill_into_edit == input_.text()) {
828 AutocompleteMatch match = HistoryURLProvider::SuggestExactInput(
829 this, input_, !HasHTTPScheme(input_.text()));
830 match.relevance = matches_.front().relevance + 1;
831 matches_.insert(matches_.begin(), match);
832 }
833
initial.commit09911bf2008-07-26 23:55:29834 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02835 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29836
[email protected]382a0642012-06-06 06:13:52837 // Check constraints that may be violated by suggested relevances.
[email protected]d1f0a7f2012-06-05 10:26:42838 if (!matches_.empty() &&
[email protected]e6acd002012-06-16 22:27:47839 (has_suggested_relevance_ || verbatim_relevance_ >= 0)) {
[email protected]382a0642012-06-06 06:13:52840 bool reconstruct_matches = false;
[email protected]1beee342012-06-19 22:22:28841 if (matches_.front().type != AutocompleteMatch::SEARCH_WHAT_YOU_TYPED &&
842 matches_.front().type != AutocompleteMatch::URL_WHAT_YOU_TYPED &&
843 matches_.front().inline_autocomplete_offset == string16::npos &&
844 matches_.front().fill_into_edit != input_.text()) {
845 // Disregard suggested relevances if the top match is not SWYT, inlinable,
846 // or URL_WHAT_YOU_TYPED (which may be top match regardless of inlining).
847 // For example, input "foo" should not invoke a search for "bar", which
848 // would happen if the "bar" search match outranked all other matches.
849 ApplyCalculatedRelevance();
850 reconstruct_matches = true;
851 } else if (matches_.front().relevance < CalculateRelevanceForVerbatim()) {
852 // Disregard the suggested verbatim relevance if the top score is below
853 // the usual verbatim value. For example, a BarProvider may rely on
854 // SearchProvider's verbatim or inlineable matches for input "foo" to
855 // always outrank its own lowly-ranked non-inlineable "bar" match.
856 verbatim_relevance_ = -1;
857 reconstruct_matches = true;
[email protected]e6acd002012-06-16 22:27:47858 }
859 if (input_.type() == AutocompleteInput::URL &&
860 matches_.front().relevance > CalculateRelevanceForVerbatim() &&
861 (matches_.front().type == AutocompleteMatch::SEARCH_SUGGEST ||
862 matches_.front().type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED)) {
863 // Disregard the suggested search and verbatim relevances if the input
864 // type is URL and the top match is a highly-ranked search suggestion.
865 // For example, prevent a search for "foo.com" from outranking another
866 // provider's navigation for "foo.com" or "foo.com/url_from_history".
867 // Reconstruction will also ensure that the new top match is inlineable.
868 ApplyCalculatedSuggestRelevance(&keyword_suggest_results_, true);
869 ApplyCalculatedSuggestRelevance(&default_suggest_results_, false);
[email protected]382a0642012-06-06 06:13:52870 verbatim_relevance_ = -1;
871 reconstruct_matches = true;
872 }
873 if (reconstruct_matches) {
874 ConvertResultsToAutocompleteMatches();
875 return;
876 }
[email protected]d1f0a7f2012-06-05 10:26:42877 }
878
[email protected]cc63dea2008-08-21 20:56:31879 UpdateStarredStateOfMatches();
[email protected]4ab4c7c2010-11-24 04:49:34880 UpdateDone();
[email protected]257ab712009-04-14 17:16:24881}
882
883void SearchProvider::AddNavigationResultsToMatches(
884 const NavigationResults& navigation_results,
885 bool is_keyword) {
886 if (!navigation_results.empty()) {
[email protected]6c535842012-05-15 05:20:55887 // TODO(kochi|msw): Add more navigational results if they get more
888 // meaningful relevance values; see http://b/1170574.
[email protected]d7ad4772012-06-01 03:12:54889 // CompareScoredResults sorts by descending relevance; so use min_element.
[email protected]6c535842012-05-15 05:20:55890 NavigationResults::const_iterator result(
[email protected]d7ad4772012-06-01 03:12:54891 std::min_element(navigation_results.begin(),
[email protected]6c535842012-05-15 05:20:55892 navigation_results.end(),
893 CompareScoredResults()));
894 matches_.push_back(NavigationToMatch(*result, is_keyword));
[email protected]257ab712009-04-14 17:16:24895 }
896}
897
898void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results,
899 bool is_keyword,
900 int did_not_accept_suggestion,
901 MatchMap* map) {
[email protected]51124552011-07-16 01:37:10902 if (results.empty())
903 return;
904
[email protected]d7ad4772012-06-01 03:12:54905 bool prevent_inline_autocomplete = input_.prevent_inline_autocomplete() ||
906 (input_.type() == AutocompleteInput::URL);
907 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
[email protected]51124552011-07-16 01:37:10908 bool input_multiple_words = HasMultipleWords(input_text);
909
[email protected]55ce8f12012-05-09 04:44:08910 SuggestResults scored_results;
911 if (!prevent_inline_autocomplete && input_multiple_words) {
912 // ScoreHistoryResults() allows autocompletion of multi-word, 1-visit
913 // queries if the input also has multiple words. But if we were already
[email protected]51124552011-07-16 01:37:10914 // autocompleting a multi-word, multi-visit query, and the current input is
915 // still a prefix of it, then changing the autocompletion suddenly feels
916 // wrong. To detect this case, first score as if only one word has been
917 // typed, then check for a best result that is an autocompleted, multi-word
918 // query. If we find one, then just keep that score set.
[email protected]55ce8f12012-05-09 04:44:08919 scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
920 false, input_text, is_keyword);
921 if ((scored_results[0].relevance() <
922 AutocompleteResult::kLowestDefaultScore) ||
923 !HasMultipleWords(scored_results[0].suggestion()))
924 scored_results.clear(); // Didn't detect the case above, score normally.
[email protected]51124552011-07-16 01:37:10925 }
[email protected]55ce8f12012-05-09 04:44:08926 if (scored_results.empty())
927 scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
928 input_multiple_words, input_text,
929 is_keyword);
930 for (SuggestResults::const_iterator i(scored_results.begin());
931 i != scored_results.end(); ++i) {
932 AddMatchToMap(i->suggestion(), input_text, i->relevance(),
[email protected]51124552011-07-16 01:37:10933 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
[email protected]55ce8f12012-05-09 04:44:08934 is_keyword, map);
[email protected]51124552011-07-16 01:37:10935 }
936}
937
[email protected]55ce8f12012-05-09 04:44:08938SearchProvider::SuggestResults SearchProvider::ScoreHistoryResults(
[email protected]51124552011-07-16 01:37:10939 const HistoryResults& results,
940 bool base_prevent_inline_autocomplete,
941 bool input_multiple_words,
942 const string16& input_text,
943 bool is_keyword) {
[email protected]810ffba2012-06-12 01:07:48944 AutocompleteClassifier* classifier =
945 AutocompleteClassifierFactory::GetForProfile(profile_);
[email protected]55ce8f12012-05-09 04:44:08946 SuggestResults scored_results;
[email protected]257ab712009-04-14 17:16:24947 for (HistoryResults::const_iterator i(results.begin()); i != results.end();
948 ++i) {
[email protected]51124552011-07-16 01:37:10949 // Don't autocomplete multi-word queries that have only been seen once
950 // unless the user has typed more than one word.
951 bool prevent_inline_autocomplete = base_prevent_inline_autocomplete ||
952 (!input_multiple_words && (i->visits < 2) && HasMultipleWords(i->term));
953
[email protected]ea3b9a502011-04-04 14:19:37954 // Don't autocomplete search terms that would normally be treated as URLs
[email protected]51124552011-07-16 01:37:10955 // when typed. For example, if the user searched for "google.com" and types
956 // "goog", don't autocomplete to the search term "google.com". Otherwise,
957 // the input will look like a URL but act like a search, which is confusing.
[email protected]cc447362011-04-06 03:57:48958 // NOTE: We don't check this in the following cases:
959 // * When inline autocomplete is disabled, we won't be inline
960 // autocompleting this term, so we don't need to worry about confusion as
961 // much. This also prevents calling Classify() again from inside the
962 // classifier (which will corrupt state and likely crash), since the
[email protected]51124552011-07-16 01:37:10963 // classifier always disables inline autocomplete.
[email protected]cc447362011-04-06 03:57:48964 // * When the user has typed the whole term, the "what you typed" history
965 // match will outrank us for URL-like inputs anyway, so we need not do
966 // anything special.
[email protected]51124552011-07-16 01:37:10967 if (!prevent_inline_autocomplete && classifier && (i->term != input_text)) {
[email protected]ea3b9a502011-04-04 14:19:37968 AutocompleteMatch match;
[email protected]72874a8d2011-05-11 03:48:54969 classifier->Classify(i->term, string16(), false, false, &match, NULL);
[email protected]2905f742011-10-13 03:51:58970 prevent_inline_autocomplete =
[email protected]749e7ae02012-09-05 18:47:46971 !AutocompleteMatch::IsSearchType(match.type);
[email protected]ea3b9a502011-04-04 14:19:37972 }
[email protected]51124552011-07-16 01:37:10973
974 int relevance = CalculateRelevanceForHistory(i->time, is_keyword,
975 prevent_inline_autocomplete);
[email protected]55ce8f12012-05-09 04:44:08976 scored_results.push_back(SuggestResult(i->term, relevance));
[email protected]257ab712009-04-14 17:16:24977 }
[email protected]51124552011-07-16 01:37:10978
979 // History returns results sorted for us. However, we may have docked some
980 // results' scores, so things are no longer in order. Do a stable sort to get
981 // things back in order without otherwise disturbing results with equal
982 // scores, then force the scores to be unique, so that the order in which
983 // they're shown is deterministic.
[email protected]55ce8f12012-05-09 04:44:08984 std::stable_sort(scored_results.begin(), scored_results.end(),
985 CompareScoredResults());
[email protected]51124552011-07-16 01:37:10986 int last_relevance = 0;
[email protected]55ce8f12012-05-09 04:44:08987 for (SuggestResults::iterator i(scored_results.begin());
988 i != scored_results.end(); ++i) {
989 if ((i != scored_results.begin()) && (i->relevance() >= last_relevance))
990 i->set_relevance(last_relevance - 1);
991 last_relevance = i->relevance();
[email protected]51124552011-07-16 01:37:10992 }
993
[email protected]55ce8f12012-05-09 04:44:08994 return scored_results;
[email protected]257ab712009-04-14 17:16:24995}
996
[email protected]55ce8f12012-05-09 04:44:08997void SearchProvider::AddSuggestResultsToMap(const SuggestResults& results,
998 bool is_keyword,
999 MatchMap* map) {
[email protected]d7ad4772012-06-01 03:12:541000 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
[email protected]55ce8f12012-05-09 04:44:081001 for (size_t i = 0; i < results.size(); ++i) {
[email protected]d7ad4772012-06-01 03:12:541002 AddMatchToMap(results[i].suggestion(), input_text, results[i].relevance(),
[email protected]55ce8f12012-05-09 04:44:081003 AutocompleteMatch::SEARCH_SUGGEST, i, is_keyword, map);
[email protected]257ab712009-04-14 17:16:241004 }
initial.commit09911bf2008-07-26 23:55:291005}
1006
[email protected]382a0642012-06-06 06:13:521007int SearchProvider::GetVerbatimRelevance() const {
[email protected]dc6943b2012-06-19 06:39:561008 // Use the suggested verbatim relevance score if it is non-negative (valid),
1009 // if inline autocomplete isn't prevented (always show verbatim on backspace),
[email protected]1beee342012-06-19 22:22:281010 // and if it won't suppress verbatim, leaving no default provider matches.
1011 // Otherwise, if the default provider returned no matches and was still able
[email protected]dc6943b2012-06-19 06:39:561012 // to suppress verbatim, the user would have no search/nav matches and may be
[email protected]1beee342012-06-19 22:22:281013 // left unable to search using their default provider from the omnibox.
[email protected]dc6943b2012-06-19 06:39:561014 // Check for results on each verbatim calculation, as results from older
1015 // queries (on previous input) may be trimmed for failing to inline new input.
1016 if (verbatim_relevance_ >= 0 && !input_.prevent_inline_autocomplete() &&
[email protected]1beee342012-06-19 22:22:281017 (verbatim_relevance_ > 0 ||
1018 !default_suggest_results_.empty() ||
[email protected]dc6943b2012-06-19 06:39:561019 !default_navigation_results_.empty())) {
[email protected]d1f0a7f2012-06-05 10:26:421020 return verbatim_relevance_;
[email protected]dc6943b2012-06-19 06:39:561021 }
[email protected]382a0642012-06-06 06:13:521022 return CalculateRelevanceForVerbatim();
1023}
[email protected]d1f0a7f2012-06-05 10:26:421024
[email protected]382a0642012-06-06 06:13:521025int SearchProvider::CalculateRelevanceForVerbatim() const {
[email protected]85b8d6f2012-05-08 20:53:471026 if (!providers_.keyword_provider().empty())
[email protected]52d08b12009-10-19 18:42:361027 return 250;
1028
initial.commit09911bf2008-07-26 23:55:291029 switch (input_.type()) {
1030 case AutocompleteInput::UNKNOWN:
[email protected]52d08b12009-10-19 18:42:361031 case AutocompleteInput::QUERY:
1032 case AutocompleteInput::FORCED_QUERY:
1033 return 1300;
initial.commit09911bf2008-07-26 23:55:291034
1035 case AutocompleteInput::REQUESTED_URL:
[email protected]52d08b12009-10-19 18:42:361036 return 1150;
initial.commit09911bf2008-07-26 23:55:291037
1038 case AutocompleteInput::URL:
[email protected]52d08b12009-10-19 18:42:361039 return 850;
initial.commit09911bf2008-07-26 23:55:291040
1041 default:
1042 NOTREACHED();
1043 return 0;
1044 }
1045}
1046
[email protected]51124552011-07-16 01:37:101047int SearchProvider::CalculateRelevanceForHistory(
1048 const Time& time,
1049 bool is_keyword,
1050 bool prevent_inline_autocomplete) const {
[email protected]aa613d62010-11-09 20:40:181051 // The relevance of past searches falls off over time. There are two distinct
1052 // equations used. If the first equation is used (searches to the primary
[email protected]51124552011-07-16 01:37:101053 // provider that we want to inline autocomplete), the score starts at 1399 and
1054 // falls to 1300. If the second equation is used the relevance of a search 15
1055 // minutes ago is discounted 50 points, while the relevance of a search two
1056 // weeks ago is discounted 450 points.
[email protected]aa613d62010-11-09 20:40:181057 double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
[email protected]51124552011-07-16 01:37:101058 bool is_primary_provider = providers_.is_primary_provider(is_keyword);
1059 if (is_primary_provider && !prevent_inline_autocomplete) {
[email protected]aa613d62010-11-09 20:40:181060 // Searches with the past two days get a different curve.
[email protected]51124552011-07-16 01:37:101061 const double autocomplete_time = 2 * 24 * 60 * 60;
[email protected]aa613d62010-11-09 20:40:181062 if (elapsed_time < autocomplete_time) {
[email protected]e17511f2011-07-13 14:09:181063 return (is_keyword ? 1599 : 1399) - static_cast<int>(99 *
[email protected]aa613d62010-11-09 20:40:181064 std::pow(elapsed_time / autocomplete_time, 2.5));
1065 }
1066 elapsed_time -= autocomplete_time;
1067 }
1068
[email protected]c3a4bd992010-08-18 20:25:011069 const int score_discount =
1070 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3));
initial.commit09911bf2008-07-26 23:55:291071
[email protected]6c85aa02009-02-27 12:08:091072 // Don't let scores go below 0. Negative relevance scores are meaningful in
1073 // a different way.
initial.commit09911bf2008-07-26 23:55:291074 int base_score;
[email protected]51124552011-07-16 01:37:101075 if (is_primary_provider)
[email protected]52d08b12009-10-19 18:42:361076 base_score = (input_.type() == AutocompleteInput::URL) ? 750 : 1050;
[email protected]51124552011-07-16 01:37:101077 else
1078 base_score = 200;
initial.commit09911bf2008-07-26 23:55:291079 return std::max(0, base_score - score_discount);
1080}
1081
[email protected]55ce8f12012-05-09 04:44:081082int SearchProvider::CalculateRelevanceForSuggestion(bool for_keyword) const {
1083 return !providers_.is_primary_provider(for_keyword) ? 100 :
1084 ((input_.type() == AutocompleteInput::URL) ? 300 : 600);
initial.commit09911bf2008-07-26 23:55:291085}
1086
[email protected]55ce8f12012-05-09 04:44:081087int SearchProvider::CalculateRelevanceForNavigation(bool for_keyword) const {
1088 return providers_.is_primary_provider(for_keyword) ? 800 : 150;
initial.commit09911bf2008-07-26 23:55:291089}
1090
[email protected]a2fedb1e2011-01-25 15:23:361091void SearchProvider::AddMatchToMap(const string16& query_string,
1092 const string16& input_text,
initial.commit09911bf2008-07-26 23:55:291093 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:001094 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:291095 int accepted_suggestion,
[email protected]257ab712009-04-14 17:16:241096 bool is_keyword,
initial.commit09911bf2008-07-26 23:55:291097 MatchMap* map) {
[email protected]92513682011-09-01 06:16:521098 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:291099 std::vector<size_t> content_param_offsets;
[email protected]85b8d6f2012-05-08 20:53:471100 // Bail out now if we don't actually have a valid provider.
1101 match.keyword = is_keyword ?
[email protected]3954c3a2012-04-10 20:17:551102 providers_.keyword_provider() : providers_.default_provider();
[email protected]dbff446582012-10-30 00:20:261103 const TemplateURL* provider_url = match.GetTemplateURL(profile_, false);
[email protected]85b8d6f2012-05-08 20:53:471104 if (provider_url == NULL)
1105 return;
1106
[email protected]70833262011-01-05 23:40:441107 match.contents.assign(query_string);
[email protected]fb5153c52009-07-31 19:40:331108 // We do intra-string highlighting for suggestions - the suggested segment
1109 // will be highlighted, e.g. for input_text = "you" the suggestion may be
1110 // "youtube", so we'll bold the "tube" section: you*tube*.
1111 if (input_text != query_string) {
[email protected]fb5153c52009-07-31 19:40:331112 size_t input_position = match.contents.find(input_text);
[email protected]a2fedb1e2011-01-25 15:23:361113 if (input_position == string16::npos) {
[email protected]fb5153c52009-07-31 19:40:331114 // The input text is not a substring of the query string, e.g. input
1115 // text is "slasdot" and the query string is "slashdot", so we bold the
1116 // whole thing.
1117 match.contents_class.push_back(
1118 ACMatchClassification(0, ACMatchClassification::MATCH));
[email protected]ec2379162009-06-09 23:58:171119 } else {
[email protected]fb5153c52009-07-31 19:40:331120 // TODO(beng): ACMatchClassification::MATCH now seems to just mean
1121 // "bold" this. Consider modifying the terminology.
1122 // We don't iterate over the string here annotating all matches because
1123 // it looks odd to have every occurrence of a substring that may be as
1124 // short as a single character highlighted in a query suggestion result,
1125 // e.g. for input text "s" and query string "southwest airlines", it
1126 // looks odd if both the first and last s are highlighted.
1127 if (input_position != 0) {
1128 match.contents_class.push_back(
1129 ACMatchClassification(0, ACMatchClassification::NONE));
1130 }
1131 match.contents_class.push_back(
1132 ACMatchClassification(input_position, ACMatchClassification::DIM));
1133 size_t next_fragment_position = input_position + input_text.length();
1134 if (next_fragment_position < query_string.length()) {
1135 match.contents_class.push_back(
1136 ACMatchClassification(next_fragment_position,
1137 ACMatchClassification::NONE));
1138 }
[email protected]ec2379162009-06-09 23:58:171139 }
initial.commit09911bf2008-07-26 23:55:291140 } else {
[email protected]fb5153c52009-07-31 19:40:331141 // Otherwise, we're dealing with the "default search" result which has no
[email protected]70833262011-01-05 23:40:441142 // completion.
[email protected]fb5153c52009-07-31 19:40:331143 match.contents_class.push_back(
1144 ACMatchClassification(0, ACMatchClassification::NONE));
initial.commit09911bf2008-07-26 23:55:291145 }
1146
1147 // When the user forced a query, we need to make sure all the fill_into_edit
1148 // values preserve that property. Otherwise, if the user starts editing a
1149 // suggestion, non-Search results will suddenly appear.
[email protected]8f2249b2012-08-29 02:27:241150 if (input_.type() == AutocompleteInput::FORCED_QUERY)
[email protected]a2fedb1e2011-01-25 15:23:361151 match.fill_into_edit.assign(ASCIIToUTF16("?"));
[email protected]8f2249b2012-08-29 02:27:241152 if (is_keyword)
[email protected]033f3422012-03-13 21:24:181153 match.fill_into_edit.append(match.keyword + char16(' '));
[email protected]8f2249b2012-08-29 02:27:241154 if (!input_.prevent_inline_autocomplete() &&
1155 StartsWith(query_string, input_text, false)) {
1156 match.inline_autocomplete_offset =
1157 match.fill_into_edit.length() + input_text.length();
[email protected]c0048b42009-05-04 21:47:171158 }
initial.commit09911bf2008-07-26 23:55:291159 match.fill_into_edit.append(query_string);
initial.commit09911bf2008-07-26 23:55:291160
[email protected]85b8d6f2012-05-08 20:53:471161 const TemplateURLRef& search_url = provider_url->url_ref();
[email protected]360ba052012-04-04 17:26:131162 DCHECK(search_url.SupportsReplacement());
[email protected]bca359b2012-06-24 07:53:041163 match.search_terms_args.reset(
1164 new TemplateURLRef::SearchTermsArgs(query_string));
1165 match.search_terms_args->original_query = input_text;
1166 match.search_terms_args->accepted_suggestion = accepted_suggestion;
1167 // This is the destination URL sans assisted query stats. This must be set
1168 // so the AutocompleteController can properly de-dupe; the controller will
1169 // eventually overwrite it before it reaches the user.
1170 match.destination_url =
1171 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get()));
initial.commit09911bf2008-07-26 23:55:291172
1173 // Search results don't look like URLs.
[email protected]2905f742011-10-13 03:51:581174 match.transition = is_keyword ?
1175 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED;
initial.commit09911bf2008-07-26 23:55:291176
1177 // Try to add |match| to |map|. If a match for |query_string| is already in
1178 // |map|, replace it if |match| is more relevant.
1179 // NOTE: Keep this ToLower() call in sync with url_database.cc.
1180 const std::pair<MatchMap::iterator, bool> i = map->insert(
[email protected]a2fedb1e2011-01-25 15:23:361181 std::pair<string16, AutocompleteMatch>(
[email protected]503d03872011-05-06 08:36:261182 base::i18n::ToLower(query_string), match));
initial.commit09911bf2008-07-26 23:55:291183 // NOTE: We purposefully do a direct relevance comparison here instead of
1184 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
1185 // first" rather than "items alphabetically first" when the scores are equal.
1186 // The only case this matters is when a user has results with the same score
1187 // that differ only by capitalization; because the history system returns
1188 // results sorted by recency, this means we'll pick the most recent such
1189 // result even if the precision of our relevance score is too low to
1190 // distinguish the two.
1191 if (!i.second && (match.relevance > i.first->second.relevance))
1192 i.first->second = match;
1193}
1194
1195AutocompleteMatch SearchProvider::NavigationToMatch(
1196 const NavigationResult& navigation,
[email protected]257ab712009-04-14 17:16:241197 bool is_keyword) {
[email protected]371dab12012-06-01 03:23:551198 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
[email protected]55ce8f12012-05-09 04:44:081199 AutocompleteMatch match(this, navigation.relevance(), false,
[email protected]4c1fb7ec2008-11-13 00:19:001200 AutocompleteMatch::NAVSUGGEST);
[email protected]55ce8f12012-05-09 04:44:081201 match.destination_url = navigation.url();
[email protected]371dab12012-06-01 03:23:551202
1203 // First look for the user's input inside the fill_into_edit as it would be
1204 // without trimming the scheme, so we can find matches at the beginning of the
1205 // scheme.
1206 const string16 untrimmed_fill_into_edit(
1207 AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
1208 StringForURLDisplay(navigation.url(), true, false)));
1209 const URLPrefix* prefix =
1210 URLPrefix::BestURLPrefix(untrimmed_fill_into_edit, input);
1211 size_t match_start = (prefix == NULL) ?
1212 untrimmed_fill_into_edit.find(input) : prefix->prefix.length();
1213 size_t inline_autocomplete_offset = (prefix == NULL) ?
1214 string16::npos : (match_start + input.length());
1215 bool trim_http = !HasHTTPScheme(input) && (!prefix || (match_start != 0));
1216
1217 // Preserve the forced query '?' prefix in |match.fill_into_edit|.
1218 // Otherwise, user edits to a suggestion would show non-Search results.
1219 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
1220 match.fill_into_edit = ASCIIToUTF16("?");
1221 if (inline_autocomplete_offset != string16::npos)
1222 ++inline_autocomplete_offset;
1223 }
1224
1225 const std::string languages(
1226 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
1227 const net::FormatUrlTypes format_types =
1228 net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP);
1229 match.fill_into_edit +=
1230 AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
1231 net::FormatUrl(navigation.url(), languages, format_types,
1232 net::UnescapeRule::SPACES, NULL, NULL,
1233 &inline_autocomplete_offset));
1234 if (!input_.prevent_inline_autocomplete())
1235 match.inline_autocomplete_offset = inline_autocomplete_offset;
1236 DCHECK((match.inline_autocomplete_offset == string16::npos) ||
1237 (match.inline_autocomplete_offset <= match.fill_into_edit.length()));
1238
1239 match.contents = net::FormatUrl(navigation.url(), languages,
1240 format_types, net::UnescapeRule::SPACES, NULL, NULL, &match_start);
1241 // If the first match in the untrimmed string was inside a scheme that we
1242 // trimmed, look for a subsequent match.
1243 if (match_start == string16::npos)
1244 match_start = match.contents.find(input);
1245 // Safe if |match_start| is npos; also safe if the input is longer than the
1246 // remaining contents after |match_start|.
1247 AutocompleteMatch::ClassifyLocationInString(match_start, input.length(),
1248 match.contents.length(), ACMatchClassification::URL,
1249 &match.contents_class);
initial.commit09911bf2008-07-26 23:55:291250
[email protected]55ce8f12012-05-09 04:44:081251 match.description = navigation.description();
[email protected]371dab12012-06-01 03:23:551252 AutocompleteMatch::ClassifyMatchInString(input, match.description,
1253 ACMatchClassification::NONE, &match.description_class);
initial.commit09911bf2008-07-26 23:55:291254 return match;
1255}
[email protected]4ab4c7c2010-11-24 04:49:341256
1257void SearchProvider::UpdateDone() {
[email protected]2cdf1172012-08-26 12:21:331258 // We're done when the timer isn't running, there are no suggest queries
1259 // pending, and we're not waiting on instant.
1260 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) &&
[email protected]b67d0a42012-09-04 20:57:351261 (instant_finalized_ ||
[email protected]e41982a72012-11-20 07:16:511262 !chrome::BrowserInstantController::IsInstantEnabled(profile_)));
[email protected]4ab4c7c2010-11-24 04:49:341263}