blob: fc7f993d3011ef2de25ad7ee9c07f7e75ae62d4b [file] [log] [blame]
[email protected]ffbec692012-02-26 20:26:421// Copyright (c) 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]3723e6e2012-06-11 21:06:5625#include "chrome/browser/autocomplete/history_url_provider.h"
[email protected]2c812ba02011-07-14 00:23:1526#include "chrome/browser/autocomplete/keyword_provider.h"
[email protected]371dab12012-06-01 03:23:5527#include "chrome/browser/autocomplete/url_prefix.h"
[email protected]ce560f82009-06-03 09:39:4428#include "chrome/browser/history/history.h"
[email protected]9d2db762012-06-19 00:01:1029#include "chrome/browser/history/history_service_factory.h"
[email protected]10c2d692012-05-11 05:32:2330#include "chrome/browser/history/in_memory_database.h"
[email protected]4ab4c7c2010-11-24 04:49:3431#include "chrome/browser/instant/instant_controller.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]8e5c89a2011-06-07 18:13:3336#include "chrome/browser/search_engines/template_url_service.h"
37#include "chrome/browser/search_engines/template_url_service_factory.h"
initial.commit09911bf2008-07-26 23:55:2938#include "chrome/common/pref_names.h"
[email protected]dcf7d352009-02-26 01:56:0239#include "chrome/common/url_constants.h"
initial.commit09911bf2008-07-26 23:55:2940#include "googleurl/src/url_util.h"
[email protected]34ac8f32009-02-22 23:03:2741#include "grit/generated_resources.h"
initial.commit09911bf2008-07-26 23:55:2942#include "net/base/escape.h"
[email protected]d3cf8682f02012-02-29 23:29:3443#include "net/base/load_flags.h"
[email protected]371dab12012-06-01 03:23:5544#include "net/base/net_util.h"
[email protected]319d9e6f2009-02-18 19:47:2145#include "net/http/http_response_headers.h"
[email protected]3dc1bc42012-06-19 08:20:5346#include "net/url_request/url_fetcher.h"
[email protected]319d9e6f2009-02-18 19:47:2147#include "net/url_request/url_request_status.h"
[email protected]c051a1b2011-01-21 23:30:1748#include "ui/base/l10n/l10n_util.h"
initial.commit09911bf2008-07-26 23:55:2949
[email protected]e1acf6f2008-10-27 20:43:3350using base::Time;
51using base::TimeDelta;
52
[email protected]51124552011-07-16 01:37:1053namespace {
54
55bool HasMultipleWords(const string16& text) {
56 base::i18n::BreakIterator i(text, base::i18n::BreakIterator::BREAK_WORD);
57 bool found_word = false;
58 if (i.Init()) {
59 while (i.Advance()) {
60 if (i.IsWord()) {
61 if (found_word)
62 return true;
63 found_word = true;
64 }
65 }
66 }
67 return false;
68}
69
[email protected]d1f0a7f2012-06-05 10:26:4270} // namespace
[email protected]51124552011-07-16 01:37:1071
[email protected]033f3422012-03-13 21:24:1872
[email protected]3954c3a2012-04-10 20:17:5573// SearchProvider::Providers --------------------------------------------------
[email protected]b547666d2009-04-23 16:37:5874
[email protected]85b8d6f2012-05-08 20:53:4775SearchProvider::Providers::Providers(TemplateURLService* template_url_service)
76 : template_url_service_(template_url_service) {
77}
78
79const TemplateURL* SearchProvider::Providers::GetDefaultProviderURL() const {
80 return default_provider_.empty() ? NULL :
81 template_url_service_->GetTemplateURLForKeyword(default_provider_);
82}
83
84const TemplateURL* SearchProvider::Providers::GetKeywordProviderURL() const {
85 return keyword_provider_.empty() ? NULL :
86 template_url_service_->GetTemplateURLForKeyword(keyword_provider_);
[email protected]257ab712009-04-14 17:16:2487}
88
[email protected]3954c3a2012-04-10 20:17:5589
90// SearchProvider -------------------------------------------------------------
91
92// static
93const int SearchProvider::kDefaultProviderURLFetcherID = 1;
94// static
95const int SearchProvider::kKeywordProviderURLFetcherID = 2;
96// static
97bool SearchProvider::query_suggest_immediately_ = false;
98
[email protected]30f5bc92012-06-26 04:14:5599SearchProvider::SearchProvider(AutocompleteProviderListener* listener,
100 Profile* profile)
[email protected]601858c02010-09-01 17:08:20101 : AutocompleteProvider(listener, profile, "Search"),
[email protected]85b8d6f2012-05-08 20:53:47102 providers_(TemplateURLServiceFactory::GetForProfile(profile)),
[email protected]601858c02010-09-01 17:08:20103 suggest_results_pending_(0),
[email protected]d1f0a7f2012-06-05 10:26:42104 has_suggested_relevance_(false),
105 verbatim_relevance_(-1),
[email protected]8e5cc282010-12-05 18:11:39106 have_suggest_results_(false),
[email protected]4ab4c7c2010-11-24 04:49:34107 instant_finalized_(false) {
[email protected]f5b95ba92012-03-27 14:05:19108 // We use GetSuggestNumberOfGroups() as the group ID to mean "not in field
109 // trial." Field trial groups run from 0 to GetSuggestNumberOfGroups() - 1
110 // (inclusive).
111 int suggest_field_trial_group_number =
112 AutocompleteFieldTrial::GetSuggestNumberOfGroups();
113 if (AutocompleteFieldTrial::InSuggestFieldTrial()) {
114 suggest_field_trial_group_number =
115 AutocompleteFieldTrial::GetSuggestGroupNameAsNumber();
116 }
117 // Add a beacon to the logs that'll allow us to identify later what
118 // suggest field trial group a user is in. Do this by incrementing a
119 // bucket in a histogram, where the bucket represents the user's
120 // suggest group id.
121 UMA_HISTOGRAM_ENUMERATION(
122 "Omnibox.SuggestFieldTrialBeacon",
123 suggest_field_trial_group_number,
124 AutocompleteFieldTrial::GetSuggestNumberOfGroups() + 1);
[email protected]4ab4c7c2010-11-24 04:49:34125}
126
[email protected]a2fedb1e2011-01-25 15:23:36127void SearchProvider::FinalizeInstantQuery(const string16& input_text,
128 const string16& suggest_text) {
[email protected]4ab4c7c2010-11-24 04:49:34129 if (done_ || instant_finalized_)
130 return;
131
132 instant_finalized_ = true;
133 UpdateDone();
134
[email protected]e918c112010-12-08 23:03:49135 if (input_text.empty()) {
[email protected]4ab4c7c2010-11-24 04:49:34136 // We only need to update the listener if we're actually done.
137 if (done_)
138 listener_->OnProviderUpdate(false);
139 return;
140 }
141
[email protected]9e789742011-01-10 23:27:32142 default_provider_suggest_text_ = suggest_text;
143
[email protected]a2fedb1e2011-01-25 15:23:36144 string16 adjusted_input_text(input_text);
[email protected]e918c112010-12-08 23:03:49145 AutocompleteInput::RemoveForcedQueryStringIfNecessary(input_.type(),
146 &adjusted_input_text);
147
[email protected]a2fedb1e2011-01-25 15:23:36148 const string16 text = adjusted_input_text + suggest_text;
[email protected]9acdcdc02012-05-21 20:58:36149 bool results_updated = false;
[email protected]4ab4c7c2010-11-24 04:49:34150 // Remove any matches that are identical to |text|. We don't use the
151 // destination_url for comparison as it varies depending upon the index passed
152 // to TemplateURL::ReplaceSearchTerms.
153 for (ACMatches::iterator i = matches_.begin(); i != matches_.end();) {
154 if (((i->type == AutocompleteMatch::SEARCH_HISTORY) ||
155 (i->type == AutocompleteMatch::SEARCH_SUGGEST)) &&
156 (i->fill_into_edit == text)) {
[email protected]e030de62010-11-24 05:41:19157 i = matches_.erase(i);
[email protected]9acdcdc02012-05-21 20:58:36158 results_updated = true;
[email protected]4ab4c7c2010-11-24 04:49:34159 } else {
160 ++i;
161 }
162 }
163
[email protected]55ce8f12012-05-09 04:44:08164 // Add the new instant suggest result. We give it a rank higher than
[email protected]4ab4c7c2010-11-24 04:49:34165 // SEARCH_WHAT_YOU_TYPED so that it gets autocompleted.
166 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
167 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
168 TemplateURLRef::NO_SUGGESTION_CHOSEN;
169 MatchMap match_map;
[email protected]382a0642012-06-06 06:13:52170 AddMatchToMap(text, adjusted_input_text, GetVerbatimRelevance() + 1,
[email protected]4ab4c7c2010-11-24 04:49:34171 AutocompleteMatch::SEARCH_SUGGEST,
[email protected]55ce8f12012-05-09 04:44:08172 did_not_accept_default_suggestion, false, &match_map);
[email protected]9acdcdc02012-05-21 20:58:36173 if (!match_map.empty()) {
174 matches_.push_back(match_map.begin()->second);
175 results_updated = true;
176 }
[email protected]4ab4c7c2010-11-24 04:49:34177
[email protected]9acdcdc02012-05-21 20:58:36178 if (results_updated || done_)
179 listener_->OnProviderUpdate(results_updated);
[email protected]601858c02010-09-01 17:08:20180}
181
initial.commit09911bf2008-07-26 23:55:29182void SearchProvider::Start(const AutocompleteInput& input,
[email protected]8deeb952008-10-09 18:21:27183 bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:29184 matches_.clear();
185
[email protected]ea3b9a502011-04-04 14:19:37186 instant_finalized_ =
187 (input.matches_requested() != AutocompleteInput::ALL_MATCHES);
[email protected]4ab4c7c2010-11-24 04:49:34188
[email protected]6c85aa02009-02-27 12:08:09189 // Can't return search/suggest results for bogus input or without a profile.
initial.commit09911bf2008-07-26 23:55:29190 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
191 Stop();
192 return;
193 }
194
[email protected]257ab712009-04-14 17:16:24195 keyword_input_text_.clear();
196 const TemplateURL* keyword_provider =
197 KeywordProvider::GetSubstitutingTemplateURLForInput(profile_, input,
198 &keyword_input_text_);
[email protected]8d457132010-11-04 18:13:40199 if (keyword_input_text_.empty())
[email protected]257ab712009-04-14 17:16:24200 keyword_provider = NULL;
[email protected]257ab712009-04-14 17:16:24201
[email protected]85b8d6f2012-05-08 20:53:47202 TemplateURLService* model = providers_.template_url_service();
203 DCHECK(model);
204 model->Load();
205 const TemplateURL* default_provider = model->GetDefaultSearchProvider();
[email protected]9b74ab52012-03-30 16:08:07206 if (default_provider && !default_provider->SupportsReplacement())
[email protected]257ab712009-04-14 17:16:24207 default_provider = NULL;
208
209 if (keyword_provider == default_provider)
[email protected]e17511f2011-07-13 14:09:18210 default_provider = NULL; // No use in querying the same provider twice.
[email protected]257ab712009-04-14 17:16:24211
212 if (!default_provider && !keyword_provider) {
213 // No valid providers.
initial.commit09911bf2008-07-26 23:55:29214 Stop();
215 return;
216 }
217
218 // If we're still running an old query but have since changed the query text
[email protected]257ab712009-04-14 17:16:24219 // or the providers, abort the query.
[email protected]85b8d6f2012-05-08 20:53:47220 string16 default_provider_keyword(default_provider ?
221 default_provider->keyword() : string16());
222 string16 keyword_provider_keyword(keyword_provider ?
223 keyword_provider->keyword() : string16());
[email protected]9e789742011-01-10 23:27:32224 if (!minimal_changes ||
[email protected]85b8d6f2012-05-08 20:53:47225 !providers_.equal(default_provider_keyword, keyword_provider_keyword)) {
[email protected]9e789742011-01-10 23:27:32226 if (done_)
227 default_provider_suggest_text_.clear();
228 else
229 Stop();
[email protected]257ab712009-04-14 17:16:24230 }
initial.commit09911bf2008-07-26 23:55:29231
[email protected]85b8d6f2012-05-08 20:53:47232 providers_.set(default_provider_keyword, keyword_provider_keyword);
initial.commit09911bf2008-07-26 23:55:29233
234 if (input.text().empty()) {
235 // User typed "?" alone. Give them a placeholder result indicating what
236 // this syntax does.
[email protected]257ab712009-04-14 17:16:24237 if (default_provider) {
[email protected]69c579e2010-04-23 20:01:00238 AutocompleteMatch match;
239 match.provider = this;
[email protected]a2fedb1e2011-01-25 15:23:36240 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
[email protected]257ab712009-04-14 17:16:24241 match.contents_class.push_back(
[email protected]2c33dd22010-02-11 21:46:35242 ACMatchClassification(0, ACMatchClassification::NONE));
[email protected]85b8d6f2012-05-08 20:53:47243 match.keyword = providers_.default_provider();
[email protected]257ab712009-04-14 17:16:24244 matches_.push_back(match);
245 }
initial.commit09911bf2008-07-26 23:55:29246 Stop();
247 return;
248 }
249
250 input_ = input;
251
[email protected]8d457132010-11-04 18:13:40252 DoHistoryQuery(minimal_changes);
[email protected]8deeb952008-10-09 18:21:27253 StartOrStopSuggestQuery(minimal_changes);
initial.commit09911bf2008-07-26 23:55:29254 ConvertResultsToAutocompleteMatches();
255}
256
[email protected]55ce8f12012-05-09 04:44:08257SearchProvider::Result::Result(int relevance) : relevance_(relevance) {}
258SearchProvider::Result::~Result() {}
259
260SearchProvider::SuggestResult::SuggestResult(const string16& suggestion,
261 int relevance)
262 : Result(relevance),
263 suggestion_(suggestion) {
264}
265
266SearchProvider::SuggestResult::~SuggestResult() {}
267
268SearchProvider::NavigationResult::NavigationResult(const GURL& url,
269 const string16& description,
270 int relevance)
271 : Result(relevance),
272 url_(url),
273 description_(description) {
274 DCHECK(url_.is_valid());
275}
276
277SearchProvider::NavigationResult::~NavigationResult() {}
278
279class SearchProvider::CompareScoredResults {
[email protected]51124552011-07-16 01:37:10280 public:
[email protected]55ce8f12012-05-09 04:44:08281 bool operator()(const Result& a, const Result& b) {
[email protected]51124552011-07-16 01:37:10282 // Sort in descending relevance order.
[email protected]55ce8f12012-05-09 04:44:08283 return a.relevance() > b.relevance();
[email protected]51124552011-07-16 01:37:10284 }
285};
286
initial.commit09911bf2008-07-26 23:55:29287void SearchProvider::Run() {
288 // Start a new request with the current input.
289 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24290 suggest_results_pending_ = 0;
[email protected]a0ad93ea2012-05-07 22:11:53291 time_suggest_request_sent_ = base::TimeTicks::Now();
[email protected]85b8d6f2012-05-08 20:53:47292 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
293 if (default_url && !default_url->suggestions_url().empty()) {
[email protected]257ab712009-04-14 17:16:24294 suggest_results_pending_++;
[email protected]033f3422012-03-13 21:24:18295 default_fetcher_.reset(CreateSuggestFetcher(kDefaultProviderURLFetcherID,
[email protected]85b8d6f2012-05-08 20:53:47296 default_url->suggestions_url_ref(), input_.text()));
[email protected]3954c3a2012-04-10 20:17:55297 }
[email protected]85b8d6f2012-05-08 20:53:47298 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
299 if (keyword_url && !keyword_url->suggestions_url().empty()) {
[email protected]3954c3a2012-04-10 20:17:55300 suggest_results_pending_++;
301 keyword_fetcher_.reset(CreateSuggestFetcher(kKeywordProviderURLFetcherID,
[email protected]85b8d6f2012-05-08 20:53:47302 keyword_url->suggestions_url_ref(), keyword_input_text_));
[email protected]257ab712009-04-14 17:16:24303 }
[email protected]85b8d6f2012-05-08 20:53:47304
305 // Both the above can fail if the providers have been modified or deleted
306 // since the query began.
307 if (suggest_results_pending_ == 0) {
308 UpdateDone();
309 // We only need to update the listener if we're actually done.
310 if (done_)
311 listener_->OnProviderUpdate(false);
312 }
initial.commit09911bf2008-07-26 23:55:29313}
314
315void SearchProvider::Stop() {
initial.commit09911bf2008-07-26 23:55:29316 StopSuggest();
317 done_ = true;
[email protected]9e789742011-01-10 23:27:32318 default_provider_suggest_text_.clear();
initial.commit09911bf2008-07-26 23:55:29319}
320
[email protected]0e9e8782012-05-15 23:01:51321void SearchProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
322 provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
323 metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
324 new_entry.set_provider(AsOmniboxEventProviderType());
325 new_entry.set_provider_done(done_);
326}
327
[email protected]10c2d692012-05-11 05:32:23328void SearchProvider::OnURLFetchComplete(const net::URLFetcher* source) {
initial.commit09911bf2008-07-26 23:55:29329 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24330 suggest_results_pending_--;
[email protected]1cb2dac2010-03-08 21:49:15331 DCHECK_GE(suggest_results_pending_, 0); // Should never go negative.
[email protected]ec9207d32008-09-26 00:51:06332 const net::HttpResponseHeaders* const response_headers =
[email protected]7cc6e5632011-10-25 17:56:12333 source->GetResponseHeaders();
[email protected]c530c852011-10-24 18:18:34334 std::string json_data;
335 source->GetResponseAsString(&json_data);
[email protected]6c85aa02009-02-27 12:08:09336 // JSON is supposed to be UTF-8, but some suggest service providers send JSON
337 // files in non-UTF-8 encodings. The actual encoding is usually specified in
338 // the Content-Type header field.
[email protected]ec9207d32008-09-26 00:51:06339 if (response_headers) {
340 std::string charset;
341 if (response_headers->GetCharset(&charset)) {
[email protected]a2fedb1e2011-01-25 15:23:36342 string16 data_16;
[email protected]ec9207d32008-09-26 00:51:06343 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
[email protected]c530c852011-10-24 18:18:34344 if (base::CodepageToUTF16(json_data, charset.c_str(),
[email protected]a2fedb1e2011-01-25 15:23:36345 base::OnStringConversionError::FAIL,
346 &data_16))
347 json_data = UTF16ToUTF8(data_16);
[email protected]ec9207d32008-09-26 00:51:06348 }
349 }
350
[email protected]d7ad4772012-06-01 03:12:54351 const bool is_keyword = (source == keyword_fetcher_.get());
[email protected]013e9a02012-05-18 20:27:10352 const bool request_succeeded =
353 source->GetStatus().is_success() && source->GetResponseCode() == 200;
[email protected]a0ad93ea2012-05-07 22:11:53354
355 // Record response time for suggest requests sent to Google. We care
356 // only about the common case: the Google default provider used in
357 // non-keyword mode.
[email protected]85b8d6f2012-05-08 20:53:47358 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
[email protected]55ce8f12012-05-09 04:44:08359 if (!is_keyword && default_url &&
[email protected]85b8d6f2012-05-08 20:53:47360 (default_url->prepopulate_id() == SEARCH_ENGINE_GOOGLE)) {
[email protected]013e9a02012-05-18 20:27:10361 const base::TimeDelta elapsed_time =
362 base::TimeTicks::Now() - time_suggest_request_sent_;
363 if (request_succeeded) {
364 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Success.GoogleResponseTime",
365 elapsed_time);
366 } else {
367 UMA_HISTOGRAM_TIMES("Omnibox.SuggestRequest.Failure.GoogleResponseTime",
368 elapsed_time);
369 }
[email protected]b4cebf82008-12-29 19:59:08370 }
371
[email protected]d7ad4772012-06-01 03:12:54372 bool results_updated = false;
373 if (request_succeeded) {
374 JSONStringValueSerializer deserializer(json_data);
375 deserializer.set_allow_trailing_comma(true);
376 scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL));
377 results_updated = data.get() && ParseSuggestResults(data.get(), is_keyword);
378 }
379
initial.commit09911bf2008-07-26 23:55:29380 ConvertResultsToAutocompleteMatches();
[email protected]d7ad4772012-06-01 03:12:54381 if (done_ || results_updated)
382 listener_->OnProviderUpdate(results_updated);
initial.commit09911bf2008-07-26 23:55:29383}
384
[email protected]601858c02010-09-01 17:08:20385SearchProvider::~SearchProvider() {
386}
387
[email protected]8d457132010-11-04 18:13:40388void SearchProvider::DoHistoryQuery(bool minimal_changes) {
389 // The history query results are synchronous, so if minimal_changes is true,
390 // we still have the last results and don't need to do anything.
391 if (minimal_changes)
initial.commit09911bf2008-07-26 23:55:29392 return;
393
[email protected]8d457132010-11-04 18:13:40394 keyword_history_results_.clear();
395 default_history_results_.clear();
initial.commit09911bf2008-07-26 23:55:29396
[email protected]8d457132010-11-04 18:13:40397 HistoryService* const history_service =
[email protected]9d2db762012-06-19 00:01:10398 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
[email protected]8d457132010-11-04 18:13:40399 history::URLDatabase* url_db = history_service ?
400 history_service->InMemoryDatabase() : NULL;
401 if (!url_db)
initial.commit09911bf2008-07-26 23:55:29402 return;
403
[email protected]51124552011-07-16 01:37:10404 // Request history for both the keyword and default provider. We grab many
405 // more matches than we'll ultimately clamp to so that if there are several
406 // recent multi-word matches who scores are lowered (see
407 // AddHistoryResultsToMap()), they won't crowd out older, higher-scoring
408 // matches. Note that this doesn't fix the problem entirely, but merely
409 // limits it to cases with a very large number of such multi-word matches; for
410 // now, this seems OK compared with the complexity of a real fix, which would
411 // require multiple searches and tracking of "single- vs. multi-word" in the
412 // database.
413 int num_matches = kMaxMatches * 5;
[email protected]85b8d6f2012-05-08 20:53:47414 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
415 if (default_url) {
416 url_db->GetMostRecentKeywordSearchTerms(default_url->id(), input_.text(),
417 num_matches, &default_history_results_);
[email protected]257ab712009-04-14 17:16:24418 }
[email protected]85b8d6f2012-05-08 20:53:47419 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
420 if (keyword_url) {
421 url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
[email protected]3954c3a2012-04-10 20:17:55422 keyword_input_text_, num_matches, &keyword_history_results_);
423 }
initial.commit09911bf2008-07-26 23:55:29424}
425
[email protected]8deeb952008-10-09 18:21:27426void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]6c85aa02009-02-27 12:08:09427 // Don't send any queries to the server until some time has elapsed after
428 // the last keypress, to avoid flooding the server with requests we are
429 // likely to end up throwing away anyway.
[email protected]02c3f6832011-11-16 18:37:40430 const int kQueryDelayMs = 200;
[email protected]6c85aa02009-02-27 12:08:09431
[email protected]83c726482008-09-10 06:36:34432 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29433 StopSuggest();
[email protected]55ce8f12012-05-09 04:44:08434 ClearResults();
initial.commit09911bf2008-07-26 23:55:29435 return;
436 }
437
438 // For the minimal_changes case, if we finished the previous query and still
439 // have its results, or are allowed to keep running it, just do that, rather
440 // than starting a new query.
441 if (minimal_changes &&
[email protected]ea3b9a502011-04-04 14:19:37442 (have_suggest_results_ ||
443 (!done_ &&
444 input_.matches_requested() == AutocompleteInput::ALL_MATCHES)))
initial.commit09911bf2008-07-26 23:55:29445 return;
446
447 // We can't keep running any previous query, so halt it.
448 StopSuggest();
[email protected]d1f0a7f2012-06-05 10:26:42449
450 // Remove existing results that cannot inline autocomplete the new input.
451 RemoveStaleResults();
initial.commit09911bf2008-07-26 23:55:29452
453 // We can't start a new query if we're only allowed synchronous results.
[email protected]ea3b9a502011-04-04 14:19:37454 if (input_.matches_requested() != AutocompleteInput::ALL_MATCHES)
initial.commit09911bf2008-07-26 23:55:29455 return;
456
[email protected]257ab712009-04-14 17:16:24457 // We'll have at least one pending fetch. Set it to 1 now, but the value is
458 // correctly set in Run. As Run isn't invoked immediately we need to set this
459 // now, else we won't think we're waiting on results from the server when we
460 // really are.
461 suggest_results_pending_ = 1;
462
initial.commit09911bf2008-07-26 23:55:29463 // Kick off a timer that will start the URL fetch if it completes before
464 // the user types another character.
[email protected]b547666d2009-04-23 16:37:58465 int delay = query_suggest_immediately_ ? 0 : kQueryDelayMs;
[email protected]d323a172011-09-02 18:23:02466 timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(delay), this,
467 &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29468}
469
[email protected]83c726482008-09-10 06:36:34470bool SearchProvider::IsQuerySuitableForSuggest() const {
[email protected]3954c3a2012-04-10 20:17:55471 // Don't run Suggest in incognito mode, if the engine doesn't support it, or
472 // if the user has disabled it.
[email protected]85b8d6f2012-05-08 20:53:47473 const TemplateURL* default_url = providers_.GetDefaultProviderURL();
474 const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
[email protected]83c726482008-09-10 06:36:34475 if (profile_->IsOffTheRecord() ||
[email protected]85b8d6f2012-05-08 20:53:47476 ((!default_url || default_url->suggestions_url().empty()) &&
477 (!keyword_url || keyword_url->suggestions_url().empty())) ||
[email protected]83c726482008-09-10 06:36:34478 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
479 return false;
480
[email protected]cac59d32010-08-09 23:23:14481 // If the input type might be a URL, we take extra care so that private data
[email protected]83c726482008-09-10 06:36:34482 // isn't sent to the server.
[email protected]83c726482008-09-10 06:36:34483
[email protected]cac59d32010-08-09 23:23:14484 // FORCED_QUERY means the user is explicitly asking us to search for this, so
485 // we assume it isn't a URL and/or there isn't private data.
486 if (input_.type() == AutocompleteInput::FORCED_QUERY)
487 return true;
[email protected]83c726482008-09-10 06:36:34488
[email protected]cac59d32010-08-09 23:23:14489 // Next we check the scheme. If this is UNKNOWN/REQUESTED_URL/URL with a
490 // scheme that isn't http/https/ftp, we shouldn't send it. Sending things
491 // like file: and data: is both a waste of time and a disclosure of
492 // potentially private, local data. Other "schemes" may actually be
493 // usernames, and we don't want to send passwords. If the scheme is OK, we
494 // still need to check other cases below. If this is QUERY, then the presence
495 // of these schemes means the user explicitly typed one, and thus this is
496 // probably a URL that's being entered and happens to currently be invalid --
497 // in which case we again want to run our checks below. Other QUERY cases are
498 // less likely to be URLs and thus we assume we're OK.
[email protected]a2fedb1e2011-01-25 15:23:36499 if (!LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpScheme) &&
500 !LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
501 !LowerCaseEqualsASCII(input_.scheme(), chrome::kFtpScheme))
[email protected]cac59d32010-08-09 23:23:14502 return (input_.type() == AutocompleteInput::QUERY);
503
504 // Don't send URLs with usernames, queries or refs. Some of these are
505 // private, and the Suggest server is unlikely to have any useful results
506 // for any of them. Also don't send URLs with ports, as we may initially
507 // think that a username + password is a host + port (and we don't want to
508 // send usernames/passwords), and even if the port really is a port, the
509 // server is once again unlikely to have and useful results.
510 const url_parse::Parsed& parts = input_.parts();
511 if (parts.username.is_nonempty() || parts.port.is_nonempty() ||
512 parts.query.is_nonempty() || parts.ref.is_nonempty())
513 return false;
514
515 // Don't send anything for https except the hostname. Hostnames are OK
516 // because they are visible when the TCP connection is established, but the
517 // specific path may reveal private information.
[email protected]a2fedb1e2011-01-25 15:23:36518 if (LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
519 parts.path.is_nonempty())
[email protected]cac59d32010-08-09 23:23:14520 return false;
[email protected]83c726482008-09-10 06:36:34521
522 return true;
523}
524
initial.commit09911bf2008-07-26 23:55:29525void SearchProvider::StopSuggest() {
[email protected]257ab712009-04-14 17:16:24526 suggest_results_pending_ = 0;
[email protected]2d316662008-09-03 18:18:14527 timer_.Stop();
[email protected]257ab712009-04-14 17:16:24528 // Stop any in-progress URL fetches.
529 keyword_fetcher_.reset();
530 default_fetcher_.reset();
[email protected]55ce8f12012-05-09 04:44:08531}
532
533void SearchProvider::ClearResults() {
[email protected]257ab712009-04-14 17:16:24534 keyword_suggest_results_.clear();
535 default_suggest_results_.clear();
536 keyword_navigation_results_.clear();
537 default_navigation_results_.clear();
[email protected]d1f0a7f2012-06-05 10:26:42538 has_suggested_relevance_ = false;
539 verbatim_relevance_ = -1;
initial.commit09911bf2008-07-26 23:55:29540 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29541}
542
[email protected]d1f0a7f2012-06-05 10:26:42543void SearchProvider::RemoveStaleResults() {
544 RemoveStaleSuggestResults(&keyword_suggest_results_, true);
545 RemoveStaleSuggestResults(&default_suggest_results_, false);
546 RemoveStaleNavigationResults(&keyword_navigation_results_, true);
547 RemoveStaleNavigationResults(&default_navigation_results_, false);
548}
549
550void SearchProvider::RemoveStaleSuggestResults(SuggestResults* list,
551 bool is_keyword) {
552 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
553 for (SuggestResults::iterator i = list->begin(); i < list->end();)
554 i = StartsWith(i->suggestion(), input, false) ? (i + 1) : list->erase(i);
555}
556
557void SearchProvider::RemoveStaleNavigationResults(NavigationResults* list,
558 bool is_keyword) {
559 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
560 for (NavigationResults::iterator i = list->begin(); i < list->end();) {
561 const string16 fill(AutocompleteInput::FormattedStringWithEquivalentMeaning(
562 i->url(), StringForURLDisplay(i->url(), true, false)));
563 i = URLPrefix::BestURLPrefix(fill, input) ? (i + 1) : list->erase(i);
564 }
565}
566
567void SearchProvider::ApplyCalculatedRelevance() {
568 ApplyCalculatedSuggestRelevance(&keyword_suggest_results_, true);
569 ApplyCalculatedSuggestRelevance(&default_suggest_results_, false);
570 ApplyCalculatedNavigationRelevance(&keyword_navigation_results_, true);
571 ApplyCalculatedNavigationRelevance(&default_navigation_results_, false);
572 has_suggested_relevance_ = false;
573 verbatim_relevance_ = -1;
574}
575
576void SearchProvider::ApplyCalculatedSuggestRelevance(SuggestResults* list,
577 bool is_keyword) {
578 for (size_t i = 0; i < list->size(); ++i) {
579 (*list)[i].set_relevance(CalculateRelevanceForSuggestion(is_keyword) +
580 (list->size() - i - 1));
581 }
582}
583
584void SearchProvider::ApplyCalculatedNavigationRelevance(NavigationResults* list,
585 bool is_keyword) {
586 for (size_t i = 0; i < list->size(); ++i) {
587 (*list)[i].set_relevance(CalculateRelevanceForNavigation(is_keyword) +
588 (list->size() - i - 1));
589 }
590}
591
[email protected]15fb2aa2012-05-22 22:52:59592net::URLFetcher* SearchProvider::CreateSuggestFetcher(
[email protected]7cc6e5632011-10-25 17:56:12593 int id,
[email protected]3954c3a2012-04-10 20:17:55594 const TemplateURLRef& suggestions_url,
[email protected]7cc6e5632011-10-25 17:56:12595 const string16& text) {
[email protected]360ba052012-04-04 17:26:13596 DCHECK(suggestions_url.SupportsReplacement());
[email protected]3dc1bc42012-06-19 08:20:53597 net::URLFetcher* fetcher = net::URLFetcher::Create(id,
[email protected]bca359b2012-06-24 07:53:04598 GURL(suggestions_url.ReplaceSearchTerms(
599 TemplateURLRef::SearchTermsArgs(text))),
[email protected]d3ec669b2012-05-23 07:12:14600 net::URLFetcher::GET, this);
[email protected]7cc6e5632011-10-25 17:56:12601 fetcher->SetRequestContext(profile_->GetRequestContext());
[email protected]d3cf8682f02012-02-29 23:29:34602 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
[email protected]257ab712009-04-14 17:16:24603 fetcher->Start();
604 return fetcher;
605}
606
[email protected]d7ad4772012-06-01 03:12:54607bool SearchProvider::ParseSuggestResults(Value* root_val, bool is_keyword) {
608 // TODO(pkasting): Fix |have_suggest_results_|; see http://crbug.com/130631
609 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29610
[email protected]d7ad4772012-06-01 03:12:54611 string16 query;
612 ListValue* root_list = NULL;
613 ListValue* results = NULL;
614 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
615 if (!root_val->GetAsList(&root_list) || !root_list->GetString(0, &query) ||
616 (query != input_text) || !root_list->GetList(1, &results))
initial.commit09911bf2008-07-26 23:55:29617 return false;
618
[email protected]55ce8f12012-05-09 04:44:08619 // 3rd element: Description list.
[email protected]d7ad4772012-06-01 03:12:54620 ListValue* descriptions = NULL;
621 root_list->GetList(2, &descriptions);
initial.commit09911bf2008-07-26 23:55:29622
[email protected]55ce8f12012-05-09 04:44:08623 // 4th element: Disregard the query URL list for now.
initial.commit09911bf2008-07-26 23:55:29624
[email protected]d1f0a7f2012-06-05 10:26:42625 // Reset suggested relevance information from the default provider.
626 if (!is_keyword) {
627 has_suggested_relevance_ = false;
628 verbatim_relevance_ = -1;
629 }
630
[email protected]55ce8f12012-05-09 04:44:08631 // 5th element: Optional key-value pairs from the Suggest server.
[email protected]d7ad4772012-06-01 03:12:54632 ListValue* types = NULL;
[email protected]d1f0a7f2012-06-05 10:26:42633 ListValue* relevances = NULL;
634 DictionaryValue* extras = NULL;
635 if (root_list->GetDictionary(4, &extras)) {
636 extras->GetList("google:suggesttype", &types);
637
638 // Only accept relevance suggestions if Instant is disabled.
639 if (!is_keyword && !InstantController::IsEnabled(profile_)) {
640 // Discard this list if its size does not match that of the suggestions.
641 if (extras->GetList("google:suggestrelevance", &relevances) &&
642 relevances->GetSize() != results->GetSize())
643 relevances = NULL;
644
645 extras->GetInteger("google:verbatimrelevance", &verbatim_relevance_);
646 }
initial.commit09911bf2008-07-26 23:55:29647 }
648
[email protected]d7ad4772012-06-01 03:12:54649 SuggestResults* suggest_results =
650 is_keyword ? &keyword_suggest_results_ : &default_suggest_results_;
651 NavigationResults* navigation_results =
652 is_keyword ? &keyword_navigation_results_ : &default_navigation_results_;
initial.commit09911bf2008-07-26 23:55:29653
[email protected]d1f0a7f2012-06-05 10:26:42654 // Clear the previous results now that new results are available.
655 suggest_results->clear();
656 navigation_results->clear();
657
658 string16 result, title;
659 std::string type;
660 int relevance = -1;
[email protected]d7ad4772012-06-01 03:12:54661 for (size_t index = 0; results->GetString(index, &result); ++index) {
[email protected]8e81f5092010-09-29 23:19:40662 // Google search may return empty suggestions for weird input characters,
[email protected]55ce8f12012-05-09 04:44:08663 // they make no sense at all and can cause problems in our code.
[email protected]d7ad4772012-06-01 03:12:54664 if (result.empty())
[email protected]8e81f5092010-09-29 23:19:40665 continue;
666
[email protected]d1f0a7f2012-06-05 10:26:42667 // Apply valid suggested relevance scores; discard invalid lists.
668 if (relevances != NULL && !relevances->GetInteger(index, &relevance))
669 relevances = NULL;
[email protected]d7ad4772012-06-01 03:12:54670 if (types && types->GetString(index, &type) && (type == "NAVIGATION")) {
[email protected]d1f0a7f2012-06-05 10:26:42671 // Do not blindly trust the URL coming from the server to be valid.
672 GURL url(URLFixerUpper::FixupURL(UTF16ToUTF8(result), std::string()));
673 if (url.is_valid()) {
674 if (descriptions != NULL)
675 descriptions->GetString(index, &title);
676 navigation_results->push_back(NavigationResult(url, title, relevance));
initial.commit09911bf2008-07-26 23:55:29677 }
678 } else {
[email protected]d1f0a7f2012-06-05 10:26:42679 // TODO(kochi): Improve calculator result presentation.
680 suggest_results->push_back(SuggestResult(result, relevance));
initial.commit09911bf2008-07-26 23:55:29681 }
682 }
683
[email protected]d1f0a7f2012-06-05 10:26:42684 // Apply calculated relevance scores if a valid list was not provided.
685 if (relevances == NULL) {
686 ApplyCalculatedSuggestRelevance(suggest_results, is_keyword);
687 ApplyCalculatedNavigationRelevance(navigation_results, is_keyword);
688 } else if (!is_keyword) {
689 has_suggested_relevance_ = true;
690 }
691
[email protected]d7ad4772012-06-01 03:12:54692 have_suggest_results_ = true;
[email protected]d1f0a7f2012-06-05 10:26:42693 return true;
initial.commit09911bf2008-07-26 23:55:29694}
695
696void SearchProvider::ConvertResultsToAutocompleteMatches() {
697 // Convert all the results to matches and add them to a map, so we can keep
698 // the most relevant match for each result.
699 MatchMap map;
[email protected]257ab712009-04-14 17:16:24700 const Time no_time;
701 int did_not_accept_keyword_suggestion = keyword_suggest_results_.empty() ?
initial.commit09911bf2008-07-26 23:55:29702 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
703 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]257ab712009-04-14 17:16:24704 // Keyword what you typed results are handled by the KeywordProvider.
initial.commit09911bf2008-07-26 23:55:29705
[email protected]382a0642012-06-06 06:13:52706 int verbatim_relevance = GetVerbatimRelevance();
[email protected]257ab712009-04-14 17:16:24707 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
[email protected]55ce8f12012-05-09 04:44:08708 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
709 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]d1f0a7f2012-06-05 10:26:42710 if (verbatim_relevance > 0) {
711 AddMatchToMap(input_.text(), input_.text(), verbatim_relevance,
712 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
713 did_not_accept_default_suggestion, false, &map);
714 }
[email protected]b9ce8532012-05-24 20:51:15715 const size_t what_you_typed_size = map.size();
[email protected]85b8d6f2012-05-08 20:53:47716 if (!default_provider_suggest_text_.empty()) {
717 AddMatchToMap(input_.text() + default_provider_suggest_text_,
[email protected]55ce8f12012-05-09 04:44:08718 input_.text(), verbatim_relevance + 1,
[email protected]85b8d6f2012-05-08 20:53:47719 AutocompleteMatch::SEARCH_SUGGEST,
[email protected]55ce8f12012-05-09 04:44:08720 did_not_accept_default_suggestion, false, &map);
initial.commit09911bf2008-07-26 23:55:29721 }
722
[email protected]257ab712009-04-14 17:16:24723 AddHistoryResultsToMap(keyword_history_results_, true,
724 did_not_accept_keyword_suggestion, &map);
725 AddHistoryResultsToMap(default_history_results_, false,
726 did_not_accept_default_suggestion, &map);
727
[email protected]55ce8f12012-05-09 04:44:08728 AddSuggestResultsToMap(keyword_suggest_results_, true, &map);
729 AddSuggestResultsToMap(default_suggest_results_, false, &map);
initial.commit09911bf2008-07-26 23:55:29730
731 // Now add the most relevant matches from the map to |matches_|.
732 matches_.clear();
733 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
734 matches_.push_back(i->second);
735
[email protected]257ab712009-04-14 17:16:24736 AddNavigationResultsToMatches(keyword_navigation_results_, true);
737 AddNavigationResultsToMatches(default_navigation_results_, false);
initial.commit09911bf2008-07-26 23:55:29738
[email protected]b9ce8532012-05-24 20:51:15739 // Allow an additional match for "what you typed" if it's present.
740 const size_t max_total_matches = kMaxMatches + what_you_typed_size;
initial.commit09911bf2008-07-26 23:55:29741 std::partial_sort(matches_.begin(),
742 matches_.begin() + std::min(max_total_matches, matches_.size()),
743 matches_.end(), &AutocompleteMatch::MoreRelevant);
[email protected]3723e6e2012-06-11 21:06:56744
745 // If the top match is effectively 'verbatim' but exceeds the calculated
746 // verbatim relevance, and REQUESTED_URL |input_| has a |desired_tld|
747 // (for example ".com" when the CTRL key is pressed for REQUESTED_URL input),
748 // promote a URL_WHAT_YOU_TYPED match to the top. Otherwise, these matches can
749 // stomp the HistoryURLProvider's similar transient URL_WHAT_YOU_TYPED match,
750 // and CTRL+ENTER will invoke the search instead of the expected navigation.
751 if ((has_suggested_relevance_ || verbatim_relevance_ >= 0) &&
752 input_.type() == AutocompleteInput::REQUESTED_URL &&
753 !input_.desired_tld().empty() && !matches_.empty() &&
754 matches_.front().relevance > CalculateRelevanceForVerbatim() &&
755 matches_.front().fill_into_edit == input_.text()) {
756 AutocompleteMatch match = HistoryURLProvider::SuggestExactInput(
757 this, input_, !HasHTTPScheme(input_.text()));
758 match.relevance = matches_.front().relevance + 1;
759 matches_.insert(matches_.begin(), match);
760 }
761
initial.commit09911bf2008-07-26 23:55:29762 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02763 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29764
[email protected]382a0642012-06-06 06:13:52765 // Check constraints that may be violated by suggested relevances.
[email protected]d1f0a7f2012-06-05 10:26:42766 if (!matches_.empty() &&
[email protected]e6acd002012-06-16 22:27:47767 (has_suggested_relevance_ || verbatim_relevance_ >= 0)) {
[email protected]382a0642012-06-06 06:13:52768 bool reconstruct_matches = false;
[email protected]1beee342012-06-19 22:22:28769 if (matches_.front().type != AutocompleteMatch::SEARCH_WHAT_YOU_TYPED &&
770 matches_.front().type != AutocompleteMatch::URL_WHAT_YOU_TYPED &&
771 matches_.front().inline_autocomplete_offset == string16::npos &&
772 matches_.front().fill_into_edit != input_.text()) {
773 // Disregard suggested relevances if the top match is not SWYT, inlinable,
774 // or URL_WHAT_YOU_TYPED (which may be top match regardless of inlining).
775 // For example, input "foo" should not invoke a search for "bar", which
776 // would happen if the "bar" search match outranked all other matches.
777 ApplyCalculatedRelevance();
778 reconstruct_matches = true;
779 } else if (matches_.front().relevance < CalculateRelevanceForVerbatim()) {
780 // Disregard the suggested verbatim relevance if the top score is below
781 // the usual verbatim value. For example, a BarProvider may rely on
782 // SearchProvider's verbatim or inlineable matches for input "foo" to
783 // always outrank its own lowly-ranked non-inlineable "bar" match.
784 verbatim_relevance_ = -1;
785 reconstruct_matches = true;
[email protected]e6acd002012-06-16 22:27:47786 }
787 if (input_.type() == AutocompleteInput::URL &&
788 matches_.front().relevance > CalculateRelevanceForVerbatim() &&
789 (matches_.front().type == AutocompleteMatch::SEARCH_SUGGEST ||
790 matches_.front().type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED)) {
791 // Disregard the suggested search and verbatim relevances if the input
792 // type is URL and the top match is a highly-ranked search suggestion.
793 // For example, prevent a search for "foo.com" from outranking another
794 // provider's navigation for "foo.com" or "foo.com/url_from_history".
795 // Reconstruction will also ensure that the new top match is inlineable.
796 ApplyCalculatedSuggestRelevance(&keyword_suggest_results_, true);
797 ApplyCalculatedSuggestRelevance(&default_suggest_results_, false);
[email protected]382a0642012-06-06 06:13:52798 verbatim_relevance_ = -1;
799 reconstruct_matches = true;
800 }
801 if (reconstruct_matches) {
802 ConvertResultsToAutocompleteMatches();
803 return;
804 }
[email protected]d1f0a7f2012-06-05 10:26:42805 }
806
[email protected]cc63dea2008-08-21 20:56:31807 UpdateStarredStateOfMatches();
[email protected]4ab4c7c2010-11-24 04:49:34808 UpdateDone();
[email protected]257ab712009-04-14 17:16:24809}
810
811void SearchProvider::AddNavigationResultsToMatches(
812 const NavigationResults& navigation_results,
813 bool is_keyword) {
814 if (!navigation_results.empty()) {
[email protected]6c535842012-05-15 05:20:55815 // TODO(kochi|msw): Add more navigational results if they get more
816 // meaningful relevance values; see http://b/1170574.
[email protected]d7ad4772012-06-01 03:12:54817 // CompareScoredResults sorts by descending relevance; so use min_element.
[email protected]6c535842012-05-15 05:20:55818 NavigationResults::const_iterator result(
[email protected]d7ad4772012-06-01 03:12:54819 std::min_element(navigation_results.begin(),
[email protected]6c535842012-05-15 05:20:55820 navigation_results.end(),
821 CompareScoredResults()));
822 matches_.push_back(NavigationToMatch(*result, is_keyword));
[email protected]257ab712009-04-14 17:16:24823 }
824}
825
826void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results,
827 bool is_keyword,
828 int did_not_accept_suggestion,
829 MatchMap* map) {
[email protected]51124552011-07-16 01:37:10830 if (results.empty())
831 return;
832
[email protected]d7ad4772012-06-01 03:12:54833 bool prevent_inline_autocomplete = input_.prevent_inline_autocomplete() ||
834 (input_.type() == AutocompleteInput::URL);
835 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
[email protected]51124552011-07-16 01:37:10836 bool input_multiple_words = HasMultipleWords(input_text);
837
[email protected]55ce8f12012-05-09 04:44:08838 SuggestResults scored_results;
839 if (!prevent_inline_autocomplete && input_multiple_words) {
840 // ScoreHistoryResults() allows autocompletion of multi-word, 1-visit
841 // queries if the input also has multiple words. But if we were already
[email protected]51124552011-07-16 01:37:10842 // autocompleting a multi-word, multi-visit query, and the current input is
843 // still a prefix of it, then changing the autocompletion suddenly feels
844 // wrong. To detect this case, first score as if only one word has been
845 // typed, then check for a best result that is an autocompleted, multi-word
846 // query. If we find one, then just keep that score set.
[email protected]55ce8f12012-05-09 04:44:08847 scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
848 false, input_text, is_keyword);
849 if ((scored_results[0].relevance() <
850 AutocompleteResult::kLowestDefaultScore) ||
851 !HasMultipleWords(scored_results[0].suggestion()))
852 scored_results.clear(); // Didn't detect the case above, score normally.
[email protected]51124552011-07-16 01:37:10853 }
[email protected]55ce8f12012-05-09 04:44:08854 if (scored_results.empty())
855 scored_results = ScoreHistoryResults(results, prevent_inline_autocomplete,
856 input_multiple_words, input_text,
857 is_keyword);
858 for (SuggestResults::const_iterator i(scored_results.begin());
859 i != scored_results.end(); ++i) {
860 AddMatchToMap(i->suggestion(), input_text, i->relevance(),
[email protected]51124552011-07-16 01:37:10861 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
[email protected]55ce8f12012-05-09 04:44:08862 is_keyword, map);
[email protected]51124552011-07-16 01:37:10863 }
864}
865
[email protected]55ce8f12012-05-09 04:44:08866SearchProvider::SuggestResults SearchProvider::ScoreHistoryResults(
[email protected]51124552011-07-16 01:37:10867 const HistoryResults& results,
868 bool base_prevent_inline_autocomplete,
869 bool input_multiple_words,
870 const string16& input_text,
871 bool is_keyword) {
[email protected]810ffba2012-06-12 01:07:48872 AutocompleteClassifier* classifier =
873 AutocompleteClassifierFactory::GetForProfile(profile_);
[email protected]55ce8f12012-05-09 04:44:08874 SuggestResults scored_results;
[email protected]257ab712009-04-14 17:16:24875 for (HistoryResults::const_iterator i(results.begin()); i != results.end();
876 ++i) {
[email protected]51124552011-07-16 01:37:10877 // Don't autocomplete multi-word queries that have only been seen once
878 // unless the user has typed more than one word.
879 bool prevent_inline_autocomplete = base_prevent_inline_autocomplete ||
880 (!input_multiple_words && (i->visits < 2) && HasMultipleWords(i->term));
881
[email protected]ea3b9a502011-04-04 14:19:37882 // Don't autocomplete search terms that would normally be treated as URLs
[email protected]51124552011-07-16 01:37:10883 // when typed. For example, if the user searched for "google.com" and types
884 // "goog", don't autocomplete to the search term "google.com". Otherwise,
885 // the input will look like a URL but act like a search, which is confusing.
[email protected]cc447362011-04-06 03:57:48886 // NOTE: We don't check this in the following cases:
887 // * When inline autocomplete is disabled, we won't be inline
888 // autocompleting this term, so we don't need to worry about confusion as
889 // much. This also prevents calling Classify() again from inside the
890 // classifier (which will corrupt state and likely crash), since the
[email protected]51124552011-07-16 01:37:10891 // classifier always disables inline autocomplete.
[email protected]cc447362011-04-06 03:57:48892 // * When the user has typed the whole term, the "what you typed" history
893 // match will outrank us for URL-like inputs anyway, so we need not do
894 // anything special.
[email protected]51124552011-07-16 01:37:10895 if (!prevent_inline_autocomplete && classifier && (i->term != input_text)) {
[email protected]ea3b9a502011-04-04 14:19:37896 AutocompleteMatch match;
[email protected]72874a8d2011-05-11 03:48:54897 classifier->Classify(i->term, string16(), false, false, &match, NULL);
[email protected]2905f742011-10-13 03:51:58898 prevent_inline_autocomplete =
899 match.transition == content::PAGE_TRANSITION_TYPED;
[email protected]ea3b9a502011-04-04 14:19:37900 }
[email protected]51124552011-07-16 01:37:10901
902 int relevance = CalculateRelevanceForHistory(i->time, is_keyword,
903 prevent_inline_autocomplete);
[email protected]55ce8f12012-05-09 04:44:08904 scored_results.push_back(SuggestResult(i->term, relevance));
[email protected]257ab712009-04-14 17:16:24905 }
[email protected]51124552011-07-16 01:37:10906
907 // History returns results sorted for us. However, we may have docked some
908 // results' scores, so things are no longer in order. Do a stable sort to get
909 // things back in order without otherwise disturbing results with equal
910 // scores, then force the scores to be unique, so that the order in which
911 // they're shown is deterministic.
[email protected]55ce8f12012-05-09 04:44:08912 std::stable_sort(scored_results.begin(), scored_results.end(),
913 CompareScoredResults());
[email protected]51124552011-07-16 01:37:10914 int last_relevance = 0;
[email protected]55ce8f12012-05-09 04:44:08915 for (SuggestResults::iterator i(scored_results.begin());
916 i != scored_results.end(); ++i) {
917 if ((i != scored_results.begin()) && (i->relevance() >= last_relevance))
918 i->set_relevance(last_relevance - 1);
919 last_relevance = i->relevance();
[email protected]51124552011-07-16 01:37:10920 }
921
[email protected]55ce8f12012-05-09 04:44:08922 return scored_results;
[email protected]257ab712009-04-14 17:16:24923}
924
[email protected]55ce8f12012-05-09 04:44:08925void SearchProvider::AddSuggestResultsToMap(const SuggestResults& results,
926 bool is_keyword,
927 MatchMap* map) {
[email protected]d7ad4772012-06-01 03:12:54928 const string16& input_text = is_keyword ? keyword_input_text_ : input_.text();
[email protected]55ce8f12012-05-09 04:44:08929 for (size_t i = 0; i < results.size(); ++i) {
[email protected]d7ad4772012-06-01 03:12:54930 AddMatchToMap(results[i].suggestion(), input_text, results[i].relevance(),
[email protected]55ce8f12012-05-09 04:44:08931 AutocompleteMatch::SEARCH_SUGGEST, i, is_keyword, map);
[email protected]257ab712009-04-14 17:16:24932 }
initial.commit09911bf2008-07-26 23:55:29933}
934
[email protected]382a0642012-06-06 06:13:52935int SearchProvider::GetVerbatimRelevance() const {
[email protected]dc6943b2012-06-19 06:39:56936 // Use the suggested verbatim relevance score if it is non-negative (valid),
937 // if inline autocomplete isn't prevented (always show verbatim on backspace),
[email protected]1beee342012-06-19 22:22:28938 // and if it won't suppress verbatim, leaving no default provider matches.
939 // Otherwise, if the default provider returned no matches and was still able
[email protected]dc6943b2012-06-19 06:39:56940 // to suppress verbatim, the user would have no search/nav matches and may be
[email protected]1beee342012-06-19 22:22:28941 // left unable to search using their default provider from the omnibox.
[email protected]dc6943b2012-06-19 06:39:56942 // Check for results on each verbatim calculation, as results from older
943 // queries (on previous input) may be trimmed for failing to inline new input.
944 if (verbatim_relevance_ >= 0 && !input_.prevent_inline_autocomplete() &&
[email protected]1beee342012-06-19 22:22:28945 (verbatim_relevance_ > 0 ||
946 !default_suggest_results_.empty() ||
[email protected]dc6943b2012-06-19 06:39:56947 !default_navigation_results_.empty())) {
[email protected]d1f0a7f2012-06-05 10:26:42948 return verbatim_relevance_;
[email protected]dc6943b2012-06-19 06:39:56949 }
[email protected]382a0642012-06-06 06:13:52950 return CalculateRelevanceForVerbatim();
951}
[email protected]d1f0a7f2012-06-05 10:26:42952
[email protected]382a0642012-06-06 06:13:52953int SearchProvider::CalculateRelevanceForVerbatim() const {
[email protected]85b8d6f2012-05-08 20:53:47954 if (!providers_.keyword_provider().empty())
[email protected]52d08b12009-10-19 18:42:36955 return 250;
956
initial.commit09911bf2008-07-26 23:55:29957 switch (input_.type()) {
958 case AutocompleteInput::UNKNOWN:
[email protected]52d08b12009-10-19 18:42:36959 case AutocompleteInput::QUERY:
960 case AutocompleteInput::FORCED_QUERY:
961 return 1300;
initial.commit09911bf2008-07-26 23:55:29962
963 case AutocompleteInput::REQUESTED_URL:
[email protected]52d08b12009-10-19 18:42:36964 return 1150;
initial.commit09911bf2008-07-26 23:55:29965
966 case AutocompleteInput::URL:
[email protected]52d08b12009-10-19 18:42:36967 return 850;
initial.commit09911bf2008-07-26 23:55:29968
969 default:
970 NOTREACHED();
971 return 0;
972 }
973}
974
[email protected]51124552011-07-16 01:37:10975int SearchProvider::CalculateRelevanceForHistory(
976 const Time& time,
977 bool is_keyword,
978 bool prevent_inline_autocomplete) const {
[email protected]aa613d62010-11-09 20:40:18979 // The relevance of past searches falls off over time. There are two distinct
980 // equations used. If the first equation is used (searches to the primary
[email protected]51124552011-07-16 01:37:10981 // provider that we want to inline autocomplete), the score starts at 1399 and
982 // falls to 1300. If the second equation is used the relevance of a search 15
983 // minutes ago is discounted 50 points, while the relevance of a search two
984 // weeks ago is discounted 450 points.
[email protected]aa613d62010-11-09 20:40:18985 double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
[email protected]51124552011-07-16 01:37:10986 bool is_primary_provider = providers_.is_primary_provider(is_keyword);
987 if (is_primary_provider && !prevent_inline_autocomplete) {
[email protected]aa613d62010-11-09 20:40:18988 // Searches with the past two days get a different curve.
[email protected]51124552011-07-16 01:37:10989 const double autocomplete_time = 2 * 24 * 60 * 60;
[email protected]aa613d62010-11-09 20:40:18990 if (elapsed_time < autocomplete_time) {
[email protected]e17511f2011-07-13 14:09:18991 return (is_keyword ? 1599 : 1399) - static_cast<int>(99 *
[email protected]aa613d62010-11-09 20:40:18992 std::pow(elapsed_time / autocomplete_time, 2.5));
993 }
994 elapsed_time -= autocomplete_time;
995 }
996
[email protected]c3a4bd992010-08-18 20:25:01997 const int score_discount =
998 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3));
initial.commit09911bf2008-07-26 23:55:29999
[email protected]6c85aa02009-02-27 12:08:091000 // Don't let scores go below 0. Negative relevance scores are meaningful in
1001 // a different way.
initial.commit09911bf2008-07-26 23:55:291002 int base_score;
[email protected]51124552011-07-16 01:37:101003 if (is_primary_provider)
[email protected]52d08b12009-10-19 18:42:361004 base_score = (input_.type() == AutocompleteInput::URL) ? 750 : 1050;
[email protected]51124552011-07-16 01:37:101005 else
1006 base_score = 200;
initial.commit09911bf2008-07-26 23:55:291007 return std::max(0, base_score - score_discount);
1008}
1009
[email protected]55ce8f12012-05-09 04:44:081010int SearchProvider::CalculateRelevanceForSuggestion(bool for_keyword) const {
1011 return !providers_.is_primary_provider(for_keyword) ? 100 :
1012 ((input_.type() == AutocompleteInput::URL) ? 300 : 600);
initial.commit09911bf2008-07-26 23:55:291013}
1014
[email protected]55ce8f12012-05-09 04:44:081015int SearchProvider::CalculateRelevanceForNavigation(bool for_keyword) const {
1016 return providers_.is_primary_provider(for_keyword) ? 800 : 150;
initial.commit09911bf2008-07-26 23:55:291017}
1018
[email protected]a2fedb1e2011-01-25 15:23:361019void SearchProvider::AddMatchToMap(const string16& query_string,
1020 const string16& input_text,
initial.commit09911bf2008-07-26 23:55:291021 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:001022 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:291023 int accepted_suggestion,
[email protected]257ab712009-04-14 17:16:241024 bool is_keyword,
initial.commit09911bf2008-07-26 23:55:291025 MatchMap* map) {
[email protected]92513682011-09-01 06:16:521026 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:291027 std::vector<size_t> content_param_offsets;
[email protected]85b8d6f2012-05-08 20:53:471028 // Bail out now if we don't actually have a valid provider.
1029 match.keyword = is_keyword ?
[email protected]3954c3a2012-04-10 20:17:551030 providers_.keyword_provider() : providers_.default_provider();
[email protected]85b8d6f2012-05-08 20:53:471031 const TemplateURL* provider_url = match.GetTemplateURL(profile_);
1032 if (provider_url == NULL)
1033 return;
1034
[email protected]70833262011-01-05 23:40:441035 match.contents.assign(query_string);
[email protected]fb5153c52009-07-31 19:40:331036 // We do intra-string highlighting for suggestions - the suggested segment
1037 // will be highlighted, e.g. for input_text = "you" the suggestion may be
1038 // "youtube", so we'll bold the "tube" section: you*tube*.
1039 if (input_text != query_string) {
[email protected]fb5153c52009-07-31 19:40:331040 size_t input_position = match.contents.find(input_text);
[email protected]a2fedb1e2011-01-25 15:23:361041 if (input_position == string16::npos) {
[email protected]fb5153c52009-07-31 19:40:331042 // The input text is not a substring of the query string, e.g. input
1043 // text is "slasdot" and the query string is "slashdot", so we bold the
1044 // whole thing.
1045 match.contents_class.push_back(
1046 ACMatchClassification(0, ACMatchClassification::MATCH));
[email protected]ec2379162009-06-09 23:58:171047 } else {
[email protected]fb5153c52009-07-31 19:40:331048 // TODO(beng): ACMatchClassification::MATCH now seems to just mean
1049 // "bold" this. Consider modifying the terminology.
1050 // We don't iterate over the string here annotating all matches because
1051 // it looks odd to have every occurrence of a substring that may be as
1052 // short as a single character highlighted in a query suggestion result,
1053 // e.g. for input text "s" and query string "southwest airlines", it
1054 // looks odd if both the first and last s are highlighted.
1055 if (input_position != 0) {
1056 match.contents_class.push_back(
1057 ACMatchClassification(0, ACMatchClassification::NONE));
1058 }
1059 match.contents_class.push_back(
1060 ACMatchClassification(input_position, ACMatchClassification::DIM));
1061 size_t next_fragment_position = input_position + input_text.length();
1062 if (next_fragment_position < query_string.length()) {
1063 match.contents_class.push_back(
1064 ACMatchClassification(next_fragment_position,
1065 ACMatchClassification::NONE));
1066 }
[email protected]ec2379162009-06-09 23:58:171067 }
initial.commit09911bf2008-07-26 23:55:291068 } else {
[email protected]fb5153c52009-07-31 19:40:331069 // Otherwise, we're dealing with the "default search" result which has no
[email protected]70833262011-01-05 23:40:441070 // completion.
[email protected]fb5153c52009-07-31 19:40:331071 match.contents_class.push_back(
1072 ACMatchClassification(0, ACMatchClassification::NONE));
initial.commit09911bf2008-07-26 23:55:291073 }
1074
1075 // When the user forced a query, we need to make sure all the fill_into_edit
1076 // values preserve that property. Otherwise, if the user starts editing a
1077 // suggestion, non-Search results will suddenly appear.
1078 size_t search_start = 0;
1079 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
[email protected]a2fedb1e2011-01-25 15:23:361080 match.fill_into_edit.assign(ASCIIToUTF16("?"));
initial.commit09911bf2008-07-26 23:55:291081 ++search_start;
1082 }
[email protected]c0048b42009-05-04 21:47:171083 if (is_keyword) {
[email protected]033f3422012-03-13 21:24:181084 match.fill_into_edit.append(match.keyword + char16(' '));
1085 search_start += match.keyword.length() + 1;
[email protected]c0048b42009-05-04 21:47:171086 }
initial.commit09911bf2008-07-26 23:55:291087 match.fill_into_edit.append(query_string);
[email protected]2c33dd22010-02-11 21:46:351088 // Not all suggestions start with the original input.
[email protected]55ce8f12012-05-09 04:44:081089 if (!input_.prevent_inline_autocomplete() &&
[email protected]257ab712009-04-14 17:16:241090 !match.fill_into_edit.compare(search_start, input_text.length(),
1091 input_text))
1092 match.inline_autocomplete_offset = search_start + input_text.length();
initial.commit09911bf2008-07-26 23:55:291093
[email protected]85b8d6f2012-05-08 20:53:471094 const TemplateURLRef& search_url = provider_url->url_ref();
[email protected]360ba052012-04-04 17:26:131095 DCHECK(search_url.SupportsReplacement());
[email protected]bca359b2012-06-24 07:53:041096 match.search_terms_args.reset(
1097 new TemplateURLRef::SearchTermsArgs(query_string));
1098 match.search_terms_args->original_query = input_text;
1099 match.search_terms_args->accepted_suggestion = accepted_suggestion;
1100 // This is the destination URL sans assisted query stats. This must be set
1101 // so the AutocompleteController can properly de-dupe; the controller will
1102 // eventually overwrite it before it reaches the user.
1103 match.destination_url =
1104 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get()));
initial.commit09911bf2008-07-26 23:55:291105
1106 // Search results don't look like URLs.
[email protected]2905f742011-10-13 03:51:581107 match.transition = is_keyword ?
1108 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED;
initial.commit09911bf2008-07-26 23:55:291109
1110 // Try to add |match| to |map|. If a match for |query_string| is already in
1111 // |map|, replace it if |match| is more relevant.
1112 // NOTE: Keep this ToLower() call in sync with url_database.cc.
1113 const std::pair<MatchMap::iterator, bool> i = map->insert(
[email protected]a2fedb1e2011-01-25 15:23:361114 std::pair<string16, AutocompleteMatch>(
[email protected]503d03872011-05-06 08:36:261115 base::i18n::ToLower(query_string), match));
initial.commit09911bf2008-07-26 23:55:291116 // NOTE: We purposefully do a direct relevance comparison here instead of
1117 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
1118 // first" rather than "items alphabetically first" when the scores are equal.
1119 // The only case this matters is when a user has results with the same score
1120 // that differ only by capitalization; because the history system returns
1121 // results sorted by recency, this means we'll pick the most recent such
1122 // result even if the precision of our relevance score is too low to
1123 // distinguish the two.
1124 if (!i.second && (match.relevance > i.first->second.relevance))
1125 i.first->second = match;
1126}
1127
1128AutocompleteMatch SearchProvider::NavigationToMatch(
1129 const NavigationResult& navigation,
[email protected]257ab712009-04-14 17:16:241130 bool is_keyword) {
[email protected]371dab12012-06-01 03:23:551131 const string16& input = is_keyword ? keyword_input_text_ : input_.text();
[email protected]55ce8f12012-05-09 04:44:081132 AutocompleteMatch match(this, navigation.relevance(), false,
[email protected]4c1fb7ec2008-11-13 00:19:001133 AutocompleteMatch::NAVSUGGEST);
[email protected]55ce8f12012-05-09 04:44:081134 match.destination_url = navigation.url();
[email protected]371dab12012-06-01 03:23:551135
1136 // First look for the user's input inside the fill_into_edit as it would be
1137 // without trimming the scheme, so we can find matches at the beginning of the
1138 // scheme.
1139 const string16 untrimmed_fill_into_edit(
1140 AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
1141 StringForURLDisplay(navigation.url(), true, false)));
1142 const URLPrefix* prefix =
1143 URLPrefix::BestURLPrefix(untrimmed_fill_into_edit, input);
1144 size_t match_start = (prefix == NULL) ?
1145 untrimmed_fill_into_edit.find(input) : prefix->prefix.length();
1146 size_t inline_autocomplete_offset = (prefix == NULL) ?
1147 string16::npos : (match_start + input.length());
1148 bool trim_http = !HasHTTPScheme(input) && (!prefix || (match_start != 0));
1149
1150 // Preserve the forced query '?' prefix in |match.fill_into_edit|.
1151 // Otherwise, user edits to a suggestion would show non-Search results.
1152 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
1153 match.fill_into_edit = ASCIIToUTF16("?");
1154 if (inline_autocomplete_offset != string16::npos)
1155 ++inline_autocomplete_offset;
1156 }
1157
1158 const std::string languages(
1159 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
1160 const net::FormatUrlTypes format_types =
1161 net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP);
1162 match.fill_into_edit +=
1163 AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
1164 net::FormatUrl(navigation.url(), languages, format_types,
1165 net::UnescapeRule::SPACES, NULL, NULL,
1166 &inline_autocomplete_offset));
1167 if (!input_.prevent_inline_autocomplete())
1168 match.inline_autocomplete_offset = inline_autocomplete_offset;
1169 DCHECK((match.inline_autocomplete_offset == string16::npos) ||
1170 (match.inline_autocomplete_offset <= match.fill_into_edit.length()));
1171
1172 match.contents = net::FormatUrl(navigation.url(), languages,
1173 format_types, net::UnescapeRule::SPACES, NULL, NULL, &match_start);
1174 // If the first match in the untrimmed string was inside a scheme that we
1175 // trimmed, look for a subsequent match.
1176 if (match_start == string16::npos)
1177 match_start = match.contents.find(input);
1178 // Safe if |match_start| is npos; also safe if the input is longer than the
1179 // remaining contents after |match_start|.
1180 AutocompleteMatch::ClassifyLocationInString(match_start, input.length(),
1181 match.contents.length(), ACMatchClassification::URL,
1182 &match.contents_class);
initial.commit09911bf2008-07-26 23:55:291183
[email protected]55ce8f12012-05-09 04:44:081184 match.description = navigation.description();
[email protected]371dab12012-06-01 03:23:551185 AutocompleteMatch::ClassifyMatchInString(input, match.description,
1186 ACMatchClassification::NONE, &match.description_class);
initial.commit09911bf2008-07-26 23:55:291187 return match;
1188}
[email protected]4ab4c7c2010-11-24 04:49:341189
1190void SearchProvider::UpdateDone() {
1191 // We're done when there are no more suggest queries pending (this is set to 1
1192 // when the timer is started) and we're not waiting on instant.
1193 done_ = ((suggest_results_pending_ == 0) &&
1194 (instant_finalized_ || !InstantController::IsEnabled(profile_)));
1195}