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