blob: 40ba841f559d886ea1192a62d5baf7253239bcb4 [file] [log] [blame]
initial.commit09911bf2008-07-26 23:55:291// Copyright 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30#include "chrome/browser/autocomplete/search_provider.h"
31
32#include "base/message_loop.h"
33#include "base/string_util.h"
34#include "chrome/browser/browser_process.h"
35#include "chrome/browser/google_util.h"
36#include "chrome/browser/profile.h"
37#include "chrome/browser/template_url_model.h"
38#include "chrome/common/json_value_serializer.h"
39#include "chrome/common/l10n_util.h"
40#include "chrome/common/pref_names.h"
41#include "chrome/common/pref_service.h"
42#include "googleurl/src/url_util.h"
43#include "net/base/escape.h"
44
45#include "generated_resources.h"
46
47const int SearchProvider::kQueryDelayMs = 200;
48
49void SearchProvider::Start(const AutocompleteInput& input,
50 bool minimal_changes,
51 bool synchronous_only) {
52 matches_.clear();
53
54 // Can't return search/suggest results for bogus input or if there is no
55 // profile.
56 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
57 Stop();
58 return;
59 }
60
61 // Can't search with no default provider.
62 const TemplateURL* const current_default_provider =
63 profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
64 // TODO(pkasting): http://b/1155786 Eventually we should not need all these
65 // checks.
66 if (!current_default_provider || !current_default_provider->url() ||
67 !current_default_provider->url()->SupportsReplacement()) {
68 Stop();
69 return;
70 }
71
72 // If we're still running an old query but have since changed the query text
73 // or the default provider, abort the query.
74 if (!done_ && (!minimal_changes ||
75 (last_default_provider_ != current_default_provider)))
76 Stop();
77
78 // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy this.
79 // Nor should we need |last_default_provider_| just to know whether the
80 // provider changed.
81 default_provider_ = *current_default_provider;
82 last_default_provider_ = current_default_provider;
83
84 if (input.text().empty()) {
85 // User typed "?" alone. Give them a placeholder result indicating what
86 // this syntax does.
87 AutocompleteMatch match;
88 static const std::wstring kNoQueryInput(
89 l10n_util::GetString(IDS_AUTOCOMPLETE_NO_QUERY));
90 match.contents.assign(l10n_util::GetStringF(
91 IDS_AUTOCOMPLETE_SEARCH_CONTENTS, default_provider_.short_name(),
92 kNoQueryInput));
93 match.contents_class.push_back(
94 ACMatchClassification(0, ACMatchClassification::DIM));
95 match.type = AutocompleteMatch::SEARCH;
96 matches_.push_back(match);
97 Stop();
98 return;
99 }
100
101 input_ = input;
102
103 StartOrStopHistoryQuery(minimal_changes, synchronous_only);
104 StartOrStopSuggestQuery(minimal_changes, synchronous_only);
105 ConvertResultsToAutocompleteMatches();
106}
107
108void SearchProvider::Run() {
109 // Start a new request with the current input.
110 DCHECK(!done_);
111 const TemplateURLRef* const suggestions_url =
112 default_provider_.suggestions_url();
113 DCHECK(suggestions_url->SupportsReplacement());
114 fetcher_.reset(new URLFetcher(GURL(suggestions_url->ReplaceSearchTerms(
115 default_provider_, input_.text(),
116 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())),
117 URLFetcher::GET, this));
118 fetcher_->set_request_context(profile_->GetRequestContext());
119 fetcher_->Start();
120}
121
122void SearchProvider::Stop() {
123 StopHistory();
124 StopSuggest();
125 done_ = true;
126}
127
128void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
129 const GURL& url,
130 const URLRequestStatus& status,
131 int response_code,
132 const ResponseCookies& cookie,
133 const std::string& data) {
134 DCHECK(!done_);
135 suggest_results_pending_ = false;
136 suggest_results_.clear();
137 navigation_results_.clear();
138 JSONStringValueSerializer deserializer(data);
139 Value* root_val = NULL;
140 have_suggest_results_ = status.is_success() && (response_code == 200) &&
141 deserializer.Deserialize(&root_val) && ParseSuggestResults(root_val);
142 delete root_val;
143 ConvertResultsToAutocompleteMatches();
144 listener_->OnProviderUpdate(!suggest_results_.empty());
145}
146
147void SearchProvider::StartOrStopHistoryQuery(bool minimal_changes,
148 bool synchronous_only) {
149 // For the minimal_changes case, if we finished the previous query and still
150 // have its results, or are allowed to keep running it, just do that, rather
151 // than starting a new query.
152 if (minimal_changes &&
153 (have_history_results_ || (!done_ && !synchronous_only)))
154 return;
155
156 // We can't keep running any previous query, so halt it.
157 StopHistory();
158
159 // We can't start a new query if we're only allowed synchronous results.
160 if (synchronous_only)
161 return;
162
163 // Start the history query.
164 HistoryService* const history_service =
165 profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
166 history_service->GetMostRecentKeywordSearchTerms(default_provider_.id(),
167 input_.text(), static_cast<int>(max_matches()),
168 &history_request_consumer_,
169 NewCallback(this, &SearchProvider::OnGotMostRecentKeywordSearchTerms));
170 history_request_pending_ = true;
171}
172
173void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes,
174 bool synchronous_only) {
175 // Don't run Suggest when off the record, the engine doesn't support it, or
176 // the user has disabled it. Also don't query the server for URLs that aren't
177 // http/https/ftp. Sending things like file: and data: is both a waste of
178 // time and a disclosure of potentially private, local data.
179 if (profile_->IsOffTheRecord() ||
180 !default_provider_.suggestions_url() ||
181 !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled) ||
182 ((input_.type() == AutocompleteInput::URL) &&
183 (input_.scheme() != L"http") && (input_.scheme() != L"https") &&
184 (input_.scheme() != L"ftp"))) {
185 StopSuggest();
186 return;
187 }
188
189 // For the minimal_changes case, if we finished the previous query and still
190 // have its results, or are allowed to keep running it, just do that, rather
191 // than starting a new query.
192 if (minimal_changes &&
193 (have_suggest_results_ || (!done_ && !synchronous_only)))
194 return;
195
196 // We can't keep running any previous query, so halt it.
197 StopSuggest();
198
199 // We can't start a new query if we're only allowed synchronous results.
200 if (synchronous_only)
201 return;
202
203 // Kick off a timer that will start the URL fetch if it completes before
204 // the user types another character.
205 suggest_results_pending_ = true;
206 MessageLoop::current()->timer_manager()->ResetTimer(timer_.get());
207}
208
209void SearchProvider::StopHistory() {
210 history_request_consumer_.CancelAllRequests();
211 history_request_pending_ = false;
212 history_results_.clear();
213 have_history_results_ = false;
214}
215
216void SearchProvider::StopSuggest() {
217 suggest_results_pending_ = false;
218 MessageLoop::current()->timer_manager()->StopTimer(timer_.get());
219 fetcher_.reset(); // Stop any in-progress URL fetch.
220 suggest_results_.clear();
221 have_suggest_results_ = false;
222 star_request_consumer_.CancelAllRequests();
223 star_requests_pending_ = false;
224}
225
226void SearchProvider::OnGotMostRecentKeywordSearchTerms(
227 CancelableRequestProvider::Handle handle,
228 HistoryResults* results) {
229 history_request_pending_ = false;
230 have_history_results_ = true;
231 history_results_ = *results;
232 ConvertResultsToAutocompleteMatches();
233 listener_->OnProviderUpdate(!history_results_.empty());
234}
235
236void SearchProvider::OnQueryURLComplete(HistoryService::Handle handle,
237 bool success,
238 const history::URLRow* url_row,
239 history::VisitVector* unused) {
240 bool is_starred = success ? url_row->starred() : false;
241 star_requests_pending_ = false;
242 // We can't just use star_request_consumer_.HasPendingRequests() here;
243 // see comment in ConvertResultsToAutocompleteMatches().
244 for (NavigationResults::iterator i(navigation_results_.begin());
245 i != navigation_results_.end(); ++i) {
246 if (i->star_request_handle == handle) {
247 i->star_request_handle = 0;
248 i->starred = is_starred;
249 } else if (i->star_request_handle) {
250 star_requests_pending_ = true;
251 }
252 }
253 if (!star_requests_pending_) {
254 // No more requests. Notify the observer.
255 ConvertResultsToAutocompleteMatches();
256 listener_->OnProviderUpdate(true);
257 }
258}
259
260bool SearchProvider::ParseSuggestResults(Value* root_val) {
261 if (!root_val->IsType(Value::TYPE_LIST))
262 return false;
263 ListValue* root_list = static_cast<ListValue*>(root_val);
264
265 Value* query_val;
266 std::wstring query_str;
267 Value* result_val;
268 if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
269 !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
270 !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
271 return false;
272
273 ListValue* description_list = NULL;
274 if (root_list->GetSize() > 2) {
275 // 3rd element: Description list.
276 Value* description_val;
277 if (root_list->Get(2, &description_val) &&
278 description_val->IsType(Value::TYPE_LIST))
279 description_list = static_cast<ListValue*>(description_val);
280 }
281
282 // We don't care about the query URL list (the fourth element in the
283 // response) for now.
284
285 // Parse optional data in the results from the Suggest server if any.
286 ListValue* type_list = NULL;
287 // 5th argument: Optional key-value pairs.
288 // TODO: We may iterate the 5th+ arguments of the root_list if any other
289 // optional data are defined.
290 if (root_list->GetSize() > 4) {
291 Value* optional_val;
292 if (root_list->Get(4, &optional_val) &&
293 optional_val->IsType(Value::TYPE_DICTIONARY)) {
294 DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
295
296 // Parse Google Suggest specific type extension.
297 static const std::wstring kGoogleSuggestType(L"google:suggesttype");
298 if (dict_val->HasKey(kGoogleSuggestType))
299 dict_val->GetList(kGoogleSuggestType, &type_list);
300 }
301 }
302
303 ListValue* result_list = static_cast<ListValue*>(result_val);
304 for (size_t i = 0; i < result_list->GetSize(); ++i) {
305 Value* suggestion_val;
306 std::wstring suggestion_str;
307 if (!result_list->Get(i, &suggestion_val) ||
308 !suggestion_val->GetAsString(&suggestion_str))
309 return false;
310
311 Value* type_val;
312 std::wstring type_str;
313 if (type_list && type_list->Get(i, &type_val) &&
314 type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
315 Value* site_val;
316 std::wstring site_name;
317 if (navigation_results_.size() < max_matches() &&
318 description_list && description_list->Get(i, &site_val) &&
319 site_val->IsType(Value::TYPE_STRING) &&
320 site_val->GetAsString(&site_name)) {
321 navigation_results_.push_back(NavigationResult(suggestion_str,
322 site_name));
323 }
324 } else {
325 // TODO(kochi): Currently we treat a calculator result as a query, but it
326 // is better to have better presentation for caluculator results.
327 if (suggest_results_.size() < max_matches())
328 suggest_results_.push_back(suggestion_str);
329 }
330 }
331
332 // Request the star state for all URLs from the history service.
333 HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
334 if (!hs)
335 return true;
336
337 for (NavigationResults::iterator i(navigation_results_.begin());
338 i != navigation_results_.end(); ++i) {
339 i->star_request_handle = hs->QueryURL(GURL(i->url), false,
340 &star_request_consumer_,
341 NewCallback(this, &SearchProvider::OnQueryURLComplete));
342 }
343 star_requests_pending_ = !navigation_results_.empty();
344
345 return true;
346}
347
348void SearchProvider::ConvertResultsToAutocompleteMatches() {
349 // Convert all the results to matches and add them to a map, so we can keep
350 // the most relevant match for each result.
351 MatchMap map;
352 const int did_not_accept_suggestion = suggest_results_.empty() ?
353 TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
354 TemplateURLRef::NO_SUGGESTION_CHOSEN;
355 const Time no_time;
356 AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
357 did_not_accept_suggestion, &map);
358
359 for (HistoryResults::const_iterator i(history_results_.begin());
360 i != history_results_.end(); ++i) {
361 AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
362 did_not_accept_suggestion, &map);
363 }
364
365 for (size_t i = 0; i < suggest_results_.size(); ++i) {
366 AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
367 static_cast<int>(i), &map);
368 }
369
370 // Now add the most relevant matches from the map to |matches_|.
371 matches_.clear();
372 for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
373 matches_.push_back(i->second);
374
375 if (navigation_results_.size()) {
376 // TODO(kochi): http://b/1170574 We add only one results for navigational
377 // suggestions. If we can get more useful information about the score,
378 // consider adding more results.
379 matches_.push_back(NavigationToMatch(navigation_results_[0],
380 CalculateRelevanceForNavigation(0),
381 navigation_results_[0].starred));
382 }
383
384 const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
385 std::partial_sort(matches_.begin(),
386 matches_.begin() + std::min(max_total_matches, matches_.size()),
387 matches_.end(), &AutocompleteMatch::MoreRelevant);
388 if (matches_.size() > max_total_matches)
389 matches_.resize(max_total_matches);
390
391 // We're done when both asynchronous subcomponents have finished.
392 // We can't use CancelableRequestConsumer.HasPendingRequests() for
393 // history and star requests here. A pending request is not cleared
394 // until after the completion callback has returned, but we've
395 // reached here from inside that callback. HasPendingRequests()
396 // would therefore return true, and if this is the last thing left
397 // to calculate for this query, we'll never mark the query "done".
398 done_ = !history_request_pending_ &&
399 !suggest_results_pending_ &&
400 !star_requests_pending_;
401}
402
403int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
404 switch (input_.type()) {
405 case AutocompleteInput::UNKNOWN:
406 return 1300;
407
408 case AutocompleteInput::REQUESTED_URL:
409 return 1200;
410
411 case AutocompleteInput::URL:
412 return 850;
413
414 case AutocompleteInput::QUERY:
415 return 1300;
416
417 case AutocompleteInput::FORCED_QUERY:
418 return 1500;
419
420 default:
421 NOTREACHED();
422 return 0;
423 }
424}
425
426int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
427 // The relevance of past searches falls off over time. This curve is chosen
428 // so that the relevance of a search 15 minutes ago is discounted about 50
429 // points, while the relevance of a search two weeks ago is discounted about
430 // 450 points.
431 const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
432 const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
433
434 // Don't let scores go below 0. Negative relevance scores are meaningful in a
435 // different way.
436 int base_score;
437 switch (input_.type()) {
438 case AutocompleteInput::UNKNOWN:
439 case AutocompleteInput::REQUESTED_URL:
440 base_score = 1050;
441 break;
442
443 case AutocompleteInput::URL:
444 base_score = 750;
445 break;
446
447 case AutocompleteInput::QUERY:
448 case AutocompleteInput::FORCED_QUERY:
449 base_score = 1250;
450 break;
451
452 default:
453 NOTREACHED();
454 base_score = 0;
455 break;
456 }
457 return std::max(0, base_score - score_discount);
458}
459
460int SearchProvider::CalculateRelevanceForSuggestion(
461 size_t suggestion_number) const {
462 DCHECK(suggestion_number < suggest_results_.size());
463 const int suggestion_value =
464 static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
465 switch (input_.type()) {
466 case AutocompleteInput::UNKNOWN:
467 case AutocompleteInput::REQUESTED_URL:
468 return 600 + suggestion_value;
469
470 case AutocompleteInput::URL:
471 return 300 + suggestion_value;
472
473 case AutocompleteInput::QUERY:
474 case AutocompleteInput::FORCED_QUERY:
475 return 800 + suggestion_value;
476
477 default:
478 NOTREACHED();
479 return 0;
480 }
481}
482
483int SearchProvider::CalculateRelevanceForNavigation(
484 size_t suggestion_number) const {
485 DCHECK(suggestion_number < navigation_results_.size());
486 // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
487 // server if possible.
488 switch (input_.type()) {
489 case AutocompleteInput::QUERY:
490 case AutocompleteInput::FORCED_QUERY:
491 return 1000 + static_cast<int>(suggestion_number);
492
493 default:
494 return 800 + static_cast<int>(suggestion_number);
495 }
496}
497
498void SearchProvider::AddMatchToMap(const std::wstring& query_string,
499 int relevance,
500 int accepted_suggestion,
501 MatchMap* map) {
502 AutocompleteMatch match(this, relevance, false);
503 match.type = AutocompleteMatch::SEARCH;
504 std::vector<size_t> content_param_offsets;
505 match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
506 default_provider_.short_name(),
507 query_string,
508 &content_param_offsets));
509 if (content_param_offsets.size() == 2) {
510 AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
511 query_string.length(),
512 match.contents.length(),
513 ACMatchClassification::NONE,
514 &match.contents_class);
515 } else {
516 // |content_param_offsets| should only not be 2 if:
517 // (a) A translator screws up
518 // (b) The strings have been changed and we haven't been rebuilt properly
519 // (c) Some sort of crazy installer error/DLL version mismatch problem that
520 // gets the wrong data out of the locale DLL?
521 // While none of these are supposed to happen, we've seen this get hit in
522 // the wild, so avoid the vector access in the conditional arm above, which
523 // will crash.
524 NOTREACHED();
525 }
526
527 // When the user forced a query, we need to make sure all the fill_into_edit
528 // values preserve that property. Otherwise, if the user starts editing a
529 // suggestion, non-Search results will suddenly appear.
530 size_t search_start = 0;
531 if (input_.type() == AutocompleteInput::FORCED_QUERY) {
532 match.fill_into_edit.assign(L"?");
533 ++search_start;
534 }
535 match.fill_into_edit.append(query_string);
536 // NOTE: All Google suggestions currently start with the original input, but
537 // not all Yahoo! suggestions do.
538 if (!input_.prevent_inline_autocomplete() &&
539 !match.fill_into_edit.compare(search_start, input_.text().length(),
540 input_.text()))
541 match.inline_autocomplete_offset = search_start + input_.text().length();
542
543 const TemplateURLRef* const search_url = default_provider_.url();
544 DCHECK(search_url->SupportsReplacement());
545 match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
546 query_string,
547 accepted_suggestion,
548 input_.text());
549
550 // Search results don't look like URLs.
551 match.transition = PageTransition::GENERATED;
552
553 // Try to add |match| to |map|. If a match for |query_string| is already in
554 // |map|, replace it if |match| is more relevant.
555 // NOTE: Keep this ToLower() call in sync with url_database.cc.
556 const std::pair<MatchMap::iterator, bool> i = map->insert(
557 std::pair<std::wstring, AutocompleteMatch>(
558 l10n_util::ToLower(query_string), match));
559 // NOTE: We purposefully do a direct relevance comparison here instead of
560 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
561 // first" rather than "items alphabetically first" when the scores are equal.
562 // The only case this matters is when a user has results with the same score
563 // that differ only by capitalization; because the history system returns
564 // results sorted by recency, this means we'll pick the most recent such
565 // result even if the precision of our relevance score is too low to
566 // distinguish the two.
567 if (!i.second && (match.relevance > i.first->second.relevance))
568 i.first->second = match;
569}
570
571AutocompleteMatch SearchProvider::NavigationToMatch(
572 const NavigationResult& navigation,
573 int relevance,
574 bool starred) {
575 AutocompleteMatch match(this, relevance, false);
576 match.destination_url = navigation.url;
577 match.contents = StringForURLDisplay(GURL(navigation.url), true);
578 // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
579 // public utility function.
580 if (!url_util::FindAndCompareScheme(input_.text(), "http", NULL))
581 TrimHttpPrefix(&match.contents);
582 AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
583 ACMatchClassification::URL,
584 &match.contents_class);
585
586 match.description = navigation.site_name;
587 AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
588 ACMatchClassification::NONE,
589 &match.description_class);
590
591 match.starred = starred;
592 // When the user forced a query, we need to make sure all the fill_into_edit
593 // values preserve that property. Otherwise, if the user starts editing a
594 // suggestion, non-Search results will suddenly appear.
595 if (input_.type() == AutocompleteInput::FORCED_QUERY)
596 match.fill_into_edit.assign(L"?");
597 match.fill_into_edit.append(match.contents);
598 // TODO(pkasting): http://b/1112879 These should perhaps be
599 // inline-autocompletable?
600
601 return match;
602}
603
604// TODO(kochi): This is duplicate from HistoryURLProvider.
605// static
606size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
607 url_parse::Component scheme;
608 if (!url_util::FindAndCompareScheme(*url, "http", &scheme))
609 return 0; // Not "http".
610
611 // Erase scheme plus up to two slashes.
612 size_t prefix_len = scheme.end() + 1; // "http:"
613 const size_t after_slashes = std::min(url->length(),
614 static_cast<size_t>(scheme.end() + 3));
615 while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
616 ++prefix_len;
617 if (prefix_len == url->length())
618 url->clear();
619 else
620 url->erase(url->begin(), url->begin() + prefix_len);
621 return prefix_len;
622}