blob: 12b32fcd98befe37899817d8871bb53960a2fe07 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/prefetch_url_loader.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache.h"
#include "content/browser/web_package/prefetched_signed_exchange_cache_adapter.h"
#include "content/browser/web_package/signed_exchange_prefetch_handler.h"
#include "content/browser/web_package/signed_exchange_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/frame_accept_header.h"
#include "content/public/common/content_features.h"
#include "net/base/load_flags.h"
#include "net/base/network_anonymization_key.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "third_party/blink/public/common/features.h"
namespace content {
namespace {
constexpr char kSignedExchangeEnabledAcceptHeaderForCrossOriginPrefetch[] =
"application/signed-exchange;v=b3;q=0.7,*/*;q=0.8";
} // namespace
PrefetchURLLoader::PrefetchURLLoader(
int32_t request_id,
uint32_t options,
FrameTreeNodeId frame_tree_node_id,
const network::ResourceRequest& resource_request,
const net::NetworkAnonymizationKey& network_anonymization_key,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory,
URLLoaderThrottlesGetter url_loader_throttles_getter,
BrowserContext* browser_context,
scoped_refptr<PrefetchedSignedExchangeCache>
prefetched_signed_exchange_cache,
const std::string& accept_langs,
RecursivePrefetchTokenGenerator recursive_prefetch_token_generator)
: frame_tree_node_id_(frame_tree_node_id),
resource_request_(resource_request),
network_anonymization_key_(network_anonymization_key),
network_loader_factory_(std::move(network_loader_factory)),
forwarding_client_(std::move(client)),
url_loader_throttles_getter_(url_loader_throttles_getter),
accept_langs_(accept_langs),
recursive_prefetch_token_generator_(
std::move(recursive_prefetch_token_generator)),
is_signed_exchange_handling_enabled_(
signed_exchange_utils::IsSignedExchangeHandlingEnabled(
browser_context)) {
DCHECK(network_loader_factory_);
CHECK(!resource_request.trusted_params ||
resource_request.trusted_params->isolation_info.request_type() ==
net::IsolationInfo::RequestType::kOther ||
resource_request.trusted_params->isolation_info.request_type() ==
net::IsolationInfo::RequestType::kMainFrame);
if (is_signed_exchange_handling_enabled_) {
// Set the SignedExchange accept header.
// (https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#internet-media-type-applicationsigned-exchange).
// TODO(crbug.com/40250488): find a solution for CORS requests,
// perhaps exempt the Accept header from the 128-byte rule
// (https://fetch.spec.whatwg.org/#cors-safelisted-request-header). For now,
// we use the frame Accept header for prefetches only in requests with a
// no-cors/same-origin mode to avoid an unintended preflight.
std::string accept_header =
resource_request_.mode == network::mojom::RequestMode::kCors
? kSignedExchangeEnabledAcceptHeaderForCrossOriginPrefetch
: FrameAcceptHeaderValue(/*allow_sxg_responses=*/true,
browser_context);
resource_request_.headers.SetHeader(net::HttpRequestHeaders::kAccept,
std::move(accept_header));
if (prefetched_signed_exchange_cache) {
prefetched_signed_exchange_cache_adapter_ =
std::make_unique<PrefetchedSignedExchangeCacheAdapter>(
std::move(prefetched_signed_exchange_cache),
browser_context->GetBlobStorageContext(), resource_request.url,
this);
}
}
network_loader_factory_->CreateLoaderAndStart(
loader_.BindNewPipeAndPassReceiver(), request_id, options,
resource_request_, client_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation);
client_receiver_.set_disconnect_handler(base::BindOnce(
&PrefetchURLLoader::OnNetworkConnectionError, base::Unretained(this)));
}
PrefetchURLLoader::~PrefetchURLLoader() = default;
void PrefetchURLLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) {
DCHECK(modified_headers.IsEmpty())
<< "Redirect with modified headers was not supported yet. "
"crbug.com/845683";
DCHECK(!new_url) << "Redirect with modified URL was not "
"supported yet. crbug.com/845683";
if (signed_exchange_prefetch_handler_) {
// Rebind |client_receiver_| and |loader_|.
client_receiver_.Bind(signed_exchange_prefetch_handler_->FollowRedirect(
loader_.BindNewPipeAndPassReceiver()));
return;
}
DCHECK(loader_);
loader_->FollowRedirect(
removed_headers, net::HttpRequestHeaders() /* modified_headers */,
net::HttpRequestHeaders() /* modified_cors_exempt_headers */,
std::nullopt);
}
void PrefetchURLLoader::SetPriority(net::RequestPriority priority,
int intra_priority_value) {
if (loader_) {
loader_->SetPriority(priority, intra_priority_value);
}
}
void PrefetchURLLoader::OnReceiveEarlyHints(
network::mojom::EarlyHintsPtr early_hints) {
forwarding_client_->OnReceiveEarlyHints(std::move(early_hints));
}
void PrefetchURLLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr response,
mojo::ScopedDataPipeConsumerHandle body,
std::optional<mojo_base::BigBuffer> cached_metadata) {
if (is_signed_exchange_handling_enabled_ &&
signed_exchange_utils::ShouldHandleAsSignedHTTPExchange(
resource_request_.url, *response)) {
DCHECK(!signed_exchange_prefetch_handler_);
const bool keep_entry_for_prefetch_cache =
!!prefetched_signed_exchange_cache_adapter_;
// Note that after this point this doesn't directly get upcalls from the
// network. (Until |this| calls the handler's FollowRedirect.)
signed_exchange_prefetch_handler_ =
std::make_unique<SignedExchangePrefetchHandler>(
frame_tree_node_id_, resource_request_, std::move(response),
std::move(body), loader_.Unbind(), client_receiver_.Unbind(),
network_loader_factory_, url_loader_throttles_getter_, this,
network_anonymization_key_, accept_langs_,
keep_entry_for_prefetch_cache);
return;
}
// If the response is marked as a restricted cross-origin prefetch, we
// populate the response's |recursive_prefetch_token| member with a unique
// token. The renderer will propagate this token to recursive prefetches
// coming from this response, in the form of preload headers. This token is
// later used by the PrefetchURLLoaderService to recover the correct
// NetworkAnonymizationKey to use when fetching the request. In the Signed
// Exchange case, we do this after redirects from the outer response, because
// we redirect back here for the inner response.
if (resource_request_.load_flags &
net::LOAD_RESTRICTED_PREFETCH_FOR_MAIN_FRAME) {
DCHECK(!recursive_prefetch_token_generator_.is_null());
base::UnguessableToken recursive_prefetch_token =
std::move(recursive_prefetch_token_generator_).Run(resource_request_);
response->recursive_prefetch_token = recursive_prefetch_token;
}
// Just drop any cached metadata; we don't need to forward it to the renderer
// for prefetch.
cached_metadata.reset();
if (!body) {
forwarding_client_->OnReceiveResponse(std::move(response),
mojo::ScopedDataPipeConsumerHandle(),
std::nullopt);
return;
}
response_ = std::move(response);
if (prefetched_signed_exchange_cache_adapter_ &&
signed_exchange_prefetch_handler_) {
prefetched_signed_exchange_cache_adapter_->OnStartLoadingResponseBody(
std::move(body));
return;
}
// Just drain the original response's body here.
DCHECK(!pipe_drainer_);
pipe_drainer_ =
std::make_unique<mojo::DataPipeDrainer>(this, std::move(body));
SendEmptyBody();
}
void PrefetchURLLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
if (prefetched_signed_exchange_cache_adapter_ &&
signed_exchange_prefetch_handler_) {
prefetched_signed_exchange_cache_adapter_->OnReceiveSignedExchange(
signed_exchange_prefetch_handler_
->TakePrefetchedSignedExchangeCacheEntry());
}
resource_request_.url = redirect_info.new_url;
resource_request_.site_for_cookies = redirect_info.new_site_for_cookies;
resource_request_.referrer = GURL(redirect_info.new_referrer);
resource_request_.referrer_policy = redirect_info.new_referrer_policy;
forwarding_client_->OnReceiveRedirect(redirect_info, std::move(head));
}
void PrefetchURLLoader::OnUploadProgress(int64_t current_position,
int64_t total_size,
base::OnceCallback<void()> callback) {
forwarding_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void PrefetchURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
network::RecordOnTransferSizeUpdatedUMA(
network::OnTransferSizeUpdatedFrom::kPrefetchURLLoader);
forwarding_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void PrefetchURLLoader::OnComplete(
const network::URLLoaderCompletionStatus& status) {
if (prefetched_signed_exchange_cache_adapter_ &&
signed_exchange_prefetch_handler_) {
prefetched_signed_exchange_cache_adapter_->OnComplete(status);
return;
}
SendOnComplete(status);
}
bool PrefetchURLLoader::SendEmptyBody() {
// Send an empty response's body.
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
if (CreateDataPipe(nullptr, producer, consumer) != MOJO_RESULT_OK) {
// No more resources available for creating a data pipe. Close the
// connection, which will in turn make this loader destroyed.
forwarding_client_->OnComplete(
network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
forwarding_client_.reset();
client_receiver_.reset();
return false;
}
DCHECK(response_);
forwarding_client_->OnReceiveResponse(std::move(response_),
std::move(consumer), std::nullopt);
return true;
}
void PrefetchURLLoader::SendOnComplete(
const network::URLLoaderCompletionStatus& completion_status) {
forwarding_client_->OnComplete(completion_status);
}
void PrefetchURLLoader::OnNetworkConnectionError() {
// The network loader has an error; we should let the client know it's closed
// by dropping this, which will in turn make this loader destroyed.
forwarding_client_.reset();
}
} // namespace content