1 // Copyright 2020 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.
4
5 #include "chrome/browser/prefetch/search_prefetch/search_prefetch_service.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/location.h"
10 #include "chrome/browser/net/prediction_options.h"
11 #include "chrome/browser/prefetch/search_prefetch/field_trial_settings.h"
12 #include "chrome/browser/prefetch/search_prefetch/prefetched_response_container.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/search_engines/template_url_service_factory.h"
15 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
16 #include "components/omnibox/browser/autocomplete_controller.h"
17 #include "components/omnibox/browser/base_search_provider.h"
18 #include "components/search_engines/template_url_service.h"
19 #include "components/variations/net/variations_http_headers.h"
20 #include "content/public/browser/browser_context.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/storage_partition.h"
23 #include "content/public/common/content_constants.h"
24 #include "net/base/load_flags.h"
25 #include "net/http/http_status_code.h"
26 #include "net/traffic_annotation/network_traffic_annotation.h"
27 #include "services/network/public/cpp/resource_request.h"
28 #include "services/network/public/cpp/shared_url_loader_factory.h"
29 #include "url/origin.h"
30
PrefetchRequest(const GURL & prefetch_url,base::OnceClosure report_error_callback)31 SearchPrefetchService::PrefetchRequest::PrefetchRequest(
32 const GURL& prefetch_url,
33 base::OnceClosure report_error_callback)
34 : prefetch_url_(prefetch_url),
35 report_error_callback_(std::move(report_error_callback)) {}
36
37 SearchPrefetchService::PrefetchRequest::~PrefetchRequest() = default;
38
StartPrefetchRequest(Profile * profile)39 void SearchPrefetchService::PrefetchRequest::StartPrefetchRequest(
40 Profile* profile) {
41 net::NetworkTrafficAnnotationTag traffic_annotation =
42 net::DefineNetworkTrafficAnnotation("search_prefetch_service", R"(
43 semantics {
44 sender: "Search Prefetch Service"
45 description:
46 "Prefetches search results page (HTML) based on omnibox hints "
47 "provided by the user's default search engine. This allows the "
48 "prefetched content to be served when the user navigates to the "
49 "omnibox hint."
50 trigger:
51 "User typing in the omnibox and the default search provider "
52 "indicates the provided omnibox hint entry is likely to be "
53 "navigated which would result in loading a search results page for "
54 "that hint."
55 data: "Credentials if user is signed in."
56 destination: OTHER
57 destination_other: "The user's default search engine."
58 }
59 policy {
60 cookies_allowed: YES
61 cookies_store: "user"
62 setting:
63 "Users can control this feature by opting out of 'Preload pages "
64 "for faster browsing and searching'"
65 chrome_policy {
66 DefaultSearchProviderEnabled {
67 policy_options {mode: MANDATORY}
68 DefaultSearchProviderEnabled: false
69 }
70 NetworkPredictionOptions {
71 NetworkPredictionOptions: 2
72 }
73 }
74 })");
75
76 auto resource_request = std::make_unique<network::ResourceRequest>();
77 resource_request->load_flags |= net::LOAD_PREFETCH;
78 resource_request->url = prefetch_url_;
79 resource_request->credentials_mode =
80 network::mojom::CredentialsMode::kInclude;
81 variations::AppendVariationsHeaderUnknownSignedIn(
82 prefetch_url_, variations::InIncognito::kNo, resource_request.get());
83 resource_request->headers.SetHeader(content::kCorsExemptPurposeHeaderName,
84 "prefetch");
85 // TODO(ryansturm): Find other headers that may need to be set.
86 // https://crbug.com/1138648
87
88 simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
89 traffic_annotation);
90
91 auto url_loader_factory =
92 content::BrowserContext::GetDefaultStoragePartition(profile)
93 ->GetURLLoaderFactoryForBrowserProcess();
94
95 simple_loader_->DownloadToString(
96 url_loader_factory.get(),
97 base::BindOnce(&SearchPrefetchService::PrefetchRequest::LoadDone,
98 base::Unretained(this)),
99 1024 * 1024);
100 }
101
LoadDone(std::unique_ptr<std::string> response_body)102 void SearchPrefetchService::PrefetchRequest::LoadDone(
103 std::unique_ptr<std::string> response_body) {
104 bool success = simple_loader_->NetError() == net::OK;
105 int response_code = 0;
106
107 // TODO(ryansturm): Handle these errors more robustly by reporting them to the
108 // service. We need to prevent prefetches for x amount of time based on the
109 // error. https://crbug.com/1138641
110 if (!success || response_body->empty()) {
111 current_status_ = SearchPrefetchStatus::kRequestFailed;
112 std::move(report_error_callback_).Run();
113 return;
114 }
115 if (simple_loader_->ResponseInfo() && simple_loader_->ResponseInfo()->headers)
116 response_code = simple_loader_->ResponseInfo()->headers->response_code();
117 if (response_code != net::HTTP_OK) {
118 current_status_ = SearchPrefetchStatus::kRequestFailed;
119 std::move(report_error_callback_).Run();
120 return;
121 }
122 current_status_ = SearchPrefetchStatus::kSuccessfullyCompleted;
123
124 prefetch_response_container_ = std::make_unique<PrefetchedResponseContainer>(
125 simple_loader_->ResponseInfo()->Clone(), std::move(response_body));
126
127 simple_loader_.reset();
128 }
129
130 std::unique_ptr<PrefetchedResponseContainer>
TakePrefetchResponse()131 SearchPrefetchService::PrefetchRequest::TakePrefetchResponse() {
132 return std::move(prefetch_response_container_);
133 }
134
CancelPrefetch()135 void SearchPrefetchService::PrefetchRequest::CancelPrefetch() {
136 DCHECK_EQ(current_status_, SearchPrefetchStatus::kInFlight);
137 current_status_ = SearchPrefetchStatus::kRequestCancelled;
138
139 simple_loader_.reset();
140 }
141
SearchPrefetchService(Profile * profile)142 SearchPrefetchService::SearchPrefetchService(Profile* profile)
143 : profile_(profile) {
144 DCHECK(!profile_->IsOffTheRecord());
145 }
146
147 SearchPrefetchService::~SearchPrefetchService() = default;
148
Shutdown()149 void SearchPrefetchService::Shutdown() {
150 if (observer_.IsObserving())
151 observer_.RemoveObservation();
152 }
153
MaybePrefetchURL(const GURL & url)154 bool SearchPrefetchService::MaybePrefetchURL(const GURL& url) {
155 if (!SearchPrefetchServicePrefetchingIsEnabled())
156 return false;
157
158 if (!chrome_browser_net::CanPreresolveAndPreconnectUI(profile_->GetPrefs())) {
159 return false;
160 }
161
162 auto* template_url_service =
163 TemplateURLServiceFactory::GetForProfile(profile_);
164 if (!template_url_service ||
165 !template_url_service->GetDefaultSearchProvider())
166 return false;
167
168 // Lazily observe Template URL Service.
169 if (!observer_.IsObserving()) {
170 observer_.Observe(template_url_service);
171 const TemplateURL* template_url =
172 template_url_service->GetDefaultSearchProvider();
173 if (template_url) {
174 template_url_service_data_ = template_url->data();
175 }
176 }
177
178 base::string16 search_terms;
179
180 // Extract the terms directly to make sure this string will match the URL
181 // interception string logic.
182 template_url_service->GetDefaultSearchProvider()->ExtractSearchTermsFromURL(
183 url, template_url_service->search_terms_data(), &search_terms);
184
185 if (search_terms.size() == 0)
186 return false;
187
188 if (last_error_time_ticks_ + SearchPrefetchErrorBackoffDuration() >
189 base::TimeTicks::Now()) {
190 return false;
191 }
192
193 if (prefetches_.size() >= SearchPrefetchMaxAttemptsPerCachingDuration())
194 return false;
195
196 // Don't prefetch the same search terms twice within the expiry duration.
197 if (prefetches_.find(search_terms) != prefetches_.end()) {
198 return false;
199 }
200
201 prefetches_.emplace(
202 search_terms, std::make_unique<PrefetchRequest>(
203 url, base::BindOnce(&SearchPrefetchService::ReportError,
204 base::Unretained(this))));
205 prefetches_[search_terms]->StartPrefetchRequest(profile_);
206 prefetch_expiry_timers_.emplace(search_terms,
207 std::make_unique<base::OneShotTimer>());
208 prefetch_expiry_timers_[search_terms]->Start(
209 FROM_HERE, SearchPrefetchCachingLimit(),
210 base::BindOnce(&SearchPrefetchService::DeletePrefetch,
211 base::Unretained(this), search_terms));
212 return true;
213 }
214
215 base::Optional<SearchPrefetchStatus>
GetSearchPrefetchStatusForTesting(base::string16 search_terms)216 SearchPrefetchService::GetSearchPrefetchStatusForTesting(
217 base::string16 search_terms) {
218 if (prefetches_.find(search_terms) == prefetches_.end())
219 return base::nullopt;
220 return prefetches_[search_terms]->current_status();
221 }
222
223 std::unique_ptr<PrefetchedResponseContainer>
TakePrefetchResponse(const GURL & url)224 SearchPrefetchService::TakePrefetchResponse(const GURL& url) {
225 auto* template_url_service =
226 TemplateURLServiceFactory::GetForProfile(profile_);
227 if (!template_url_service ||
228 !template_url_service->GetDefaultSearchProvider())
229 return nullptr;
230
231 base::string16 search_terms;
232 template_url_service->GetDefaultSearchProvider()->ExtractSearchTermsFromURL(
233 url, template_url_service->search_terms_data(), &search_terms);
234
235 if (search_terms.length() == 0) {
236 return nullptr;
237 }
238
239 const auto& iter = prefetches_.find(search_terms);
240
241 if (iter == prefetches_.end()) {
242 return nullptr;
243 }
244
245 // Verify that the URL is the same origin as the prefetch URL. While other
246 // checks should address this by clearing prefetches on user changes to
247 // default search, it is paramount to never serve content from one origin to
248 // another.
249 if (url::Origin::Create(url) !=
250 url::Origin::Create(iter->second->prefetch_url())) {
251 return nullptr;
252 }
253
254 if (iter->second->current_status() !=
255 SearchPrefetchStatus::kSuccessfullyCompleted) {
256 return nullptr;
257 }
258
259 std::unique_ptr<PrefetchedResponseContainer> response =
260 iter->second->TakePrefetchResponse();
261
262 // TODO(ryansturm): For metrics reporting, the prefetch request data should be
263 // moved to the correct tab helper object, for now, the object can be deleted
264 // entirely. Alternatively, the object can remain here with a new timeout in
265 // a set of currently being served requests.
266 DeletePrefetch(search_terms);
267
268 return response;
269 }
270
ClearPrefetches()271 void SearchPrefetchService::ClearPrefetches() {
272 prefetches_.clear();
273 prefetch_expiry_timers_.clear();
274 }
275
DeletePrefetch(base::string16 search_terms)276 void SearchPrefetchService::DeletePrefetch(base::string16 search_terms) {
277 DCHECK(prefetches_.find(search_terms) != prefetches_.end());
278 DCHECK(prefetch_expiry_timers_.find(search_terms) !=
279 prefetch_expiry_timers_.end());
280
281 prefetches_.erase(search_terms);
282 prefetch_expiry_timers_.erase(search_terms);
283 }
284
ReportError()285 void SearchPrefetchService::ReportError() {
286 last_error_time_ticks_ = base::TimeTicks::Now();
287 }
288
OnResultChanged(AutocompleteController * controller)289 void SearchPrefetchService::OnResultChanged(
290 AutocompleteController* controller) {
291 const auto& result = controller->result();
292 const auto* default_match = result.default_match();
293
294 // Cancel Unneeded prefetch requests.
295 if (SearchPrefetchShouldCancelUneededInflightRequests()) {
296 auto* template_url_service =
297 TemplateURLServiceFactory::GetForProfile(profile_);
298 if (!template_url_service)
299 return;
300
301 // Since we limit the number of prefetches in the map, this should be fast
302 // despite the two loops.
303 for (const auto& kv_pair : prefetches_) {
304 const auto& search_terms = kv_pair.first;
305 auto& prefetch_request = kv_pair.second;
306 if (prefetch_request->current_status() !=
307 SearchPrefetchStatus::kInFlight) {
308 continue;
309 }
310 bool should_cancel_request = true;
311 for (const auto& match : result) {
312 base::string16 match_search_terms;
313 template_url_service->GetDefaultSearchProvider()
314 ->ExtractSearchTermsFromURL(
315 match.destination_url,
316 template_url_service->search_terms_data(), &match_search_terms);
317
318 if (search_terms == match_search_terms) {
319 should_cancel_request = false;
320 break;
321 }
322 }
323
324 // Cancel the inflight request and mark it as canceled.
325 if (should_cancel_request) {
326 prefetch_request->CancelPrefetch();
327 }
328 }
329 }
330
331 // One arm of the experiment only prefetches the top match when it is default.
332 if (SearchPrefetchOnlyFetchDefaultMatch()) {
333 if (default_match && BaseSearchProvider::ShouldPrefetch(*default_match)) {
334 MaybePrefetchURL(default_match->destination_url);
335 }
336 return;
337 }
338
339 for (const auto& match : result) {
340 if (BaseSearchProvider::ShouldPrefetch(match)) {
341 MaybePrefetchURL(match.destination_url);
342 }
343 }
344 }
345
OnTemplateURLServiceChanged()346 void SearchPrefetchService::OnTemplateURLServiceChanged() {
347 auto* template_url_service =
348 TemplateURLServiceFactory::GetForProfile(profile_);
349 DCHECK(template_url_service);
350
351 base::Optional<TemplateURLData> template_url_service_data;
352
353 const TemplateURL* template_url =
354 template_url_service->GetDefaultSearchProvider();
355 if (template_url) {
356 template_url_service_data = template_url->data();
357 }
358
359 if (!template_url_service_data_.has_value() &&
360 !template_url_service_data.has_value()) {
361 return;
362 }
363
364 UIThreadSearchTermsData search_data;
365
366 if (template_url_service_data_.has_value() &&
367 template_url_service_data.has_value() &&
368 TemplateURL::MatchesData(
369 template_url, &(template_url_service_data_.value()), search_data)) {
370 return;
371 }
372
373 template_url_service_data_ = template_url_service_data;
374 ClearPrefetches();
375 }
376