1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/search/start_suggest_service.h"
10 #include "base/functional/bind.h"
11 #include "base/functional/callback_helpers.h"
12 #include "base/rand_util.h"
13 #include "base/stl_util.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/values.h"
16 #include "components/omnibox/browser/autocomplete_scheme_classifier.h"
17 #include "components/omnibox/browser/search_suggestion_parser.h"
18 #include "components/search/search_provider_observer.h"
19 #include "components/search_engines/template_url.h"
20 #include "components/search_engines/template_url_service.h"
21 #include "net/base/net_errors.h"
22 #include "net/base/url_util.h"
23 #include "net/http/http_status_code.h"
24 #include "net/traffic_annotation/network_traffic_annotation.h"
25 #include "services/network/public/cpp/resource_request.h"
26 #include "services/network/public/cpp/shared_url_loader_factory.h"
27 #include "services/network/public/cpp/simple_url_loader.h"
30 // A cache of trending query suggestions using JSON serialized into a string.
31 const char kTrendingQuerySuggestionCachedResults[] =
32 "TrendingQuerySuggestionCachedResults";
34 const char kXSSIResponsePreamble[] = ")]}'";
37 StartSuggestService::StartSuggestService(
38 TemplateURLService* template_url_service,
39 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
40 std::unique_ptr<AutocompleteSchemeClassifier> scheme_classifier,
41 const std::string& application_country,
42 const std::string& application_locale,
43 const GURL& request_initiator_url)
44 : template_url_service_(template_url_service),
45 url_loader_factory_(url_loader_factory),
46 scheme_classifier_(std::move(scheme_classifier)),
47 application_country_(application_country),
48 application_locale_(application_locale),
49 request_initiator_url_(request_initiator_url),
50 search_provider_observer_(std::make_unique<SearchProviderObserver>(
51 template_url_service_,
52 base::BindRepeating(&StartSuggestService::SearchProviderChanged,
53 base::Unretained(this)))) {
54 DCHECK(template_url_service);
55 DCHECK(url_loader_factory_);
56 DCHECK(scheme_classifier_);
59 StartSuggestService::~StartSuggestService() = default;
61 void StartSuggestService::FetchSuggestions(
62 const TemplateURLRef::SearchTermsArgs& args,
63 SuggestResultCallback callback,
64 bool fetch_from_server) {
65 // Do nothing if Google not default search engine.
66 if (!search_provider_observer()->is_google()) {
67 std::move(callback).Run(QuerySuggestions());
71 // If there are saved suggestions from a previous request, return that.
72 if (!fetch_from_server &&
73 suggestions_cache_.find(kTrendingQuerySuggestionCachedResults) !=
74 suggestions_cache_.end()) {
75 QuerySuggestions cache =
76 suggestions_cache_[kTrendingQuerySuggestionCachedResults];
78 std::move(callback).Run(std::move(cache));
83 net::NetworkTrafficAnnotationTag traffic_annotation =
84 net::DefineNetworkTrafficAnnotation("chrome_search_suggest_service",
87 sender: "Chrome Search Suggest Service"
89 "Fetch query suggestions to be shown in NTP."
91 "Displaying on the new tab page, if Google is the "
92 "configured search provider."
94 destination: GOOGLE_OWNED_SERVICE
99 "Users can control this feature by selecting a non-Google default "
100 "search engine in Chrome settings under 'Search Engine'"
102 DefaultSearchProviderEnabled {
103 policy_options {mode: MANDATORY}
104 DefaultSearchProviderEnabled: false
109 auto resource_request = std::make_unique<network::ResourceRequest>();
110 const GURL& request_url = GetRequestURL(args);
112 resource_request->url = request_url;
113 // Do not send credentials since Trending Queries is locale-based.
114 resource_request->credentials_mode =
115 network::mojom::CredentialsMode::kOmitBug_775438_Workaround;
116 resource_request->request_initiator =
117 url::Origin::Create(request_initiator_url_);
119 loaders_.push_back(network::SimpleURLLoader::Create(
120 std::move(resource_request), traffic_annotation));
121 loaders_.back()->DownloadToString(
122 url_loader_factory_.get(),
123 base::BindOnce(&StartSuggestService::SuggestResponseLoaded,
124 weak_ptr_factory_.GetWeakPtr(), loaders_.back().get(),
125 std::move(callback)),
126 network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
129 void StartSuggestService::SearchProviderChanged() {
130 // Remove cached results if the default search engine changes.
131 suggestions_cache_.clear();
134 GURL StartSuggestService::GetRequestURL(
135 const TemplateURLRef::SearchTermsArgs& search_terms_args) {
136 const TemplateURL* default_provider =
137 template_url_service_->GetDefaultSearchProvider();
138 DCHECK(default_provider);
139 const TemplateURLRef& suggestion_url_ref =
140 default_provider->suggestions_url_ref();
141 const SearchTermsData& search_terms_data =
142 template_url_service_->search_terms_data();
143 DCHECK(suggestion_url_ref.SupportsReplacement(search_terms_data));
144 GURL url = GURL(suggestion_url_ref.ReplaceSearchTerms(search_terms_args,
146 if (!application_country_.empty()) {
147 // Trending Queries are country-based, so passing this helps determine
149 url = net::AppendQueryParameter(url, "gl", application_country_);
151 if (!application_locale_.empty()) {
152 // Language is also used in addition to country to rank suggestions,
153 // ensuring that there can be separate ranks for different languages in the
154 // same country (i.e. fr-ca and en-ca.
155 url = net::AppendQueryParameter(url, "hl", application_locale_);
160 GURL StartSuggestService::GetQueryDestinationURL(
161 const std::u16string& query,
162 const TemplateURL* search_provider) {
163 TemplateURLRef::SearchTermsArgs search_terms_args(query);
164 DCHECK(search_provider);
165 const TemplateURLRef& search_url_ref = search_provider->url_ref();
166 const SearchTermsData& search_terms_data =
167 template_url_service_->search_terms_data();
168 DCHECK(search_url_ref.SupportsReplacement(search_terms_data));
170 search_url_ref.ReplaceSearchTerms(search_terms_args, search_terms_data));
173 SearchProviderObserver* StartSuggestService::search_provider_observer() {
174 return search_provider_observer_.get();
177 void StartSuggestService::SuggestResponseLoaded(
178 network::SimpleURLLoader* loader,
179 SuggestResultCallback callback,
180 std::unique_ptr<std::string> response) {
181 // Ensure the request succeeded and that the provider used is still available.
182 // A verbatim match cannot be generated without this provider, causing errors.
183 const bool request_succeeded = response && loader->NetError() == net::OK;
184 base::EraseIf(loaders_, [loader](const auto& loader_ptr) {
185 return loader == loader_ptr.get();
187 if (!request_succeeded) {
188 std::move(callback).Run(QuerySuggestions());
192 if (base::StartsWith(*response, kXSSIResponsePreamble,
193 base::CompareCase::SENSITIVE)) {
194 *response = response->substr(strlen(kXSSIResponsePreamble));
197 data_decoder::DataDecoder::ParseJsonIsolated(
199 base::BindOnce(&StartSuggestService::SuggestionsParsed,
200 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
203 void StartSuggestService::SuggestionsParsed(
204 SuggestResultCallback callback,
205 data_decoder::DataDecoder::ValueOrError result) {
206 std::move(callback).Run([&] {
207 QuerySuggestions query_suggestions;
208 if (result.has_value() && result.value().is_list()) {
209 SearchSuggestionParser::Results results;
210 AutocompleteInput input;
211 if (SearchSuggestionParser::ParseSuggestResults(
212 result->GetList(), input, *scheme_classifier_,
213 /*default_result_relevance=*/-1, /*is_keyword_result=*/false,
215 for (SearchSuggestionParser::SuggestResult suggest :
216 results.suggest_results) {
217 QuerySuggestion query;
218 query.query = suggest.suggestion();
219 query.destination_url = GetQueryDestinationURL(
220 query.query, template_url_service_->GetDefaultSearchProvider());
221 query_suggestions.push_back(std::move(query));
223 suggestions_cache_[kTrendingQuerySuggestionCachedResults] =
227 return query_suggestions;