blob: 235341cfb6609ce8e6d3c64e3b3487335af230df [file] [log] [blame]
[email protected]f90bf0d92011-01-13 02:12:441// Copyright (c) 2011 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]503d03872011-05-06 08:36:2611#include "base/i18n/case_conversion.h"
[email protected]d6e58c6e2009-10-10 20:40:5012#include "base/i18n/icu_string_conversions.h"
initial.commit09911bf2008-07-26 23:55:2913#include "base/message_loop.h"
[email protected]dc9a6762010-08-16 07:13:5314#include "base/string16.h"
[email protected]1cb2dac2010-03-08 21:49:1515#include "base/utf_string_conversions.h"
[email protected]ea3b9a502011-04-04 14:19:3716#include "chrome/browser/autocomplete/autocomplete_classifier.h"
[email protected]257ab712009-04-14 17:16:2417#include "chrome/browser/autocomplete/keyword_provider.h"
[email protected]9ac40092010-10-27 23:05:2618#include "chrome/browser/autocomplete/autocomplete_match.h"
[email protected]f7578f52010-08-30 22:22:4919#include "chrome/browser/google/google_util.h"
[email protected]ce560f82009-06-03 09:39:4420#include "chrome/browser/history/history.h"
[email protected]4ab4c7c2010-11-24 04:49:3421#include "chrome/browser/instant/instant_controller.h"
[email protected]f870a322009-01-16 21:47:2722#include "chrome/browser/net/url_fixer_upper.h"
[email protected]37858e52010-08-26 00:22:0223#include "chrome/browser/prefs/pref_service.h"
[email protected]8ecad5e2010-12-02 21:18:3324#include "chrome/browser/profiles/profile.h"
[email protected]8d457132010-11-04 18:13:4025#include "chrome/browser/history/in_memory_database.h"
[email protected]d54e03a52009-01-16 00:31:0426#include "chrome/browser/search_engines/template_url_model.h"
initial.commit09911bf2008-07-26 23:55:2927#include "chrome/common/pref_names.h"
[email protected]dcf7d352009-02-26 01:56:0228#include "chrome/common/url_constants.h"
[email protected]c3113022011-04-16 03:26:3029#include "content/common/json_value_serializer.h"
initial.commit09911bf2008-07-26 23:55:2930#include "googleurl/src/url_util.h"
[email protected]34ac8f32009-02-22 23:03:2731#include "grit/generated_resources.h"
initial.commit09911bf2008-07-26 23:55:2932#include "net/base/escape.h"
[email protected]319d9e6f2009-02-18 19:47:2133#include "net/http/http_response_headers.h"
34#include "net/url_request/url_request_status.h"
[email protected]c051a1b2011-01-21 23:30:1735#include "ui/base/l10n/l10n_util.h"
initial.commit09911bf2008-07-26 23:55:2936
[email protected]e1acf6f2008-10-27 20:43:3337using base::Time;
38using base::TimeDelta;
39
[email protected]b547666d2009-04-23 16:37:5840// static
41const int SearchProvider::kDefaultProviderURLFetcherID = 1;
42// static
43const int SearchProvider::kKeywordProviderURLFetcherID = 2;
44
45// static
46bool SearchProvider::query_suggest_immediately_ = false;
47
[email protected]257ab712009-04-14 17:16:2448void SearchProvider::Providers::Set(const TemplateURL* default_provider,
49 const TemplateURL* keyword_provider) {
50 // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy
51 // this. Nor should we need |default_provider_| and |keyword_provider_|
52 // just to know whether the provider changed.
53 default_provider_ = default_provider;
54 if (default_provider)
55 cached_default_provider_ = *default_provider;
56 keyword_provider_ = keyword_provider;
57 if (keyword_provider)
58 cached_keyword_provider_ = *keyword_provider;
59}
60
[email protected]601858c02010-09-01 17:08:2061SearchProvider::SearchProvider(ACProviderListener* listener, Profile* profile)
62 : AutocompleteProvider(listener, profile, "Search"),
[email protected]601858c02010-09-01 17:08:2063 suggest_results_pending_(0),
[email protected]8e5cc282010-12-05 18:11:3964 have_suggest_results_(false),
[email protected]4ab4c7c2010-11-24 04:49:3465 instant_finalized_(false) {
66}
67
[email protected]a2fedb1e2011-01-25 15:23:3668void SearchProvider::FinalizeInstantQuery(const string16& input_text,
69 const string16& suggest_text) {
[email protected]4ab4c7c2010-11-24 04:49:3470 if (done_ || instant_finalized_)
71 return;
72
73 instant_finalized_ = true;
74 UpdateDone();
75
[email protected]e918c112010-12-08 23:03:4976 if (input_text.empty()) {
[email protected]4ab4c7c2010-11-24 04:49:3477 // We only need to update the listener if we're actually done.
78 if (done_)
79 listener_->OnProviderUpdate(false);
80 return;
81 }
82
[email protected]9e789742011-01-10 23:27:3283 default_provider_suggest_text_ = suggest_text;
84
[email protected]a2fedb1e2011-01-25 15:23:3685 string16 adjusted_input_text(input_text);
[email protected]e918c112010-12-08 23:03:4986 AutocompleteInput::RemoveForcedQueryStringIfNecessary(input_.type(),
87 &adjusted_input_text);
88
[email protected]a2fedb1e2011-01-25 15:23:3689 const string16 text = adjusted_input_text + suggest_text;
[email protected]4ab4c7c2010-11-24 04:49:3490 // Remove any matches that are identical to |text|. We don't use the
91 // destination_url for comparison as it varies depending upon the index passed
92 // to TemplateURL::ReplaceSearchTerms.
93 for (ACMatches::iterator i = matches_.begin(); i != matches_.end();) {
[email protected]70833262011-01-05 23:40:4494 // Reset the description/description_class of all searches. We'll set the
95 // description of the new first match in the call to
96 // UpdateFirstSearchMatchDescription() below.
97 if ((i->type == AutocompleteMatch::SEARCH_HISTORY) ||
98 (i->type == AutocompleteMatch::SEARCH_SUGGEST) ||
99 (i->type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED)) {
100 i->description.clear();
101 i->description_class.clear();
102 }
103
[email protected]4ab4c7c2010-11-24 04:49:34104 if (((i->type == AutocompleteMatch::SEARCH_HISTORY) ||
105 (i->type == AutocompleteMatch::SEARCH_SUGGEST)) &&
106 (i->fill_into_edit == text)) {
[email protected]e030de62010-11-24 05:41:19107 i = matches_.erase(i);
[email protected]4ab4c7c2010-11-24 04:49:34108 } else {
109 ++i;
110 }
111 }
112
113 // Add the new suggest result. We give it a rank higher than
114 // SEARCH_WHAT_YOU_TYPED so that it gets autocompleted.
115 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
116 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
117 TemplateURLRef::NO_SUGGESTION_CHOSEN;
118 MatchMap match_map;
[email protected]e918c112010-12-08 23:03:49119 AddMatchToMap(text, adjusted_input_text,
120 CalculateRelevanceForWhatYouTyped() + 1,
[email protected]4ab4c7c2010-11-24 04:49:34121 AutocompleteMatch::SEARCH_SUGGEST,
[email protected]e918c112010-12-08 23:03:49122 did_not_accept_default_suggestion, false,
123 input_.initial_prevent_inline_autocomplete(), &match_map);
[email protected]4ab4c7c2010-11-24 04:49:34124 DCHECK_EQ(1u, match_map.size());
125 matches_.push_back(match_map.begin()->second);
[email protected]70833262011-01-05 23:40:44126 // Sort the results so that UpdateFirstSearchDescription does the right thing.
127 std::sort(matches_.begin(), matches_.end(), &AutocompleteMatch::MoreRelevant);
128
129 UpdateFirstSearchMatchDescription();
[email protected]4ab4c7c2010-11-24 04:49:34130
131 listener_->OnProviderUpdate(true);
[email protected]601858c02010-09-01 17:08:20132}
133
initial.commit09911bf2008-07-26 23:55:29134void SearchProvider::Start(const AutocompleteInput& input,
[email protected]8deeb952008-10-09 18:21:27135 bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:29136 matches_.clear();
137
[email protected]ea3b9a502011-04-04 14:19:37138 instant_finalized_ =
139 (input.matches_requested() != AutocompleteInput::ALL_MATCHES);
[email protected]4ab4c7c2010-11-24 04:49:34140
[email protected]6c85aa02009-02-27 12:08:09141 // Can't return search/suggest results for bogus input or without a profile.
initial.commit09911bf2008-07-26 23:55:29142 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
143 Stop();
144 return;
145 }
146
[email protected]257ab712009-04-14 17:16:24147 keyword_input_text_.clear();
148 const TemplateURL* keyword_provider =
149 KeywordProvider::GetSubstitutingTemplateURLForInput(profile_, input,
150 &keyword_input_text_);
[email protected]8d457132010-11-04 18:13:40151 if (keyword_input_text_.empty())
[email protected]257ab712009-04-14 17:16:24152 keyword_provider = NULL;
[email protected]257ab712009-04-14 17:16:24153
154 const TemplateURL* default_provider =
initial.commit09911bf2008-07-26 23:55:29155 profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
[email protected]257ab712009-04-14 17:16:24156 if (!TemplateURL::SupportsReplacement(default_provider))
157 default_provider = NULL;
158
159 if (keyword_provider == default_provider)
160 keyword_provider = NULL; // No use in querying the same provider twice.
161
162 if (!default_provider && !keyword_provider) {
163 // No valid providers.
initial.commit09911bf2008-07-26 23:55:29164 Stop();
165 return;
166 }
167
168 // If we're still running an old query but have since changed the query text
[email protected]257ab712009-04-14 17:16:24169 // or the providers, abort the query.
[email protected]9e789742011-01-10 23:27:32170 if (!minimal_changes ||
171 !providers_.equals(default_provider, keyword_provider)) {
172 if (done_)
173 default_provider_suggest_text_.clear();
174 else
175 Stop();
[email protected]58d788b2011-02-01 16:29:52176 } else if (minimal_changes &&
177 (input_.original_text() != input.original_text())) {
178 default_provider_suggest_text_.clear();
[email protected]257ab712009-04-14 17:16:24179 }
initial.commit09911bf2008-07-26 23:55:29180
[email protected]257ab712009-04-14 17:16:24181 providers_.Set(default_provider, keyword_provider);
initial.commit09911bf2008-07-26 23:55:29182
183 if (input.text().empty()) {
184 // User typed "?" alone. Give them a placeholder result indicating what
185 // this syntax does.
[email protected]257ab712009-04-14 17:16:24186 if (default_provider) {
[email protected]69c579e2010-04-23 20:01:00187 AutocompleteMatch match;
188 match.provider = this;
[email protected]a2fedb1e2011-01-25 15:23:36189 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE));
[email protected]257ab712009-04-14 17:16:24190 match.contents_class.push_back(
[email protected]2c33dd22010-02-11 21:46:35191 ACMatchClassification(0, ACMatchClassification::NONE));
[email protected]257ab712009-04-14 17:16:24192 matches_.push_back(match);
[email protected]70833262011-01-05 23:40:44193 UpdateFirstSearchMatchDescription();
[email protected]257ab712009-04-14 17:16:24194 }
initial.commit09911bf2008-07-26 23:55:29195 Stop();
196 return;
197 }
198
199 input_ = input;
200
[email protected]8d457132010-11-04 18:13:40201 DoHistoryQuery(minimal_changes);
[email protected]8deeb952008-10-09 18:21:27202 StartOrStopSuggestQuery(minimal_changes);
initial.commit09911bf2008-07-26 23:55:29203 ConvertResultsToAutocompleteMatches();
204}
205
206void SearchProvider::Run() {
207 // Start a new request with the current input.
208 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24209 suggest_results_pending_ = 0;
210 if (providers_.valid_suggest_for_keyword_provider()) {
211 suggest_results_pending_++;
212 keyword_fetcher_.reset(
[email protected]b547666d2009-04-23 16:37:58213 CreateSuggestFetcher(kKeywordProviderURLFetcherID,
214 providers_.keyword_provider(),
[email protected]257ab712009-04-14 17:16:24215 keyword_input_text_));
216 }
217 if (providers_.valid_suggest_for_default_provider()) {
218 suggest_results_pending_++;
219 default_fetcher_.reset(
[email protected]b547666d2009-04-23 16:37:58220 CreateSuggestFetcher(kDefaultProviderURLFetcherID,
221 providers_.default_provider(), input_.text()));
[email protected]257ab712009-04-14 17:16:24222 }
223 // We should only get here if we have a suggest url for the keyword or default
224 // providers.
[email protected]1cb2dac2010-03-08 21:49:15225 DCHECK_GT(suggest_results_pending_, 0);
initial.commit09911bf2008-07-26 23:55:29226}
227
228void SearchProvider::Stop() {
initial.commit09911bf2008-07-26 23:55:29229 StopSuggest();
230 done_ = true;
[email protected]9e789742011-01-10 23:27:32231 default_provider_suggest_text_.clear();
initial.commit09911bf2008-07-26 23:55:29232}
233
234void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
235 const GURL& url,
[email protected]f90bf0d92011-01-13 02:12:44236 const net::URLRequestStatus& status,
initial.commit09911bf2008-07-26 23:55:29237 int response_code,
[email protected]cb04f5e2011-05-06 01:10:00238 const net::ResponseCookies& cookie,
initial.commit09911bf2008-07-26 23:55:29239 const std::string& data) {
240 DCHECK(!done_);
[email protected]257ab712009-04-14 17:16:24241 suggest_results_pending_--;
[email protected]1cb2dac2010-03-08 21:49:15242 DCHECK_GE(suggest_results_pending_, 0); // Should never go negative.
[email protected]ec9207d32008-09-26 00:51:06243 const net::HttpResponseHeaders* const response_headers =
244 source->response_headers();
245 std::string json_data(data);
[email protected]6c85aa02009-02-27 12:08:09246 // JSON is supposed to be UTF-8, but some suggest service providers send JSON
247 // files in non-UTF-8 encodings. The actual encoding is usually specified in
248 // the Content-Type header field.
[email protected]ec9207d32008-09-26 00:51:06249 if (response_headers) {
250 std::string charset;
251 if (response_headers->GetCharset(&charset)) {
[email protected]a2fedb1e2011-01-25 15:23:36252 string16 data_16;
[email protected]ec9207d32008-09-26 00:51:06253 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
[email protected]a2fedb1e2011-01-25 15:23:36254 if (base::CodepageToUTF16(data, charset.c_str(),
255 base::OnStringConversionError::FAIL,
256 &data_16))
257 json_data = UTF16ToUTF8(data_16);
[email protected]ec9207d32008-09-26 00:51:06258 }
259 }
260
[email protected]257ab712009-04-14 17:16:24261 bool is_keyword_results = (source == keyword_fetcher_.get());
262 SuggestResults* suggest_results = is_keyword_results ?
263 &keyword_suggest_results_ : &default_suggest_results_;
264
[email protected]b4cebf82008-12-29 19:59:08265 if (status.is_success() && response_code == 200) {
266 JSONStringValueSerializer deserializer(json_data);
267 deserializer.set_allow_trailing_comma(true);
[email protected]ba399672010-04-06 15:42:39268 scoped_ptr<Value> root_val(deserializer.Deserialize(NULL, NULL));
[email protected]a2fedb1e2011-01-25 15:23:36269 const string16& input_text =
[email protected]257ab712009-04-14 17:16:24270 is_keyword_results ? keyword_input_text_ : input_.text();
[email protected]b4cebf82008-12-29 19:59:08271 have_suggest_results_ =
[email protected]257ab712009-04-14 17:16:24272 root_val.get() &&
273 ParseSuggestResults(root_val.get(), is_keyword_results, input_text,
274 suggest_results);
[email protected]b4cebf82008-12-29 19:59:08275 }
276
initial.commit09911bf2008-07-26 23:55:29277 ConvertResultsToAutocompleteMatches();
[email protected]257ab712009-04-14 17:16:24278 listener_->OnProviderUpdate(!suggest_results->empty());
initial.commit09911bf2008-07-26 23:55:29279}
280
[email protected]601858c02010-09-01 17:08:20281SearchProvider::~SearchProvider() {
282}
283
[email protected]8d457132010-11-04 18:13:40284void SearchProvider::DoHistoryQuery(bool minimal_changes) {
285 // The history query results are synchronous, so if minimal_changes is true,
286 // we still have the last results and don't need to do anything.
287 if (minimal_changes)
initial.commit09911bf2008-07-26 23:55:29288 return;
289
[email protected]8d457132010-11-04 18:13:40290 keyword_history_results_.clear();
291 default_history_results_.clear();
initial.commit09911bf2008-07-26 23:55:29292
[email protected]8d457132010-11-04 18:13:40293 HistoryService* const history_service =
294 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
295 history::URLDatabase* url_db = history_service ?
296 history_service->InMemoryDatabase() : NULL;
297 if (!url_db)
initial.commit09911bf2008-07-26 23:55:29298 return;
299
[email protected]257ab712009-04-14 17:16:24300 // Request history for both the keyword and default provider.
301 if (providers_.valid_keyword_provider()) {
[email protected]8d457132010-11-04 18:13:40302 url_db->GetMostRecentKeywordSearchTerms(
303 providers_.keyword_provider().id(),
[email protected]a2fedb1e2011-01-25 15:23:36304 keyword_input_text_,
[email protected]8d457132010-11-04 18:13:40305 static_cast<int>(kMaxMatches),
306 &keyword_history_results_);
[email protected]257ab712009-04-14 17:16:24307 }
308 if (providers_.valid_default_provider()) {
[email protected]8d457132010-11-04 18:13:40309 url_db->GetMostRecentKeywordSearchTerms(
310 providers_.default_provider().id(),
[email protected]a2fedb1e2011-01-25 15:23:36311 input_.text(),
[email protected]8d457132010-11-04 18:13:40312 static_cast<int>(kMaxMatches),
313 &default_history_results_);
[email protected]257ab712009-04-14 17:16:24314 }
initial.commit09911bf2008-07-26 23:55:29315}
316
[email protected]8deeb952008-10-09 18:21:27317void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]6c85aa02009-02-27 12:08:09318 // Don't send any queries to the server until some time has elapsed after
319 // the last keypress, to avoid flooding the server with requests we are
320 // likely to end up throwing away anyway.
321 static const int kQueryDelayMs = 200;
322
[email protected]83c726482008-09-10 06:36:34323 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29324 StopSuggest();
325 return;
326 }
327
328 // For the minimal_changes case, if we finished the previous query and still
329 // have its results, or are allowed to keep running it, just do that, rather
330 // than starting a new query.
331 if (minimal_changes &&
[email protected]ea3b9a502011-04-04 14:19:37332 (have_suggest_results_ ||
333 (!done_ &&
334 input_.matches_requested() == AutocompleteInput::ALL_MATCHES)))
initial.commit09911bf2008-07-26 23:55:29335 return;
336
337 // We can't keep running any previous query, so halt it.
338 StopSuggest();
339
340 // We can't start a new query if we're only allowed synchronous results.
[email protected]ea3b9a502011-04-04 14:19:37341 if (input_.matches_requested() != AutocompleteInput::ALL_MATCHES)
initial.commit09911bf2008-07-26 23:55:29342 return;
343
[email protected]257ab712009-04-14 17:16:24344 // We'll have at least one pending fetch. Set it to 1 now, but the value is
345 // correctly set in Run. As Run isn't invoked immediately we need to set this
346 // now, else we won't think we're waiting on results from the server when we
347 // really are.
348 suggest_results_pending_ = 1;
349
initial.commit09911bf2008-07-26 23:55:29350 // Kick off a timer that will start the URL fetch if it completes before
351 // the user types another character.
[email protected]b547666d2009-04-23 16:37:58352 int delay = query_suggest_immediately_ ? 0 : kQueryDelayMs;
353 timer_.Start(TimeDelta::FromMilliseconds(delay), this, &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29354}
355
[email protected]83c726482008-09-10 06:36:34356bool SearchProvider::IsQuerySuitableForSuggest() const {
[email protected]2c910b72011-03-08 21:16:32357 // Don't run Suggest in incognito mode, the engine doesn't support it, or
[email protected]83c726482008-09-10 06:36:34358 // the user has disabled it.
359 if (profile_->IsOffTheRecord() ||
[email protected]257ab712009-04-14 17:16:24360 (!providers_.valid_suggest_for_keyword_provider() &&
361 !providers_.valid_suggest_for_default_provider()) ||
[email protected]83c726482008-09-10 06:36:34362 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
363 return false;
364
[email protected]cac59d32010-08-09 23:23:14365 // If the input type might be a URL, we take extra care so that private data
[email protected]83c726482008-09-10 06:36:34366 // isn't sent to the server.
[email protected]83c726482008-09-10 06:36:34367
[email protected]cac59d32010-08-09 23:23:14368 // FORCED_QUERY means the user is explicitly asking us to search for this, so
369 // we assume it isn't a URL and/or there isn't private data.
370 if (input_.type() == AutocompleteInput::FORCED_QUERY)
371 return true;
[email protected]83c726482008-09-10 06:36:34372
[email protected]cac59d32010-08-09 23:23:14373 // Next we check the scheme. If this is UNKNOWN/REQUESTED_URL/URL with a
374 // scheme that isn't http/https/ftp, we shouldn't send it. Sending things
375 // like file: and data: is both a waste of time and a disclosure of
376 // potentially private, local data. Other "schemes" may actually be
377 // usernames, and we don't want to send passwords. If the scheme is OK, we
378 // still need to check other cases below. If this is QUERY, then the presence
379 // of these schemes means the user explicitly typed one, and thus this is
380 // probably a URL that's being entered and happens to currently be invalid --
381 // in which case we again want to run our checks below. Other QUERY cases are
382 // less likely to be URLs and thus we assume we're OK.
[email protected]a2fedb1e2011-01-25 15:23:36383 if (!LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpScheme) &&
384 !LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
385 !LowerCaseEqualsASCII(input_.scheme(), chrome::kFtpScheme))
[email protected]cac59d32010-08-09 23:23:14386 return (input_.type() == AutocompleteInput::QUERY);
387
388 // Don't send URLs with usernames, queries or refs. Some of these are
389 // private, and the Suggest server is unlikely to have any useful results
390 // for any of them. Also don't send URLs with ports, as we may initially
391 // think that a username + password is a host + port (and we don't want to
392 // send usernames/passwords), and even if the port really is a port, the
393 // server is once again unlikely to have and useful results.
394 const url_parse::Parsed& parts = input_.parts();
395 if (parts.username.is_nonempty() || parts.port.is_nonempty() ||
396 parts.query.is_nonempty() || parts.ref.is_nonempty())
397 return false;
398
399 // Don't send anything for https except the hostname. Hostnames are OK
400 // because they are visible when the TCP connection is established, but the
401 // specific path may reveal private information.
[email protected]a2fedb1e2011-01-25 15:23:36402 if (LowerCaseEqualsASCII(input_.scheme(), chrome::kHttpsScheme) &&
403 parts.path.is_nonempty())
[email protected]cac59d32010-08-09 23:23:14404 return false;
[email protected]83c726482008-09-10 06:36:34405
406 return true;
407}
408
initial.commit09911bf2008-07-26 23:55:29409void SearchProvider::StopSuggest() {
[email protected]257ab712009-04-14 17:16:24410 suggest_results_pending_ = 0;
[email protected]2d316662008-09-03 18:18:14411 timer_.Stop();
[email protected]257ab712009-04-14 17:16:24412 // Stop any in-progress URL fetches.
413 keyword_fetcher_.reset();
414 default_fetcher_.reset();
415 keyword_suggest_results_.clear();
416 default_suggest_results_.clear();
417 keyword_navigation_results_.clear();
418 default_navigation_results_.clear();
initial.commit09911bf2008-07-26 23:55:29419 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29420}
421
[email protected]b547666d2009-04-23 16:37:58422URLFetcher* SearchProvider::CreateSuggestFetcher(int id,
423 const TemplateURL& provider,
[email protected]a2fedb1e2011-01-25 15:23:36424 const string16& text) {
[email protected]257ab712009-04-14 17:16:24425 const TemplateURLRef* const suggestions_url = provider.suggestions_url();
426 DCHECK(suggestions_url->SupportsReplacement());
[email protected]b547666d2009-04-23 16:37:58427 URLFetcher* fetcher = URLFetcher::Create(id,
[email protected]ddd231e2010-06-29 20:35:19428 GURL(suggestions_url->ReplaceSearchTerms(
[email protected]a2fedb1e2011-01-25 15:23:36429 provider, text,
[email protected]400b133f2011-01-19 18:32:30430 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, string16())),
[email protected]257ab712009-04-14 17:16:24431 URLFetcher::GET, this);
432 fetcher->set_request_context(profile_->GetRequestContext());
433 fetcher->Start();
434 return fetcher;
435}
436
437bool SearchProvider::ParseSuggestResults(Value* root_val,
438 bool is_keyword,
[email protected]a2fedb1e2011-01-25 15:23:36439 const string16& input_text,
[email protected]257ab712009-04-14 17:16:24440 SuggestResults* suggest_results) {
initial.commit09911bf2008-07-26 23:55:29441 if (!root_val->IsType(Value::TYPE_LIST))
442 return false;
443 ListValue* root_list = static_cast<ListValue*>(root_val);
444
445 Value* query_val;
[email protected]dc9a6762010-08-16 07:13:53446 string16 query_str;
initial.commit09911bf2008-07-26 23:55:29447 Value* result_val;
448 if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
[email protected]dc9a6762010-08-16 07:13:53449 !query_val->GetAsString(&query_str) ||
[email protected]a2fedb1e2011-01-25 15:23:36450 (query_str != input_text) ||
initial.commit09911bf2008-07-26 23:55:29451 !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
452 return false;
453
454 ListValue* description_list = NULL;
455 if (root_list->GetSize() > 2) {
456 // 3rd element: Description list.
457 Value* description_val;
458 if (root_list->Get(2, &description_val) &&
459 description_val->IsType(Value::TYPE_LIST))
460 description_list = static_cast<ListValue*>(description_val);
461 }
462
463 // We don't care about the query URL list (the fourth element in the
464 // response) for now.
465
466 // Parse optional data in the results from the Suggest server if any.
467 ListValue* type_list = NULL;
468 // 5th argument: Optional key-value pairs.
469 // TODO: We may iterate the 5th+ arguments of the root_list if any other
470 // optional data are defined.
471 if (root_list->GetSize() > 4) {
472 Value* optional_val;
473 if (root_list->Get(4, &optional_val) &&
474 optional_val->IsType(Value::TYPE_DICTIONARY)) {
475 DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
476
477 // Parse Google Suggest specific type extension.
[email protected]a65175d2010-08-17 04:00:57478 static const std::string kGoogleSuggestType("google:suggesttype");
initial.commit09911bf2008-07-26 23:55:29479 if (dict_val->HasKey(kGoogleSuggestType))
480 dict_val->GetList(kGoogleSuggestType, &type_list);
481 }
482 }
483
484 ListValue* result_list = static_cast<ListValue*>(result_val);
485 for (size_t i = 0; i < result_list->GetSize(); ++i) {
486 Value* suggestion_val;
[email protected]dc9a6762010-08-16 07:13:53487 string16 suggestion_str;
initial.commit09911bf2008-07-26 23:55:29488 if (!result_list->Get(i, &suggestion_val) ||
489 !suggestion_val->GetAsString(&suggestion_str))
490 return false;
491
[email protected]8e81f5092010-09-29 23:19:40492 // Google search may return empty suggestions for weird input characters,
493 // they make no sense at all and can cause problem in our code.
494 // See http://crbug.com/56214
495 if (!suggestion_str.length())
496 continue;
497
initial.commit09911bf2008-07-26 23:55:29498 Value* type_val;
[email protected]dc9a6762010-08-16 07:13:53499 std::string type_str;
initial.commit09911bf2008-07-26 23:55:29500 if (type_list && type_list->Get(i, &type_val) &&
[email protected]dc9a6762010-08-16 07:13:53501 type_val->GetAsString(&type_str) && (type_str == "NAVIGATION")) {
initial.commit09911bf2008-07-26 23:55:29502 Value* site_val;
[email protected]dc9a6762010-08-16 07:13:53503 string16 site_name;
[email protected]257ab712009-04-14 17:16:24504 NavigationResults& navigation_results =
505 is_keyword ? keyword_navigation_results_ :
506 default_navigation_results_;
[email protected]0be9b612010-05-18 01:13:41507 if ((navigation_results.size() < kMaxMatches) &&
initial.commit09911bf2008-07-26 23:55:29508 description_list && description_list->Get(i, &site_val) &&
509 site_val->IsType(Value::TYPE_STRING) &&
510 site_val->GetAsString(&site_name)) {
[email protected]16afe222009-01-08 18:57:45511 // We can't blindly trust the URL coming from the server to be valid.
[email protected]dc9a6762010-08-16 07:13:53512 GURL result_url(URLFixerUpper::FixupURL(UTF16ToUTF8(suggestion_str),
[email protected]76e7da22010-06-18 22:44:49513 std::string()));
[email protected]dc9a6762010-08-16 07:13:53514 if (result_url.is_valid()) {
[email protected]a2fedb1e2011-01-25 15:23:36515 navigation_results.push_back(NavigationResult(result_url, site_name));
[email protected]dc9a6762010-08-16 07:13:53516 }
initial.commit09911bf2008-07-26 23:55:29517 }
518 } else {
519 // TODO(kochi): Currently we treat a calculator result as a query, but it
520 // is better to have better presentation for caluculator results.
[email protected]0be9b612010-05-18 01:13:41521 if (suggest_results->size() < kMaxMatches)
[email protected]a2fedb1e2011-01-25 15:23:36522 suggest_results->push_back(suggestion_str);
initial.commit09911bf2008-07-26 23:55:29523 }
524 }
525
initial.commit09911bf2008-07-26 23:55:29526 return true;
527}
528
529void SearchProvider::ConvertResultsToAutocompleteMatches() {
530 // Convert all the results to matches and add them to a map, so we can keep
531 // the most relevant match for each result.
532 MatchMap map;
[email protected]257ab712009-04-14 17:16:24533 const Time no_time;
534 int did_not_accept_keyword_suggestion = keyword_suggest_results_.empty() ?
initial.commit09911bf2008-07-26 23:55:29535 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
536 TemplateURLRef::NO_SUGGESTION_CHOSEN;
[email protected]257ab712009-04-14 17:16:24537 // Keyword what you typed results are handled by the KeywordProvider.
initial.commit09911bf2008-07-26 23:55:29538
[email protected]257ab712009-04-14 17:16:24539 int did_not_accept_default_suggestion = default_suggest_results_.empty() ?
540 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
541 TemplateURLRef::NO_SUGGESTION_CHOSEN;
542 if (providers_.valid_default_provider()) {
[email protected]e918c112010-12-08 23:03:49543 AddMatchToMap(input_.text(), input_.text(),
544 CalculateRelevanceForWhatYouTyped(),
[email protected]257ab712009-04-14 17:16:24545 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
[email protected]e918c112010-12-08 23:03:49546 did_not_accept_default_suggestion, false,
547 input_.initial_prevent_inline_autocomplete(), &map);
[email protected]9e789742011-01-10 23:27:32548 if (!default_provider_suggest_text_.empty()) {
549 AddMatchToMap(input_.text() + default_provider_suggest_text_,
550 input_.text(), CalculateRelevanceForWhatYouTyped() + 1,
551 AutocompleteMatch::SEARCH_SUGGEST,
552 did_not_accept_default_suggestion, false,
553 input_.initial_prevent_inline_autocomplete(), &map);
554 }
initial.commit09911bf2008-07-26 23:55:29555 }
556
[email protected]257ab712009-04-14 17:16:24557 AddHistoryResultsToMap(keyword_history_results_, true,
558 did_not_accept_keyword_suggestion, &map);
559 AddHistoryResultsToMap(default_history_results_, false,
560 did_not_accept_default_suggestion, &map);
561
562 AddSuggestResultsToMap(keyword_suggest_results_, true,
563 did_not_accept_keyword_suggestion, &map);
564 AddSuggestResultsToMap(default_suggest_results_, false,
565 did_not_accept_default_suggestion, &map);
initial.commit09911bf2008-07-26 23:55:29566
567 // Now add the most relevant matches from the map to |matches_|.
568 matches_.clear();
569 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
570 matches_.push_back(i->second);
571
[email protected]257ab712009-04-14 17:16:24572 AddNavigationResultsToMatches(keyword_navigation_results_, true);
573 AddNavigationResultsToMatches(default_navigation_results_, false);
initial.commit09911bf2008-07-26 23:55:29574
[email protected]0be9b612010-05-18 01:13:41575 const size_t max_total_matches = kMaxMatches + 1; // 1 for "what you typed"
initial.commit09911bf2008-07-26 23:55:29576 std::partial_sort(matches_.begin(),
577 matches_.begin() + std::min(max_total_matches, matches_.size()),
578 matches_.end(), &AutocompleteMatch::MoreRelevant);
579 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02580 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29581
[email protected]70833262011-01-05 23:40:44582 UpdateFirstSearchMatchDescription();
583
[email protected]cc63dea2008-08-21 20:56:31584 UpdateStarredStateOfMatches();
585
[email protected]4ab4c7c2010-11-24 04:49:34586 UpdateDone();
[email protected]257ab712009-04-14 17:16:24587}
588
589void SearchProvider::AddNavigationResultsToMatches(
590 const NavigationResults& navigation_results,
591 bool is_keyword) {
592 if (!navigation_results.empty()) {
593 // TODO(kochi): http://b/1170574 We add only one results for navigational
594 // suggestions. If we can get more useful information about the score,
595 // consider adding more results.
[email protected]52d08b12009-10-19 18:42:36596 const size_t num_results = is_keyword ?
597 keyword_navigation_results_.size() : default_navigation_results_.size();
598 matches_.push_back(NavigationToMatch(navigation_results.front(),
599 CalculateRelevanceForNavigation(num_results, 0, is_keyword),
600 is_keyword));
[email protected]257ab712009-04-14 17:16:24601 }
602}
603
604void SearchProvider::AddHistoryResultsToMap(const HistoryResults& results,
605 bool is_keyword,
606 int did_not_accept_suggestion,
607 MatchMap* map) {
[email protected]ab49320e2011-01-06 22:45:15608 int last_relevance = 0;
[email protected]ea3b9a502011-04-04 14:19:37609 AutocompleteClassifier* classifier = profile_->GetAutocompleteClassifier();
[email protected]257ab712009-04-14 17:16:24610 for (HistoryResults::const_iterator i(results.begin()); i != results.end();
611 ++i) {
[email protected]ab49320e2011-01-06 22:45:15612 // History returns results sorted for us. We force the relevance to decrease
613 // so that the sort from history is honored. We should never end up with a
614 // match having a relevance greater than the previous, but they might be
615 // equal. If we didn't force the relevance to decrease and we ended up in a
616 // situation where the relevance was equal, then which was shown first would
617 // be random.
618 // This uses >= to handle the case where 3 or more results have the same
619 // relevance.
[email protected]ea3b9a502011-04-04 14:19:37620 bool term_looks_like_url = false;
621 // Don't autocomplete search terms that would normally be treated as URLs
622 // when typed. For example, if the user searched for google.com and types
623 // goog, don't autocomplete to the search term google.com. Otherwise, the
624 // input will look like a URL but act like a search, which is confusing.
[email protected]cc447362011-04-06 03:57:48625 // NOTE: We don't check this in the following cases:
626 // * When inline autocomplete is disabled, we won't be inline
627 // autocompleting this term, so we don't need to worry about confusion as
628 // much. This also prevents calling Classify() again from inside the
629 // classifier (which will corrupt state and likely crash), since the
630 // classifier always disabled inline autocomplete.
631 // * When the user has typed the whole term, the "what you typed" history
632 // match will outrank us for URL-like inputs anyway, so we need not do
633 // anything special.
634 if (!input_.prevent_inline_autocomplete() && classifier &&
635 i->term != input_.text()) {
[email protected]ea3b9a502011-04-04 14:19:37636 AutocompleteMatch match;
637 classifier->Classify(i->term, string16(), false, &match, NULL);
638 term_looks_like_url = match.transition == PageTransition::TYPED;
639 }
640 int relevance = CalculateRelevanceForHistory(i->time, term_looks_like_url,
641 is_keyword);
[email protected]ab49320e2011-01-06 22:45:15642 if (i != results.begin() && relevance >= last_relevance)
643 relevance = last_relevance - 1;
644 last_relevance = relevance;
[email protected]a2fedb1e2011-01-25 15:23:36645 AddMatchToMap(i->term,
[email protected]e918c112010-12-08 23:03:49646 is_keyword ? keyword_input_text_ : input_.text(),
[email protected]ab49320e2011-01-06 22:45:15647 relevance,
[email protected]257ab712009-04-14 17:16:24648 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
[email protected]e918c112010-12-08 23:03:49649 is_keyword, input_.initial_prevent_inline_autocomplete(),
650 map);
[email protected]257ab712009-04-14 17:16:24651 }
652}
653
654void SearchProvider::AddSuggestResultsToMap(
655 const SuggestResults& suggest_results,
656 bool is_keyword,
657 int did_not_accept_suggestion,
658 MatchMap* map) {
659 for (size_t i = 0; i < suggest_results.size(); ++i) {
660 AddMatchToMap(suggest_results[i],
[email protected]e918c112010-12-08 23:03:49661 is_keyword ? keyword_input_text_ : input_.text(),
[email protected]52d08b12009-10-19 18:42:36662 CalculateRelevanceForSuggestion(suggest_results.size(), i,
[email protected]257ab712009-04-14 17:16:24663 is_keyword),
664 AutocompleteMatch::SEARCH_SUGGEST,
[email protected]e918c112010-12-08 23:03:49665 static_cast<int>(i), is_keyword,
666 input_.initial_prevent_inline_autocomplete(), map);
[email protected]257ab712009-04-14 17:16:24667 }
initial.commit09911bf2008-07-26 23:55:29668}
669
670int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
[email protected]52d08b12009-10-19 18:42:36671 if (providers_.valid_keyword_provider())
672 return 250;
673
initial.commit09911bf2008-07-26 23:55:29674 switch (input_.type()) {
675 case AutocompleteInput::UNKNOWN:
[email protected]52d08b12009-10-19 18:42:36676 case AutocompleteInput::QUERY:
677 case AutocompleteInput::FORCED_QUERY:
678 return 1300;
initial.commit09911bf2008-07-26 23:55:29679
680 case AutocompleteInput::REQUESTED_URL:
[email protected]52d08b12009-10-19 18:42:36681 return 1150;
initial.commit09911bf2008-07-26 23:55:29682
683 case AutocompleteInput::URL:
[email protected]52d08b12009-10-19 18:42:36684 return 850;
initial.commit09911bf2008-07-26 23:55:29685
686 default:
687 NOTREACHED();
688 return 0;
689 }
690}
691
[email protected]257ab712009-04-14 17:16:24692int SearchProvider::CalculateRelevanceForHistory(const Time& time,
[email protected]ea3b9a502011-04-04 14:19:37693 bool looks_like_url,
[email protected]257ab712009-04-14 17:16:24694 bool is_keyword) const {
[email protected]aa613d62010-11-09 20:40:18695 // The relevance of past searches falls off over time. There are two distinct
696 // equations used. If the first equation is used (searches to the primary
[email protected]ea3b9a502011-04-04 14:19:37697 // provider with a type other than URL that don't autocomplete to a url) the
698 // score starts at 1399 and falls to 1300. If the second equation is used the
699 // relevance of a search 15 minutes ago is discounted about 50 points, while
700 // the relevance of a search two weeks ago is discounted about 450 points.
[email protected]aa613d62010-11-09 20:40:18701 double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
702
703 if (providers_.is_primary_provider(is_keyword) &&
[email protected]bdca9beb2010-11-15 22:56:19704 input_.type() != AutocompleteInput::URL &&
[email protected]ea3b9a502011-04-04 14:19:37705 !input_.prevent_inline_autocomplete() && !looks_like_url) {
[email protected]aa613d62010-11-09 20:40:18706 // Searches with the past two days get a different curve.
707 const double autocomplete_time= 2 * 24 * 60 * 60;
708 if (elapsed_time < autocomplete_time) {
709 return 1399 - static_cast<int>(99 *
710 std::pow(elapsed_time / autocomplete_time, 2.5));
711 }
712 elapsed_time -= autocomplete_time;
713 }
714
[email protected]c3a4bd992010-08-18 20:25:01715 const int score_discount =
716 static_cast<int>(6.5 * std::pow(elapsed_time, 0.3));
initial.commit09911bf2008-07-26 23:55:29717
[email protected]6c85aa02009-02-27 12:08:09718 // Don't let scores go below 0. Negative relevance scores are meaningful in
719 // a different way.
initial.commit09911bf2008-07-26 23:55:29720 int base_score;
[email protected]52d08b12009-10-19 18:42:36721 if (!providers_.is_primary_provider(is_keyword))
722 base_score = 200;
723 else
724 base_score = (input_.type() == AutocompleteInput::URL) ? 750 : 1050;
initial.commit09911bf2008-07-26 23:55:29725 return std::max(0, base_score - score_discount);
726}
727
[email protected]52d08b12009-10-19 18:42:36728int SearchProvider::CalculateRelevanceForSuggestion(size_t num_results,
729 size_t result_number,
730 bool is_keyword) const {
731 DCHECK(result_number < num_results);
732 int base_score;
733 if (!providers_.is_primary_provider(is_keyword))
734 base_score = 100;
735 else
736 base_score = (input_.type() == AutocompleteInput::URL) ? 300 : 600;
737 return base_score +
738 static_cast<int>(num_results - 1 - result_number);
initial.commit09911bf2008-07-26 23:55:29739}
740
[email protected]52d08b12009-10-19 18:42:36741int SearchProvider::CalculateRelevanceForNavigation(size_t num_results,
742 size_t result_number,
743 bool is_keyword) const {
744 DCHECK(result_number < num_results);
initial.commit09911bf2008-07-26 23:55:29745 // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
746 // server if possible.
[email protected]52d08b12009-10-19 18:42:36747 return (providers_.is_primary_provider(is_keyword) ? 800 : 150) +
748 static_cast<int>(num_results - 1 - result_number);
initial.commit09911bf2008-07-26 23:55:29749}
750
[email protected]a2fedb1e2011-01-25 15:23:36751void SearchProvider::AddMatchToMap(const string16& query_string,
752 const string16& input_text,
initial.commit09911bf2008-07-26 23:55:29753 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:00754 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:29755 int accepted_suggestion,
[email protected]257ab712009-04-14 17:16:24756 bool is_keyword,
[email protected]e918c112010-12-08 23:03:49757 bool prevent_inline_autocomplete,
initial.commit09911bf2008-07-26 23:55:29758 MatchMap* map) {
[email protected]4c1fb7ec2008-11-13 00:19:00759 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:29760 std::vector<size_t> content_param_offsets;
[email protected]257ab712009-04-14 17:16:24761 const TemplateURL& provider = is_keyword ? providers_.keyword_provider() :
762 providers_.default_provider();
[email protected]70833262011-01-05 23:40:44763 match.contents.assign(query_string);
[email protected]fb5153c52009-07-31 19:40:33764 // We do intra-string highlighting for suggestions - the suggested segment
765 // will be highlighted, e.g. for input_text = "you" the suggestion may be
766 // "youtube", so we'll bold the "tube" section: you*tube*.
767 if (input_text != query_string) {
[email protected]fb5153c52009-07-31 19:40:33768 size_t input_position = match.contents.find(input_text);
[email protected]a2fedb1e2011-01-25 15:23:36769 if (input_position == string16::npos) {
[email protected]fb5153c52009-07-31 19:40:33770 // The input text is not a substring of the query string, e.g. input
771 // text is "slasdot" and the query string is "slashdot", so we bold the
772 // whole thing.
773 match.contents_class.push_back(
774 ACMatchClassification(0, ACMatchClassification::MATCH));
[email protected]ec2379162009-06-09 23:58:17775 } else {
[email protected]fb5153c52009-07-31 19:40:33776 // TODO(beng): ACMatchClassification::MATCH now seems to just mean
777 // "bold" this. Consider modifying the terminology.
778 // We don't iterate over the string here annotating all matches because
779 // it looks odd to have every occurrence of a substring that may be as
780 // short as a single character highlighted in a query suggestion result,
781 // e.g. for input text "s" and query string "southwest airlines", it
782 // looks odd if both the first and last s are highlighted.
783 if (input_position != 0) {
784 match.contents_class.push_back(
785 ACMatchClassification(0, ACMatchClassification::NONE));
786 }
787 match.contents_class.push_back(
788 ACMatchClassification(input_position, ACMatchClassification::DIM));
789 size_t next_fragment_position = input_position + input_text.length();
790 if (next_fragment_position < query_string.length()) {
791 match.contents_class.push_back(
792 ACMatchClassification(next_fragment_position,
793 ACMatchClassification::NONE));
794 }
[email protected]ec2379162009-06-09 23:58:17795 }
initial.commit09911bf2008-07-26 23:55:29796 } else {
[email protected]fb5153c52009-07-31 19:40:33797 // Otherwise, we're dealing with the "default search" result which has no
[email protected]70833262011-01-05 23:40:44798 // completion.
[email protected]fb5153c52009-07-31 19:40:33799 match.contents_class.push_back(
800 ACMatchClassification(0, ACMatchClassification::NONE));
initial.commit09911bf2008-07-26 23:55:29801 }
802
803 // When the user forced a query, we need to make sure all the fill_into_edit
804 // values preserve that property. Otherwise, if the user starts editing a
805 // suggestion, non-Search results will suddenly appear.
806 size_t search_start = 0;
807 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
[email protected]a2fedb1e2011-01-25 15:23:36808 match.fill_into_edit.assign(ASCIIToUTF16("?"));
initial.commit09911bf2008-07-26 23:55:29809 ++search_start;
810 }
[email protected]c0048b42009-05-04 21:47:17811 if (is_keyword) {
[email protected]a2fedb1e2011-01-25 15:23:36812 match.fill_into_edit.append(
813 providers_.keyword_provider().keyword() + char16(' '));
[email protected]c0048b42009-05-04 21:47:17814 match.template_url = &providers_.keyword_provider();
815 }
initial.commit09911bf2008-07-26 23:55:29816 match.fill_into_edit.append(query_string);
[email protected]2c33dd22010-02-11 21:46:35817 // Not all suggestions start with the original input.
[email protected]e918c112010-12-08 23:03:49818 if (!prevent_inline_autocomplete &&
[email protected]257ab712009-04-14 17:16:24819 !match.fill_into_edit.compare(search_start, input_text.length(),
820 input_text))
821 match.inline_autocomplete_offset = search_start + input_text.length();
initial.commit09911bf2008-07-26 23:55:29822
[email protected]257ab712009-04-14 17:16:24823 const TemplateURLRef* const search_url = provider.url();
initial.commit09911bf2008-07-26 23:55:29824 DCHECK(search_url->SupportsReplacement());
[email protected]7b9f3672009-06-15 18:31:22825 match.destination_url =
[email protected]ddd231e2010-06-29 20:35:19826 GURL(search_url->ReplaceSearchTerms(provider,
[email protected]a2fedb1e2011-01-25 15:23:36827 query_string,
[email protected]ddd231e2010-06-29 20:35:19828 accepted_suggestion,
[email protected]a2fedb1e2011-01-25 15:23:36829 input_text));
initial.commit09911bf2008-07-26 23:55:29830
831 // Search results don't look like URLs.
[email protected]0bfc29a2009-04-27 16:15:44832 match.transition =
833 is_keyword ? PageTransition::KEYWORD : PageTransition::GENERATED;
initial.commit09911bf2008-07-26 23:55:29834
835 // Try to add |match| to |map|. If a match for |query_string| is already in
836 // |map|, replace it if |match| is more relevant.
837 // NOTE: Keep this ToLower() call in sync with url_database.cc.
838 const std::pair<MatchMap::iterator, bool> i = map->insert(
[email protected]a2fedb1e2011-01-25 15:23:36839 std::pair<string16, AutocompleteMatch>(
[email protected]503d03872011-05-06 08:36:26840 base::i18n::ToLower(query_string), match));
initial.commit09911bf2008-07-26 23:55:29841 // NOTE: We purposefully do a direct relevance comparison here instead of
842 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
843 // first" rather than "items alphabetically first" when the scores are equal.
844 // The only case this matters is when a user has results with the same score
845 // that differ only by capitalization; because the history system returns
846 // results sorted by recency, this means we'll pick the most recent such
847 // result even if the precision of our relevance score is too low to
848 // distinguish the two.
849 if (!i.second && (match.relevance > i.first->second.relevance))
850 i.first->second = match;
851}
852
853AutocompleteMatch SearchProvider::NavigationToMatch(
854 const NavigationResult& navigation,
[email protected]257ab712009-04-14 17:16:24855 int relevance,
856 bool is_keyword) {
[email protected]a2fedb1e2011-01-25 15:23:36857 const string16& input_text =
[email protected]257ab712009-04-14 17:16:24858 is_keyword ? keyword_input_text_ : input_.text();
[email protected]4c1fb7ec2008-11-13 00:19:00859 AutocompleteMatch match(this, relevance, false,
860 AutocompleteMatch::NAVSUGGEST);
initial.commit09911bf2008-07-26 23:55:29861 match.destination_url = navigation.url;
[email protected]76e7da22010-06-18 22:44:49862 match.contents =
863 StringForURLDisplay(navigation.url, true, !HasHTTPScheme(input_text));
[email protected]257ab712009-04-14 17:16:24864 AutocompleteMatch::ClassifyMatchInString(input_text, match.contents,
initial.commit09911bf2008-07-26 23:55:29865 ACMatchClassification::URL,
866 &match.contents_class);
867
868 match.description = navigation.site_name;
[email protected]257ab712009-04-14 17:16:24869 AutocompleteMatch::ClassifyMatchInString(input_text, navigation.site_name,
initial.commit09911bf2008-07-26 23:55:29870 ACMatchClassification::NONE,
871 &match.description_class);
872
initial.commit09911bf2008-07-26 23:55:29873 // When the user forced a query, we need to make sure all the fill_into_edit
874 // values preserve that property. Otherwise, if the user starts editing a
875 // suggestion, non-Search results will suddenly appear.
876 if (input_.type() == AutocompleteInput::FORCED_QUERY)
[email protected]a2fedb1e2011-01-25 15:23:36877 match.fill_into_edit.assign(ASCIIToUTF16("?"));
[email protected]79845ef2010-06-02 02:37:40878 match.fill_into_edit.append(
879 AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url,
880 match.contents));
initial.commit09911bf2008-07-26 23:55:29881 // TODO(pkasting): http://b/1112879 These should perhaps be
882 // inline-autocompletable?
883
884 return match;
885}
[email protected]4ab4c7c2010-11-24 04:49:34886
887void SearchProvider::UpdateDone() {
888 // We're done when there are no more suggest queries pending (this is set to 1
889 // when the timer is started) and we're not waiting on instant.
890 done_ = ((suggest_results_pending_ == 0) &&
891 (instant_finalized_ || !InstantController::IsEnabled(profile_)));
892}
[email protected]70833262011-01-05 23:40:44893
894void SearchProvider::UpdateFirstSearchMatchDescription() {
895 if (!providers_.valid_default_provider() || matches_.empty())
896 return;
897
898 for (ACMatches::iterator i = matches_.begin(); i != matches_.end(); ++i) {
899 AutocompleteMatch& match = *i;
900 switch (match.type) {
901 case AutocompleteMatch::SEARCH_WHAT_YOU_TYPED:
902 case AutocompleteMatch::SEARCH_HISTORY:
903 case AutocompleteMatch::SEARCH_SUGGEST:
[email protected]a2fedb1e2011-01-25 15:23:36904 match.description.assign(l10n_util::GetStringFUTF16(
905 IDS_AUTOCOMPLETE_SEARCH_DESCRIPTION,
906 providers_.default_provider().
907 AdjustedShortNameForLocaleDirection()));
[email protected]70833262011-01-05 23:40:44908 match.description_class.push_back(
909 ACMatchClassification(0, ACMatchClassification::DIM));
910 // Only the first search match gets a description.
911 return;
912
913 default:
914 break;
915 }
916 }
917}