blob: 1b51449e49fb4404c737de9e3b6b2a4249af4529 [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294
5#include "chrome/browser/autocomplete/search_provider.h"
6
7#include "base/message_loop.h"
8#include "base/string_util.h"
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/google_util.h"
[email protected]f870a322009-01-16 21:47:2711#include "chrome/browser/net/url_fixer_upper.h"
initial.commit09911bf2008-07-26 23:55:2912#include "chrome/browser/profile.h"
[email protected]d54e03a52009-01-16 00:31:0413#include "chrome/browser/search_engines/template_url_model.h"
initial.commit09911bf2008-07-26 23:55:2914#include "chrome/common/json_value_serializer.h"
15#include "chrome/common/l10n_util.h"
16#include "chrome/common/pref_names.h"
17#include "chrome/common/pref_service.h"
[email protected]dcf7d352009-02-26 01:56:0218#include "chrome/common/url_constants.h"
initial.commit09911bf2008-07-26 23:55:2919#include "googleurl/src/url_util.h"
[email protected]34ac8f32009-02-22 23:03:2720#include "grit/generated_resources.h"
initial.commit09911bf2008-07-26 23:55:2921#include "net/base/escape.h"
[email protected]319d9e6f2009-02-18 19:47:2122#include "net/http/http_response_headers.h"
23#include "net/url_request/url_request_status.h"
initial.commit09911bf2008-07-26 23:55:2924
[email protected]e1acf6f2008-10-27 20:43:3325using base::Time;
26using base::TimeDelta;
27
initial.commit09911bf2008-07-26 23:55:2928const int SearchProvider::kQueryDelayMs = 200;
29
30void SearchProvider::Start(const AutocompleteInput& input,
[email protected]8deeb952008-10-09 18:21:2731 bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:2932 matches_.clear();
33
34 // Can't return search/suggest results for bogus input or if there is no
35 // profile.
36 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
37 Stop();
38 return;
39 }
40
41 // Can't search with no default provider.
42 const TemplateURL* const current_default_provider =
43 profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
44 // TODO(pkasting): http://b/1155786 Eventually we should not need all these
45 // checks.
46 if (!current_default_provider || !current_default_provider->url() ||
47 !current_default_provider->url()->SupportsReplacement()) {
48 Stop();
49 return;
50 }
51
52 // If we're still running an old query but have since changed the query text
53 // or the default provider, abort the query.
54 if (!done_ && (!minimal_changes ||
55 (last_default_provider_ != current_default_provider)))
56 Stop();
57
58 // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy this.
59 // Nor should we need |last_default_provider_| just to know whether the
60 // provider changed.
61 default_provider_ = *current_default_provider;
62 last_default_provider_ = current_default_provider;
63
64 if (input.text().empty()) {
65 // User typed "?" alone. Give them a placeholder result indicating what
66 // this syntax does.
[email protected]4c1fb7ec2008-11-13 00:19:0067 AutocompleteMatch match(this, 0, false,
68 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED);
initial.commit09911bf2008-07-26 23:55:2969 static const std::wstring kNoQueryInput(
70 l10n_util::GetString(IDS_AUTOCOMPLETE_NO_QUERY));
71 match.contents.assign(l10n_util::GetStringF(
72 IDS_AUTOCOMPLETE_SEARCH_CONTENTS, default_provider_.short_name(),
73 kNoQueryInput));
74 match.contents_class.push_back(
75 ACMatchClassification(0, ACMatchClassification::DIM));
initial.commit09911bf2008-07-26 23:55:2976 matches_.push_back(match);
77 Stop();
78 return;
79 }
80
81 input_ = input;
82
[email protected]8deeb952008-10-09 18:21:2783 StartOrStopHistoryQuery(minimal_changes);
84 StartOrStopSuggestQuery(minimal_changes);
initial.commit09911bf2008-07-26 23:55:2985 ConvertResultsToAutocompleteMatches();
86}
87
88void SearchProvider::Run() {
89 // Start a new request with the current input.
90 DCHECK(!done_);
91 const TemplateURLRef* const suggestions_url =
92 default_provider_.suggestions_url();
93 DCHECK(suggestions_url->SupportsReplacement());
[email protected]e7a5b7872008-12-10 23:52:4394 fetcher_.reset(new URLFetcher(suggestions_url->ReplaceSearchTerms(
initial.commit09911bf2008-07-26 23:55:2995 default_provider_, input_.text(),
[email protected]e7a5b7872008-12-10 23:52:4396 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()),
initial.commit09911bf2008-07-26 23:55:2997 URLFetcher::GET, this));
98 fetcher_->set_request_context(profile_->GetRequestContext());
99 fetcher_->Start();
100}
101
102void SearchProvider::Stop() {
103 StopHistory();
104 StopSuggest();
105 done_ = true;
106}
107
108void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
109 const GURL& url,
110 const URLRequestStatus& status,
111 int response_code,
112 const ResponseCookies& cookie,
113 const std::string& data) {
114 DCHECK(!done_);
115 suggest_results_pending_ = false;
116 suggest_results_.clear();
117 navigation_results_.clear();
[email protected]ec9207d32008-09-26 00:51:06118 const net::HttpResponseHeaders* const response_headers =
119 source->response_headers();
120 std::string json_data(data);
121 // JSON is supposed to be in UTF-8, but some suggest service
122 // providers send JSON files in non-UTF-8 encodings, but they're
123 // usually correctly specified in Content-Type header field.
124 if (response_headers) {
125 std::string charset;
126 if (response_headers->GetCharset(&charset)) {
127 std::wstring wide_data;
128 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
129 if (CodepageToWide(data, charset.c_str(),
130 OnStringUtilConversionError::FAIL, &wide_data))
131 json_data = WideToUTF8(wide_data);
132 }
133 }
134
[email protected]b4cebf82008-12-29 19:59:08135 if (status.is_success() && response_code == 200) {
136 JSONStringValueSerializer deserializer(json_data);
137 deserializer.set_allow_trailing_comma(true);
138 scoped_ptr<Value> root_val(deserializer.Deserialize(NULL));
139 have_suggest_results_ =
140 root_val.get() && ParseSuggestResults(root_val.get());
141 }
142
initial.commit09911bf2008-07-26 23:55:29143 ConvertResultsToAutocompleteMatches();
144 listener_->OnProviderUpdate(!suggest_results_.empty());
145}
146
[email protected]8deeb952008-10-09 18:21:27147void SearchProvider::StartOrStopHistoryQuery(bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:29148 // For the minimal_changes case, if we finished the previous query and still
149 // have its results, or are allowed to keep running it, just do that, rather
150 // than starting a new query.
151 if (minimal_changes &&
[email protected]8deeb952008-10-09 18:21:27152 (have_history_results_ || (!done_ && !input_.synchronous_only())))
initial.commit09911bf2008-07-26 23:55:29153 return;
154
155 // We can't keep running any previous query, so halt it.
156 StopHistory();
157
158 // We can't start a new query if we're only allowed synchronous results.
[email protected]8deeb952008-10-09 18:21:27159 if (input_.synchronous_only())
initial.commit09911bf2008-07-26 23:55:29160 return;
161
162 // Start the history query.
163 HistoryService* const history_service =
164 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
165 history_service->GetMostRecentKeywordSearchTerms(default_provider_.id(),
166 input_.text(), static_cast<int>(max_matches()),
167 &history_request_consumer_,
168 NewCallback(this, &SearchProvider::OnGotMostRecentKeywordSearchTerms));
169 history_request_pending_ = true;
170}
171
[email protected]8deeb952008-10-09 18:21:27172void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]83c726482008-09-10 06:36:34173 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29174 StopSuggest();
175 return;
176 }
177
178 // For the minimal_changes case, if we finished the previous query and still
179 // have its results, or are allowed to keep running it, just do that, rather
180 // than starting a new query.
181 if (minimal_changes &&
[email protected]8deeb952008-10-09 18:21:27182 (have_suggest_results_ || (!done_ && !input_.synchronous_only())))
initial.commit09911bf2008-07-26 23:55:29183 return;
184
185 // We can't keep running any previous query, so halt it.
186 StopSuggest();
187
188 // We can't start a new query if we're only allowed synchronous results.
[email protected]8deeb952008-10-09 18:21:27189 if (input_.synchronous_only())
initial.commit09911bf2008-07-26 23:55:29190 return;
191
192 // Kick off a timer that will start the URL fetch if it completes before
193 // the user types another character.
194 suggest_results_pending_ = true;
[email protected]2d316662008-09-03 18:18:14195
196 timer_.Stop();
197 timer_.Start(TimeDelta::FromMilliseconds(kQueryDelayMs), this,
198 &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29199}
200
[email protected]83c726482008-09-10 06:36:34201bool SearchProvider::IsQuerySuitableForSuggest() const {
202 // Don't run Suggest when off the record, the engine doesn't support it, or
203 // the user has disabled it.
204 if (profile_->IsOffTheRecord() ||
205 !default_provider_.suggestions_url() ||
206 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
207 return false;
208
209 // If the input type is URL, we take extra care so that private data in URL
210 // isn't sent to the server.
211 if (input_.type() == AutocompleteInput::URL) {
212 // Don't query the server for URLs that aren't http/https/ftp. Sending
213 // things like file: and data: is both a waste of time and a disclosure of
214 // potentially private, local data.
215 if ((input_.scheme() != L"http") && (input_.scheme() != L"https") &&
216 (input_.scheme() != L"ftp"))
217 return false;
218
219 // Don't leak private data in URL
220 const url_parse::Parsed& parts = input_.parts();
221
222 // Don't send URLs with usernames, queries or refs. Some of these are
223 // private, and the Suggest server is unlikely to have any useful results
224 // for any of them.
225 // Password is optional and may be omitted. Checking username is
226 // sufficient.
227 if (parts.username.is_nonempty() || parts.query.is_nonempty() ||
228 parts.ref.is_nonempty())
229 return false;
230 // Don't send anything for https except hostname and port number.
231 // Hostname and port number are OK because they are visible when TCP
232 // connection is established and the Suggest server may provide some
233 // useful completed URL.
234 if (input_.scheme() == L"https" && parts.path.is_nonempty())
235 return false;
236 }
237
238 return true;
239}
240
initial.commit09911bf2008-07-26 23:55:29241void SearchProvider::StopHistory() {
242 history_request_consumer_.CancelAllRequests();
243 history_request_pending_ = false;
244 history_results_.clear();
245 have_history_results_ = false;
246}
247
248void SearchProvider::StopSuggest() {
249 suggest_results_pending_ = false;
[email protected]2d316662008-09-03 18:18:14250 timer_.Stop();
initial.commit09911bf2008-07-26 23:55:29251 fetcher_.reset(); // Stop any in-progress URL fetch.
252 suggest_results_.clear();
253 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29254}
255
256void SearchProvider::OnGotMostRecentKeywordSearchTerms(
257 CancelableRequestProvider::Handle handle,
258 HistoryResults* results) {
259 history_request_pending_ = false;
260 have_history_results_ = true;
261 history_results_ = *results;
262 ConvertResultsToAutocompleteMatches();
263 listener_->OnProviderUpdate(!history_results_.empty());
264}
265
initial.commit09911bf2008-07-26 23:55:29266bool SearchProvider::ParseSuggestResults(Value* root_val) {
267 if (!root_val->IsType(Value::TYPE_LIST))
268 return false;
269 ListValue* root_list = static_cast<ListValue*>(root_val);
270
271 Value* query_val;
272 std::wstring query_str;
273 Value* result_val;
274 if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
275 !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
276 !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
277 return false;
278
279 ListValue* description_list = NULL;
280 if (root_list->GetSize() > 2) {
281 // 3rd element: Description list.
282 Value* description_val;
283 if (root_list->Get(2, &description_val) &&
284 description_val->IsType(Value::TYPE_LIST))
285 description_list = static_cast<ListValue*>(description_val);
286 }
287
288 // We don't care about the query URL list (the fourth element in the
289 // response) for now.
290
291 // Parse optional data in the results from the Suggest server if any.
292 ListValue* type_list = NULL;
293 // 5th argument: Optional key-value pairs.
294 // TODO: We may iterate the 5th+ arguments of the root_list if any other
295 // optional data are defined.
296 if (root_list->GetSize() > 4) {
297 Value* optional_val;
298 if (root_list->Get(4, &optional_val) &&
299 optional_val->IsType(Value::TYPE_DICTIONARY)) {
300 DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
301
302 // Parse Google Suggest specific type extension.
303 static const std::wstring kGoogleSuggestType(L"google:suggesttype");
304 if (dict_val->HasKey(kGoogleSuggestType))
305 dict_val->GetList(kGoogleSuggestType, &type_list);
306 }
307 }
308
309 ListValue* result_list = static_cast<ListValue*>(result_val);
310 for (size_t i = 0; i < result_list->GetSize(); ++i) {
311 Value* suggestion_val;
312 std::wstring suggestion_str;
313 if (!result_list->Get(i, &suggestion_val) ||
314 !suggestion_val->GetAsString(&suggestion_str))
315 return false;
316
317 Value* type_val;
318 std::wstring type_str;
319 if (type_list && type_list->Get(i, &type_val) &&
320 type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
321 Value* site_val;
322 std::wstring site_name;
[email protected]16afe222009-01-08 18:57:45323 if ((navigation_results_.size() < max_matches()) &&
initial.commit09911bf2008-07-26 23:55:29324 description_list && description_list->Get(i, &site_val) &&
325 site_val->IsType(Value::TYPE_STRING) &&
326 site_val->GetAsString(&site_name)) {
[email protected]16afe222009-01-08 18:57:45327 // We can't blindly trust the URL coming from the server to be valid.
328 GURL result_url =
[email protected]1d73faa2009-02-24 19:32:15329 GURL(URLFixerUpper::FixupURL(WideToUTF8(suggestion_str),
330 std::string()));
[email protected]16afe222009-01-08 18:57:45331 if (result_url.is_valid()) {
332 navigation_results_.push_back(NavigationResult(result_url,
333 site_name));
334 }
initial.commit09911bf2008-07-26 23:55:29335 }
336 } else {
337 // TODO(kochi): Currently we treat a calculator result as a query, but it
338 // is better to have better presentation for caluculator results.
339 if (suggest_results_.size() < max_matches())
340 suggest_results_.push_back(suggestion_str);
341 }
342 }
343
initial.commit09911bf2008-07-26 23:55:29344 return true;
345}
346
347void SearchProvider::ConvertResultsToAutocompleteMatches() {
348 // Convert all the results to matches and add them to a map, so we can keep
349 // the most relevant match for each result.
350 MatchMap map;
351 const int did_not_accept_suggestion = suggest_results_.empty() ?
352 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
353 TemplateURLRef::NO_SUGGESTION_CHOSEN;
354 const Time no_time;
355 AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
[email protected]4c1fb7ec2008-11-13 00:19:00356 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
initial.commit09911bf2008-07-26 23:55:29357 did_not_accept_suggestion, &map);
358
359 for (HistoryResults::const_iterator i(history_results_.begin());
360 i != history_results_.end(); ++i) {
361 AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
[email protected]4c1fb7ec2008-11-13 00:19:00362 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
363 &map);
initial.commit09911bf2008-07-26 23:55:29364 }
365
366 for (size_t i = 0; i < suggest_results_.size(); ++i) {
367 AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
[email protected]4c1fb7ec2008-11-13 00:19:00368 AutocompleteMatch::SEARCH_SUGGEST,
initial.commit09911bf2008-07-26 23:55:29369 static_cast<int>(i), &map);
370 }
371
372 // Now add the most relevant matches from the map to |matches_|.
373 matches_.clear();
374 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
375 matches_.push_back(i->second);
376
[email protected]ffaf78a2008-11-12 17:38:33377 if (!navigation_results_.empty()) {
initial.commit09911bf2008-07-26 23:55:29378 // TODO(kochi): http://b/1170574 We add only one results for navigational
379 // suggestions. If we can get more useful information about the score,
380 // consider adding more results.
[email protected]ffaf78a2008-11-12 17:38:33381 matches_.push_back(NavigationToMatch(navigation_results_.front(),
[email protected]cc63dea2008-08-21 20:56:31382 CalculateRelevanceForNavigation(0)));
initial.commit09911bf2008-07-26 23:55:29383 }
384
385 const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
386 std::partial_sort(matches_.begin(),
387 matches_.begin() + std::min(max_total_matches, matches_.size()),
388 matches_.end(), &AutocompleteMatch::MoreRelevant);
389 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02390 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29391
[email protected]cc63dea2008-08-21 20:56:31392 UpdateStarredStateOfMatches();
393
initial.commit09911bf2008-07-26 23:55:29394 // We're done when both asynchronous subcomponents have finished.
395 // We can't use CancelableRequestConsumer.HasPendingRequests() for
[email protected]cc63dea2008-08-21 20:56:31396 // history requests here. A pending request is not cleared until after the
397 // completion callback has returned, but we've reached here from inside that
398 // callback. HasPendingRequests() would therefore return true, and if this is
399 // the last thing left to calculate for this query, we'll never mark the query
400 // "done".
initial.commit09911bf2008-07-26 23:55:29401 done_ = !history_request_pending_ &&
[email protected]cc63dea2008-08-21 20:56:31402 !suggest_results_pending_;
initial.commit09911bf2008-07-26 23:55:29403}
404
405int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
406 switch (input_.type()) {
407 case AutocompleteInput::UNKNOWN:
408 return 1300;
409
410 case AutocompleteInput::REQUESTED_URL:
411 return 1200;
412
413 case AutocompleteInput::URL:
414 return 850;
415
416 case AutocompleteInput::QUERY:
417 return 1300;
418
419 case AutocompleteInput::FORCED_QUERY:
420 return 1500;
421
422 default:
423 NOTREACHED();
424 return 0;
425 }
426}
427
428int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
429 // The relevance of past searches falls off over time. This curve is chosen
430 // so that the relevance of a search 15 minutes ago is discounted about 50
431 // points, while the relevance of a search two weeks ago is discounted about
432 // 450 points.
433 const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
434 const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
435
436 // Don't let scores go below 0. Negative relevance scores are meaningful in a
437 // different way.
438 int base_score;
439 switch (input_.type()) {
440 case AutocompleteInput::UNKNOWN:
441 case AutocompleteInput::REQUESTED_URL:
442 base_score = 1050;
443 break;
444
445 case AutocompleteInput::URL:
446 base_score = 750;
447 break;
448
449 case AutocompleteInput::QUERY:
450 case AutocompleteInput::FORCED_QUERY:
451 base_score = 1250;
452 break;
453
454 default:
455 NOTREACHED();
456 base_score = 0;
457 break;
458 }
459 return std::max(0, base_score - score_discount);
460}
461
462int SearchProvider::CalculateRelevanceForSuggestion(
463 size_t suggestion_number) const {
464 DCHECK(suggestion_number < suggest_results_.size());
465 const int suggestion_value =
466 static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
467 switch (input_.type()) {
468 case AutocompleteInput::UNKNOWN:
469 case AutocompleteInput::REQUESTED_URL:
470 return 600 + suggestion_value;
471
472 case AutocompleteInput::URL:
473 return 300 + suggestion_value;
474
475 case AutocompleteInput::QUERY:
476 case AutocompleteInput::FORCED_QUERY:
477 return 800 + suggestion_value;
478
479 default:
480 NOTREACHED();
481 return 0;
482 }
483}
484
485int SearchProvider::CalculateRelevanceForNavigation(
486 size_t suggestion_number) const {
487 DCHECK(suggestion_number < navigation_results_.size());
488 // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
489 // server if possible.
490 switch (input_.type()) {
491 case AutocompleteInput::QUERY:
492 case AutocompleteInput::FORCED_QUERY:
493 return 1000 + static_cast<int>(suggestion_number);
494
495 default:
496 return 800 + static_cast<int>(suggestion_number);
497 }
498}
499
500void SearchProvider::AddMatchToMap(const std::wstring& query_string,
501 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:00502 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:29503 int accepted_suggestion,
504 MatchMap* map) {
[email protected]4c1fb7ec2008-11-13 00:19:00505 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:29506 std::vector<size_t> content_param_offsets;
507 match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
508 default_provider_.short_name(),
509 query_string,
510 &content_param_offsets));
511 if (content_param_offsets.size() == 2) {
512 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
513 query_string.length(),
514 match.contents.length(),
515 ACMatchClassification::NONE,
516 &match.contents_class);
517 } else {
518 // |content_param_offsets| should only not be 2 if:
519 // (a) A translator screws up
520 // (b) The strings have been changed and we haven't been rebuilt properly
521 // (c) Some sort of crazy installer error/DLL version mismatch problem that
522 // gets the wrong data out of the locale DLL?
523 // While none of these are supposed to happen, we've seen this get hit in
524 // the wild, so avoid the vector access in the conditional arm above, which
525 // will crash.
526 NOTREACHED();
527 }
528
529 // When the user forced a query, we need to make sure all the fill_into_edit
530 // values preserve that property. Otherwise, if the user starts editing a
531 // suggestion, non-Search results will suddenly appear.
532 size_t search_start = 0;
533 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
534 match.fill_into_edit.assign(L"?");
535 ++search_start;
536 }
537 match.fill_into_edit.append(query_string);
538 // NOTE: All Google suggestions currently start with the original input, but
539 // not all Yahoo! suggestions do.
540 if (!input_.prevent_inline_autocomplete() &&
541 !match.fill_into_edit.compare(search_start, input_.text().length(),
542 input_.text()))
543 match.inline_autocomplete_offset = search_start + input_.text().length();
544
545 const TemplateURLRef* const search_url = default_provider_.url();
546 DCHECK(search_url->SupportsReplacement());
547 match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
548 query_string,
549 accepted_suggestion,
550 input_.text());
551
552 // Search results don't look like URLs.
553 match.transition = PageTransition::GENERATED;
554
555 // Try to add |match| to |map|. If a match for |query_string| is already in
556 // |map|, replace it if |match| is more relevant.
557 // NOTE: Keep this ToLower() call in sync with url_database.cc.
558 const std::pair<MatchMap::iterator, bool> i = map->insert(
559 std::pair<std::wstring, AutocompleteMatch>(
560 l10n_util::ToLower(query_string), match));
561 // NOTE: We purposefully do a direct relevance comparison here instead of
562 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
563 // first" rather than "items alphabetically first" when the scores are equal.
564 // The only case this matters is when a user has results with the same score
565 // that differ only by capitalization; because the history system returns
566 // results sorted by recency, this means we'll pick the most recent such
567 // result even if the precision of our relevance score is too low to
568 // distinguish the two.
569 if (!i.second && (match.relevance > i.first->second.relevance))
570 i.first->second = match;
571}
572
573AutocompleteMatch SearchProvider::NavigationToMatch(
574 const NavigationResult& navigation,
[email protected]cc63dea2008-08-21 20:56:31575 int relevance) {
[email protected]4c1fb7ec2008-11-13 00:19:00576 AutocompleteMatch match(this, relevance, false,
577 AutocompleteMatch::NAVSUGGEST);
initial.commit09911bf2008-07-26 23:55:29578 match.destination_url = navigation.url;
[email protected]e7a5b7872008-12-10 23:52:43579 match.contents = StringForURLDisplay(navigation.url, true);
initial.commit09911bf2008-07-26 23:55:29580 // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
581 // public utility function.
[email protected]1d73faa2009-02-24 19:32:15582 if (!url_util::FindAndCompareScheme(WideToUTF8(input_.text()),
583 "http", NULL))
initial.commit09911bf2008-07-26 23:55:29584 TrimHttpPrefix(&match.contents);
585 AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
586 ACMatchClassification::URL,
587 &match.contents_class);
588
589 match.description = navigation.site_name;
590 AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
591 ACMatchClassification::NONE,
592 &match.description_class);
593
initial.commit09911bf2008-07-26 23:55:29594 // When the user forced a query, we need to make sure all the fill_into_edit
595 // values preserve that property. Otherwise, if the user starts editing a
596 // suggestion, non-Search results will suddenly appear.
597 if (input_.type() == AutocompleteInput::FORCED_QUERY)
598 match.fill_into_edit.assign(L"?");
599 match.fill_into_edit.append(match.contents);
600 // TODO(pkasting): http://b/1112879 These should perhaps be
601 // inline-autocompletable?
602
603 return match;
604}
605
606// TODO(kochi): This is duplicate from HistoryURLProvider.
607// static
608size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
609 url_parse::Component scheme;
[email protected]dcf7d352009-02-26 01:56:02610 if (!url_util::FindAndCompareScheme(WideToUTF8(*url), chrome::kHttpScheme,
611 &scheme))
initial.commit09911bf2008-07-26 23:55:29612 return 0; // Not "http".
613
614 // Erase scheme plus up to two slashes.
615 size_t prefix_len = scheme.end() + 1; // "http:"
616 const size_t after_slashes = std::min(url->length(),
617 static_cast<size_t>(scheme.end() + 3));
618 while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
619 ++prefix_len;
620 if (prefix_len == url->length())
621 url->clear();
622 else
623 url->erase(url->begin(), url->begin() + prefix_len);
624 return prefix_len;
625}