blob: 23d4407c3f2dab06f6f02caeec85eec52da6049a [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:2928void 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
[email protected]6c85aa02009-02-27 12:08:0932 // Can't return search/suggest results for bogus input or without a profile.
initial.commit09911bf2008-07-26 23:55:2933 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
34 Stop();
35 return;
36 }
37
[email protected]6c85aa02009-02-27 12:08:0938 // Can't search without a default provider.
initial.commit09911bf2008-07-26 23:55:2939 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);
[email protected]6c85aa02009-02-27 12:08:09118 // JSON is supposed to be UTF-8, but some suggest service providers send JSON
119 // files in non-UTF-8 encodings. The actual encoding is usually specified in
120 // the Content-Type header field.
[email protected]ec9207d32008-09-26 00:51:06121 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]6c85aa02009-02-27 12:08:09170 // Don't send any queries to the server until some time has elapsed after
171 // the last keypress, to avoid flooding the server with requests we are
172 // likely to end up throwing away anyway.
173 static const int kQueryDelayMs = 200;
174
[email protected]83c726482008-09-10 06:36:34175 if (!IsQuerySuitableForSuggest()) {
initial.commit09911bf2008-07-26 23:55:29176 StopSuggest();
177 return;
178 }
179
180 // For the minimal_changes case, if we finished the previous query and still
181 // have its results, or are allowed to keep running it, just do that, rather
182 // than starting a new query.
183 if (minimal_changes &&
[email protected]8deeb952008-10-09 18:21:27184 (have_suggest_results_ || (!done_ && !input_.synchronous_only())))
initial.commit09911bf2008-07-26 23:55:29185 return;
186
187 // We can't keep running any previous query, so halt it.
188 StopSuggest();
189
190 // We can't start a new query if we're only allowed synchronous results.
[email protected]8deeb952008-10-09 18:21:27191 if (input_.synchronous_only())
initial.commit09911bf2008-07-26 23:55:29192 return;
193
194 // Kick off a timer that will start the URL fetch if it completes before
195 // the user types another character.
196 suggest_results_pending_ = true;
[email protected]2d316662008-09-03 18:18:14197
198 timer_.Stop();
199 timer_.Start(TimeDelta::FromMilliseconds(kQueryDelayMs), this,
200 &SearchProvider::Run);
initial.commit09911bf2008-07-26 23:55:29201}
202
[email protected]83c726482008-09-10 06:36:34203bool SearchProvider::IsQuerySuitableForSuggest() const {
204 // Don't run Suggest when off the record, the engine doesn't support it, or
205 // the user has disabled it.
206 if (profile_->IsOffTheRecord() ||
207 !default_provider_.suggestions_url() ||
208 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled))
209 return false;
210
211 // If the input type is URL, we take extra care so that private data in URL
212 // isn't sent to the server.
213 if (input_.type() == AutocompleteInput::URL) {
214 // Don't query the server for URLs that aren't http/https/ftp. Sending
215 // things like file: and data: is both a waste of time and a disclosure of
216 // potentially private, local data.
217 if ((input_.scheme() != L"http") && (input_.scheme() != L"https") &&
218 (input_.scheme() != L"ftp"))
219 return false;
220
221 // Don't leak private data in URL
222 const url_parse::Parsed& parts = input_.parts();
223
224 // Don't send URLs with usernames, queries or refs. Some of these are
225 // private, and the Suggest server is unlikely to have any useful results
226 // for any of them.
227 // Password is optional and may be omitted. Checking username is
228 // sufficient.
229 if (parts.username.is_nonempty() || parts.query.is_nonempty() ||
230 parts.ref.is_nonempty())
231 return false;
232 // Don't send anything for https except hostname and port number.
233 // Hostname and port number are OK because they are visible when TCP
234 // connection is established and the Suggest server may provide some
235 // useful completed URL.
236 if (input_.scheme() == L"https" && parts.path.is_nonempty())
237 return false;
238 }
239
240 return true;
241}
242
initial.commit09911bf2008-07-26 23:55:29243void SearchProvider::StopHistory() {
244 history_request_consumer_.CancelAllRequests();
245 history_request_pending_ = false;
246 history_results_.clear();
247 have_history_results_ = false;
248}
249
250void SearchProvider::StopSuggest() {
251 suggest_results_pending_ = false;
[email protected]2d316662008-09-03 18:18:14252 timer_.Stop();
initial.commit09911bf2008-07-26 23:55:29253 fetcher_.reset(); // Stop any in-progress URL fetch.
254 suggest_results_.clear();
255 have_suggest_results_ = false;
initial.commit09911bf2008-07-26 23:55:29256}
257
258void SearchProvider::OnGotMostRecentKeywordSearchTerms(
259 CancelableRequestProvider::Handle handle,
260 HistoryResults* results) {
261 history_request_pending_ = false;
262 have_history_results_ = true;
263 history_results_ = *results;
264 ConvertResultsToAutocompleteMatches();
265 listener_->OnProviderUpdate(!history_results_.empty());
266}
267
initial.commit09911bf2008-07-26 23:55:29268bool SearchProvider::ParseSuggestResults(Value* root_val) {
269 if (!root_val->IsType(Value::TYPE_LIST))
270 return false;
271 ListValue* root_list = static_cast<ListValue*>(root_val);
272
273 Value* query_val;
274 std::wstring query_str;
275 Value* result_val;
276 if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
277 !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
278 !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
279 return false;
280
281 ListValue* description_list = NULL;
282 if (root_list->GetSize() > 2) {
283 // 3rd element: Description list.
284 Value* description_val;
285 if (root_list->Get(2, &description_val) &&
286 description_val->IsType(Value::TYPE_LIST))
287 description_list = static_cast<ListValue*>(description_val);
288 }
289
290 // We don't care about the query URL list (the fourth element in the
291 // response) for now.
292
293 // Parse optional data in the results from the Suggest server if any.
294 ListValue* type_list = NULL;
295 // 5th argument: Optional key-value pairs.
296 // TODO: We may iterate the 5th+ arguments of the root_list if any other
297 // optional data are defined.
298 if (root_list->GetSize() > 4) {
299 Value* optional_val;
300 if (root_list->Get(4, &optional_val) &&
301 optional_val->IsType(Value::TYPE_DICTIONARY)) {
302 DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
303
304 // Parse Google Suggest specific type extension.
[email protected]8e50b602009-03-03 22:59:43305 static const std::wstring kGoogleSuggestType(L"google:suggesttype");
initial.commit09911bf2008-07-26 23:55:29306 if (dict_val->HasKey(kGoogleSuggestType))
307 dict_val->GetList(kGoogleSuggestType, &type_list);
308 }
309 }
310
311 ListValue* result_list = static_cast<ListValue*>(result_val);
312 for (size_t i = 0; i < result_list->GetSize(); ++i) {
313 Value* suggestion_val;
314 std::wstring suggestion_str;
315 if (!result_list->Get(i, &suggestion_val) ||
316 !suggestion_val->GetAsString(&suggestion_str))
317 return false;
318
319 Value* type_val;
320 std::wstring type_str;
321 if (type_list && type_list->Get(i, &type_val) &&
322 type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
323 Value* site_val;
324 std::wstring site_name;
[email protected]16afe222009-01-08 18:57:45325 if ((navigation_results_.size() < max_matches()) &&
initial.commit09911bf2008-07-26 23:55:29326 description_list && description_list->Get(i, &site_val) &&
327 site_val->IsType(Value::TYPE_STRING) &&
328 site_val->GetAsString(&site_name)) {
[email protected]16afe222009-01-08 18:57:45329 // We can't blindly trust the URL coming from the server to be valid.
330 GURL result_url =
[email protected]1d73faa2009-02-24 19:32:15331 GURL(URLFixerUpper::FixupURL(WideToUTF8(suggestion_str),
332 std::string()));
[email protected]16afe222009-01-08 18:57:45333 if (result_url.is_valid()) {
334 navigation_results_.push_back(NavigationResult(result_url,
335 site_name));
336 }
initial.commit09911bf2008-07-26 23:55:29337 }
338 } else {
339 // TODO(kochi): Currently we treat a calculator result as a query, but it
340 // is better to have better presentation for caluculator results.
341 if (suggest_results_.size() < max_matches())
342 suggest_results_.push_back(suggestion_str);
343 }
344 }
345
initial.commit09911bf2008-07-26 23:55:29346 return true;
347}
348
349void SearchProvider::ConvertResultsToAutocompleteMatches() {
350 // Convert all the results to matches and add them to a map, so we can keep
351 // the most relevant match for each result.
352 MatchMap map;
353 const int did_not_accept_suggestion = suggest_results_.empty() ?
354 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
355 TemplateURLRef::NO_SUGGESTION_CHOSEN;
356 const Time no_time;
357 AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
[email protected]4c1fb7ec2008-11-13 00:19:00358 AutocompleteMatch::SEARCH_WHAT_YOU_TYPED,
initial.commit09911bf2008-07-26 23:55:29359 did_not_accept_suggestion, &map);
360
361 for (HistoryResults::const_iterator i(history_results_.begin());
362 i != history_results_.end(); ++i) {
363 AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
[email protected]4c1fb7ec2008-11-13 00:19:00364 AutocompleteMatch::SEARCH_HISTORY, did_not_accept_suggestion,
365 &map);
initial.commit09911bf2008-07-26 23:55:29366 }
367
368 for (size_t i = 0; i < suggest_results_.size(); ++i) {
369 AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
[email protected]4c1fb7ec2008-11-13 00:19:00370 AutocompleteMatch::SEARCH_SUGGEST,
initial.commit09911bf2008-07-26 23:55:29371 static_cast<int>(i), &map);
372 }
373
374 // Now add the most relevant matches from the map to |matches_|.
375 matches_.clear();
376 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
377 matches_.push_back(i->second);
378
[email protected]ffaf78a2008-11-12 17:38:33379 if (!navigation_results_.empty()) {
initial.commit09911bf2008-07-26 23:55:29380 // TODO(kochi): http://b/1170574 We add only one results for navigational
381 // suggestions. If we can get more useful information about the score,
382 // consider adding more results.
[email protected]ffaf78a2008-11-12 17:38:33383 matches_.push_back(NavigationToMatch(navigation_results_.front(),
[email protected]cc63dea2008-08-21 20:56:31384 CalculateRelevanceForNavigation(0)));
initial.commit09911bf2008-07-26 23:55:29385 }
386
387 const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
388 std::partial_sort(matches_.begin(),
389 matches_.begin() + std::min(max_total_matches, matches_.size()),
390 matches_.end(), &AutocompleteMatch::MoreRelevant);
391 if (matches_.size() > max_total_matches)
[email protected]a28e95662008-11-12 19:19:02392 matches_.erase(matches_.begin() + max_total_matches, matches_.end());
initial.commit09911bf2008-07-26 23:55:29393
[email protected]cc63dea2008-08-21 20:56:31394 UpdateStarredStateOfMatches();
395
[email protected]6c85aa02009-02-27 12:08:09396 // We're done when both asynchronous subcomponents have finished. We can't
397 // use CancelableRequestConsumer.HasPendingRequests() for history requests
398 // here. A pending request is not cleared until after the completion
399 // callback has returned, but we've reached here from inside that callback.
400 // HasPendingRequests() would therefore return true, and if this is the last
401 // thing left to calculate for this query, we'll never mark the query "done".
initial.commit09911bf2008-07-26 23:55:29402 done_ = !history_request_pending_ &&
[email protected]cc63dea2008-08-21 20:56:31403 !suggest_results_pending_;
initial.commit09911bf2008-07-26 23:55:29404}
405
406int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
407 switch (input_.type()) {
408 case AutocompleteInput::UNKNOWN:
409 return 1300;
410
411 case AutocompleteInput::REQUESTED_URL:
412 return 1200;
413
414 case AutocompleteInput::URL:
415 return 850;
416
417 case AutocompleteInput::QUERY:
418 return 1300;
419
420 case AutocompleteInput::FORCED_QUERY:
421 return 1500;
422
423 default:
424 NOTREACHED();
425 return 0;
426 }
427}
428
429int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
430 // The relevance of past searches falls off over time. This curve is chosen
431 // so that the relevance of a search 15 minutes ago is discounted about 50
432 // points, while the relevance of a search two weeks ago is discounted about
433 // 450 points.
434 const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
435 const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
436
[email protected]6c85aa02009-02-27 12:08:09437 // Don't let scores go below 0. Negative relevance scores are meaningful in
438 // a different way.
initial.commit09911bf2008-07-26 23:55:29439 int base_score;
440 switch (input_.type()) {
441 case AutocompleteInput::UNKNOWN:
442 case AutocompleteInput::REQUESTED_URL:
443 base_score = 1050;
444 break;
445
446 case AutocompleteInput::URL:
447 base_score = 750;
448 break;
449
450 case AutocompleteInput::QUERY:
451 case AutocompleteInput::FORCED_QUERY:
452 base_score = 1250;
453 break;
454
455 default:
456 NOTREACHED();
457 base_score = 0;
458 break;
459 }
460 return std::max(0, base_score - score_discount);
461}
462
463int SearchProvider::CalculateRelevanceForSuggestion(
464 size_t suggestion_number) const {
465 DCHECK(suggestion_number < suggest_results_.size());
466 const int suggestion_value =
467 static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
468 switch (input_.type()) {
469 case AutocompleteInput::UNKNOWN:
470 case AutocompleteInput::REQUESTED_URL:
471 return 600 + suggestion_value;
472
473 case AutocompleteInput::URL:
474 return 300 + suggestion_value;
475
476 case AutocompleteInput::QUERY:
477 case AutocompleteInput::FORCED_QUERY:
478 return 800 + suggestion_value;
479
480 default:
481 NOTREACHED();
482 return 0;
483 }
484}
485
486int SearchProvider::CalculateRelevanceForNavigation(
487 size_t suggestion_number) const {
488 DCHECK(suggestion_number < navigation_results_.size());
489 // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
490 // server if possible.
491 switch (input_.type()) {
492 case AutocompleteInput::QUERY:
493 case AutocompleteInput::FORCED_QUERY:
494 return 1000 + static_cast<int>(suggestion_number);
495
496 default:
497 return 800 + static_cast<int>(suggestion_number);
498 }
499}
500
501void SearchProvider::AddMatchToMap(const std::wstring& query_string,
502 int relevance,
[email protected]4c1fb7ec2008-11-13 00:19:00503 AutocompleteMatch::Type type,
initial.commit09911bf2008-07-26 23:55:29504 int accepted_suggestion,
505 MatchMap* map) {
[email protected]4c1fb7ec2008-11-13 00:19:00506 AutocompleteMatch match(this, relevance, false, type);
initial.commit09911bf2008-07-26 23:55:29507 std::vector<size_t> content_param_offsets;
508 match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
509 default_provider_.short_name(),
510 query_string,
511 &content_param_offsets));
512 if (content_param_offsets.size() == 2) {
513 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
514 query_string.length(),
515 match.contents.length(),
516 ACMatchClassification::NONE,
517 &match.contents_class);
518 } else {
519 // |content_param_offsets| should only not be 2 if:
520 // (a) A translator screws up
521 // (b) The strings have been changed and we haven't been rebuilt properly
522 // (c) Some sort of crazy installer error/DLL version mismatch problem that
523 // gets the wrong data out of the locale DLL?
524 // While none of these are supposed to happen, we've seen this get hit in
525 // the wild, so avoid the vector access in the conditional arm above, which
526 // will crash.
527 NOTREACHED();
528 }
529
530 // When the user forced a query, we need to make sure all the fill_into_edit
531 // values preserve that property. Otherwise, if the user starts editing a
532 // suggestion, non-Search results will suddenly appear.
533 size_t search_start = 0;
534 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
535 match.fill_into_edit.assign(L"?");
536 ++search_start;
537 }
538 match.fill_into_edit.append(query_string);
539 // NOTE: All Google suggestions currently start with the original input, but
540 // not all Yahoo! suggestions do.
541 if (!input_.prevent_inline_autocomplete() &&
542 !match.fill_into_edit.compare(search_start, input_.text().length(),
543 input_.text()))
544 match.inline_autocomplete_offset = search_start + input_.text().length();
545
546 const TemplateURLRef* const search_url = default_provider_.url();
547 DCHECK(search_url->SupportsReplacement());
548 match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
549 query_string,
550 accepted_suggestion,
551 input_.text());
552
553 // Search results don't look like URLs.
554 match.transition = PageTransition::GENERATED;
555
556 // Try to add |match| to |map|. If a match for |query_string| is already in
557 // |map|, replace it if |match| is more relevant.
558 // NOTE: Keep this ToLower() call in sync with url_database.cc.
559 const std::pair<MatchMap::iterator, bool> i = map->insert(
560 std::pair<std::wstring, AutocompleteMatch>(
561 l10n_util::ToLower(query_string), match));
562 // NOTE: We purposefully do a direct relevance comparison here instead of
563 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
564 // first" rather than "items alphabetically first" when the scores are equal.
565 // The only case this matters is when a user has results with the same score
566 // that differ only by capitalization; because the history system returns
567 // results sorted by recency, this means we'll pick the most recent such
568 // result even if the precision of our relevance score is too low to
569 // distinguish the two.
570 if (!i.second && (match.relevance > i.first->second.relevance))
571 i.first->second = match;
572}
573
574AutocompleteMatch SearchProvider::NavigationToMatch(
575 const NavigationResult& navigation,
[email protected]cc63dea2008-08-21 20:56:31576 int relevance) {
[email protected]4c1fb7ec2008-11-13 00:19:00577 AutocompleteMatch match(this, relevance, false,
578 AutocompleteMatch::NAVSUGGEST);
initial.commit09911bf2008-07-26 23:55:29579 match.destination_url = navigation.url;
[email protected]e7a5b7872008-12-10 23:52:43580 match.contents = StringForURLDisplay(navigation.url, true);
initial.commit09911bf2008-07-26 23:55:29581 // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
582 // public utility function.
[email protected]1d73faa2009-02-24 19:32:15583 if (!url_util::FindAndCompareScheme(WideToUTF8(input_.text()),
584 "http", NULL))
initial.commit09911bf2008-07-26 23:55:29585 TrimHttpPrefix(&match.contents);
586 AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
587 ACMatchClassification::URL,
588 &match.contents_class);
589
590 match.description = navigation.site_name;
591 AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
592 ACMatchClassification::NONE,
593 &match.description_class);
594
initial.commit09911bf2008-07-26 23:55:29595 // When the user forced a query, we need to make sure all the fill_into_edit
596 // values preserve that property. Otherwise, if the user starts editing a
597 // suggestion, non-Search results will suddenly appear.
598 if (input_.type() == AutocompleteInput::FORCED_QUERY)
599 match.fill_into_edit.assign(L"?");
600 match.fill_into_edit.append(match.contents);
601 // TODO(pkasting): http://b/1112879 These should perhaps be
602 // inline-autocompletable?
603
604 return match;
605}
606
607// TODO(kochi): This is duplicate from HistoryURLProvider.
608// static
609size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
610 url_parse::Component scheme;
[email protected]dcf7d352009-02-26 01:56:02611 if (!url_util::FindAndCompareScheme(WideToUTF8(*url), chrome::kHttpScheme,
612 &scheme))
initial.commit09911bf2008-07-26 23:55:29613 return 0; // Not "http".
614
615 // Erase scheme plus up to two slashes.
616 size_t prefix_len = scheme.end() + 1; // "http:"
617 const size_t after_slashes = std::min(url->length(),
618 static_cast<size_t>(scheme.end() + 3));
619 while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
620 ++prefix_len;
621 if (prefix_len == url->length())
622 url->clear();
623 else
624 url->erase(url->begin(), url->begin() + prefix_len);
625 return prefix_len;
626}