summaryrefslogtreecommitdiffstats
path: root/chrome/browser/autocomplete/search_provider.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/autocomplete/search_provider.cc')
-rw-r--r--chrome/browser/autocomplete/search_provider.cc622
1 files changed, 622 insertions, 0 deletions
diff --git a/chrome/browser/autocomplete/search_provider.cc b/chrome/browser/autocomplete/search_provider.cc
new file mode 100644
index 0000000..40ba841
--- /dev/null
+++ b/chrome/browser/autocomplete/search_provider.cc
@@ -0,0 +1,622 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "chrome/browser/autocomplete/search_provider.h"
+
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/google_util.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/template_url_model.h"
+#include "chrome/common/json_value_serializer.h"
+#include "chrome/common/l10n_util.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/pref_service.h"
+#include "googleurl/src/url_util.h"
+#include "net/base/escape.h"
+
+#include "generated_resources.h"
+
+const int SearchProvider::kQueryDelayMs = 200;
+
+void SearchProvider::Start(const AutocompleteInput& input,
+ bool minimal_changes,
+ bool synchronous_only) {
+ matches_.clear();
+
+ // Can't return search/suggest results for bogus input or if there is no
+ // profile.
+ if (!profile_ || (input.type() == AutocompleteInput::INVALID)) {
+ Stop();
+ return;
+ }
+
+ // Can't search with no default provider.
+ const TemplateURL* const current_default_provider =
+ profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
+ // TODO(pkasting): http://b/1155786 Eventually we should not need all these
+ // checks.
+ if (!current_default_provider || !current_default_provider->url() ||
+ !current_default_provider->url()->SupportsReplacement()) {
+ Stop();
+ return;
+ }
+
+ // If we're still running an old query but have since changed the query text
+ // or the default provider, abort the query.
+ if (!done_ && (!minimal_changes ||
+ (last_default_provider_ != current_default_provider)))
+ Stop();
+
+ // TODO(pkasting): http://b/1162970 We shouldn't need to structure-copy this.
+ // Nor should we need |last_default_provider_| just to know whether the
+ // provider changed.
+ default_provider_ = *current_default_provider;
+ last_default_provider_ = current_default_provider;
+
+ if (input.text().empty()) {
+ // User typed "?" alone. Give them a placeholder result indicating what
+ // this syntax does.
+ AutocompleteMatch match;
+ static const std::wstring kNoQueryInput(
+ l10n_util::GetString(IDS_AUTOCOMPLETE_NO_QUERY));
+ match.contents.assign(l10n_util::GetStringF(
+ IDS_AUTOCOMPLETE_SEARCH_CONTENTS, default_provider_.short_name(),
+ kNoQueryInput));
+ match.contents_class.push_back(
+ ACMatchClassification(0, ACMatchClassification::DIM));
+ match.type = AutocompleteMatch::SEARCH;
+ matches_.push_back(match);
+ Stop();
+ return;
+ }
+
+ input_ = input;
+
+ StartOrStopHistoryQuery(minimal_changes, synchronous_only);
+ StartOrStopSuggestQuery(minimal_changes, synchronous_only);
+ ConvertResultsToAutocompleteMatches();
+}
+
+void SearchProvider::Run() {
+ // Start a new request with the current input.
+ DCHECK(!done_);
+ const TemplateURLRef* const suggestions_url =
+ default_provider_.suggestions_url();
+ DCHECK(suggestions_url->SupportsReplacement());
+ fetcher_.reset(new URLFetcher(GURL(suggestions_url->ReplaceSearchTerms(
+ default_provider_, input_.text(),
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())),
+ URLFetcher::GET, this));
+ fetcher_->set_request_context(profile_->GetRequestContext());
+ fetcher_->Start();
+}
+
+void SearchProvider::Stop() {
+ StopHistory();
+ StopSuggest();
+ done_ = true;
+}
+
+void SearchProvider::OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookie,
+ const std::string& data) {
+ DCHECK(!done_);
+ suggest_results_pending_ = false;
+ suggest_results_.clear();
+ navigation_results_.clear();
+ JSONStringValueSerializer deserializer(data);
+ Value* root_val = NULL;
+ have_suggest_results_ = status.is_success() && (response_code == 200) &&
+ deserializer.Deserialize(&root_val) && ParseSuggestResults(root_val);
+ delete root_val;
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(!suggest_results_.empty());
+}
+
+void SearchProvider::StartOrStopHistoryQuery(bool minimal_changes,
+ bool synchronous_only) {
+ // For the minimal_changes case, if we finished the previous query and still
+ // have its results, or are allowed to keep running it, just do that, rather
+ // than starting a new query.
+ if (minimal_changes &&
+ (have_history_results_ || (!done_ && !synchronous_only)))
+ return;
+
+ // We can't keep running any previous query, so halt it.
+ StopHistory();
+
+ // We can't start a new query if we're only allowed synchronous results.
+ if (synchronous_only)
+ return;
+
+ // Start the history query.
+ HistoryService* const history_service =
+ profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ history_service->GetMostRecentKeywordSearchTerms(default_provider_.id(),
+ input_.text(), static_cast<int>(max_matches()),
+ &history_request_consumer_,
+ NewCallback(this, &SearchProvider::OnGotMostRecentKeywordSearchTerms));
+ history_request_pending_ = true;
+}
+
+void SearchProvider::StartOrStopSuggestQuery(bool minimal_changes,
+ bool synchronous_only) {
+ // Don't run Suggest when off the record, the engine doesn't support it, or
+ // the user has disabled it. Also don't query the server for URLs that aren't
+ // http/https/ftp. Sending things like file: and data: is both a waste of
+ // time and a disclosure of potentially private, local data.
+ if (profile_->IsOffTheRecord() ||
+ !default_provider_.suggestions_url() ||
+ !profile_->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled) ||
+ ((input_.type() == AutocompleteInput::URL) &&
+ (input_.scheme() != L"http") && (input_.scheme() != L"https") &&
+ (input_.scheme() != L"ftp"))) {
+ StopSuggest();
+ return;
+ }
+
+ // For the minimal_changes case, if we finished the previous query and still
+ // have its results, or are allowed to keep running it, just do that, rather
+ // than starting a new query.
+ if (minimal_changes &&
+ (have_suggest_results_ || (!done_ && !synchronous_only)))
+ return;
+
+ // We can't keep running any previous query, so halt it.
+ StopSuggest();
+
+ // We can't start a new query if we're only allowed synchronous results.
+ if (synchronous_only)
+ return;
+
+ // Kick off a timer that will start the URL fetch if it completes before
+ // the user types another character.
+ suggest_results_pending_ = true;
+ MessageLoop::current()->timer_manager()->ResetTimer(timer_.get());
+}
+
+void SearchProvider::StopHistory() {
+ history_request_consumer_.CancelAllRequests();
+ history_request_pending_ = false;
+ history_results_.clear();
+ have_history_results_ = false;
+}
+
+void SearchProvider::StopSuggest() {
+ suggest_results_pending_ = false;
+ MessageLoop::current()->timer_manager()->StopTimer(timer_.get());
+ fetcher_.reset(); // Stop any in-progress URL fetch.
+ suggest_results_.clear();
+ have_suggest_results_ = false;
+ star_request_consumer_.CancelAllRequests();
+ star_requests_pending_ = false;
+}
+
+void SearchProvider::OnGotMostRecentKeywordSearchTerms(
+ CancelableRequestProvider::Handle handle,
+ HistoryResults* results) {
+ history_request_pending_ = false;
+ have_history_results_ = true;
+ history_results_ = *results;
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(!history_results_.empty());
+}
+
+void SearchProvider::OnQueryURLComplete(HistoryService::Handle handle,
+ bool success,
+ const history::URLRow* url_row,
+ history::VisitVector* unused) {
+ bool is_starred = success ? url_row->starred() : false;
+ star_requests_pending_ = false;
+ // We can't just use star_request_consumer_.HasPendingRequests() here;
+ // see comment in ConvertResultsToAutocompleteMatches().
+ for (NavigationResults::iterator i(navigation_results_.begin());
+ i != navigation_results_.end(); ++i) {
+ if (i->star_request_handle == handle) {
+ i->star_request_handle = 0;
+ i->starred = is_starred;
+ } else if (i->star_request_handle) {
+ star_requests_pending_ = true;
+ }
+ }
+ if (!star_requests_pending_) {
+ // No more requests. Notify the observer.
+ ConvertResultsToAutocompleteMatches();
+ listener_->OnProviderUpdate(true);
+ }
+}
+
+bool SearchProvider::ParseSuggestResults(Value* root_val) {
+ if (!root_val->IsType(Value::TYPE_LIST))
+ return false;
+ ListValue* root_list = static_cast<ListValue*>(root_val);
+
+ Value* query_val;
+ std::wstring query_str;
+ Value* result_val;
+ if ((root_list->GetSize() < 2) || !root_list->Get(0, &query_val) ||
+ !query_val->GetAsString(&query_str) || (query_str != input_.text()) ||
+ !root_list->Get(1, &result_val) || !result_val->IsType(Value::TYPE_LIST))
+ return false;
+
+ ListValue* description_list = NULL;
+ if (root_list->GetSize() > 2) {
+ // 3rd element: Description list.
+ Value* description_val;
+ if (root_list->Get(2, &description_val) &&
+ description_val->IsType(Value::TYPE_LIST))
+ description_list = static_cast<ListValue*>(description_val);
+ }
+
+ // We don't care about the query URL list (the fourth element in the
+ // response) for now.
+
+ // Parse optional data in the results from the Suggest server if any.
+ ListValue* type_list = NULL;
+ // 5th argument: Optional key-value pairs.
+ // TODO: We may iterate the 5th+ arguments of the root_list if any other
+ // optional data are defined.
+ if (root_list->GetSize() > 4) {
+ Value* optional_val;
+ if (root_list->Get(4, &optional_val) &&
+ optional_val->IsType(Value::TYPE_DICTIONARY)) {
+ DictionaryValue* dict_val = static_cast<DictionaryValue*>(optional_val);
+
+ // Parse Google Suggest specific type extension.
+ static const std::wstring kGoogleSuggestType(L"google:suggesttype");
+ if (dict_val->HasKey(kGoogleSuggestType))
+ dict_val->GetList(kGoogleSuggestType, &type_list);
+ }
+ }
+
+ ListValue* result_list = static_cast<ListValue*>(result_val);
+ for (size_t i = 0; i < result_list->GetSize(); ++i) {
+ Value* suggestion_val;
+ std::wstring suggestion_str;
+ if (!result_list->Get(i, &suggestion_val) ||
+ !suggestion_val->GetAsString(&suggestion_str))
+ return false;
+
+ Value* type_val;
+ std::wstring type_str;
+ if (type_list && type_list->Get(i, &type_val) &&
+ type_val->GetAsString(&type_str) && (type_str == L"NAVIGATION")) {
+ Value* site_val;
+ std::wstring site_name;
+ if (navigation_results_.size() < max_matches() &&
+ description_list && description_list->Get(i, &site_val) &&
+ site_val->IsType(Value::TYPE_STRING) &&
+ site_val->GetAsString(&site_name)) {
+ navigation_results_.push_back(NavigationResult(suggestion_str,
+ site_name));
+ }
+ } else {
+ // TODO(kochi): Currently we treat a calculator result as a query, but it
+ // is better to have better presentation for caluculator results.
+ if (suggest_results_.size() < max_matches())
+ suggest_results_.push_back(suggestion_str);
+ }
+ }
+
+ // Request the star state for all URLs from the history service.
+ HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS);
+ if (!hs)
+ return true;
+
+ for (NavigationResults::iterator i(navigation_results_.begin());
+ i != navigation_results_.end(); ++i) {
+ i->star_request_handle = hs->QueryURL(GURL(i->url), false,
+ &star_request_consumer_,
+ NewCallback(this, &SearchProvider::OnQueryURLComplete));
+ }
+ star_requests_pending_ = !navigation_results_.empty();
+
+ return true;
+}
+
+void SearchProvider::ConvertResultsToAutocompleteMatches() {
+ // Convert all the results to matches and add them to a map, so we can keep
+ // the most relevant match for each result.
+ MatchMap map;
+ const int did_not_accept_suggestion = suggest_results_.empty() ?
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE :
+ TemplateURLRef::NO_SUGGESTION_CHOSEN;
+ const Time no_time;
+ AddMatchToMap(input_.text(), CalculateRelevanceForWhatYouTyped(),
+ did_not_accept_suggestion, &map);
+
+ for (HistoryResults::const_iterator i(history_results_.begin());
+ i != history_results_.end(); ++i) {
+ AddMatchToMap(i->term, CalculateRelevanceForHistory(i->time),
+ did_not_accept_suggestion, &map);
+ }
+
+ for (size_t i = 0; i < suggest_results_.size(); ++i) {
+ AddMatchToMap(suggest_results_[i], CalculateRelevanceForSuggestion(i),
+ static_cast<int>(i), &map);
+ }
+
+ // Now add the most relevant matches from the map to |matches_|.
+ matches_.clear();
+ for (MatchMap::const_iterator i(map.begin()); i != map.end(); ++i)
+ matches_.push_back(i->second);
+
+ if (navigation_results_.size()) {
+ // TODO(kochi): http://b/1170574 We add only one results for navigational
+ // suggestions. If we can get more useful information about the score,
+ // consider adding more results.
+ matches_.push_back(NavigationToMatch(navigation_results_[0],
+ CalculateRelevanceForNavigation(0),
+ navigation_results_[0].starred));
+ }
+
+ const size_t max_total_matches = max_matches() + 1; // 1 for "what you typed"
+ std::partial_sort(matches_.begin(),
+ matches_.begin() + std::min(max_total_matches, matches_.size()),
+ matches_.end(), &AutocompleteMatch::MoreRelevant);
+ if (matches_.size() > max_total_matches)
+ matches_.resize(max_total_matches);
+
+ // We're done when both asynchronous subcomponents have finished.
+ // We can't use CancelableRequestConsumer.HasPendingRequests() for
+ // history and star requests here. A pending request is not cleared
+ // until after the completion callback has returned, but we've
+ // reached here from inside that callback. HasPendingRequests()
+ // would therefore return true, and if this is the last thing left
+ // to calculate for this query, we'll never mark the query "done".
+ done_ = !history_request_pending_ &&
+ !suggest_results_pending_ &&
+ !star_requests_pending_;
+}
+
+int SearchProvider::CalculateRelevanceForWhatYouTyped() const {
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ return 1300;
+
+ case AutocompleteInput::REQUESTED_URL:
+ return 1200;
+
+ case AutocompleteInput::URL:
+ return 850;
+
+ case AutocompleteInput::QUERY:
+ return 1300;
+
+ case AutocompleteInput::FORCED_QUERY:
+ return 1500;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+int SearchProvider::CalculateRelevanceForHistory(const Time& time) const {
+ // The relevance of past searches falls off over time. This curve is chosen
+ // so that the relevance of a search 15 minutes ago is discounted about 50
+ // points, while the relevance of a search two weeks ago is discounted about
+ // 450 points.
+ const double elapsed_time = std::max((Time::Now() - time).InSecondsF(), 0.);
+ const int score_discount = static_cast<int>(6.5 * pow(elapsed_time, 0.3));
+
+ // Don't let scores go below 0. Negative relevance scores are meaningful in a
+ // different way.
+ int base_score;
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ base_score = 1050;
+ break;
+
+ case AutocompleteInput::URL:
+ base_score = 750;
+ break;
+
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ base_score = 1250;
+ break;
+
+ default:
+ NOTREACHED();
+ base_score = 0;
+ break;
+ }
+ return std::max(0, base_score - score_discount);
+}
+
+int SearchProvider::CalculateRelevanceForSuggestion(
+ size_t suggestion_number) const {
+ DCHECK(suggestion_number < suggest_results_.size());
+ const int suggestion_value =
+ static_cast<int>(suggest_results_.size() - 1 - suggestion_number);
+ switch (input_.type()) {
+ case AutocompleteInput::UNKNOWN:
+ case AutocompleteInput::REQUESTED_URL:
+ return 600 + suggestion_value;
+
+ case AutocompleteInput::URL:
+ return 300 + suggestion_value;
+
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ return 800 + suggestion_value;
+
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+int SearchProvider::CalculateRelevanceForNavigation(
+ size_t suggestion_number) const {
+ DCHECK(suggestion_number < navigation_results_.size());
+ // TODO(kochi): http://b/784900 Use relevance score from the NavSuggest
+ // server if possible.
+ switch (input_.type()) {
+ case AutocompleteInput::QUERY:
+ case AutocompleteInput::FORCED_QUERY:
+ return 1000 + static_cast<int>(suggestion_number);
+
+ default:
+ return 800 + static_cast<int>(suggestion_number);
+ }
+}
+
+void SearchProvider::AddMatchToMap(const std::wstring& query_string,
+ int relevance,
+ int accepted_suggestion,
+ MatchMap* map) {
+ AutocompleteMatch match(this, relevance, false);
+ match.type = AutocompleteMatch::SEARCH;
+ std::vector<size_t> content_param_offsets;
+ match.contents.assign(l10n_util::GetStringF(IDS_AUTOCOMPLETE_SEARCH_CONTENTS,
+ default_provider_.short_name(),
+ query_string,
+ &content_param_offsets));
+ if (content_param_offsets.size() == 2) {
+ AutocompleteMatch::ClassifyLocationInString(content_param_offsets[1],
+ query_string.length(),
+ match.contents.length(),
+ ACMatchClassification::NONE,
+ &match.contents_class);
+ } else {
+ // |content_param_offsets| should only not be 2 if:
+ // (a) A translator screws up
+ // (b) The strings have been changed and we haven't been rebuilt properly
+ // (c) Some sort of crazy installer error/DLL version mismatch problem that
+ // gets the wrong data out of the locale DLL?
+ // While none of these are supposed to happen, we've seen this get hit in
+ // the wild, so avoid the vector access in the conditional arm above, which
+ // will crash.
+ NOTREACHED();
+ }
+
+ // When the user forced a query, we need to make sure all the fill_into_edit
+ // values preserve that property. Otherwise, if the user starts editing a
+ // suggestion, non-Search results will suddenly appear.
+ size_t search_start = 0;
+ if (input_.type() == AutocompleteInput::FORCED_QUERY) {
+ match.fill_into_edit.assign(L"?");
+ ++search_start;
+ }
+ match.fill_into_edit.append(query_string);
+ // NOTE: All Google suggestions currently start with the original input, but
+ // not all Yahoo! suggestions do.
+ if (!input_.prevent_inline_autocomplete() &&
+ !match.fill_into_edit.compare(search_start, input_.text().length(),
+ input_.text()))
+ match.inline_autocomplete_offset = search_start + input_.text().length();
+
+ const TemplateURLRef* const search_url = default_provider_.url();
+ DCHECK(search_url->SupportsReplacement());
+ match.destination_url = search_url->ReplaceSearchTerms(default_provider_,
+ query_string,
+ accepted_suggestion,
+ input_.text());
+
+ // Search results don't look like URLs.
+ match.transition = PageTransition::GENERATED;
+
+ // Try to add |match| to |map|. If a match for |query_string| is already in
+ // |map|, replace it if |match| is more relevant.
+ // NOTE: Keep this ToLower() call in sync with url_database.cc.
+ const std::pair<MatchMap::iterator, bool> i = map->insert(
+ std::pair<std::wstring, AutocompleteMatch>(
+ l10n_util::ToLower(query_string), match));
+ // NOTE: We purposefully do a direct relevance comparison here instead of
+ // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
+ // first" rather than "items alphabetically first" when the scores are equal.
+ // The only case this matters is when a user has results with the same score
+ // that differ only by capitalization; because the history system returns
+ // results sorted by recency, this means we'll pick the most recent such
+ // result even if the precision of our relevance score is too low to
+ // distinguish the two.
+ if (!i.second && (match.relevance > i.first->second.relevance))
+ i.first->second = match;
+}
+
+AutocompleteMatch SearchProvider::NavigationToMatch(
+ const NavigationResult& navigation,
+ int relevance,
+ bool starred) {
+ AutocompleteMatch match(this, relevance, false);
+ match.destination_url = navigation.url;
+ match.contents = StringForURLDisplay(GURL(navigation.url), true);
+ // TODO(kochi): Consider moving HistoryURLProvider::TrimHttpPrefix() to some
+ // public utility function.
+ if (!url_util::FindAndCompareScheme(input_.text(), "http", NULL))
+ TrimHttpPrefix(&match.contents);
+ AutocompleteMatch::ClassifyMatchInString(input_.text(), match.contents,
+ ACMatchClassification::URL,
+ &match.contents_class);
+
+ match.description = navigation.site_name;
+ AutocompleteMatch::ClassifyMatchInString(input_.text(), navigation.site_name,
+ ACMatchClassification::NONE,
+ &match.description_class);
+
+ match.starred = starred;
+ // When the user forced a query, we need to make sure all the fill_into_edit
+ // values preserve that property. Otherwise, if the user starts editing a
+ // suggestion, non-Search results will suddenly appear.
+ if (input_.type() == AutocompleteInput::FORCED_QUERY)
+ match.fill_into_edit.assign(L"?");
+ match.fill_into_edit.append(match.contents);
+ // TODO(pkasting): http://b/1112879 These should perhaps be
+ // inline-autocompletable?
+
+ return match;
+}
+
+// TODO(kochi): This is duplicate from HistoryURLProvider.
+// static
+size_t SearchProvider::TrimHttpPrefix(std::wstring* url) {
+ url_parse::Component scheme;
+ if (!url_util::FindAndCompareScheme(*url, "http", &scheme))
+ return 0; // Not "http".
+
+ // Erase scheme plus up to two slashes.
+ size_t prefix_len = scheme.end() + 1; // "http:"
+ const size_t after_slashes = std::min(url->length(),
+ static_cast<size_t>(scheme.end() + 3));
+ while ((prefix_len < after_slashes) && ((*url)[prefix_len] == L'/'))
+ ++prefix_len;
+ if (prefix_len == url->length())
+ url->clear();
+ else
+ url->erase(url->begin(), url->begin() + prefix_len);
+ return prefix_len;
+}