blob: 8b7f2a3fe53a0e96e1198c0cd101dcfb54099d49 [file] [log] [blame]
// Copyright 2025 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/renderer_host/navigation_throttle_registry_impl.h"
#include <algorithm>
#include "base/check_deref.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/preloading/prefetch/contamination_delay_navigation_throttle.h"
#include "content/browser/preloading/prerender/prerender_navigation_throttle.h"
#include "content/browser/preloading/prerender/prerender_subframe_navigation_throttle.h"
#include "content/browser/renderer_host/ancestor_throttle.h"
#include "content/browser/renderer_host/back_forward_cache_subframe_navigation_throttle.h"
#include "content/browser/renderer_host/blocked_scheme_navigation_throttle.h"
#include "content/browser/renderer_host/http_error_navigation_throttle.h"
#include "content/browser/renderer_host/isolated_web_app_throttle.h"
#include "content/browser/renderer_host/mixed_content_navigation_throttle.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigation_throttle_runner.h"
#include "content/browser/renderer_host/navigation_throttle_runner2.h"
#include "content/browser/renderer_host/navigator_delegate.h"
#include "content/browser/renderer_host/partitioned_popins/partitioned_popins_navigation_throttle.h"
#include "content/browser/renderer_host/renderer_cancellation_throttle.h"
#include "content/browser/renderer_host/subframe_history_navigation_throttle.h"
#include "content/common/features.h"
#include "content/public/browser/navigation_handle.h"
#if !BUILDFLAG(IS_ANDROID)
#include "content/browser/picture_in_picture/document_picture_in_picture_navigation_throttle.h"
#endif // !BUILDFLAG(IS_ANDROID)
namespace content {
namespace {
std::unique_ptr<NavigationThrottleRunnerBase> CreateNavigationThrottleRunner(
NavigationThrottleRegistryBase* registry,
int64_t navigation_id,
bool is_primary_main_frame) {
if (base::FeatureList::IsEnabled(features::kNavigationThrottleRunner2)) {
return std::make_unique<NavigationThrottleRunner2>(registry, navigation_id,
is_primary_main_frame);
}
return std::make_unique<NavigationThrottleRunner>(registry, navigation_id,
is_primary_main_frame);
}
} // namespace
NavigationThrottleRegistryBase::~NavigationThrottleRegistryBase() = default;
NavigationThrottleRegistryImpl::NavigationThrottleRegistryImpl(
NavigationRequest* navigation_request)
: navigation_request_(CHECK_DEREF(navigation_request)),
navigation_throttle_runner_(CreateNavigationThrottleRunner(
this,
navigation_request->GetNavigationId(),
navigation_request->IsInPrimaryMainFrame())) {}
NavigationThrottleRegistryImpl::~NavigationThrottleRegistryImpl() = default;
void NavigationThrottleRegistryImpl::RegisterNavigationThrottles() {
TRACE_EVENT0("navigation",
"NavigationThrottleRegistryImpl::RegisterNavigationThrottles");
// Note: `throttles_` might not be empty. Some NavigationThrottles might have
// been registered with RegisterThrottleForTesting. These must reside at the
// end of `throttles_`. TestNavigationManagerThrottle expects that the
// NavigationThrottles added for test are the last NavigationThrottles to
// execute. Take them out while appending the rest of the
// NavigationThrottles.
std::vector<std::unique_ptr<NavigationThrottle>> testing_throttles =
std::move(throttles_);
// The NavigationRequest associated with the NavigationThrottles this
// NavigationThrottleRunner manages.
navigation_request_->GetDelegate()->CreateThrottlesForNavigation(*this);
// Check for renderer-initiated main frame navigations to blocked URL schemes
// (data, filesystem). This is done early as it may block the main frame
// navigation altogether.
BlockedSchemeNavigationThrottle::MaybeCreateAndAdd(*this);
#if !BUILDFLAG(IS_ANDROID)
// Prevent cross-document navigations from document picture-in-picture
// windows.
DocumentPictureInPictureNavigationThrottle::MaybeCreateAndAdd(*this);
#endif // !BUILDFLAG(IS_ANDROID)
AncestorThrottle::CreateAndAdd(*this);
// Check for mixed content. This is done after the AncestorThrottle and the
// FormSubmissionThrottle so that when folks block mixed content with a CSP
// policy, they don't get a warning. They'll still get a warning in the
// console about CSP blocking the load.
MixedContentNavigationThrottle::CreateAndAdd(*this);
// Delay response processing for certain prefetch responses where it might
// otherwise reveal information about cross-site state.
ContaminationDelayNavigationThrottle::MaybeCreateAndAdd(*this);
// Block certain requests that are not permitted for prerendering.
PrerenderNavigationThrottle::MaybeCreateAndAdd(*this);
// Defer cross-origin subframe loading during prerendering state.
PrerenderSubframeNavigationThrottle::MaybeCreateAndAdd(*this);
// Prevent navigations to/from Isolated Web Apps.
IsolatedWebAppThrottle::MaybeCreateAndAdd(*this);
devtools_instrumentation::CreateAndAddNavigationThrottles(*this);
// Make main frame navigations with error HTTP status code and an empty body
// commit an error page instead. Note that this should take lower priority
// than other throttles that might care about those navigations, e.g.
// throttles handling pages with 407 errors that require extra authentication.
HttpErrorNavigationThrottle::MaybeCreateAndAdd(*this);
// Wait for renderer-initiated navigation cancelation window to end. This will
// wait for the JS task that starts the navigation to finish, so add it close
// to the end to not delay running other throttles.
RendererCancellationThrottle::MaybeCreateAndAdd(*this);
// Defer any cross-document subframe history navigations if there is an
// associated main-frame same-document history navigation in progress, until
// the main frame has had an opportunity to fire a navigate event in the
// renderer. If the navigate event cancels the history navigation, the
// subframe navigations should not proceed.
SubframeHistoryNavigationThrottle::MaybeCreateAndAdd(*this);
// Defer subframe navigation in bfcached page if it hasn't sent a network
// request.
// This must be the last throttle to run. See https://crrev.com/c/5316738.
BackForwardCacheSubframeNavigationThrottle::MaybeCreateAndAdd(*this);
// Add a throttle to manage top-frame navigations from a partitioned popin.
// See https://explainers-by-googlers.github.io/partitioned-popins/
PartitionedPopinsNavigationThrottle::MaybeCreateAndAdd(*this);
// DO NOT ADD any throttles after this line.
// Insert all testing NavigationThrottles last.
throttles_.insert(throttles_.end(),
std::make_move_iterator(testing_throttles.begin()),
std::make_move_iterator(testing_throttles.end()));
base::UmaHistogramCounts100("Navigation.ThrottleCount", throttles_.size());
}
void NavigationThrottleRegistryImpl::
RegisterNavigationThrottlesForCommitWithoutUrlLoader() {
// Note: `throttles_` might not be empty. Some NavigationThrottles might have
// been registered with RegisterThrottleForTesting. These must reside at the
// end of `throttles_`. TestNavigationManagerThrottle expects that the
// NavigationThrottles added for test are the last NavigationThrottles to
// execute. Take them out while appending the rest of the
// NavigationThrottles.
std::vector<std::unique_ptr<NavigationThrottle>> testing_throttles =
std::move(throttles_);
// Defer any same-document subframe history navigations if there is an
// associated main-frame same-document history navigation in progress, until
// the main frame has had an opportunity to fire a navigate event in the
// renderer. If the navigate event cancels the history navigation, the
// subframe navigations should not proceed.
SubframeHistoryNavigationThrottle::MaybeCreateAndAdd(*this);
// Defer cross-origin about:srcdoc subframe loading during prerendering state.
PrerenderSubframeNavigationThrottle::MaybeCreateAndAdd(*this);
// Defer subframe navigation in bfcached page.
BackForwardCacheSubframeNavigationThrottle::MaybeCreateAndAdd(*this);
RendererCancellationThrottle::MaybeCreateAndAdd(*this);
#if !BUILDFLAG(IS_ANDROID)
// Prevent cross-document navigations from document picture-in-picture
// windows.
DocumentPictureInPictureNavigationThrottle::MaybeCreateAndAdd(*this);
#endif // !BUILDFLAG(IS_ANDROID)
// Insert all testing NavigationThrottles last.
throttles_.insert(throttles_.end(),
std::make_move_iterator(testing_throttles.begin()),
std::make_move_iterator(testing_throttles.end()));
}
void NavigationThrottleRegistryImpl::ProcessNavigationEvent(
NavigationThrottleEvent event) {
base::WeakPtr<NavigationThrottleRegistryImpl> weak_ref =
weak_factory_.GetWeakPtr();
navigation_throttle_runner_->ProcessNavigationEvent(event);
// DO NOT ADD CODE BETWEEN THIS AND THE WEAK_REF CHECK BELOW, as the
// NavigationHandle might have been deleted by the previous call.
if (!weak_ref) {
// The NavigationEvent handling might have destroyed NavigationHandle
// and its owning this instance. Return immediately.
return;
}
if (!deferring_throttles_.empty() && first_deferral_callback_for_testing_) {
std::move(first_deferral_callback_for_testing_).Run(); // IN-TEST
}
}
void NavigationThrottleRegistryImpl::ResumeProcessingNavigationEvent(
NavigationThrottle* resuming_throttle) {
if (!deferring_throttles_.contains(resuming_throttle)) {
// TODO(https://crbug.com/411238078): Upgrade to CHECK_EQ once remaining
// known cases are fixed. Until then, collect dump data and ignore the
// resume request to avoid bypassing required throttle checks.
const char* deferring_throttle_name =
deferring_throttles_.empty()
? "null"
: (*deferring_throttles_.begin())->GetNameForLogging();
SCOPED_CRASH_KEY_STRING32("Bug411238078", "expected_throttle",
deferring_throttle_name);
SCOPED_CRASH_KEY_STRING32("Bug411238078", "actual_throttle",
resuming_throttle->GetNameForLogging());
base::debug::DumpWithoutCrashing();
return;
}
CHECK_EQ(1u, deferring_throttles_.erase(resuming_throttle));
navigation_throttle_runner_->ResumeProcessingNavigationEvent(
resuming_throttle);
// DO NOT ADD CODE AFTER THIS, as the NavigationHandle might have been deleted
// by the previous call.
}
void NavigationThrottleRegistryImpl::OnDeferProcessingNavigationEvent(
NavigationThrottle* deferring_throttle) {
deferring_throttles_.insert(deferring_throttle);
}
const std::set<NavigationThrottle*>&
NavigationThrottleRegistryImpl::GetDeferringThrottles() const {
return deferring_throttles_;
}
void NavigationThrottleRegistryImpl::SetFirstDeferralCallbackForTesting(
base::OnceClosure callback) {
CHECK(deferring_throttles_.empty());
first_deferral_callback_for_testing_ = std::move(callback);
}
NavigationHandle& NavigationThrottleRegistryImpl::GetNavigationHandle() {
return *navigation_request_;
}
void NavigationThrottleRegistryImpl::AddThrottle(
std::unique_ptr<NavigationThrottle> navigation_throttle) {
CHECK(navigation_throttle);
TRACE_EVENT1("navigation", "NavigationThrottleRegistryImpl::AddThrottle",
"navigation_throttle", navigation_throttle->GetNameForLogging());
throttles_.push_back(std::move(navigation_throttle));
}
bool NavigationThrottleRegistryImpl::HasThrottle(const std::string& name) {
return std::ranges::find_if(throttles_, [name](const auto& throttle) {
return throttle->GetNameForLogging() == name;
}) != throttles_.end();
}
bool NavigationThrottleRegistryImpl::EraseThrottleForTesting(
const std::string& name) {
return std::erase_if(throttles_, [name](const auto& throttle) {
return throttle->GetNameForLogging() == name;
});
}
bool NavigationThrottleRegistryImpl::IsHTTPOrHTTPS() {
static bool is_cache_enabled = base::FeatureList::IsEnabled(
features::kNavigationThrottleRegistryAttributeCache);
// The cached properties are only safe to access at throttle registration
// time, and not safe afterward because the URL could change (e.g., due to
// redirects).
CHECK_LE(navigation_request_->state(),
NavigationRequest::NavigationState::WILL_START_REQUEST);
if (!is_cache_enabled) {
return GetNavigationHandle().GetURL().SchemeIsHTTPOrHTTPS();
}
if (!is_http_or_https_.has_value()) {
is_http_or_https_ = GetNavigationHandle().GetURL().SchemeIsHTTPOrHTTPS();
}
return *is_http_or_https_;
}
void NavigationThrottleRegistryImpl::OnEventProcessed(
NavigationThrottleEvent event,
NavigationThrottle::ThrottleCheckResult result) {
navigation_request_->OnNavigationEventProcessed(event, result);
}
std::vector<std::unique_ptr<NavigationThrottle>>&
NavigationThrottleRegistryImpl::GetThrottles() {
return throttles_;
}
NavigationThrottle& NavigationThrottleRegistryImpl::GetThrottleAtIndex(
size_t index) {
CHECK_LT(index, throttles_.size());
return *throttles_[index];
}
} // namespace content