#include "content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h"
#include "base/debug/dump_without_crashing.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.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_url_loader_helper.h"
#include "content/browser/renderer_host/frame_tree.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_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;
}
}
void PrefetchURLLoaderInterceptor::SetPrefetchCompleteCallbackForTesting(
PrefetchCompleteCallbackForTesting callback) {
GetPrefetchCompleteCallbackForTesting() = std::move(callback);
}
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 (!features::IsPrefetchServiceWorkerEnabled(
BrowserContextFromFrameTreeNodeId(frame_tree_node_id_))) {
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_EVENT_BEGIN("loading",
"PrefetchURLLoaderInterceptor::MaybeCreateLoader",
perfetto::Flow::FromPointer(this));
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!loader_callback_);
loader_callback_ = std::move(callback);
if (tentative_resource_request.method !=
net::HttpRequestHeaders::kGetMethod) {
redirect_serving_handle_ = PrefetchServingHandle();
TRACE_EVENT_END("loading");
std::move(loader_callback_).Run(std::nullopt);
return;
}
if (tentative_resource_request.skip_service_worker &&
expected_service_worker_state_ ==
PrefetchServiceWorkerState::kControlled) {
redirect_serving_handle_ = PrefetchServingHandle();
TRACE_EVENT_END("loading");
std::move(loader_callback_).Run(std::nullopt);
return;
}
if (redirect_serving_handle_ &&
redirect_serving_handle_.DoesCurrentURLToServeMatch(
tentative_resource_request.url)) {
if (redirect_serving_handle_.HaveDefaultContextCookiesChanged()) {
PrefetchContainer* prefetch_container =
redirect_serving_handle_.GetPrefetchContainer();
CHECK(prefetch_container);
prefetch_container->OnDetectedCookiesChange(
std::nullopt);
} else {
TRACE_EVENT_END("loading");
OnGotPrefetchToServe(
frame_tree_node_id_, tentative_resource_request.url,
base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
weak_factory_.GetWeakPtr(),
tentative_resource_request.url,
ServiceWorkerMainResourceHandle::
TopFrameOriginForInitializeForRequest(
tentative_resource_request)),
std::move(redirect_serving_handle_));
return;
}
}
if (redirect_serving_handle_) {
RecordWasFullRedirectChainServedHistogram(false);
redirect_serving_handle_ = PrefetchServingHandle();
}
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!frame_tree_node->IsOutermostMainFrame()) {
TRACE_EVENT_END("loading");
std::move(loader_callback_).Run(std::nullopt);
return;
}
TRACE_EVENT_END("loading");
GetPrefetch(
tentative_resource_request.url,
base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
weak_factory_.GetWeakPtr(), tentative_resource_request.url,
ServiceWorkerMainResourceHandle::
TopFrameOriginForInitializeForRequest(
tentative_resource_request)));
}
void PrefetchURLLoaderInterceptor::GetPrefetch(
const GURL& url,
base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback)
const {
TRACE_EVENT_BEGIN("loading", "PrefetchURLLoaderInterceptor::GetPrefetch",
perfetto::Flow::FromPointer(
const_cast<PrefetchURLLoaderInterceptor*>(this)));
PrefetchService* prefetch_service =
PrefetchService::GetFromFrameTreeNodeId(frame_tree_node_id_);
if (!prefetch_service) {
TRACE_EVENT_END("loading");
std::move(get_prefetch_callback).Run({});
return;
}
if (!initiator_document_token_.has_value()) {
CHECK(!serving_page_metrics_container_);
}
auto callback = base::BindOnce(&OnGotPrefetchToServe, frame_tree_node_id_,
url, std::move(get_prefetch_callback));
auto key = PrefetchKey(initiator_document_token_, url);
TRACE_EVENT_END("loading");
PrefetchMatchResolver::FindPrefetch(
frame_tree_node_id_, *prefetch_service, std::move(key),
expected_service_worker_state_, serving_page_metrics_container_,
std::move(callback),
perfetto::Flow::FromPointer(
const_cast<PrefetchURLLoaderInterceptor*>(this)));
}
void PrefetchURLLoaderInterceptor::OnGetPrefetchComplete(
const GURL& url,
const std::optional<url::Origin>& top_frame_origin,
PrefetchServingHandle serving_handle) {
TRACE_EVENT_BEGIN("loading",
"PrefetchURLLoaderInterceptor::OnGetPrefetchComplete",
perfetto::Flow::FromPointer(this));
PrefetchRequestHandler request_handler;
base::WeakPtr<ServiceWorkerClient> client_for_prefetch;
if (serving_handle) {
std::tie(request_handler, client_for_prefetch) =
serving_handle.CreateRequestHandler();
}
if (expected_service_worker_state_ ==
PrefetchServiceWorkerState::kControlled &&
request_handler) {
CHECK(serving_handle.IsEnd());
if (!service_worker_handle_for_navigation_ || !client_for_prefetch) {
request_handler = PrefetchRequestHandler();
} else if (!service_worker_handle_for_navigation_->InitializeForRequest(
url, top_frame_origin, client_for_prefetch.get())) {
DCHECK(false);
base::debug::DumpWithoutCrashing();
request_handler = PrefetchRequestHandler();
}
}
if (!request_handler) {
redirect_serving_handle_ = PrefetchServingHandle();
if (GetPrefetchCompleteCallbackForTesting()) {
GetPrefetchCompleteCallbackForTesting().Run(nullptr);
}
TRACE_EVENT_END("loading");
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 = serving_handle.GetPrefetchContainer();
if (serving_handle.IsEnd()) {
if (redirect_serving_handle_) {
RecordWasFullRedirectChainServedHistogram(true);
}
redirect_serving_handle_ = PrefetchServingHandle();
} else {
CHECK_EQ(expected_service_worker_state_,
PrefetchServiceWorkerState::kDisallowed);
redirect_serving_handle_ = std::move(serving_handle);
}
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);
}
TRACE_EVENT_END("loading");
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())),
{}));
}
}