blob: 78c07ca3de55c391628f3121392dcf012d0ea0d7 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/prefetch_matching_url_loader_factory.h"
#include <memory>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "services/network/cors/cors_url_loader_factory.h"
#include "services/network/prefetch_cache.h"
#include "services/network/prefetch_url_loader_client.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/resource_scheduler/resource_scheduler_client.h"
namespace network {
PrefetchMatchingURLLoaderFactory::PrefetchMatchingURLLoaderFactory(
NetworkContext* context,
mojom::URLLoaderFactoryParamsPtr params,
scoped_refptr<ResourceSchedulerClient> resource_scheduler_client,
mojo::PendingReceiver<mojom::URLLoaderFactory> receiver,
const cors::OriginAccessList* origin_access_list,
PrefetchCache* cache)
: ignore_factory_reset_(params->ignore_factory_reset),
next_(std::make_unique<cors::CorsURLLoaderFactory>(
context,
std::move(params),
std::move(resource_scheduler_client),
origin_access_list,
this)),
context_(context),
cache_(cache),
use_matches_(base::FeatureList::IsEnabled(
features::kNetworkContextPrefetchUseMatches)) {
receivers_.Add(this, std::move(receiver));
// This use of base::Unretained() is safe because `receivers_` won't call the
// disconnect handler after it has been destroyed.
receivers_.set_disconnect_handler(base::BindRepeating(
&PrefetchMatchingURLLoaderFactory::OnDisconnect, base::Unretained(this)));
}
PrefetchMatchingURLLoaderFactory::~PrefetchMatchingURLLoaderFactory() = default;
void PrefetchMatchingURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const ResourceRequest& request,
mojo::PendingRemote<mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// TODO: crbug.com/332706093 - This path was getting hit by the
// iphone-simulator bot in unittests. See if this can be turned into a
// NOTREACHED().
ResourceRequest copy(request);
CreateLoaderAndStart(std::move(loader), request_id, options, copy,
std::move(client), traffic_annotation);
}
void PrefetchMatchingURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
ResourceRequest& request,
mojo::PendingRemote<mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// If we don't think the request should be permitted from a render process, we
// don't try to match it and instead let CorsURLLoaderFactory deal with the
// issue.
if (cache_ && IsRequestSafeForMatching(request)) {
PrefetchURLLoaderClient* prefetch_client = cache_->Lookup(
next_->isolation_info().network_isolation_key(), request.url);
if (prefetch_client) {
// A prefetch exists with the same NIK and URL.
if (prefetch_client->Matches(request)) {
if (use_matches_) {
// The match has succeeded, and we are going to hand-off the
// in-progress prefetch to the renderer.
// TODO(crbug.com/342445996): Check that `options` is compatible with
// the prefetch and whether anything needs to be done to the ongoing
// request to match it.
prefetch_client->Consume(std::move(loader), std::move(client));
return;
}
// The match has succeeded, but the kNetworkContextPrefetchUseMatches
// feature is not enabled. Try to make it possible for the render
// process to reuse the cache entry.
// Give the real URLLoader time to reach the cache, then cancel the
// prefetch.
cache_->DelayedErase(prefetch_client);
} else {
// There was an entry with the same NIK and URL, but it failed to match
// on some other field. It is unlikely the renderer will subsequently
// issue a matching request for the same URL, so erase the cache entry
// to save resources.
cache_->Erase(prefetch_client);
}
}
}
next_->CreateLoaderAndStart(std::move(loader), request_id, options, request,
std::move(client), traffic_annotation);
}
void PrefetchMatchingURLLoaderFactory::Clone(
mojo::PendingReceiver<URLLoaderFactory> receiver) {
receivers_.Add(this, std::move(receiver));
}
void PrefetchMatchingURLLoaderFactory::ClearBindings() {
receivers_.Clear();
next_->DeleteIfNeeded();
}
net::handles::NetworkHandle
PrefetchMatchingURLLoaderFactory::GetBoundNetworkForTesting() const {
return next_->GetBoundNetworkForTesting(); // IN-TEST
}
void PrefetchMatchingURLLoaderFactory::
CancelRequestsIfNonceMatchesAndUrlNotExempted(
const base::UnguessableToken& nonce,
const std::set<GURL>& exemptions) {
next_->CancelRequestsIfNonceMatchesAndUrlNotExempted(nonce, exemptions);
}
void PrefetchMatchingURLLoaderFactory::DestroyURLLoaderFactory(
cors::CorsURLLoaderFactory* factory) {
CHECK_EQ(factory, next_.get());
context_->DestroyURLLoaderFactory(this);
}
bool PrefetchMatchingURLLoaderFactory::HasAdditionalReferences() const {
return !receivers_.empty();
}
bool PrefetchMatchingURLLoaderFactory::ShouldIgnoreFactoryReset() const {
return ignore_factory_reset_;
}
cors::CorsURLLoaderFactory*
PrefetchMatchingURLLoaderFactory::GetCorsURLLoaderFactoryForTesting() {
return next_.get();
}
void PrefetchMatchingURLLoaderFactory::OnDisconnect() {
if (receivers_.empty()) {
next_->DeleteIfNeeded();
// `this` may be deleted here.
}
}
bool PrefetchMatchingURLLoaderFactory::IsRequestSafeForMatching(
const ResourceRequest& request) {
// We never match requests from the browser process.
if (next_->process_id() == mojom::kBrowserProcessId) {
return false;
}
// `trusted_params` should never be set on a request from a render process.
if (request.trusted_params) {
return false;
}
// Ensure that the value of `request_initiator` is permitted for this render
// process.
InitiatorLockCompatibility compatibility =
VerifyRequestInitiatorLock(next_->request_initiator_origin_lock(),
request.mode == mojom::RequestMode::kNavigate
? url::Origin::Create(request.url)
: request.request_initiator);
// TODO(crbug.com/342445996): Share code with CorsURLLoaderFactory.
switch (compatibility) {
case InitiatorLockCompatibility::kCompatibleLock:
return true;
case InitiatorLockCompatibility::kBrowserProcess:
NOTREACHED();
case InitiatorLockCompatibility::kNoLock:
// `request_initiator_origin_lock` should always be set in a
// URLLoaderFactory vended to a renderer process. See also
// https://crbug.com/1114906.
NOTREACHED();
case InitiatorLockCompatibility::kNoInitiator:
// Requests from the renderer need to always specify an initiator.
mojo::ReportBadMessage(
"CorsURLLoaderFactory: no initiator in a renderer request");
return false;
case InitiatorLockCompatibility::kIncorrectLock:
// Requests from the renderer need to always specify a correct initiator.
mojo::ReportBadMessage(
"CorsURLLoaderFactory: lock VS initiator mismatch");
return false;
}
NOTREACHED();
}
} // namespace network