#include "content/browser/preloading/prefetch/prefetch_url_loader_helper.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "content/browser/preloading/prefetch/prefetch_origin_prober.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_probe_result.h"
#include "content/browser/preloading/prefetch/prefetch_servable_state.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_serving_handle.h"
#include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_partition_key_collection.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
namespace content {
namespace {
PrefetchServingPageMetricsContainer*
PrefetchServingPageMetricsContainerFromFrameTreeNodeId(
FrameTreeNodeId frame_tree_node_id) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!frame_tree_node || !frame_tree_node->navigation_request()) {
return nullptr;
}
return PrefetchServingPageMetricsContainer::GetForNavigationHandle(
*frame_tree_node->navigation_request());
}
void RecordCookieWaitTime(base::TimeDelta wait_time) {
UMA_HISTOGRAM_CUSTOM_TIMES(
"PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", wait_time,
base::TimeDelta(), base::Seconds(5), 50);
}
struct OnGotPrefetchToServeState {
const FrameTreeNodeId frame_tree_node_id;
const GURL tentative_url;
base::OnceCallback<void(PrefetchServingHandle)> callback;
PrefetchServingHandle serving_handle;
std::optional<bool> cookies_matched;
std::optional<PrefetchProbeResult> probe_result;
bool cookie_copy_complete_if_required = false;
};
void ContinueOnGotPrefetchToServe(
std::unique_ptr<OnGotPrefetchToServeState> state);
void StartCookieValidation(std::unique_ptr<OnGotPrefetchToServeState> state);
void OnGotCookiesForValidation(
std::unique_ptr<OnGotPrefetchToServeState> state,
const std::vector<net::CookieWithAccessResult>& cookies,
const std::vector<net::CookieWithAccessResult>& excluded_cookies);
void OnProbeComplete(std::unique_ptr<OnGotPrefetchToServeState> state,
base::TimeTicks probe_start_time,
PrefetchProbeResult probe_result);
void OnCookieCopyComplete(std::unique_ptr<OnGotPrefetchToServeState> state,
base::TimeTicks cookie_copy_start_time);
void ContinueOnGotPrefetchToServe(
std::unique_ptr<OnGotPrefetchToServeState> state) {
if (base::FeatureList::IsEnabled(features::kPrefetchCookieIndices)) {
if (!state->cookies_matched.has_value()) {
WebContents* web_contents =
WebContents::FromFrameTreeNodeId(state->frame_tree_node_id);
if (!web_contents || !state->serving_handle) {
state->cookies_matched = false;
} else if (!state->serving_handle.VariesOnCookieIndices()) {
state->cookies_matched = true;
} else {
StartCookieValidation(std::move(state));
return;
}
}
CHECK(state->cookies_matched.has_value());
if (!state->cookies_matched.value()) {
std::move(state->callback).Run({});
return;
}
}
if (!state->probe_result.has_value()) {
PrefetchService* prefetch_service =
PrefetchService::GetFromFrameTreeNodeId(state->frame_tree_node_id);
if (!prefetch_service || !prefetch_service->GetPrefetchOriginProber()) {
std::move(state->callback).Run({});
return;
}
PrefetchOriginProber* prober = prefetch_service->GetPrefetchOriginProber();
if (state->serving_handle.IsIsolatedNetworkContextRequiredToServe() &&
prober->ShouldProbeOrigins()) {
GURL probe_url = url::SchemeHostPort(state->tentative_url).GetURL();
prober->Probe(
probe_url,
base::BindOnce(&OnProbeComplete, std::move(state),
base::TimeTicks::Now()));
return;
}
state->probe_result = PrefetchProbeResult::kNoProbing;
}
if (!PrefetchProbeResultIsSuccess(state->probe_result.value())) {
std::move(state->callback).Run({});
return;
}
if (!state->cookie_copy_complete_if_required) {
if (state->serving_handle) {
if (!state->serving_handle.HasIsolatedCookieCopyStarted()) {
if (PrefetchService* prefetch_service =
PrefetchService::GetFromFrameTreeNodeId(
state->frame_tree_node_id)) {
prefetch_service->CopyIsolatedCookies(state->serving_handle);
}
}
state->serving_handle.OnInterceptorCheckCookieCopy();
if (state->serving_handle.IsIsolatedCookieCopyInProgress()) {
state->serving_handle.SetOnCookieCopyCompleteCallback(
base::BindOnce(&OnCookieCopyComplete, std::move(state),
base::TimeTicks::Now()));
return;
}
}
RecordCookieWaitTime(base::TimeDelta());
state->cookie_copy_complete_if_required = true;
}
CHECK(!base::FeatureList::IsEnabled(features::kPrefetchCookieIndices) ||
state->cookies_matched.value_or(false));
CHECK(PrefetchProbeResultIsSuccess(state->probe_result.value()));
CHECK(state->cookie_copy_complete_if_required);
if (!state->serving_handle) {
std::move(state->callback).Run({});
return;
}
switch (state->serving_handle.GetServableState(PrefetchCacheableDuration())) {
case PrefetchServableState::kNotServable:
case PrefetchServableState::kShouldBlockUntilEligibilityGot:
case PrefetchServableState::kShouldBlockUntilHeadReceived:
std::move(state->callback).Run({});
return;
case PrefetchServableState::kServable:
break;
}
state->serving_handle.OnPrefetchProbeResult(state->probe_result.value());
PrefetchServingPageMetricsContainer* serving_page_metrics_container =
PrefetchServingPageMetricsContainerFromFrameTreeNodeId(
state->frame_tree_node_id);
if (serving_page_metrics_container) {
serving_page_metrics_container->SetPrefetchStatus(
state->serving_handle.GetPrefetchStatus());
}
std::move(state->callback).Run(std::move(state->serving_handle));
}
void StartCookieValidation(std::unique_ptr<OnGotPrefetchToServeState> state) {
WebContents* web_contents =
WebContents::FromFrameTreeNodeId(state->frame_tree_node_id);
network::mojom::CookieManager* cookie_manager =
web_contents->GetBrowserContext()
->GetDefaultStoragePartition()
->GetCookieManagerForBrowserProcess();
CHECK(FrameTreeNode::GloballyFindByID(state->frame_tree_node_id)
->IsMainFrame());
const GURL& url = state->serving_handle.GetCurrentURLToServe();
net::SchemefulSite site(url);
cookie_manager->GetCookieList(
url, net::CookieOptions::MakeAllInclusive(),
net::CookiePartitionKeyCollection(
net::CookiePartitionKey::FromNetworkIsolationKey(
net::NetworkIsolationKey(site, site), net::SiteForCookies(site),
site, true)),
base::BindOnce(&OnGotCookiesForValidation, std::move(state)));
}
void OnGotCookiesForValidation(
std::unique_ptr<OnGotPrefetchToServeState> state,
const std::vector<net::CookieWithAccessResult>& cookies,
const std::vector<net::CookieWithAccessResult>& excluded_cookies) {
std::vector<std::pair<std::string, std::string>> cookie_values;
cookie_values.reserve(cookies.size());
for (const net::CookieWithAccessResult& cookie : cookies) {
cookie_values.emplace_back(cookie.cookie.Name(), cookie.cookie.Value());
}
state->cookies_matched =
state->serving_handle &&
state->serving_handle.MatchesCookieIndices(cookie_values);
ContinueOnGotPrefetchToServe(std::move(state));
}
void OnProbeComplete(std::unique_ptr<OnGotPrefetchToServeState> state,
base::TimeTicks probe_start_time,
PrefetchProbeResult probe_result) {
state->probe_result = probe_result;
PrefetchServingPageMetricsContainer* serving_page_metrics_container =
PrefetchServingPageMetricsContainerFromFrameTreeNodeId(
state->frame_tree_node_id);
if (serving_page_metrics_container) {
serving_page_metrics_container->SetProbeLatency(base::TimeTicks::Now() -
probe_start_time);
}
if (!PrefetchProbeResultIsSuccess(probe_result) && state->serving_handle) {
state->serving_handle.OnPrefetchProbeResult(probe_result);
if (serving_page_metrics_container) {
serving_page_metrics_container->SetPrefetchStatus(
state->serving_handle.GetPrefetchStatus());
}
}
ContinueOnGotPrefetchToServe(std::move(state));
}
void OnCookieCopyComplete(std::unique_ptr<OnGotPrefetchToServeState> state,
base::TimeTicks cookie_copy_start_time) {
base::TimeDelta wait_time = base::TimeTicks::Now() - cookie_copy_start_time;
CHECK_GE(wait_time, base::TimeDelta());
RecordCookieWaitTime(wait_time);
state->cookie_copy_complete_if_required = true;
ContinueOnGotPrefetchToServe(std::move(state));
}
}
void OnGotPrefetchToServe(
FrameTreeNodeId frame_tree_node_id,
const GURL& tentative_resource_request_url,
base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback,
PrefetchServingHandle serving_handle) {
#if DCHECK_IS_ON()
if (serving_handle) {
GURL::Replacements replacements;
replacements.ClearRef();
replacements.ClearQuery();
DCHECK_EQ(
tentative_resource_request_url.ReplaceComponents(replacements),
serving_handle.GetCurrentURLToServe().ReplaceComponents(replacements));
}
#endif
if (!serving_handle) {
std::move(get_prefetch_callback).Run({});
return;
}
switch (serving_handle.GetServableState(PrefetchCacheableDuration())) {
case PrefetchServableState::kNotServable:
case PrefetchServableState::kShouldBlockUntilEligibilityGot:
case PrefetchServableState::kShouldBlockUntilHeadReceived:
std::move(get_prefetch_callback).Run({});
return;
case PrefetchServableState::kServable:
break;
}
CHECK(!serving_handle.HaveDefaultContextCookiesChanged());
ContinueOnGotPrefetchToServe(base::WrapUnique(new OnGotPrefetchToServeState{
.frame_tree_node_id = frame_tree_node_id,
.tentative_url = tentative_resource_request_url,
.callback = std::move(get_prefetch_callback),
.serving_handle = std::move(serving_handle)}));
}
}