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