Skip to content

Commit

Permalink
Prerender: Simplify PrerenderNavigationThrottle
Browse files Browse the repository at this point in the history
This is a pure refactoring. This doesn't change functional behavior.

- Makes PrerenderNavigationThrottle keep a raw_ptr to PrerenderHost.
  This should be safe as PrerenderHost is never changed through the
  lifetime of PrerenderNavigationThrottle and outlives the throttle.
- Factors out code to cancel prerendering into CancelPrerendering().
- Adds IsCrossSite() helper. Before this change,
  PrerenderNavigationThrottle negated IsSameSite() for checking
  cross-site navigation. This is correct, but IsCrossSite() should be
  more intuitive.

Bug: 1422248
Change-Id: Id20790c922fe35f8bb64e6fe67fd89921f8f6757
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4353255
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: Lingqi Chi <lingqi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1120398}
  • Loading branch information
nhiroki authored and Chromium LUCI CQ committed Mar 22, 2023
1 parent 58a2d91 commit 849e920
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 84 deletions.
137 changes: 54 additions & 83 deletions content/browser/preloading/prerender/prerender_navigation_throttle.cc
Expand Up @@ -95,11 +95,8 @@ PrerenderNavigationThrottle::MaybeCreateThrottleFor(
auto* navigation_request = NavigationRequest::From(navigation_handle);
FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
if (frame_tree_node->GetFrameType() == FrameType::kPrerenderMainFrame) {
PrerenderHost* prerender_host =
static_cast<PrerenderHost*>(frame_tree_node->frame_tree().delegate());
DCHECK(prerender_host);

return base::WrapUnique(new PrerenderNavigationThrottle(navigation_handle));
return base::WrapUnique(
new PrerenderNavigationThrottle(navigation_request));
}
return nullptr;
}
Expand All @@ -119,68 +116,47 @@ PrerenderNavigationThrottle::WillRedirectRequest() {
}

