blob: df6baa2e0ce727b77f539ed0cd9735f954403d63 [file] [log] [blame]
// Copyright 2022 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/preloading/prefetch/prefetch_url_loader_interceptor.h"
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "content/browser/browser_context_impl.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/url_loader_factory_utils.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_match_resolver.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
#include "content/browser/preloading/prefetch/prefetch_url_loader_helper.h"
#include "content/browser/preloading/prerender/prerender_host.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/service_worker/service_worker_client.h"
#include "content/browser/service_worker/service_worker_main_resource_handle.h"
#include "content/public/browser/web_contents.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
namespace content {
namespace {
BrowserContext* BrowserContextFromFrameTreeNodeId(
FrameTreeNodeId frame_tree_node_id) {
WebContents* web_content =
WebContents::FromFrameTreeNodeId(frame_tree_node_id);
if (!web_content)
return nullptr;
return web_content->GetBrowserContext();
}
void RecordWasFullRedirectChainServedHistogram(
bool was_full_redirect_chain_served) {
UMA_HISTOGRAM_BOOLEAN("PrefetchProxy.AfterClick.WasFullRedirectChainServed",
was_full_redirect_chain_served);
}
PrefetchCompleteCallbackForTesting& GetPrefetchCompleteCallbackForTesting() {
static base::NoDestructor<PrefetchCompleteCallbackForTesting>
get_prefetch_complete_callback_for_testing;
return *get_prefetch_complete_callback_for_testing;
}
} // namespace
// static
void PrefetchURLLoaderInterceptor::SetPrefetchCompleteCallbackForTesting(
PrefetchCompleteCallbackForTesting callback) {
GetPrefetchCompleteCallbackForTesting() = std::move(callback); // IN-TEST
}
PrefetchURLLoaderInterceptor::PrefetchURLLoaderInterceptor(
PrefetchServiceWorkerState expected_service_worker_state,
base::WeakPtr<ServiceWorkerMainResourceHandle>
service_worker_handle_for_navigation,
FrameTreeNodeId frame_tree_node_id,
std::optional<blink::DocumentToken> initiator_document_token,
base::WeakPtr<PrefetchServingPageMetricsContainer>
serving_page_metrics_container)
: expected_service_worker_state_(expected_service_worker_state),
service_worker_handle_for_navigation_(
std::move(service_worker_handle_for_navigation)),
frame_tree_node_id_(frame_tree_node_id),
initiator_document_token_(std::move(initiator_document_token)),
serving_page_metrics_container_(
std::move(serving_page_metrics_container)) {
if (!base::FeatureList::IsEnabled(features::kPrefetchServiceWorker)) {
CHECK_EQ(expected_service_worker_state_,
PrefetchServiceWorkerState::kDisallowed);
}
}
PrefetchURLLoaderInterceptor::~PrefetchURLLoaderInterceptor() = default;
void PrefetchURLLoaderInterceptor::MaybeCreateLoader(
const network::ResourceRequest& tentative_resource_request,
BrowserContext* browser_context,
NavigationLoaderInterceptor::LoaderCallback callback,
NavigationLoaderInterceptor::FallbackCallback fallback_callback) {
TRACE_EVENT0("loading", "PrefetchURLLoaderInterceptor::MaybeCreateLoader");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!loader_callback_);
loader_callback_ = std::move(callback);
if (redirect_reader_ && redirect_reader_.DoesCurrentURLToServeMatch(
tentative_resource_request.url)) {
if (redirect_reader_.HaveDefaultContextCookiesChanged()) {
// Cookies have changed for the next redirect hop's URL since the fetch,
// so we cannot use this prefetch anymore.
PrefetchContainer* prefetch_container =
redirect_reader_.GetPrefetchContainer();
CHECK(prefetch_container);
// Use `std::nullopt` as we need to record the crash key to identify
// which case in `PrefetchMatchResolver` is the cause.
prefetch_container->OnDetectedCookiesChange(
/*is_unblock_for_cookies_changed_triggered_by_this_prefetch_container*/
std::nullopt);
} else {
OnGotPrefetchToServe(
frame_tree_node_id_, tentative_resource_request.url,
base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
weak_factory_.GetWeakPtr(),
tentative_resource_request.url),
std::move(redirect_reader_));
return;
}
}
if (redirect_reader_) {
RecordWasFullRedirectChainServedHistogram(false);
redirect_reader_ = PrefetchContainer::Reader();
}
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!frame_tree_node->IsOutermostMainFrame()) {
// The prefetch code does not currently deal with prefetching within a frame
// (i.e., where the partition which should be assigned to the request is not
// the same as the partition belonging to its site at the top level).
//
// This could be made smarter in the future (to do those prefetches within
// the right partition, or at minimum to use it from that partition if they
// happen to be the same, i.e., the URL remains within the same site as the
// top-level document).
std::move(loader_callback_).Run(std::nullopt);
return;
}
GetPrefetch(tentative_resource_request,
base::BindOnce(
&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
weak_factory_.GetWeakPtr(), tentative_resource_request.url));
}
void PrefetchURLLoaderInterceptor::GetPrefetch(
const network::ResourceRequest& tentative_resource_request,
base::OnceCallback<void(PrefetchContainer::Reader)> get_prefetch_callback)
const {
TRACE_EVENT0("loading", "PrefetchURLLoaderInterceptor::GetPrefetch");
PrefetchService* prefetch_service =
PrefetchService::GetFromFrameTreeNodeId(frame_tree_node_id_);
if (!prefetch_service) {
std::move(get_prefetch_callback).Run({});
return;
}
if (!initiator_document_token_.has_value()) {
if (!PrefetchBrowserInitiatedTriggersEnabled()) {
std::move(get_prefetch_callback).Run({});
return;
}
// TODO(crbug.com/40288091): Currently PrefetchServingPageMetricsContainer
// is created only when the navigation is renderer-initiated and its
// initiator document has PrefetchDocumentManager.
CHECK(!serving_page_metrics_container_);
}
const GURL tentative_resource_request_url = tentative_resource_request.url;
auto callback = base::BindOnce(&OnGotPrefetchToServe, frame_tree_node_id_,
tentative_resource_request_url,
std::move(get_prefetch_callback));
auto key = PrefetchContainer::Key(initiator_document_token_,
tentative_resource_request_url);
const bool is_nav_prerender = [&]() -> bool {
auto* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!frame_tree_node) {
return false;
}
return frame_tree_node->frame_tree().is_prerendering();
}();
PrefetchMatchResolver::FindPrefetch(
std::move(key), expected_service_worker_state_, is_nav_prerender,
*prefetch_service, serving_page_metrics_container_,
std::move(callback));
}
void PrefetchURLLoaderInterceptor::OnGetPrefetchComplete(
GURL navigation_url,
PrefetchContainer::Reader reader) {
TRACE_EVENT0("loading",
"PrefetchURLLoaderInterceptor::OnGetPrefetchComplete");
PrefetchRequestHandler request_handler;
base::WeakPtr<ServiceWorkerClient> client_for_prefetch;
if (reader) {
std::tie(request_handler, client_for_prefetch) =
reader.CreateRequestHandler();
}
if (expected_service_worker_state_ ==
PrefetchServiceWorkerState::kControlled &&
request_handler) {
// ServiceWorker-controlled prefetch should be always non-redirecting.
CHECK(reader.IsEnd());
if (service_worker_handle_for_navigation_ && client_for_prefetch) {
service_worker_handle_for_navigation_->service_worker_client()
->InheritControllerFromPrefetch(*client_for_prefetch, navigation_url);
} else {
// Do not intercept the request.
request_handler = PrefetchRequestHandler();
}
}
if (!request_handler) {
// Do not intercept the request.
redirect_reader_ = PrefetchContainer::Reader();
if (GetPrefetchCompleteCallbackForTesting()) {
GetPrefetchCompleteCallbackForTesting().Run(nullptr); // IN-TEST
}
std::move(loader_callback_).Run(std::nullopt);
return;
}
scoped_refptr<network::SingleRequestURLLoaderFactory>
single_request_url_loader_factory =
base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
std::move(request_handler));
PrefetchContainer* prefetch_container = reader.GetPrefetchContainer();
// If |prefetch_container| is done serving the prefetch, clear out
// |redirect_reader_|, but otherwise cache it in |redirect_reader_|.
if (reader.IsEnd()) {
if (redirect_reader_) {
RecordWasFullRedirectChainServedHistogram(true);
}
redirect_reader_ = PrefetchContainer::Reader();
} else {
CHECK_EQ(expected_service_worker_state_,
PrefetchServiceWorkerState::kDisallowed);
redirect_reader_ = std::move(reader);
}
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
RenderFrameHost* render_frame_host = frame_tree_node->current_frame_host();
NavigationRequest* navigation_request = frame_tree_node->navigation_request();
bool bypass_redirect_checks = false;
if (GetPrefetchCompleteCallbackForTesting()) {
GetPrefetchCompleteCallbackForTesting().Run(prefetch_container); // IN-TEST
}
// TODO (https://crbug.com/1369766): Investigate if
// `HeaderClientOption::kAllowed` should be used for `TerminalParams`, and
// then how to utilize it.
std::move(loader_callback_)
.Run(NavigationLoaderInterceptor::Result(
url_loader_factory::Create(
ContentBrowserClient::URLLoaderFactoryType::kNavigation,
url_loader_factory::TerminalParams::ForNonNetwork(
std::move(single_request_url_loader_factory),
network::mojom::kBrowserProcessId),
url_loader_factory::ContentClientParams(
BrowserContextFromFrameTreeNodeId(frame_tree_node_id_),
render_frame_host,
render_frame_host->GetProcess()->GetDeprecatedID(),
url::Origin(), net::IsolationInfo(),
ukm::SourceIdObj::FromInt64(
navigation_request->GetNextPageUkmSourceId()),
&bypass_redirect_checks,
navigation_request->GetNavigationId())),
/*subresource_loader_params=*/{}));
}
} // namespace content