blob: 17bdcc8dd62f1e7b65efea53b8553310e7c1490f [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"
11#include "chrome/browser/profile.h"
12#include "chrome/browser/template_url_model.h"
13#include "chrome/common/json_value_serializer.h"
14#include "chrome/common/l10n_util.h"
15#include "chrome/common/pref_names.h"
16#include "chrome/common/pref_service.h"
17#include "googleurl/src/url_util.h"
18#include "net/base/escape.h"
19
20#include "generated_resources.h"
21
[email protected]e1acf6f2008-10-27 20:43:3322using base::Time;
23using base::TimeDelta;
24
initial.commit09911bf2008-07-26 23:55:2925const int SearchProvider::kQueryDelayMs = 200;
26
27void SearchProvider::Start(const AutocompleteInput& input,
[email protected]8deeb952008-10-09 18:21:2728 bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:2929 matches_.clear();
30
31 // Can't return search/suggest results for bogus input or if there is no
32 // profile.
33 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
34 Stop();
35 return;
36 }
37
38 // Can't search with no default provider.
39 const TemplateURL* const current_default_provider =
40 profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
41 // TODO(pkasting): http://b/1155786 Eventually we should not need all these
42 // checks.
43 if (!current_default_provider || !current_default_provider->url() ||
44 !current_default_provider->url()->SupportsReplacement()) {
45 Stop();
46 return;
47 }
48
49 // If we're still running an old query but have since changed the query text
50 // or the default provider, abort the query.
51 if (!done_ && (!minimal_changes ||
52 (last_default_provider_ != current_default_provider)))
53 Stop();
54
55 // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy this.
56 // Nor should we need |last_default_provider_| just to know whether the
57 // provider changed.
58 default_provider_ = *current_default_provider;
59 last_default_provider_ = current_default_provider;
60
61 if (input.text().empty()) {
62 // User typed "?" alone. Give them a placeholder result indicating what
63 // this syntax does.
[email protected]4c1fb7ec2008-11-13 00:19:0064 AutocompleteMatch match(this, 0, false,
65 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED);
initial.commit09911bf2008-07-26 23:55:2966 static const std::wstring kNoQueryInput(
67 l10n_util::GetString(IDS_AUTOCOMPLETE_NO_QUERY));
68 match.contents.assign(l10n_util::GetStringF(
69 IDS_AUTOCOMPLETE_SEARCH_CONTENTS, default_provider_.short_name(),
70 kNoQueryInput));
71 match.contents_class.push_back(
72 ACMatchClassification(0, ACMatchClassification::DIM));
initial.commit09911bf2008-07-26 23:55:2973 matches_.push_back(match);
74 Stop();
75 return;
76 }
77
78 input_ = input;
79
[email protected]8deeb952008-10-09 18:21:2780 StartOrStopHistoryQuery(minimal_changes);
81 StartOrStopSuggestQuery(minimal_changes);
initial.commit09911bf2008-07-26 23:55:2982 ConvertResultsToAutocompleteMatches();
83}
84
85void SearchProvider::Run() {
86 // Start a new request with the current input.
87 DCHECK(!done_);
88 const TemplateURLRef* const suggestions_url =
89 default_provider_.suggestions_url();
90 DCHECK(suggestions_url->SupportsReplacement());
[email protected]e7a5b7872008-12-10 23:52:4391 fetcher_.reset(new URLFetcher(suggestions_url->ReplaceSearchTerms(
initial.commit09911bf2008-07-26 23:55:2992 default_provider_, input_.text(),
[email protected]e7a5b7872008-12-10 23:52:4393 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()),
initial.commit09911bf2008-07-26 23:55:2994 URLFetcher::GET, this));
95 fetcher_->set_request_context(profile_->GetRequestContext());
96 fetcher_->Start();
97}
98
99void SearchProvider::Stop() {
100 StopHistory();
101 StopSuggest();
102 done_ = true;
103}
104
105void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
106 const GURL& url,
107 const URLRequestStatus& status,
108 int response_code,
109 const ResponseCookies& cookie,
110 const std::string& data) {
111 DCHECK(!done_);
112 suggest_results_pending_ = false;
113 suggest_results_.clear();
114 navigation_results_.clear();
[email protected]ec9207d32008-09-26 00:51:06115 const net::HttpResponseHeaders* const response_headers =
116 source->response_headers();
117 std::string json_data(data);
118 // JSON is supposed to be in UTF-8, but some suggest service
119 // providers send JSON files in non-UTF-8 encodings, but they're
120 // usually correctly specified in Content-Type header field.
121 if (response_headers) {
122 std::string charset;
123 if (response_headers->GetCharset(&charset)) {
124 std::wstring wide_data;
125 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
126 if (CodepageToWide(data, charset.c_str(),
127 OnStringUtilConversionError::FAIL, &wide_data))
128 json_data = WideToUTF8(wide_data);
129 }
130 }
131
[email protected]b4cebf82008-12-29 19:59:08132 if (status.is_success() && response_code == 200) {
133 JSONStringValueSerializer deserializer(json_data);
134 deserializer.set_allow_trailing_comma(true);
135 scoped_ptr<Value> root_val(deserializer.Deserialize(NULL));
136 have_suggest_results_ =
137 root_val.get() && ParseSuggestResults(root_val.get());
138 }
139
initial.commit09911bf2008-07-26 23:55:29140 ConvertResultsToAutocompleteMatches();
141 listener_->OnProviderUpdate(!suggest_results_.empty());
142}
143
[email protected]8deeb952008-10-09 18:21:27144void SearchProvider::StartOrStopHistoryQuery(bool minimal_changes) {
initial.commit09911bf2008-07-26 23:55:29145 // For the minimal_changes case, if we finished the previous query and still
146 // have its results, or are allowed to keep running it, just do that, rather
147 // than starting a new query.
148 if (minimal_changes &&
[email protected]8deeb952008-10-09 18:21:27149 (have_history_results_ || (!done_ && !input_.synchronous_only())))
initial.commit09911bf2008-07-26 23:55:29150 return;
151
152 // We can't keep running any previous query, so halt it.
153 StopHistory();
154
155 // We can't start a new query if we're only allowed synchronous results.
[email protected]8deeb952008-10-09 18:21:27156 if (input_.synchronous_only())
initial.commit09911bf2008-07-26 23:55:29157 return;
158
159 // Start the history query.
160 HistoryService* const history_service =
161 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
162 history_service->GetMostRecentKeywordSearchTerms(default_provider_.id(),
163 input_.text(), static_cast<int>(max_matches()),
164 &history_request_consumer_,
165 NewCallback(this, &SearchProvider::OnGotMostRecentKeywordSearchTerms));
166 history_request_pending_ = true;
167}
168
[email protected]8deeb952008-10-09 18:21:27169void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes) {
[email protected]83c726482008-09-10 06:36:34170 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29171 StopSuggest();
172 return;
173 }
174
175 // For the minimal_changes case, if we finished the previous query and still
176 // have its results, or are allowed to keep running it, just do that, rather
177 // than starting a new query.
178 if (minimal_changes &&
[email protected]8deeb952008-10-09 18:21:27179 (have_suggest_results_ || (!done_ && !input_.synchronous_only())))
initial.commit09911bf2008-07-26 23:55:29180 return;
181
182 // We can't keep running any previous query, so halt it.
183 StopSuggest();
184
185 // We can't start a new query if we're only allowed synchronous results.
[email protected]8deeb952008-10-09 18:21:27186 if (input_.synchronous_only())
initial.commit09911bf2008-07-26 23:55:29187 return;
188
189 // Kick off a timer that will start the URL fetch if it completes before
190 // the user types another character.
191 suggest_results_pending_ = true;
[email protected]2d316662008-09-03 18:18:14192
193 timer_.Stop();
194 timer_.Start(TimeDelta::FromMilliseconds(kQueryDelayMs), this,
195 &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29196}
197
[email protected]83c726482008-09-10 06:36:34198bool SearchProvider::IsQuerySuitableForSuggest() const {
199 // Don't run Suggest when off the record, the engine doesn't support it, or
200 // the user has disabled it.
201 if (profile_->IsOffTheRecord() ||
202 !default_provider_.suggestions_url() ||
203 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
204 return false;
205
206 // If the input type is URL, we take extra care so that private data in URL
207 // isn't sent to the server.
208 if (input_.type() == AutocompleteInput::URL) {
209 // Don't query the server for URLs that aren't http/https/ftp. Sending
210 // things like file: and data: is both a waste of time and a disclosure of
211 // potentially private, local data.
212 if ((input_.scheme() != L"http") && (input_.scheme() != L"https") &&
213 (input_.scheme() != L"ftp"))
214 return false;
215
216 // Don't leak private data in URL
217 const url_parse::Parsed& parts = input_.parts();
218
219 // Don't send URLs with usernames, queries or refs. Some of these are
220 // private, and the Suggest server is unlikely to have any useful results
221 // for any of them.
222 // Password is optional and may be omitted. Checking username is
223 // sufficient.
224 if (parts.username.is_nonempty() || parts.query.is_nonempty() ||
225 parts.ref.is_nonempty())
226 return false;
227 // Don't send anything for https except hostname and port number.
228 // Hostname and port number are OK because they are visible when TCP
229 // connection is established and the Suggest server may provide some
230 // useful completed URL.
231 if (input_.scheme() == L"https" && parts.path.is_nonempty())
232 return false;
233 }
234
235 return true;
236}
237
initial.commit09911bf2008-07-26 23:55:29238void SearchProvider::StopHistory() {
239 history_request_consumer_.CancelAllRequests();
240 history_request_pending_ = false;
241 history_results_.clear();
242 have_history_results_ = false;
243}
244
245void SearchProvider::StopSuggest() {
246 suggest_results_pending_ = false;
[email protected]2d316662008-09-03 18:18:14247 timer_.Stop();
initial.commit09911bf2008-07-26 23:55:29248 fetcher_.reset(); // Stop any in-progress URL fetch.
249 suggest_results_.clear();
250 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29251}
252
253void SearchProvider::OnGotMostRecentKeywordSearchTerms(
254 CancelableRequestProvider::Handle handle,
255 HistoryResults* results) {
256 history_request_pending_ = false;
257 have_history_results_ = true;
258 history_results_ = *results;
259 ConvertResultsToAutocompleteMatches();
260 listener_->OnProviderUpdate(!history_results_.empty());
261}
262
initial.commit09911bf2008-07-26 23:55:29263bool SearchProvider::ParseSuggestResults(Value* root_val) {
264 if (!root_val->IsType(Value::TYPE_LIST))
265 return false;
266 ListValue* root_list = static_cast<ListValue*>(root_val);
267
268 Value* query_val;
269 std::wstring query_str;
270 Value* result_val;
271 if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
272 !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
273 !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
274 return false;
275
276 ListValue* description_list = NULL;
277 if (root_list->GetSize() > 2) {
278 // 3rd element: Description list.
279 Value* description_val;
280 if (root_list->Get(2, &description_val) &&
281 description_val->IsType(Value::TYPE_LIST))
282 description_list = static_cast<ListValue*>(description_val);
283 }
284
285 // We don't care about the query URL list (the fourth element in the
286 // response) for now.
287
288 // Parse optional data in the results from the Suggest server if any.
289 ListValue* type_list = NULL;
290 // 5th argument: Optional key-value pairs.
291 // TODO: We may iterate the 5th+ arguments of the root_list if any other
292 // optional data are defined.
293 if (root_list->GetSize() > 4) {
294 Value* optional_val;
295 if (root_list->Get(4, &optional_val) &&
296 optional_val->IsType(Value::TYPE_DICTIONARY)) {
297 DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
298
299 // Parse Google Suggest specific type extension.
300 static const std::wstring kGoogleSuggestType(L"google:suggesttype");
301 if (dict_val->HasKey(kGoogleSuggestType))
302 dict_val->GetList(kGoogleSuggestType, &type_list);
303 }
304 }
305
306 ListValue* result_list = static_cast<ListValue*>(result_val);
307 for (size_t i = 0; i < result_list->GetSize(); ++i) {
308 Value* suggestion_val;
309 std::wstring suggestion_str;
310 if (!result_list->Get(i, &suggestion_val) ||
311 !suggestion_val->GetAsString(&suggestion_str))
312 return false;
313
314 Value* type_val;
315 std::wstring type_str;
316 if (type_list && type_list->Get(i, &type_val) &&
317 type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
318 Value* site_val;
319 std::wstring site_name;
320 if (navigation_results_.size() < max_matches() &&
321 description_list && description_list->Get(i, &site_val) &&
322 site_val->IsType(Value::TYPE_STRING) &&
323 site_val->GetAsString(&site_name)) {
[email protected]e7a5b7872008-12-10 23:52:43324 navigation_results_.push_back(NavigationResult(GURL(suggestion_str),
initial.commit09911bf2008-07-26 23:55:29325 site_name));
326 }
327 } else {
328 // TODO(kochi): Currently we treat a calculator result as a query, but it
329 // is better to have better presentation for caluculator results.
330 if (suggest_results_.size() < max_matches())
331 suggest_results_.push_back(suggestion_str);
332 }
333 }
334
initial.commit09911bf2008-07-26 23:55:29335 return true;
336}
337
338void SearchProvider::ConvertResultsToAutocompleteMatches() {
339 // Convert all the results to matches and add them to a map, so we can keep
340 // the most relevant match for each result.
341 MatchMap map;
342 const int did_not_accept_suggestion = suggest_results_.empty() ?
343 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
344 TemplateURLRef::NO_SUGGESTION_CHOSEN;
345 const Time no_time;
346 AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
[email protected]4c1fb7ec2008-11-13 00:19:00347 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
initial.commit09911bf2008-07-26 23:55:29348 did_not_accept_suggestion, &map);
349
350 for (HistoryResults::const_iterator i(history_results_.begin());
351 i != history_results_.end(); ++i) {
352 AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
[email protected]4c1fb7ec2008-11-13 00:19:00353 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
354 &map);
initial.commit09911bf2008-07-26 23:55:29355 }
356
357 for (size_t i = 0; i < suggest_results_.size(); ++i) {
358 AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
[email protected]4c1fb7ec2008-11-13 00:19:00359 AutocompleteMatch::SEARCH_SUGGEST,
initial.commit09911bf2008-07-26 23:55:29360 static_cast<int>(i), &map);
361 }
362
363 // Now add the most relevant matches from the map to |matches_|.
364 matches_.clear();
365 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
366 matches_.push_back(i->second);
367
[email protected]ffaf78a2008-11-12 17:38:33368 if (!navigation_results_.empty()) {
initial.commit09911bf2008-07-26 23:55:29369 // TODO(kochi): http://b/1170574 We add only one results for navigational
370 // suggestions. If we can get more useful information about the score,
371 // consider adding more results.
[email protected]ffaf78a2008-11-12 17:38:33372 matches_.push_back(NavigationToMatch(navigation_results_.front(),
[email protected]cc63dea2008-08-21 20:56:31373 CalculateRelevanceForNavigation(0)));
initial.commit09911bf2008-07-26 23:55:29374 }
375
376 const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
377 std::partial_sort(matches_.begin(),
378 matches_.begin() + std::min(max_total_matches, matches_.size()),
379 matches_.end(), &AutocompleteMatch::MoreRelevant);
380 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02381 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29382
[email protected]cc63dea2008-08-21 20:56:31383 UpdateStarredStateOfMatches();
384
initial.commit09911bf2008-07-26 23:55:29385 // We're done when both asynchronous subcomponents have finished.
386 // We can't use CancelableRequestConsumer.HasPendingRequests() for
[email protected]cc63dea2008-08-21 20:56:31387 // history requests here. A pending request is not cleared until after the
388 // completion callback has returned, but we've reached here from inside that
389 // callback. HasPendingRequests() would therefore return true, and if this is
390 // the last thing left to calculate for this query, we'll never mark the query
391 // "done".
initial.commit09911bf2008-07-26 23:55:29392 done_ = !history_request_pending_ &&
[email protected]cc63dea2008-08-21 20:56:31393 !suggest_results_pending_;
initial.commit09911bf2008-07-26 23:55:29394}
395
396int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
397 switch (input_.type()) {
398 case AutocompleteInput::UNKNOWN:
399 return 1300;
400
401 case AutocompleteInput::REQUESTED_URL:
402 return 1200;
403
404 case AutocompleteInput::URL:
405 return 850;
406
407 case AutocompleteInput::QUERY:
408 return 1300;
409
410 case AutocompleteInput::FORCED_QUERY:
411 return 1500;
412
413 default:
414 NOTREACHED();
415 return 0;
416 }
417}
418
419int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
420 // The relevance of past searches falls off over time. This curve is chosen
421 // so that the relevance of a search 15 minutes ago is discounted about 50
422 // points, while the relevance of a search two weeks ago is discounted about
423 // 450 points.
424 const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
425 const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
426
427 // Don't let scores go below 0. Negative relevance scores are meaningful in a
428 // different way.
429 int base_score;
430 switch (input_.type()) {
431 case AutocompleteInput::UNKNOWN:
432 case AutocompleteInput::REQUESTED_URL:
433 base_score = 1050;
434 break;
435
436 case AutocompleteInput::URL:
437 base_score = 750;
438 break;
439
440 case AutocompleteInput::QUERY:
441 case AutocompleteInput::FORCED_QUERY:
442 base_score = 1250;
443 break;
444
445 default:
446 NOTREACHED();
447 base_score = 0;
448 break;
449 }
450 return std::max(0, base_score - score_discount);
451}
452
453int SearchProvider::CalculateRelevanceForSuggestion(
454 size_t suggestion_number) const {
455 DCHECK(suggestion_number < suggest_results_.size());
456 const int suggestion_value =
457 static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
458 switch (input_.type()) {
459 case AutocompleteInput::UNKNOWN:
460 case AutocompleteInput::REQUESTED_URL:
461 return 600 + suggestion_value;
462
463 case AutocompleteInput::URL:
464 return 300 + suggestion_value;
465
466 case AutocompleteInput::QUERY:
467 case AutocompleteInput::FORCED_QUERY:
468 return 800 + suggestion_value;
469
470 default:
471 NOTREACHED();
472 return 0;
473 }
474}
475
476int SearchProvider::CalculateRelevanceForNavigation(
477 size_t suggestion_number) const {
478 DCHECK(suggestion_number < navigation_results_.size());
479 // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
480 // server if possible.
481 switch (input_.type()) {
482 case AutocompleteInput::QUERY:
483 case AutocompleteInput::FORCED_QUERY:
484 return 1000 + static_cast<int>(suggestion_number);
485
486 default:
487 return 800 + static_cast<int>(suggestion_number);
488 }
489}
490
491void SearchProvider::AddMatchToMap(const std::wstring& query_string,
492 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:00493 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:29494 int accepted_suggestion,
495 MatchMap* map) {
[email protected]4c1fb7ec2008-11-13 00:19:00496 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:29497 std::vector<size_t> content_param_offsets;
498 match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
499 default_provider_.short_name(),
500 query_string,
501 &content_param_offsets));
502 if (content_param_offsets.size() == 2) {
503 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
504 query_string.length(),
505 match.contents.length(),
506 ACMatchClassification::NONE,
507 &match.contents_class);
508 } else {
509 // |content_param_offsets| should only not be 2 if:
510 // (a) A translator screws up
511 // (b) The strings have been changed and we haven't been rebuilt properly
512 // (c) Some sort of crazy installer error/DLL version mismatch problem that
513 // gets the wrong data out of the locale DLL?
514 // While none of these are supposed to happen, we've seen this get hit in
515 // the wild, so avoid the vector access in the conditional arm above, which
516 // will crash.
517 NOTREACHED();
518 }
519
520 // When the user forced a query, we need to make sure all the fill_into_edit
521 // values preserve that property. Otherwise, if the user starts editing a
522 // suggestion, non-Search results will suddenly appear.
523 size_t search_start = 0;
524 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
525 match.fill_into_edit.assign(L"?");
526 ++search_start;
527 }
528 match.fill_into_edit.append(query_string);
529 // NOTE: All Google suggestions currently start with the original input, but
530 // not all Yahoo! suggestions do.
531 if (!input_.prevent_inline_autocomplete() &&
532 !match.fill_into_edit.compare(search_start, input_.text().length(),
533 input_.text()))
534 match.inline_autocomplete_offset = search_start + input_.text().length();
535
536 const TemplateURLRef* const search_url = default_provider_.url();
537 DCHECK(search_url->SupportsReplacement());
538 match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
539 query_string,
540 accepted_suggestion,
541 input_.text());
542
543 // Search results don't look like URLs.
544 match.transition = PageTransition::GENERATED;
545
546 // Try to add |match| to |map|. If a match for |query_string| is already in
547 // |map|, replace it if |match| is more relevant.
548 // NOTE: Keep this ToLower() call in sync with url_database.cc.
549 const std::pair<MatchMap::iterator, bool> i = map->insert(
550 std::pair<std::wstring, AutocompleteMatch>(
551 l10n_util::ToLower(query_string), match));
552 // NOTE: We purposefully do a direct relevance comparison here instead of
553 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
554 // first" rather than "items alphabetically first" when the scores are equal.
555 // The only case this matters is when a user has results with the same score
556 // that differ only by capitalization; because the history system returns
557 // results sorted by recency, this means we'll pick the most recent such
558 // result even if the precision of our relevance score is too low to
559 // distinguish the two.
560 if (!i.second && (match.relevance > i.first->second.relevance))
561 i.first->second = match;
562}
563
564AutocompleteMatch SearchProvider::NavigationToMatch(
565 const NavigationResult& navigation,
[email protected]cc63dea2008-08-21 20:56:31566 int relevance) {
[email protected]4c1fb7ec2008-11-13 00:19:00567 AutocompleteMatch match(this, relevance, false,
568 AutocompleteMatch::NAVSUGGEST);
initial.commit09911bf2008-07-26 23:55:29569 match.destination_url = navigation.url;
[email protected]e7a5b7872008-12-10 23:52:43570 match.contents = StringForURLDisplay(navigation.url, true);
initial.commit09911bf2008-07-26 23:55:29571 // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
572 // public utility function.
573 if (!url_util::FindAndCompareScheme(input_.text(), "http", NULL))
574 TrimHttpPrefix(&match.contents);
575 AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
576 ACMatchClassification::URL,
577 &match.contents_class);
578
579 match.description = navigation.site_name;
580 AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
581 ACMatchClassification::NONE,
582 &match.description_class);
583
initial.commit09911bf2008-07-26 23:55:29584 // When the user forced a query, we need to make sure all the fill_into_edit
585 // values preserve that property. Otherwise, if the user starts editing a
586 // suggestion, non-Search results will suddenly appear.
587 if (input_.type() == AutocompleteInput::FORCED_QUERY)
588 match.fill_into_edit.assign(L"?");
589 match.fill_into_edit.append(match.contents);
590 // TODO(pkasting): http://b/1112879 These should perhaps be
591 // inline-autocompletable?
592
593 return match;
594}
595
596// TODO(kochi): This is duplicate from HistoryURLProvider.
597// static
598size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
599 url_parse::Component scheme;
600 if (!url_util::FindAndCompareScheme(*url, "http", &scheme))
601 return 0; // Not "http".
602
603 // Erase scheme plus up to two slashes.
604 size_t prefix_len = scheme.end() + 1; // "http:"
605 const size_t after_slashes = std::min(url->length(),
606 static_cast<size_t>(scheme.end() + 3));
607 while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
608 ++prefix_len;
609 if (prefix_len == url->length())
610 url->clear();
611 else
612 url->erase(url->begin(), url->begin() + prefix_len);
613 return prefix_len;
614}