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