blob: 4ceaf4e463de86965dbe20ff2ae3cefcfad0f53b [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.h"
#include <vector>
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/pref_names.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/net/variations_http_headers.h"
#include "content/public/browser/client_hints.h"
#include "content/public/browser/frame_accept_header.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/url_loader_throttles.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_constants.h"
#include "net/base/load_flags.h"
#include "net/cookies/site_for_cookies.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
#include "url/origin.h"
#if defined(OS_ANDROID)
#include "chrome/browser/android/omnibox/geolocation_header.h"
#endif // defined(OS_ANDROID)
namespace {
// A custom URLLoaderThrottle delegate that is very sensitive. Anything that
// would delay or cancel the request is treated the same, which would prevent
// the prefetch request.
class CheckForCancelledOrPausedDelegate
: public blink::URLLoaderThrottle::Delegate {
public:
CheckForCancelledOrPausedDelegate() = default;
~CheckForCancelledOrPausedDelegate() override = default;
CheckForCancelledOrPausedDelegate(const CheckForCancelledOrPausedDelegate&) =
delete;
CheckForCancelledOrPausedDelegate& operator=(
const CheckForCancelledOrPausedDelegate&) = delete;
// URLLoaderThrottle::Delegate:
void CancelWithError(int error_code,
base::StringPiece custom_reason) override {
cancelled_or_paused_ = true;
}
void Resume() override {}
void PauseReadingBodyFromNet() override { cancelled_or_paused_ = true; }
void RestartWithFlags(int additional_load_flags) override {
cancelled_or_paused_ = true;
}
void RestartWithURLResetAndFlags(int additional_load_flags) override {
cancelled_or_paused_ = true;
}
void RestartWithURLResetAndFlagsNow(int additional_load_flags) override {
cancelled_or_paused_ = true;
}
void RestartWithModifiedHeadersNow(
const net::HttpRequestHeaders& modified_headers) override {
cancelled_or_paused_ = true;
}
bool cancelled_or_paused() const { return cancelled_or_paused_; }
private:
bool cancelled_or_paused_ = false;
};
} // namespace
BaseSearchPrefetchRequest::BaseSearchPrefetchRequest(
const GURL& prefetch_url,
base::OnceClosure report_error_callback)
: prefetch_url_(prefetch_url),
report_error_callback_(std::move(report_error_callback)) {}
BaseSearchPrefetchRequest::~BaseSearchPrefetchRequest() = default;
// static
net::NetworkTrafficAnnotationTag
BaseSearchPrefetchRequest::NetworkAnnotationForPrefetch() {
return net::DefineNetworkTrafficAnnotation("search_prefetch_service", R"(
semantics {
sender: "Search Prefetch Service"
description:
"Prefetches search results page (HTML) based on omnibox hints "
"provided by the user's default search engine. This allows the "
"prefetched content to be served when the user navigates to the "
"omnibox hint."
trigger:
"User typing in the omnibox and the default search provider "
"indicates the provided omnibox hint entry is likely to be "
"navigated which would result in loading a search results page for "
"that hint."
data: "Credentials if user is signed in."
destination: OTHER
destination_other: "The user's default search engine."
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Users can control this feature by opting out of 'Preload pages "
"for faster browsing and searching'"
chrome_policy {
DefaultSearchProviderEnabled {
policy_options {mode: MANDATORY}
DefaultSearchProviderEnabled: false
}
NetworkPredictionOptions {
NetworkPredictionOptions: 2
}
}
})");
}
bool BaseSearchPrefetchRequest::StartPrefetchRequest(Profile* profile) {
net::NetworkTrafficAnnotationTag network_traffic_annotation =
NetworkAnnotationForPrefetch();
url::Origin prefetch_origin = url::Origin::Create(prefetch_url_);
auto resource_request = std::make_unique<network::ResourceRequest>();
// This prefetch is not as high priority as navigation, but due to its
// navigation speeding and relatively high likelihood of being served to a
// navigation, the request is relatively high priority.
resource_request->priority = net::MEDIUM;
resource_request->url = prefetch_url_;
// Search prefetch URL Loaders should check |report_raw_headers| on the
// intercepted request to clear out the raw headers when |report_raw_headers|
// is false.
resource_request->report_raw_headers = true;
resource_request->credentials_mode =
network::mojom::CredentialsMode::kInclude;
resource_request->method = "GET";
resource_request->mode = network::mojom::RequestMode::kNavigate;
resource_request->site_for_cookies =
net::SiteForCookies::FromUrl(prefetch_url_);
resource_request->destination = network::mojom::RequestDestination::kDocument;
resource_request->resource_type =
static_cast<int>(blink::mojom::ResourceType::kMainFrame);
resource_request->trusted_params = network::ResourceRequest::TrustedParams();
// We don't handle redirects, so |kOther| makes sense here.
resource_request->trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kOther, prefetch_origin, prefetch_origin,
resource_request->site_for_cookies);
resource_request->referrer_policy = net::ReferrerPolicy::NO_REFERRER;
// Tack an 'Upgrade-Insecure-Requests' header to outgoing navigational
// requests, as described in
// https://w3c.github.io/webappsec/specs/upgrade/#feature-detect
resource_request->headers.SetHeader("Upgrade-Insecure-Requests", "1");
resource_request->headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
embedder_support::GetUserAgent());
resource_request->headers.SetHeader(content::kCorsExemptPurposeHeaderName,
"prefetch");
resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAccept,
content::FrameAcceptHeaderValue(/*allow_sxg_responses=*/true, profile));
bool js_enabled = profile->GetPrefs() && profile->GetPrefs()->GetBoolean(
prefs::kWebKitJavascriptEnabled);
AddClientHintsHeadersToPrefetchNavigation(
resource_request->url, &(resource_request->headers), profile,
profile->GetClientHintsControllerDelegate(),
/*is_ua_override_on=*/false, js_enabled);
#if defined(OS_ANDROID)
base::Optional<std::string> geo_header =
GetGeolocationHeaderIfAllowed(resource_request->url, profile);
if (geo_header) {
resource_request->headers.AddHeaderFromString(geo_header.value());
}
#endif // defined(OS_ANDROID)
// Before sending out the request, allow throttles to modify the request (not
// the URL). The rest of the URL Loader throttle calls are captured in the
// navigation stack. Headers can be added by throttles at this point, which we
// want to capture.
auto wc_getter =
base::BindRepeating([]() -> content::WebContents* { return nullptr; });
std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
content::CreateContentBrowserURLLoaderThrottles(
*resource_request, profile, std::move(wc_getter),
/*navigation_ui_data=*/nullptr,
content::RenderFrameHost::kNoFrameTreeNodeId);
auto* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
DCHECK(template_url_service);
auto* default_search = template_url_service->GetDefaultSearchProvider();
DCHECK(default_search);
std::u16string prefetch_url_search_terms;
default_search->ExtractSearchTermsFromURL(
prefetch_url_, template_url_service->search_terms_data(),
&prefetch_url_search_terms);
bool should_defer = false;
for (auto& throttle : throttles) {
CheckForCancelledOrPausedDelegate cancel_or_pause_delegate;
throttle->set_delegate(&cancel_or_pause_delegate);
throttle->WillStartRequest(resource_request.get(), &should_defer);
// Make sure throttles are deleted before |cancel_or_pause_delegate| in case
// they call into the delegate in the destructor.
throttle.reset();
std::u16string new_url_search_terms;
// Check that search terms still match. Google URLs can be changed by
// AndroidDarkSearch (and in other cases like safe search). Make sure the
// URL still has the same search terms for the DSE.
default_search->ExtractSearchTermsFromURL(
resource_request->url, template_url_service->search_terms_data(),
&new_url_search_terms);
if (should_defer || new_url_search_terms != prefetch_url_search_terms ||
cancel_or_pause_delegate.cancelled_or_paused()) {
return false;
}
}
prefetch_url_ = resource_request->url;
current_status_ = SearchPrefetchStatus::kInFlight;
StartPrefetchRequestInternal(profile, std::move(resource_request),
network_traffic_annotation);
return true;
}
void BaseSearchPrefetchRequest::CancelPrefetch() {
DCHECK(current_status_ == SearchPrefetchStatus::kInFlight ||
current_status_ == SearchPrefetchStatus::kCanBeServed);
current_status_ = SearchPrefetchStatus::kRequestCancelled;
StopPrefetch();
}
void BaseSearchPrefetchRequest::ErrorEncountered() {
DCHECK(!report_error_callback_.is_null());
DCHECK(current_status_ == SearchPrefetchStatus::kInFlight ||
current_status_ == SearchPrefetchStatus::kCanBeServed ||
current_status_ == SearchPrefetchStatus::kCanBeServedAndUserClicked);
current_status_ = SearchPrefetchStatus::kRequestFailed;
std::move(report_error_callback_).Run();
StopPrefetch();
}
void BaseSearchPrefetchRequest::MarkPrefetchAsServable() {
DCHECK(current_status_ == SearchPrefetchStatus::kInFlight);
current_status_ = SearchPrefetchStatus::kCanBeServed;
}
void BaseSearchPrefetchRequest::MarkPrefetchAsComplete() {
DCHECK(current_status_ == SearchPrefetchStatus::kInFlight ||
current_status_ == SearchPrefetchStatus::kCanBeServed ||
current_status_ == SearchPrefetchStatus::kCanBeServedAndUserClicked);
current_status_ = SearchPrefetchStatus::kComplete;
}
void BaseSearchPrefetchRequest::MarkPrefetchAsClicked() {
DCHECK(current_status_ == SearchPrefetchStatus::kCanBeServed);
current_status_ = SearchPrefetchStatus::kCanBeServedAndUserClicked;
}
bool BaseSearchPrefetchRequest::CanServePrefetchRequest(
const scoped_refptr<net::HttpResponseHeaders> headers) {
if (!headers)
return false;
// Any 200 response can be served.
if (headers->response_code() >= net::HTTP_OK &&
headers->response_code() < net::HTTP_MULTIPLE_CHOICES) {
return true;
}
return false;
}