PrerenderNavigationThrottle::PrerenderNavigationThrottle(
NavigationHandle* navigation_handle)
: NavigationThrottle(navigation_handle) {
auto* navigation_request = NavigationRequest::From(navigation_handle);
PrerenderHost* prerender_host = static_cast<PrerenderHost*>(
navigation_request->frame_tree_node()->frame_tree().delegate());
DCHECK(prerender_host);
NavigationRequest* navigation_request)
: NavigationThrottle(navigation_request),
prerender_host_(static_cast<PrerenderHost*>(
navigation_request->frame_tree_node()->frame_tree().delegate())) {
CHECK(prerender_host_);

// This throttle is responsible for setting the initial navigation id on the
// PrerenderHost, since the PrerenderHost obtains the NavigationRequest,
// which has the ID, only after the navigation throttles run.
if (prerender_host->GetInitialNavigationId().has_value()) {
if (prerender_host_->GetInitialNavigationId().has_value()) {
// If the host already has an initial navigation id, this throttle
// will later cancel the navigation in Will*Request(). Just do nothing
// until then.
} else {
prerender_host->SetInitialNavigation(
static_cast<NavigationRequest*>(navigation_handle));
prerender_host_->SetInitialNavigation(navigation_request);
}
}

NavigationThrottle::ThrottleCheckResult
PrerenderNavigationThrottle::WillStartOrRedirectRequest(bool is_redirection) {
// Take the root frame tree node of the prerendering page.
auto* navigation_request = NavigationRequest::From(navigation_handle());
FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
DCHECK_EQ(frame_tree_node->GetFrameType(), FrameType::kPrerenderMainFrame);

PrerenderHostRegistry* prerender_host_registry =
frame_tree_node->current_frame_host()
->delegate()
->GetPrerenderHostRegistry();

// Get the prerender host of the prerendering page.
PrerenderHost* prerender_host =
static_cast<PrerenderHost*>(frame_tree_node->frame_tree().delegate());
DCHECK(prerender_host);

GURL navigation_url = navigation_handle()->GetURL();
url::Origin navigation_origin = url::Origin::Create(navigation_url);
url::Origin initial_prerendering_origin =
url::Origin::Create(prerender_host->GetInitialUrl());
url::Origin::Create(prerender_host_->GetInitialUrl());

// Check if the main frame navigation happens after the initial prerendering
// navigation in a prerendered page.
if (prerender_host->GetInitialNavigationId() !=
navigation_request->GetNavigationId()) {
if (!IsInitialNavigation()) {
if (!base::FeatureList::IsEnabled(
blink::features::kPrerender2MainFrameNavigation)) {
// Navigations after the initial prerendering navigation are disallowed
// when the kPrerender2MainFrameNavigation feature is disabled.
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
PrerenderFinalStatus::kMainFrameNavigation);
CancelPrerendering(PrerenderFinalStatus::kMainFrameNavigation);
return CANCEL;
}

// Cross-site navigations after the initial prerendering navigation are
// disallowed.
if (!prerender_navigation_utils::IsSameSite(navigation_url,
if (prerender_navigation_utils::IsCrossSite(navigation_url,
initial_prerendering_origin)) {
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
CancelPrerendering(
is_redirection
? PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation
: PrerenderFinalStatus::
Expand All @@ -189,64 +165,59 @@ PrerenderNavigationThrottle::WillStartOrRedirectRequest(bool is_redirection) {
}
}

if ((prerender_host->trigger_type() == PrerenderTriggerType::kEmbedder) &&
if (prerender_host_->IsBrowserInitiated() &&
ShouldSkipHostInBlockList(navigation_url)) {
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
PrerenderFinalStatus::kEmbedderHostDisallowed);
CancelPrerendering(PrerenderFinalStatus::kEmbedderHostDisallowed);
return CANCEL;
}

// Allow only HTTP(S) schemes.
// https://wicg.github.io/nav-speculation/prerendering.html#no-bad-navs
if (!navigation_url.SchemeIsHTTPOrHTTPS()) {
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
is_redirection ? PrerenderFinalStatus::kInvalidSchemeRedirect
: PrerenderFinalStatus::kInvalidSchemeNavigation);
CancelPrerendering(is_redirection
? PrerenderFinalStatus::kInvalidSchemeRedirect
: PrerenderFinalStatus::kInvalidSchemeNavigation);
return CANCEL;
}

if (!prerender_host->IsBrowserInitiated() &&
navigation_origin == prerender_host->initiator_origin()) {
is_same_site_cross_origin_prerender_ =
same_site_cross_origin_prerender_did_redirect_ = false;
if (!prerender_host_->IsBrowserInitiated() &&
navigation_origin == prerender_host_->initiator_origin()) {
is_same_site_cross_origin_prerender_ = false;
same_site_cross_origin_prerender_did_redirect_ = false;
}

if (prerender_host->IsBrowserInitiated()) {
if (prerender_host_->IsBrowserInitiated()) {
// Cancel an embedder triggered prerendering if it is redirected to a URL
// cross-site to the initial prerendering URL.
if (is_redirection && !prerender_navigation_utils::IsSameSite(
if (is_redirection && prerender_navigation_utils::IsCrossSite(
navigation_url, initial_prerendering_origin)) {
AnalyzeCrossOriginRedirection(
navigation_origin, initial_prerendering_origin,
prerender_host->trigger_type(),
prerender_host->embedder_histogram_suffix());
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
prerender_host_->trigger_type(),
prerender_host_->embedder_histogram_suffix());
CancelPrerendering(
PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation);
return CANCEL;
}

// Skip the same-site check for non-redirected cases as the initiator
// origin is nullopt for browser-initiated prerendering.
DCHECK(!prerender_host->initiator_origin().has_value());
} else if (!prerender_navigation_utils::IsSameSite(
navigation_url, prerender_host->initiator_origin().value())) {
DCHECK(!prerender_host_->initiator_origin().has_value());
} else if (prerender_navigation_utils::IsCrossSite(
navigation_url, prerender_host_->initiator_origin().value())) {
// TODO(crbug.com/1176054): Once cross-site prerendering is implemented,
// we'll need to enforce strict referrer policies
// (https://wicg.github.io/nav-speculation/prefetch.html#list-of-sufficiently-strict-speculative-navigation-referrer-policies).
//
// Cancel prerendering if this is cross-site prerendering, cross-site
// redirection during prerendering, or cross-site navigation from a
// prerendered page.
prerender_host_registry->CancelHost(
frame_tree_node->frame_tree_node_id(),
CancelPrerendering(
is_redirection
? PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation
: PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation);
return CANCEL;
} else if (navigation_origin != prerender_host->initiator_origin()) {
} else if (navigation_origin != prerender_host_->initiator_origin()) {
is_same_site_cross_origin_prerender_ = true;
same_site_cross_origin_prerender_did_redirect_ = is_redirection;
}
Expand All @@ -258,14 +229,6 @@ NavigationThrottle::ThrottleCheckResult
PrerenderNavigationThrottle::WillProcessResponse() {
auto* navigation_request = NavigationRequest::From(navigation_handle());

FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
DCHECK_EQ(frame_tree_node->GetFrameType(), FrameType::kPrerenderMainFrame);

PrerenderHostRegistry* prerender_host_registry =
frame_tree_node->current_frame_host()
->delegate()
->GetPrerenderHostRegistry();

// https://wicg.github.io/nav-speculation/prerendering.html#navigate-fetch-patch
// "1. If browsingContext is a prerendering browsing context and
// responseOrigin is not same origin with incumbentNavigationOrigin, then:"
Expand All @@ -280,18 +243,10 @@ PrerenderNavigationThrottle::WillProcessResponse() {
navigation_request->response()->parsed_headers->supports_loading_mode,
network::mojom::LoadingMode::kCredentialedPrerender);
if (!is_credentialed_prerender && is_same_site_cross_origin_prerender_) {
// Get the prerender host of the prerendering page.
auto* prerender_host =
static_cast<PrerenderHost*>(frame_tree_node->frame_tree().delegate());
CHECK(prerender_host);

// Check if the main frame navigation happens after the initial
// prerendering navigation in a prerendered page.
bool is_initial_navigation = prerender_host->GetInitialNavigationId() ==
navigation_request->GetNavigationId();

PrerenderFinalStatus final_status = PrerenderFinalStatus::kDestroyed;
if (is_initial_navigation) {
if (IsInitialNavigation()) {
final_status =
same_site_cross_origin_prerender_did_redirect_
? PrerenderFinalStatus::
Expand All @@ -306,8 +261,7 @@ PrerenderNavigationThrottle::WillProcessResponse() {
: PrerenderFinalStatus::
kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation;
}
prerender_host_registry->CancelHost(frame_tree_node->frame_tree_node_id(),
final_status);
CancelPrerendering(final_status);
return CANCEL;
}

Expand All @@ -324,11 +278,28 @@ PrerenderNavigationThrottle::WillProcessResponse() {
}

if (cancel_reason.has_value()) {
prerender_host_registry->CancelHost(frame_tree_node->frame_tree_node_id(),
cancel_reason.value());
CancelPrerendering(cancel_reason.value());
return CANCEL;
}
return PROCEED;
}

bool PrerenderNavigationThrottle::IsInitialNavigation() const {
return prerender_host_->GetInitialNavigationId() ==
navigation_handle()->GetNavigationId();
}

void PrerenderNavigationThrottle::CancelPrerendering(
PrerenderFinalStatus final_status) {
auto* navigation_request = NavigationRequest::From(navigation_handle());
FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
CHECK_EQ(frame_tree_node->GetFrameType(), FrameType::kPrerenderMainFrame);
PrerenderHostRegistry* prerender_host_registry =
frame_tree_node->current_frame_host()
->delegate()
->GetPrerenderHostRegistry();
prerender_host_registry->CancelHost(prerender_host_->frame_tree_node_id(),
final_status);
}

} // namespace content
Expand Up @@ -9,6 +9,10 @@

namespace content {

class NavigationRequest;
class PrerenderHost;
enum class PrerenderFinalStatus;

// PrerenderNavigationThrottle applies restrictions to prerendering navigation
// on the main frame. Specifically this cancels prerendering in the following
// cases.
Expand All @@ -30,10 +34,19 @@ class PrerenderNavigationThrottle : public NavigationThrottle {
ThrottleCheckResult WillProcessResponse() override;

private:
explicit PrerenderNavigationThrottle(NavigationHandle* navigation_handle);
explicit PrerenderNavigationThrottle(NavigationRequest* navigation_request);

ThrottleCheckResult WillStartOrRedirectRequest(bool is_redirection);

// Returns true if this throttle is for prerender initial navigation.
bool IsInitialNavigation() const;

// Cancels prerendering hosting this navigation with `final_status`.
void CancelPrerendering(PrerenderFinalStatus final_status);

// Raw ptr should be safe as `prerender_host_` indirectly owns `this`.
const raw_ptr<PrerenderHost> prerender_host_ = nullptr;

bool is_same_site_cross_origin_prerender_ = false;
bool same_site_cross_origin_prerender_did_redirect_ = false;
};
Expand Down
Expand Up @@ -25,4 +25,8 @@ bool IsSameSite(const GURL& target_url, const url::Origin& origin) {
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}

bool IsCrossSite(const GURL& target_url, const url::Origin& origin) {
return !IsSameSite(target_url, origin);
}

} // namespace content::prerender_navigation_utils
Expand Up @@ -18,6 +18,9 @@ bool IsDisallowedHttpResponseCode(int response_code);
// Returns true if target_url is in the same site as origin.
bool IsSameSite(const GURL& target_url, const url::Origin& origin);

// Returns true if target_url is not in the same site as origin.
bool IsCrossSite(const GURL& target_url, const url::Origin& origin);

} // namespace content::prerender_navigation_utils

#endif // CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_NAVIGATION_UTILS_H_

0 comments on commit 849e920

Please sign in to comment.